From d56c6b1330f4ff2ee8ecba648bd59169cd25acc9 Mon Sep 17 00:00:00 2001 From: Fifciuu Date: Fri, 11 Oct 2024 15:58:16 +0200 Subject: [PATCH] chore: snapshot of done work --- .../logger/src/interfaces/LoggerInterface.ts | 6 +- .../bootstrap/api/success/index.ts | 6 +- .../__tests__/integration/bootstrap/server.ts | 9 +- .../integration/bootstrap/serverWithInitFn.ts | 32 ++ .../integration/createServer.spec.ts | 31 +- .../integration/loggerScopes.spec.ts | 441 ++++++++++++++++++ .../src/apiClientFactory/applyContextToApi.ts | 90 +++- .../middleware/src/apiClientFactory/index.ts | 37 +- .../src/handlers/prepareApiFunction/index.ts | 16 +- .../src/integrations/registerIntegrations.ts | 17 +- .../middleware/src/logger/injectMetadata.ts | 5 +- packages/middleware/src/types/common.ts | 49 +- 12 files changed, 696 insertions(+), 43 deletions(-) create mode 100644 packages/middleware/__tests__/integration/bootstrap/serverWithInitFn.ts create mode 100644 packages/middleware/__tests__/integration/loggerScopes.spec.ts diff --git a/packages/logger/src/interfaces/LoggerInterface.ts b/packages/logger/src/interfaces/LoggerInterface.ts index a610a58456..2fea416ba4 100644 --- a/packages/logger/src/interfaces/LoggerInterface.ts +++ b/packages/logger/src/interfaces/LoggerInterface.ts @@ -1,7 +1,11 @@ /** * Metadata is a record with string keys and arbitrary values. */ -export type Metadata = Record; +export type Metadata = { + [key: string]: any; + troubleshoot?: Record | string[]; + alokai?: never; +}; /** * Log data can be a string, an error, or and arbitrary object. diff --git a/packages/middleware/__tests__/integration/bootstrap/api/success/index.ts b/packages/middleware/__tests__/integration/bootstrap/api/success/index.ts index 04351e3b88..748432bc0d 100644 --- a/packages/middleware/__tests__/integration/bootstrap/api/success/index.ts +++ b/packages/middleware/__tests__/integration/bootstrap/api/success/index.ts @@ -1,4 +1,8 @@ -export const success = () => { +import { getLogger } from "../../../../../src"; + +export const success = (context) => { + const logger = getLogger(context); + logger.info("success"); return Promise.resolve({ status: 200, message: "ok", diff --git a/packages/middleware/__tests__/integration/bootstrap/server.ts b/packages/middleware/__tests__/integration/bootstrap/server.ts index 4619134fee..8d96d65b0e 100644 --- a/packages/middleware/__tests__/integration/bootstrap/server.ts +++ b/packages/middleware/__tests__/integration/bootstrap/server.ts @@ -1,7 +1,14 @@ import { apiClientFactory } from "../../../src/apiClientFactory"; import * as api from "./api"; +import { AlokaiContainer, getLogger } from "../../../src"; + +const onCreate = async ( + config: Record = {}, + alokai: AlokaiContainer +) => { + const logger = getLogger(alokai); + logger.info("oncreate"); -const onCreate = async (config: Record = {}) => { return { config, client: null, diff --git a/packages/middleware/__tests__/integration/bootstrap/serverWithInitFn.ts b/packages/middleware/__tests__/integration/bootstrap/serverWithInitFn.ts new file mode 100644 index 0000000000..7725000d0d --- /dev/null +++ b/packages/middleware/__tests__/integration/bootstrap/serverWithInitFn.ts @@ -0,0 +1,32 @@ +import { apiClientFactory } from "../../../src/apiClientFactory"; +import * as api from "./api"; +import { AlokaiContainer, getLogger } from "../../../src"; + +const init = (settings: any, alokai: AlokaiContainer) => { + const logger = getLogger(alokai); + logger.info("Init fn!"); + return { + config: settings, + client: null, + }; +}; + +const onCreate = async ( + config: Record = {}, + alokai: AlokaiContainer +) => { + const logger = getLogger(alokai); + logger.info("oncreate"); + + return { + config, + client: null, + }; +}; + +const { createApiClient } = apiClientFactory({ + onCreate, + api, +}); + +export { createApiClient, init }; diff --git a/packages/middleware/__tests__/integration/createServer.spec.ts b/packages/middleware/__tests__/integration/createServer.spec.ts index 2317b97702..48e63ae493 100644 --- a/packages/middleware/__tests__/integration/createServer.spec.ts +++ b/packages/middleware/__tests__/integration/createServer.spec.ts @@ -2,6 +2,7 @@ import request from "supertest"; import { Server } from "http"; import { createServer } from "../../src/index"; import { success } from "./bootstrap/api"; +import { logger } from "../../__mocks__/logger"; describe("[Integration] Create server", () => { let app: Server; @@ -109,7 +110,15 @@ describe("[Integration] Create server", () => { const response = JSON.parse(text); // This is the result of the original "success" function from the integration - const apiMethodResult = await success(); + const apiMethodResult = await success({ + res: { + locals: { + alokai: { + logger, + }, + }, + }, + } as any); expect(status).toEqual(200); expect(response).toEqual(apiMethodResult); @@ -161,7 +170,15 @@ describe("[Integration] Create server", () => { const response = JSON.parse(text); // This is the result of the original "success" function from the integration - const apiMethodResult = await success(); + const apiMethodResult = await success({ + res: { + locals: { + alokai: { + logger, + }, + }, + }, + } as any); expect(status).toEqual(200); expect(response).toEqual(apiMethodResult); @@ -202,7 +219,15 @@ describe("[Integration] Create server", () => { const response = JSON.parse(text); // This is the result of the original "success" function from the integration - const apiMethodResult = await success(); + const apiMethodResult = await success({ + res: { + locals: { + alokai: { + logger, + }, + }, + }, + } as any); // If merged, the response would be { message: "error", error: true, status: 404 } expect(status).toEqual(200); diff --git a/packages/middleware/__tests__/integration/loggerScopes.spec.ts b/packages/middleware/__tests__/integration/loggerScopes.spec.ts new file mode 100644 index 0000000000..8db16c2dea --- /dev/null +++ b/packages/middleware/__tests__/integration/loggerScopes.spec.ts @@ -0,0 +1,441 @@ +import request from "supertest"; +import { Server } from "http"; +import { createServer, getLogger } from "../../src/index"; + +const Logger = { + info: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + warning: jest.fn(), + notice: jest.fn(), + emergency: jest.fn(), + alert: jest.fn(), + critical: jest.fn(), +}; + +const testingExtension = { + name: "testing-extension", + extendApp({ logger }) { + logger.info("testing!"); + }, + extendApiMethods: { + addedCustomEndpoint(context) { + const logger = getLogger(context); + logger.info("some log"); + return {}; + }, + setCookieHeader(context) { + const logger = getLogger(context); + logger.info("some log"); + return {}; + }, + async reuseOtherMethod(context) { + const logger = getLogger(context); + logger.info("some log"); + return await context.api.success(); + }, + async resueOtherIntegrationMethod(context) { + const logger = getLogger(context); + logger.info("some log"); + const int = await context.getApiClient("replicated_integration"); + return await int.api[ + "replicated-integration-extension" + ].orchestrationTest(); + }, + }, + hooks(req, res, alokai) { + const logger = getLogger(alokai); + logger.info("hooks"); + return { + beforeCall({ args }) { + logger.info("bc1"); + return args; + }, + beforeCreate(a) { + logger.info("bc2"); + return a; + }, + afterCall({ response }) { + logger.info("after1"); + return response; + }, + afterCreate(a) { + logger.info("after2"); + return a; + }, + }; + }, +}; + +const namespacedTestingExtension = { + name: "namespaced-testing-extension", + isNamespaced: true, + extendApiMethods: { + setCookieHeader(context) { + const logger = getLogger(context); + logger.info("some log"); + return {}; + }, + }, +}; + +const replicatedIntegrationExtension = { + name: "replicated-integration-extension", + isNamespaced: true, + extendApiMethods: { + async orchestrationTest(context) { + const logger = getLogger(context); + logger.info("test"); + return await context.api.success(); + }, + }, +}; + +/** + * The following test suite is responsible for making sure + * logs have proper scopes attached when printed from different places + * inside extension and integration. + */ +describe("[Integration] Logger scopes", () => { + let app: Server; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + beforeAll(async () => { + app = await createServer({ + logger: { + handler: Logger, + }, + integrations: { + test_integration: { + configuration: {}, + location: "./__tests__/integration/bootstrap/server", + logger: { + handler: Logger, + }, + extensions() { + return [testingExtension as any, namespacedTestingExtension as any]; + }, + }, + replicated_integration: { + configuration: {}, + location: "./__tests__/integration/bootstrap/server", + logger: { + handler: Logger, + }, + extensions() { + return [ + testingExtension as any, + replicatedIntegrationExtension as any, + ]; + }, + }, + }, + }); + }); + + test("scope of log from API methods from the integration", async () => { + await request(app).post("/test_integration/success"); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: undefined, + functionName: "success", + integrationName: "test_integration", + type: "endpoint", + }, + }); + }); + + test("scope of log from API methods added in the extension", async () => { + await request(app).post("/test_integration/addedCustomEndpoint"); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "testing-extension", + functionName: "addedCustomEndpoint", + integrationName: "test_integration", + type: "endpoint", + }, + }); + }); + + test("scope of log from API methods overwritten in the extension (without namespace)", async () => { + await request(app).post("/test_integration/setCookieHeader"); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "testing-extension", + functionName: "setCookieHeader", + integrationName: "test_integration", + type: "endpoint", + }, + }); + }); + + test("scope of log from API methods overwritten in the extension (with namespace)", async () => { + await request(app).post( + "/test_integration/namespaced-testing-extension/setCookieHeader" + ); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "namespaced-testing-extension", + functionName: "setCookieHeader", + integrationName: "test_integration", + type: "endpoint", + }, + }); + }); + + test("scope of log from hooks method in the extension", async () => { + await request(app).post("/test_integration/setCookieHeader"); + + expect(Logger.info).toBeCalledWith("hooks", { + context: "middleware", + scope: { + extensionName: "testing-extension", + functionName: "setCookieHeader", + integrationName: "test_integration", + hookName: "hooks", + type: "requestHook", + }, + }); + }); + + test("scope of log from hooks method in the extension triggered when calling other method from other extension", async () => { + await request(app).post( + "/test_integration/namespaced-testing-extension/setCookieHeader" + ); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "testing-extension", + functionName: "setCookieHeader", + integrationName: "test_integration", + hookName: "hooks", + type: "requestHook", + }, + }); + }); + + test("scope of log from beforeCreate method in the extension", async () => { + await request(app).post("/test_integration/setCookieHeader"); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "testing-extension", + functionName: "setCookieHeader", + integrationName: "test_integration", + hookName: "beforeCreate", + type: "requestHook", + }, + }); + }); + + test("scope of log from beforeCall method in the extension", async () => { + await request(app).post("/test_integration/setCookieHeader"); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "testing-extension", + functionName: "setCookieHeader", + integrationName: "test_integration", + hookName: "beforeCall", + type: "requestHook", + }, + }); + }); + + test("scope of log from afterCreate method in the extension", async () => { + await request(app).post("/test_integration/setCookieHeader"); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "testing-extension", + functionName: "setCookieHeader", + integrationName: "test_integration", + hookName: "afterCreate", + type: "requestHook", + }, + }); + }); + + test("scope of log from afterCall method in the extension", async () => { + await request(app).post("/test_integration/setCookieHeader"); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "testing-extension", + functionName: "setCookieHeader", + integrationName: "test_integration", + hookName: "afterCall", + type: "requestHook", + }, + }); + }); + + test("scope of log from endpoint resued in custom endpoint", async () => { + await request(app).post("/test_integration/reuseOtherMethod"); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "testing-extension", + functionName: "reuseOtherMethod", + integrationName: "test_integration", + type: "endpoint", + }, + }); + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + functionName: "success", + integrationName: "test_integration", + type: "endpoint", + }, + }); + }); + + test("scope of log from: different integration called from extension", async () => { + await request(app).post("/test_integration/resueOtherIntegrationMethod"); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "testing-extension", + functionName: "resueOtherIntegrationMethod", + integrationName: "test_integration", + type: "endpoint", + }, + }); + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + extensionName: "replicated-integration-extension", + functionName: "orchestrationTest", + integrationName: "replicated_integration", + type: "endpoint", + }, + }); + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + functionName: "success", + integrationName: "replicated_integration", + type: "endpoint", + }, + }); + }); + + test("scope of log extendApp", async () => { + await createServer({ + logger: { + handler: Logger, + }, + integrations: { + test_integration: { + configuration: {}, + location: "./__tests__/integration/bootstrap/server", + logger: { + handler: Logger, + }, + extensions() { + return [testingExtension as any, namespacedTestingExtension as any]; + }, + }, + replicated_integration: { + configuration: {}, + location: "./__tests__/integration/bootstrap/server", + logger: { + handler: Logger, + }, + extensions() { + return [ + testingExtension as any, + replicatedIntegrationExtension as any, + ]; + }, + }, + }, + }); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + integrationName: "test_integration", + extensionName: "testing-extension", + type: "bootstrapHook", + hookName: "extendApp", + }, + }); + }); + + test("scope of log init function", async () => { + await createServer({ + logger: { + handler: Logger, + }, + integrations: { + test_integration: { + configuration: {}, + location: "./__tests__/integration/bootstrap/serverWithInitFn", + logger: { + handler: Logger, + }, + extensions() { + return [testingExtension as any, namespacedTestingExtension as any]; + }, + }, + }, + }); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + integrationName: "test_integration", + type: "bootstrapHook", + hookName: "init", + }, + }); + }); + + test("scope of log onCreate function", async () => { + const app = await createServer({ + logger: { + handler: Logger, + }, + integrations: { + test_integration: { + configuration: {}, + location: "./__tests__/integration/bootstrap/server", + logger: { + handler: Logger, + }, + }, + }, + }); + await request(app).post("/test_integration/success"); + + expect(Logger.info).toBeCalledWith(expect.any(String), { + context: "middleware", + scope: { + integrationName: "test_integration", + functionName: "success", + type: "requestHook", + hookName: "onCreate", + }, + }); + }); +}); diff --git a/packages/middleware/src/apiClientFactory/applyContextToApi.ts b/packages/middleware/src/apiClientFactory/applyContextToApi.ts index 281cb482d0..0c878f7bc4 100644 --- a/packages/middleware/src/apiClientFactory/applyContextToApi.ts +++ b/packages/middleware/src/apiClientFactory/applyContextToApi.ts @@ -15,6 +15,10 @@ const nopAfter = ({ response, }: AfterCallParams) => response; +const checkMetadataScope = ( + context: CONTEXT +) => Boolean(context.res.locals?.alokai?.metadata?.scope); + /** * Wraps api methods with context and hooks triggers */ @@ -35,38 +39,86 @@ const applyContextToApi = < (prev, [callName, fn]) => ({ ...prev, [callName]: async (...args: Parameters) => { + const hasMetadataScope = checkMetadataScope(context); + if (!hasMetadataScope) { + const logger = getLogger(context.res); + logger.warning( + `Alokai's metadata object is missing in the context under 'res.locals.alokai'. + This could indicate that the extension's scope or metadata has not been properly initialized. + Without this metadata, certain custom API client functionalities may not work as expected, + including tracking of extension-specific actions or data. + Please ensure that the Alokai metadata object is correctly set up in 'res.locals.alokai' before proceeding with the request. + + Steps to troubleshoot: + 1. Verify if content of res.locals hasn't been overwritten, instead of extended. + 2. If you're unsure, please consult Alokai's team for further assistance or review the source code implementation. + + Call Name: ${callName} + Function Name: ${fn.name || "Unnamed function"}` + ); + } const extendQuery = createExtendQuery(context); + let tmpIntegrationName = ""; + let tmpFnName = ""; + if (hasMetadataScope) { + context.res.locals.alokai.metadata.scope.type = "requestHook"; + tmpIntegrationName = + context.res.locals.alokai.metadata.scope.integrationName; + context.res.locals.alokai.metadata.scope.integrationName = + context.integrationKey; + + tmpFnName = context.res.locals.alokai.metadata.scope.functionName; + context.res.locals.alokai.metadata.scope.functionName = callName; + } const transformedArgs = await hooks.before({ callName, args }); const apiClientContext = { ...context, extendQuery }; if (isExtensionEndpointHandler(fn)) { - if (context.res.locals?.alokai?.metadata?.scope) { - context.res.locals.alokai.metadata.scope.extensionName = - fn._extensionName; - } else { - const logger = getLogger(context.res); - logger.warning( - `Alokai's metadata object is missing in the context under 'res.locals.alokai'. - This could indicate that the extension's scope or metadata has not been properly initialized. - Without this metadata, certain custom API client functionalities may not work as expected, - including tracking of extension-specific actions or data. - Please ensure that the Alokai metadata object is correctly set up in 'res.locals.alokai' before proceeding with the request. - - Steps to troubleshoot: - 1. Verify if content of res.locals hasn't been overwritten, instead of extended. - 2. If you're unsure, please consult Alokai's team for further assistance or review the source code implementation. - - Call Name: ${callName} - Function Name: ${fn.name || "Unnamed function"}` - ); + context.res.locals.alokai.metadata.scope.extensionName = + fn._extensionName; + } + let tmp = { + extensionName: null, + functionName: null, + }; + const isTmpSet = (tmp) => !!tmp.functionName; // as we never set fn name to nullish + if (hasMetadataScope) { + context.res.locals.alokai.metadata.scope.type = "endpoint"; + context.res.locals.alokai.metadata.scope.hookName = undefined; + if (!(fn as any)._extensionName) { + tmp = { + extensionName: + context.res.locals.alokai.metadata.scope.extensionName, + functionName: + context.res.locals.alokai.metadata.scope.functionName, + }; + context.res.locals.alokai.metadata.scope.extensionName = undefined; + context.res.locals.alokai.metadata.scope.functionName = callName; + // console.log(`wywolam ${fn.name} lub ${callName}`, { ...tmp }); } } const response = await fn(apiClientContext, ...transformedArgs); + if (hasMetadataScope) { + if (isTmpSet(tmp)) { + context.res.locals.alokai.metadata.scope.extensionName = + tmp.extensionName; + context.res.locals.alokai.metadata.scope.functionName = + tmp.functionName; + } + // console.log("co ustawiam?: ", { ...tmp }); + context.res.locals.alokai.metadata.scope.type = "requestHook"; + } const transformedResponse = await hooks.after({ callName, args, response, }); + if (hasMetadataScope) { + context.res.locals.alokai.metadata.scope.integrationName = + tmpIntegrationName; + context.res.locals.alokai.metadata.scope.functionName = tmpFnName; + } + return transformedResponse; }, }), diff --git a/packages/middleware/src/apiClientFactory/index.ts b/packages/middleware/src/apiClientFactory/index.ts index a45d6b31d1..c250292cae 100644 --- a/packages/middleware/src/apiClientFactory/index.ts +++ b/packages/middleware/src/apiClientFactory/index.ts @@ -51,6 +51,8 @@ const apiClientFactory = < this?.middleware?.extensions || []; const logger = getLogger(this.middleware.res); + this.middleware.res.locals.alokai.metadata.scope.hookName = "hooks"; + this.middleware.res.locals.alokai.metadata.scope.type = "requestHook"; const lifecycles = await Promise.all( rawExtensions .filter((extension): extension is ExtensionWith<"hooks"> => @@ -66,7 +68,6 @@ const apiClientFactory = < scope: { ...metadata?.scope, extensionName: name, - extensionNamePointsHookSource: true, // If we have hook on custom endpoint, extensionName value is confusing without this field }, })); @@ -82,13 +83,22 @@ const apiClientFactory = < ) .reduce(async (configSoFar, extension) => { const resolvedConfig = await configSoFar; + this.middleware.res.locals.alokai.metadata.scope.hookName = + "beforeCreate"; return await extension.beforeCreate({ configuration: resolvedConfig, }); }, Promise.resolve(config)); + const loggerWithMetadata = injectMetadata(logger, () => ({ + scope: { + integrationName: this.middleware?.integrationKey, + type: "requestHook", + hookName: "onCreate", + }, + })); const settings = (await factoryParams.onCreate) - ? await factoryParams.onCreate(_config, { logger }) + ? await factoryParams.onCreate(_config, { logger: loggerWithMetadata }) : { config, client: config.client }; settings.config = await lifecycles @@ -97,11 +107,15 @@ const apiClientFactory = < ) .reduce(async (configSoFar, extension) => { const resolvedConfig = await configSoFar; + this.middleware.res.locals.alokai.metadata.scope.hookName = + "afterCreate"; return await extension.afterCreate({ configuration: resolvedConfig }); }, Promise.resolve(settings.config)); const extensionHooks: ApplyingContextHooks = { before: async (params) => { + this.middleware.res.locals.alokai.metadata.scope.hookName = + "beforeCall"; return await lifecycles .filter((extension): extension is ExtensionHookWith<"beforeCall"> => isFunction(extension?.beforeCall) @@ -109,6 +123,7 @@ const apiClientFactory = < .reduce(async (argsSoFar, extension) => { const resolvedArgs = await argsSoFar; const resolvedSettings = await settings; + return extension.beforeCall({ ...params, configuration: resolvedSettings.config, @@ -117,6 +132,8 @@ const apiClientFactory = < }, Promise.resolve(params.args)); }, after: async (params) => { + this.middleware.res.locals.alokai.metadata.scope.hookName = + "afterCall"; return await lifecycles .filter((extension): extension is ExtensionHookWith<"afterCall"> => isFunction(extension.afterCall) @@ -133,7 +150,10 @@ const apiClientFactory = < }, }; - const context = { ...settings, ...(this?.middleware || {}) }; + const context = { + ...settings, + ...(this?.middleware || {}), + }; const api = await resolveApi(factoryParams.api, settings); @@ -146,9 +166,18 @@ const apiClientFactory = < settings ); if (extension.isNamespaced) { + const markedExtendedApiMethods = Object.entries( + extendedApiMethods + ).reduce((total, [name, fn]: [string, ExtensionEndpointHandler]) => { + return { + ...total, + [name]: markWithExtensionName(fn, extension.name), + }; + }, {}); + namespacedExtensions[extension.name] = { ...(namespacedExtensions?.[extension.name] ?? {}), - ...extendedApiMethods, + ...markedExtendedApiMethods, }; } else { const markedExtendedApiMethods = Object.entries( diff --git a/packages/middleware/src/handlers/prepareApiFunction/index.ts b/packages/middleware/src/handlers/prepareApiFunction/index.ts index 4c1d2bfc30..53b344f784 100644 --- a/packages/middleware/src/handlers/prepareApiFunction/index.ts +++ b/packages/middleware/src/handlers/prepareApiFunction/index.ts @@ -19,15 +19,15 @@ export function prepareApiFunction( ...res.locals?.alokai?.metadata, scope: { integrationName, - extensionName, functionName, + ...(extensionName ? { extensionName } : {}), }, - errorBoundary: { - scope: { - integrationName, - functionName, - }, - }, + // errorBoundary: { + // scope: { + // integrationName, + // functionName, + // }, + // }, }; const { @@ -44,6 +44,7 @@ export function prepareApiFunction( extensions, customQueries, integrations, + integrationKey: integrationName, getApiClient: (integrationKey: string) => { if (!(integrationKey in integrations)) { const keys = Object.keys(integrations); @@ -62,6 +63,7 @@ export function prepareApiFunction( const innerMiddlewareContext: MiddlewareContext = { ...middlewareContext, + integrationKey, extensions: innerExtensions, customQueries: innerCustomQueries, }; diff --git a/packages/middleware/src/integrations/registerIntegrations.ts b/packages/middleware/src/integrations/registerIntegrations.ts index 7706906ba8..9ab14447c2 100644 --- a/packages/middleware/src/integrations/registerIntegrations.ts +++ b/packages/middleware/src/integrations/registerIntegrations.ts @@ -42,8 +42,9 @@ async function triggerExtendAppHook( if (extendApp) { const loggerWithMetadata = injectMetadata(logger, () => ({ scope: { + integrationName: tag, extensionName: name, - type: "hook", + type: "bootstrapHook", hookName: "extendApp", }, })); @@ -64,11 +65,23 @@ async function loadIntegration( ); const rawExtensions = createRawExtensions(apiClient, integration); const extensions = createExtensions(rawExtensions, alokai); + const loggerWithMetadata = injectMetadata(getLogger(alokai), (metadata) => ({ + ...metadata, + scope: { + ...metadata?.scope, + hookName: "init", + type: "bootstrapHook", + integrationName: tag, + }, + })); const initConfig = await getInitConfig({ apiClient, integration, tag, - alokai, + alokai: { + ...alokai, + logger: loggerWithMetadata, + }, }); const configuration = { ...integration.configuration, diff --git a/packages/middleware/src/logger/injectMetadata.ts b/packages/middleware/src/logger/injectMetadata.ts index 38d898d2eb..bd1a4ed730 100644 --- a/packages/middleware/src/logger/injectMetadata.ts +++ b/packages/middleware/src/logger/injectMetadata.ts @@ -1,10 +1,13 @@ import type { LoggerInterface } from "@vue-storefront/logger"; +import { AlokaiLocal } from "../types"; const METHODS_TO_SKIP = ["log"]; +type Metadata = AlokaiLocal["metadata"] & Record; + export function injectMetadata( logger: LoggerInterface, - metadataGetter: (metadata: Record) => Record + metadataGetter: (metadata: Metadata) => Metadata ): LoggerInterface { return new Proxy(logger, { get(target, prop) { diff --git a/packages/middleware/src/types/common.ts b/packages/middleware/src/types/common.ts index 492fb9fa85..c6de3a51f2 100644 --- a/packages/middleware/src/types/common.ts +++ b/packages/middleware/src/types/common.ts @@ -11,6 +11,14 @@ import { import { WithRequired } from "./index"; import { ApiClient, ApiClientConfig, ApiClientFactory } from "./server"; +type ResponseWithAlokaiLocals = Response< + any, + { + alokai?: AlokaiLocal; + [key: string]: any; + } +>; + export type ExtensionEndpointHandler = ApiClientMethod & { _extensionName?: string; }; @@ -84,7 +92,7 @@ export interface ApiClientExtension { }) => Promise | void; hooks?: ( req: Request, - res: Response, + res: ResponseWithAlokaiLocals, hooksContext: AlokaiContainer ) => ApiClientExtensionHooks; } @@ -102,7 +110,11 @@ export interface Integration< ) => ApiClientExtension[]; customQueries?: Record; initConfig?: TObject; - errorHandler?: (error: unknown, req: Request, res: Response) => void; + errorHandler?: ( + error: unknown, + req: Request, + res: ResponseWithAlokaiLocals + ) => void; } export interface RequestParams { @@ -119,7 +131,11 @@ export interface IntegrationLoaded< configuration: CONFIG; extensions: ApiClientExtension[]; customQueries?: Record; - errorHandler: (error: unknown, req: Request, res: Response) => void; + errorHandler: ( + error: unknown, + req: Request, + res: ResponseWithAlokaiLocals + ) => void; } export interface LoadInitConfigProps { @@ -136,10 +152,11 @@ export type IntegrationsLoaded< export interface MiddlewareContext { req: Request; - res: Response; + res: ResponseWithAlokaiLocals; extensions: ApiClientExtension[]; customQueries: Record; integrations: IntegrationsLoaded; + integrationKey: string; getApiClient: ( integrationName: string ) => ApiClient; @@ -162,6 +179,7 @@ export interface ApiContext { export type CallableContext = { middleware: MiddlewareContext; + integrationKey: string; }; export interface ApplyingContextHooks { @@ -202,3 +220,26 @@ export type WithoutContext = { ? (...arguments_: P) => R : never; }; + +export type LogScope = { + integrationName: string; + extensionName?: string; + functionName?: string; + hookName?: + | "extendApp" + | "hooks" + | "onCreate" + | "init" + | "beforeCall" + | "beforeCreate" + | "afterCall" + | "afterCreate"; + type: "endpoint" | "bootstrapHook" | "requestHook"; +}; + +export type AlokaiLocal = { + metadata?: { + context?: string; + scope?: LogScope; + }; +};