diff --git a/core-libs/setup/package.json b/core-libs/setup/package.json index 9daad99515b..2ea51fe651f 100644 --- a/core-libs/setup/package.json +++ b/core-libs/setup/package.json @@ -1,6 +1,6 @@ { "name": "@spartacus/setup", - "version": "6.2.0-1", + "version": "6.2.1", "description": "Includes features that makes Spartacus and it's setup easier and streamlined.", "keywords": [ "spartacus", @@ -20,10 +20,10 @@ }, "peerDependencies": { "@angular/core": "^15.2.4", - "@spartacus/cart": "6.2.0-1", - "@spartacus/core": "6.2.0-1", - "@spartacus/order": "6.2.0-1", - "@spartacus/user": "6.2.0-1" + "@spartacus/cart": "6.2.1", + "@spartacus/core": "6.2.1", + "@spartacus/order": "6.2.1", + "@spartacus/user": "6.2.1" }, "optionalDependencies": { "@angular/platform-server": "^15.2.4", diff --git a/core-libs/setup/ssr/logger/index.ts b/core-libs/setup/ssr/logger/index.ts index 025449a0835..3d9e6184868 100644 --- a/core-libs/setup/ssr/logger/index.ts +++ b/core-libs/setup/ssr/logger/index.ts @@ -4,5 +4,5 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from './loggers'; -export * from './services'; +export * from './loggers/index'; +export * from './services/index'; diff --git a/core-libs/setup/ssr/logger/loggers/default-express-server-logger.spec.ts b/core-libs/setup/ssr/logger/loggers/default-express-server-logger.spec.ts index b81197b9e1c..52f8ce642f7 100644 --- a/core-libs/setup/ssr/logger/loggers/default-express-server-logger.spec.ts +++ b/core-libs/setup/ssr/logger/loggers/default-express-server-logger.spec.ts @@ -1,6 +1,17 @@ +import * as angularCore from '@angular/core'; import { Request } from 'express'; import { DefaultExpressServerLogger } from './default-express-server-logger'; -import { ExpressServerLoggerContext } from './express-server-logger'; + +const request = { + originalUrl: 'test', + res: { + locals: { + cx: { + request: { uuid: 'test', timeReceived: new Date('2023-05-26') }, + }, + }, + }, +} as unknown as Request; describe('DefaultExpressServerLogger', () => { let logger: DefaultExpressServerLogger; @@ -21,7 +32,7 @@ describe('DefaultExpressServerLogger', () => { logger.log('test', { request: {} as Request }); expect(logSpy).toHaveBeenCalledWith( - logger['createLogMessage']('test', { request: {} as Request }) + logger['stringifyWithContext']('test', { request: {} as Request }) ); }); @@ -31,7 +42,7 @@ describe('DefaultExpressServerLogger', () => { logger.warn('test', { request: {} as Request }); expect(warnSpy).toHaveBeenCalledWith( - logger['createLogMessage']('test', { request: {} as Request }) + logger['stringifyWithContext']('test', { request: {} as Request }) ); }); @@ -43,7 +54,7 @@ describe('DefaultExpressServerLogger', () => { logger.error('test', { request: {} as Request }); expect(errorSpy).toHaveBeenCalledWith( - logger['createLogMessage']('test', { request: {} as Request }) + logger['stringifyWithContext']('test', { request: {} as Request }) ); }); @@ -53,7 +64,7 @@ describe('DefaultExpressServerLogger', () => { logger.info('test', { request: {} as Request }); expect(infoSpy).toHaveBeenCalledWith( - logger['createLogMessage']('test', { request: {} as Request }) + logger['stringifyWithContext']('test', { request: {} as Request }) ); }); @@ -65,23 +76,233 @@ describe('DefaultExpressServerLogger', () => { logger.debug('test', { request: {} as Request }); expect(debugSpy).toHaveBeenCalledWith( - logger['createLogMessage']('test', { request: {} as Request }) + logger['stringifyWithContext']('test', { request: {} as Request }) ); }); + + describe('is not dev mode', () => { + beforeEach(() => { + jest.spyOn(angularCore, 'isDevMode').mockReturnValue(false); + }); + + it('should log proper shape of the JSON', () => { + const debugSpy = jest + .spyOn(console, 'log') + .mockImplementation(() => {}); + + logger.log('test', { request }); + + expect(debugSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "{"message":"test","context":{"timestamp":"2023-05-26T00:00:00.000Z","request":{"url":"test","uuid":"test","timeReceived":"2023-05-26T00:00:00.000Z"}}}", + ] + `); + }); + + it('should warn proper shape of the JSON', () => { + const debugSpy = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + + logger.warn('test', { request }); + + expect(debugSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "{"message":"test","context":{"timestamp":"2023-05-26T00:00:00.000Z","request":{"url":"test","uuid":"test","timeReceived":"2023-05-26T00:00:00.000Z"}}}", + ] + `); + }); + + it('should error proper shape of the JSON', () => { + const debugSpy = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); + + logger.error('test', { request }); + + expect(debugSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "{"message":"test","context":{"timestamp":"2023-05-26T00:00:00.000Z","request":{"url":"test","uuid":"test","timeReceived":"2023-05-26T00:00:00.000Z"}}}", + ] + `); + }); + + it('should info proper shape of the JSON', () => { + const request = { + originalUrl: 'test', + res: { + locals: { + cx: { + request: { uuid: 'test', timeReceived: new Date() }, + }, + }, + }, + } as unknown as Request; + + const debugSpy = jest + .spyOn(console, 'info') + .mockImplementation(() => {}); + + logger.info('test', { request }); + + expect(debugSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "{"message":"test","context":{"timestamp":"2023-05-26T00:00:00.000Z","request":{"url":"test","uuid":"test","timeReceived":"2023-05-26T00:00:00.000Z"}}}", + ] + `); + }); + + it('should debug proper shape of the JSON', () => { + const debugSpy = jest + .spyOn(console, 'debug') + .mockImplementation(() => {}); + + logger.debug('test', { request }); + + expect(debugSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "{"message":"test","context":{"timestamp":"2023-05-26T00:00:00.000Z","request":{"url":"test","uuid":"test","timeReceived":"2023-05-26T00:00:00.000Z"}}}", + ] + `); + }); + }); + + describe('is dev mode', () => { + beforeEach(() => { + jest.spyOn(angularCore, 'isDevMode').mockReturnValue(true); + }); + + it('should log proper shape of the JSON', () => { + const debugSpy = jest + .spyOn(console, 'log') + .mockImplementation(() => {}); + + logger.log('test', { request }); + + expect(debugSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "{ + "message": "test", + "context": { + "timestamp": "2023-05-26T00:00:00.000Z", + "request": { + "url": "test", + "uuid": "test", + "timeReceived": "2023-05-26T00:00:00.000Z" + } + } + }", + ] + `); + }); + + it('should warn proper shape of the JSON', () => { + const debugSpy = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + + logger.warn('test', { request }); + + expect(debugSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "{ + "message": "test", + "context": { + "timestamp": "2023-05-26T00:00:00.000Z", + "request": { + "url": "test", + "uuid": "test", + "timeReceived": "2023-05-26T00:00:00.000Z" + } + } + }", + ] + `); + }); + + it('should error proper shape of the JSON', () => { + const debugSpy = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); + + logger.error('test', { request }); + + expect(debugSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "{ + "message": "test", + "context": { + "timestamp": "2023-05-26T00:00:00.000Z", + "request": { + "url": "test", + "uuid": "test", + "timeReceived": "2023-05-26T00:00:00.000Z" + } + } + }", + ] + `); + }); + + it('should info proper shape of the JSON', () => { + const debugSpy = jest + .spyOn(console, 'info') + .mockImplementation(() => {}); + + logger.info('test', { request }); + + expect(debugSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "{ + "message": "test", + "context": { + "timestamp": "2023-05-26T00:00:00.000Z", + "request": { + "url": "test", + "uuid": "test", + "timeReceived": "2023-05-26T00:00:00.000Z" + } + } + }", + ] + `); + }); + + it('should debug proper shape of the JSON', () => { + const debugSpy = jest + .spyOn(console, 'debug') + .mockImplementation(() => {}); + + logger.debug('test', { request }); + + expect(debugSpy.mock.lastCall).toMatchInlineSnapshot(` + [ + "{ + "message": "test", + "context": { + "timestamp": "2023-05-26T00:00:00.000Z", + "request": { + "url": "test", + "uuid": "test", + "timeReceived": "2023-05-26T00:00:00.000Z" + } + } + }", + ] + `); + }); + }); }); describe('create log message', () => { it('should return message without request', () => { - const logMessage = logger['createLogMessage']( - 'test', - {} as ExpressServerLoggerContext - ); + const logMessage = logger['stringifyWithContext']('test', {}); expect(logMessage).not.toContain('request'); }); it('should return message with request', () => { - const logMessage = logger['createLogMessage']('test', { + const logMessage = logger['stringifyWithContext']('test', { request: {} as Request, }); @@ -89,6 +310,36 @@ describe('DefaultExpressServerLogger', () => { }); }); + describe('map context', () => { + it('should return context without request', () => { + const context = logger['mapContext']({ + options: {}, + }); + + expect(context).toMatchInlineSnapshot(` + { + "options": {}, + "timestamp": "2023-05-26T00:00:00.000Z", + } + `); + }); + + it('should return context with request', () => { + const context = logger['mapContext']({ request }); + + expect(context).toMatchInlineSnapshot(` + { + "request": { + "timeReceived": 2023-05-26T00:00:00.000Z, + "url": "test", + "uuid": "test", + }, + "timestamp": "2023-05-26T00:00:00.000Z", + } + `); + }); + }); + describe('map request', () => { it('should return mapped request', () => { const request = { @@ -106,7 +357,7 @@ describe('DefaultExpressServerLogger', () => { expect(mappedRequest).toEqual({ url: 'test', - ...request.res.locals.cx.request, + ...request.res?.locals.cx.request, }); }); }); diff --git a/core-libs/setup/ssr/logger/loggers/default-express-server-logger.ts b/core-libs/setup/ssr/logger/loggers/default-express-server-logger.ts index ce6359d8e87..d6b7fc82b2f 100644 --- a/core-libs/setup/ssr/logger/loggers/default-express-server-logger.ts +++ b/core-libs/setup/ssr/logger/loggers/default-express-server-logger.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { isDevMode } from '@angular/core'; import { Request } from 'express'; import { ExpressServerLogger, @@ -19,46 +20,73 @@ import { export class DefaultExpressServerLogger implements ExpressServerLogger { log(message: string, context: ExpressServerLoggerContext): void { /* eslint-disable-next-line no-console */ - console.log(this.createLogMessage(message, context)); + console.log(this.stringifyWithContext(message, context)); } warn(message: string, context: ExpressServerLoggerContext): void { /* eslint-disable-next-line no-console */ - console.warn(this.createLogMessage(message, context)); + console.warn(this.stringifyWithContext(message, context)); } error(message: string, context: ExpressServerLoggerContext): void { /* eslint-disable-next-line no-console */ - console.error(this.createLogMessage(message, context)); + console.error(this.stringifyWithContext(message, context)); } info(message: string, context: ExpressServerLoggerContext): void { /* eslint-disable-next-line no-console */ - console.info(this.createLogMessage(message, context)); + console.info(this.stringifyWithContext(message, context)); } debug(message: string, context: ExpressServerLoggerContext): void { /* eslint-disable-next-line no-console */ - console.debug(this.createLogMessage(message, context)); + console.debug(this.stringifyWithContext(message, context)); } - protected createLogMessage( + /** + * Converts a message and an ExpressServerLoggerContext object into a single JSON string containing both pieces of information, which can be used for logging purposes. + * + * @protected + * @param message - The message to be included in the log entry. + * @param context - The context object associated with the log entry. + * @returns A JSON string containing both the message and context information, suitable for logging. + */ + protected stringifyWithContext( message: string, context: ExpressServerLoggerContext ): string { + const logObject = { message, context: this.mapContext(context) }; + + return isDevMode() + ? JSON.stringify(logObject, null, 2) + : JSON.stringify(logObject); + } + + /** + * Map the context for the ExpressServerLogger + * + * @protected + * @param context - The logging context object to be mapped + * @returns - The mapped context with timestamp and request (if available) + */ + protected mapContext( + context: ExpressServerLoggerContext + ): Record { const timestamp = new Date().toISOString(); - const object = { - message, - context: { - timestamp, - ...context, - }, - }; + const outputContext = { timestamp, ...context }; + if (context.request) { - Object.assign(object.context, { + Object.assign(outputContext, { request: this.mapRequest(context.request), }); } - return JSON.stringify(object); + return outputContext; } + /** + * Maps a Request object into a JavaScript object with specific properties. + * + * @protected + * @param request - An Express Request object. + * @returns - A mapped request object. By default, it contains only "url", a random "uuid" and "timeReceived" of the request. + */ protected mapRequest(request: Request): Record { return { url: request.originalUrl, diff --git a/core-libs/setup/ssr/logger/loggers/express-server-logger.ts b/core-libs/setup/ssr/logger/loggers/express-server-logger.ts index 2c59386308c..5cff6937dd3 100644 --- a/core-libs/setup/ssr/logger/loggers/express-server-logger.ts +++ b/core-libs/setup/ssr/logger/loggers/express-server-logger.ts @@ -7,11 +7,26 @@ import { InjectionToken } from '@angular/core'; import { Request } from 'express'; +/** + * ExpressServerLoggerContext is used for log message in server side rendering. + * It contains optional request object and additional properties that can be used in log message. + */ export interface ExpressServerLoggerContext { - request: Request; + request?: Request; [_key: string]: any; } +/** + * ExpressServerLogger is used for log message in server side rendering. + * It contains methods for logging messages with different levels. Each method accepts message and context. + * Context is an object that contains additional information about the log message. + * + * @property log - logs message with level "log" + * @property warn - logs message with level "warn" + * @property error - logs message with level "error" + * @property info - logs message with level "info" + * @property debug - logs message with level "debug" + */ export interface ExpressServerLogger { log(message: string, context: ExpressServerLoggerContext): void; warn(message: string, context: ExpressServerLoggerContext): void; @@ -20,6 +35,15 @@ export interface ExpressServerLogger { debug(message: string, context: ExpressServerLoggerContext): void; } +/** + * Injection token for ExpressServerLogger used for log message in server side rendering. + * EXPRESS_SERVER_LOGGER is used to provide proper logger to LoggerService instance. + * + * Spartacus is providing two types of server loggers: + * - DefaultExpressServerLogger - default implementation used for logging contextual messages in SSR. + * - LegacyExpressServerLogger - used for logging if contextual logging is disabled + * + */ export const EXPRESS_SERVER_LOGGER = new InjectionToken( 'EXPRESS_SERVER_LOGGER' ); diff --git a/core-libs/setup/ssr/logger/loggers/index.ts b/core-libs/setup/ssr/logger/loggers/index.ts index eb6e18dfe33..09d18a173cf 100644 --- a/core-libs/setup/ssr/logger/loggers/index.ts +++ b/core-libs/setup/ssr/logger/loggers/index.ts @@ -6,4 +6,4 @@ export * from './default-express-server-logger'; export * from './express-server-logger'; -export * from './legacy-server-logger'; +export * from './legacy-express-server-logger'; diff --git a/core-libs/setup/ssr/logger/loggers/legacy-server-logger.spec.ts b/core-libs/setup/ssr/logger/loggers/legacy-express-server-logger.spec.ts similarity index 94% rename from core-libs/setup/ssr/logger/loggers/legacy-server-logger.spec.ts rename to core-libs/setup/ssr/logger/loggers/legacy-express-server-logger.spec.ts index adcfe6b876e..a53d707e5f3 100644 --- a/core-libs/setup/ssr/logger/loggers/legacy-server-logger.spec.ts +++ b/core-libs/setup/ssr/logger/loggers/legacy-express-server-logger.spec.ts @@ -1,4 +1,4 @@ -import { LegacyExpressServerLogger } from './legacy-server-logger'; +import { LegacyExpressServerLogger } from './legacy-express-server-logger'; describe('LegacyExpressServerLogger', () => { const logger = new LegacyExpressServerLogger(); diff --git a/core-libs/setup/ssr/logger/loggers/legacy-server-logger.ts b/core-libs/setup/ssr/logger/loggers/legacy-express-server-logger.ts similarity index 100% rename from core-libs/setup/ssr/logger/loggers/legacy-server-logger.ts rename to core-libs/setup/ssr/logger/loggers/legacy-express-server-logger.ts diff --git a/core-libs/setup/ssr/logger/services/express-logger.service.ts b/core-libs/setup/ssr/logger/services/express-logger.service.ts index 04fdcc60cff..221f425a292 100644 --- a/core-libs/setup/ssr/logger/services/express-logger.service.ts +++ b/core-libs/setup/ssr/logger/services/express-logger.service.ts @@ -10,6 +10,16 @@ import { LoggerService } from '@spartacus/core'; import { formatWithOptions } from 'util'; import { EXPRESS_SERVER_LOGGER } from '../loggers'; +/** + * Custom `LoggerService` used in ExpressJS. + * + * It converts the input arguments to a final string message similar as the native `console` + * does (using the native function `format` from `node:util`) and passes this message + * to a concrete server logger, used in ExpressJS. + * + * Besides the message, it also passes the current `request` of ExpressJS as an additional + * context to the concrete server logger. + */ @Injectable({ providedIn: 'root' }) export class ExpressLoggerService implements LoggerService { request = inject(REQUEST); @@ -42,11 +52,9 @@ export class ExpressLoggerService implements LoggerService { } protected formatLogMessage(message?: any, ...optionalParams: any[]): string { - /** - * built-in util function 'formatWithOptions' was used to not break provided message. - * That helps to present logs in moonitoring tools like Kibana in a proper way. - */ return formatWithOptions( + // Prevent automatically breaking a long string message into multiple lines. + // Otherwise, multi-line logs would be treated on the server as separate log { breakLength: Infinity }, message, ...optionalParams diff --git a/core-libs/setup/ssr/logger/services/prerendering-logger.service.ts b/core-libs/setup/ssr/logger/services/prerendering-logger.service.ts index 2f5c6a9f1bc..e1a6fbbf11a 100644 --- a/core-libs/setup/ssr/logger/services/prerendering-logger.service.ts +++ b/core-libs/setup/ssr/logger/services/prerendering-logger.service.ts @@ -7,6 +7,11 @@ import { Injectable } from '@angular/core'; import { LoggerService } from '@spartacus/core'; +/** + * Custom `LoggerService` used in pre-rendering in the server environment. + * + * It simply forwards the arguments to the native `console` methods. + */ @Injectable({ providedIn: 'root', }) diff --git a/core-libs/setup/ssr/optimized-engine/get-loggable-ssr-optimization-options.spec.ts b/core-libs/setup/ssr/optimized-engine/get-loggable-ssr-optimization-options.spec.ts new file mode 100644 index 00000000000..38e14228007 --- /dev/null +++ b/core-libs/setup/ssr/optimized-engine/get-loggable-ssr-optimization-options.spec.ts @@ -0,0 +1,93 @@ +import { DefaultExpressServerLogger } from '../logger'; +import { getLoggableSsrOptimizationOptions } from './get-loggable-ssr-optimization-options'; +import { SsrOptimizationOptions } from './ssr-optimization-options'; + +class MockLogger extends DefaultExpressServerLogger { + constructor() { + super(); + } +} + +describe('getLoggableSsrOptimizationOptions', () => { + const ssrOptions: SsrOptimizationOptions = { + concurrency: 10, + timeout: 3000, + forcedSsrTimeout: 60000, + maxRenderTime: 300000, + reuseCurrentRendering: true, + debug: false, + }; + + it('should return options in the same shape as provided', () => { + expect(getLoggableSsrOptimizationOptions(ssrOptions)) + .toMatchInlineSnapshot(` + { + "concurrency": 10, + "debug": false, + "forcedSsrTimeout": 60000, + "maxRenderTime": 300000, + "reuseCurrentRendering": true, + "timeout": 3000, + } + `); + }); + + it('should return constructor name if property is not a plain object', () => { + expect( + getLoggableSsrOptimizationOptions({ + ...ssrOptions, + logger: new MockLogger(), + }) + ).toMatchInlineSnapshot(` + { + "concurrency": 10, + "debug": false, + "forcedSsrTimeout": 60000, + "logger": "MockLogger", + "maxRenderTime": 300000, + "reuseCurrentRendering": true, + "timeout": 3000, + } + `); + }); + + it('should not return constructor name if property is a plain object', () => { + expect( + getLoggableSsrOptimizationOptions({ + ...ssrOptions, + somePlainObject: { config: 'value' }, + } as SsrOptimizationOptions) + ).toMatchInlineSnapshot(` + { + "concurrency": 10, + "debug": false, + "forcedSsrTimeout": 60000, + "maxRenderTime": 300000, + "reuseCurrentRendering": true, + "somePlainObject": { + "config": "value", + }, + "timeout": 3000, + } + `); + }); + + it('should change function to string if property is a function', () => { + expect( + getLoggableSsrOptimizationOptions({ + ...ssrOptions, + renderKeyResolver: (req: any) => req.url, + }) + ).toMatchInlineSnapshot(` + { + "concurrency": 10, + "debug": false, + "forcedSsrTimeout": 60000, + "maxRenderTime": 300000, + "renderKeyResolver": "(req) => req.url", + "reuseCurrentRendering": true, + "timeout": 3000, + } + `); + }); +}); diff --git a/core-libs/setup/ssr/optimized-engine/get-loggable-ssr-optimization-options.ts b/core-libs/setup/ssr/optimized-engine/get-loggable-ssr-optimization-options.ts new file mode 100644 index 00000000000..e1877e44801 --- /dev/null +++ b/core-libs/setup/ssr/optimized-engine/get-loggable-ssr-optimization-options.ts @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SsrOptimizationOptions } from './ssr-optimization-options'; + +/** + * Helper function that maps optimization options to primitive values. + * This is useful for logging and monitoring purposes. + * + * @param value optimization options that should be logged + * @returns options containing only primitive values that are easier to read by developers and monitoring tools + */ +export const getLoggableSsrOptimizationOptions = ( + value: SsrOptimizationOptions +) => { + const newValue: Record = { ...value }; + Object.keys(value).forEach((key) => { + if (isClassInstance(newValue[key])) { + newValue[key] = newValue[key].constructor?.name; + } + if (typeof newValue[key] === 'function') { + newValue[key] = newValue[key].toString(); + } + }); + return newValue; +}; + +/** + * Checks if the given value is a class instance, + * but not a plain Object. + * + * @private + */ +const isClassInstance = (value: any): boolean => { + return typeof value === 'object' && value.constructor !== Object; +}; diff --git a/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.spec.ts b/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.spec.ts index 0abebd2beb4..ac97df28ba5 100644 --- a/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.spec.ts +++ b/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.spec.ts @@ -1183,7 +1183,21 @@ describe('OptimizedSsrEngine', () => { }); expect(consoleLogSpy.mock.lastCall).toMatchInlineSnapshot(` [ - "{"message":"[spartacus] SSR optimization engine initialized","context":{"timestamp":"2023-05-26T00:00:00.000Z","options":{"concurrency":10,"timeout":3000,"forcedSsrTimeout":60000,"maxRenderTime":300000,"reuseCurrentRendering":true,"debug":false,"logger":true}}}", + "{ + "message": "[spartacus] SSR optimization engine initialized", + "context": { + "timestamp": "2023-05-26T00:00:00.000Z", + "options": { + "concurrency": 10, + "timeout": 3000, + "forcedSsrTimeout": 60000, + "maxRenderTime": 300000, + "reuseCurrentRendering": true, + "debug": false, + "logger": true + } + } + }", ] `); }); @@ -1211,17 +1225,17 @@ describe('OptimizedSsrEngine', () => { it('should use the legacy server logger, if logger option not specified', () => { new TestEngineRunner({}); expect(consoleLogSpy.mock.lastCall).toMatchInlineSnapshot(` - [ - "[spartacus] SSR optimization engine initialized with the following options: { - "concurrency": 10, - "timeout": 3000, - "forcedSsrTimeout": 60000, - "maxRenderTime": 300000, - "reuseCurrentRendering": true, - "debug": false - }", - ] - `); + [ + "[spartacus] SSR optimization engine initialized with the following options: { + "concurrency": 10, + "timeout": 3000, + "forcedSsrTimeout": 60000, + "maxRenderTime": 300000, + "reuseCurrentRendering": true, + "debug": false + }", + ] + `); }); }); }); diff --git a/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.ts b/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.ts index 714dba192aa..8faa7c2f275 100644 --- a/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.ts +++ b/core-libs/setup/ssr/optimized-engine/optimized-ssr-engine.ts @@ -17,6 +17,7 @@ import { ExpressServerLoggerContext, LegacyExpressServerLogger, } from '../logger'; +import { getLoggableSsrOptimizationOptions } from './get-loggable-ssr-optimization-options'; import { RenderingCache } from './rendering-cache'; import { RenderingStrategy, @@ -86,30 +87,22 @@ export class OptimizedSsrEngine { return; } - const replacer = (_key: string, value: unknown): unknown => { - if (typeof value === 'function') { - return value.toString(); - } - if (this.isServerLogger(value)) { - return value.constructor.name; - } - return value; - }; + const loggableSsrOptions = getLoggableSsrOptimizationOptions( + this.ssrOptions + ); - const stringifiedOptions = JSON.stringify(this.ssrOptions, replacer, 2); // This check has been introduced to avoid breaking changes. Remove it in Spartacus version 7.0 - const message = this.ssrOptions.logger - ? `[spartacus] SSR optimization engine initialized` - : `[spartacus] SSR optimization engine initialized with the following options: ${stringifiedOptions}`; - this.log(message, true, { - options: { - ...this.ssrOptions, - logger: - this.ssrOptions.logger === true - ? this.ssrOptions.logger - : this.ssrOptions.logger?.constructor?.name, - }, - } as unknown as ExpressServerLoggerContext); //it expects ExpressServerLoggerContext, but the current logged message is printed a the start f the server and there is no request available yet. + if (this.ssrOptions.logger) { + this.log(`[spartacus] SSR optimization engine initialized`, true, { + options: loggableSsrOptions, + }); + } else { + const stringifiedOptions = JSON.stringify(loggableSsrOptions, null, 2); + this.log( + `[spartacus] SSR optimization engine initialized with the following options: ${stringifiedOptions}`, + true + ); + } } /** @@ -339,13 +332,10 @@ export class OptimizedSsrEngine { message: string, debug = true, //CXSPA-3680 - in a new major, let's make this argument required - logMetadata?: ExpressServerLoggerContext + context?: ExpressServerLoggerContext ): void { if (debug || this.ssrOptions?.debug) { - this.logger.log( - message, - logMetadata || ({} as ExpressServerLoggerContext) - ); + this.logger.log(message, context || {}); } } @@ -502,15 +492,4 @@ export class OptimizedSsrEngine { } return ssrOptions?.logger || new LegacyExpressServerLogger(); } - - private isServerLogger(logger: unknown): logger is ExpressServerLogger { - return ( - logger instanceof Object && - 'log' in logger && - 'error' in logger && - 'warn' in logger && - 'info' in logger && - 'debug' in logger - ); - } } diff --git a/feature-libs/asm/assets/translations/en/asm.ts b/feature-libs/asm/assets/translations/en/asm.ts index 69556602568..000f9678367 100644 --- a/feature-libs/asm/assets/translations/en/asm.ts +++ b/feature-libs/asm/assets/translations/en/asm.ts @@ -36,6 +36,7 @@ export const asm = { label: 'Customer Name/Email Address', }, submit: 'Start Session', + startEmulation: 'Start Emulation', noMatch: 'No customer found.', noMatchResult: 'This account cannot be found.', createCustomer: 'Create New Customer', @@ -58,6 +59,13 @@ export const asm = { }, createAccountAlert: 'The customer session starts once you create the customer account.', + validationErrors: { + firstName: 'Enter a valid first name.', + lastName: 'Enter a valid last name.', + emailAddress: 'Enter a valid email address.', + }, + badRequestDuplicatedEmail: + 'Enter a different email address as {{ emailAddress }} already exists.', }, customerList: { title: 'Customer List', @@ -145,6 +153,7 @@ export const asm = { }, csagentTokenExpired: 'Your customer support agent session is expired.', endSession: 'End Session', + endEmulation: 'End Emulation', agentSessionTimer: { label: 'Session Timeout', minutes: 'min', diff --git a/feature-libs/asm/components/asm-create-customer-form/asm-create-customer-form.component.html b/feature-libs/asm/components/asm-create-customer-form/asm-create-customer-form.component.html index 53456704776..38e9cde6b90 100644 --- a/feature-libs/asm/components/asm-create-customer-form/asm-create-customer-form.component.html +++ b/feature-libs/asm/components/asm-create-customer-form/asm-create-customer-form.component.html @@ -22,13 +22,17 @@