From 01abbd39828bbe37c799bd778c0b1334a2e0ec36 Mon Sep 17 00:00:00 2001 From: Daniel Ziegler Date: Mon, 15 Jan 2024 07:49:06 +0100 Subject: [PATCH 1/2] feature - Improve logging --- README.md | 8 ++- package-lock.json | 4 +- package.json | 2 +- src/appInsights/appInsightsWrapper.ts | 76 +++++++++++++++++++-------- 4 files changed, 64 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index bedf033..27543ed 100644 --- a/README.md +++ b/README.md @@ -160,8 +160,8 @@ export default middleware(functionHandler, [], [postFunction]); ### Logging and Tracing with appInsights -To enhance the logging and tracing with appInsights you can wrap your function with the appInsightWrapper. Currently, this will log the query-parameter -and binding-context of request into the customProperties, which will make your logs more searchable. +To enhance the logging and tracing with appInsights you can wrap your function with the appInsightWrapper. +Currently, this will add request parameters and workflow data into the customProperties, which will make your logs more searchable. Use the `AppInsightForHttpTrigger` for your http-functions: ```typescript @@ -172,6 +172,10 @@ export default middleware([AppInsightForHttpTrigger.setup], handler, [AppInsight and the `AppInsightForNonHttpTrigger` for functions with different kinds of trigger (e.g. `activityTrigger` or `timerTrigger`). +Per default the request and response bodies of http requests are only logged if the request fails. You can customize this +behavior by using `AppInsightForHttpTrigger.finalizeWithConfig(...)` instead of `AppInsightForHttpTrigger.finalizeAppInsight`. +There you can also provide a function to sanitize the request and response bodies to prevent logging of sensitive data. + ## Support and Contact If you encounter any issues or have questions about using this middleware, please file an issue in this repository or contact the maintainers at or . \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 71b0e59..c51ec60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@senacor/azure-function-middleware", - "version": "2.2.1", + "version": "2.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@senacor/azure-function-middleware", - "version": "2.2.1", + "version": "2.3.0", "license": "MIT", "dependencies": { "@azure/functions": "^3.0.0", diff --git a/package.json b/package.json index ad22467..8f69ae5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@senacor/azure-function-middleware", - "version": "2.2.2", + "version": "2.3.0", "description": "Middleware for azure functions to handle authentication, authorization, error handling and logging", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/appInsights/appInsightsWrapper.ts b/src/appInsights/appInsightsWrapper.ts index a14d39d..169b711 100644 --- a/src/appInsights/appInsightsWrapper.ts +++ b/src/appInsights/appInsightsWrapper.ts @@ -4,6 +4,10 @@ import { TelemetryClient } from 'applicationinsights'; import { consoleLogger, createAppInsightsLogger } from './Logger'; +type LogBehavior = 'always' | 'on_error' | 'on_success' | 'never'; +type LogDataSanitizer = (data: unknown) => unknown; +const noOpLogDataSanitizer: LogDataSanitizer = (data) => data; + const telemetryClients: { [key: string]: TelemetryClient } = {}; const isDisabled = @@ -36,12 +40,7 @@ if (!isDisabled) { console.log('Started appInsights'); } -const setupAppInsight = async (context: Context): Promise => { - if (isDisabled) { - context.log = consoleLogger; - return; - } - +const setupTelemetryClient = (context: Context, additionalProperties: object) => { context.log('Setting up AppInsights'); const telemetryClient = new TelemetryClient(); @@ -49,18 +48,55 @@ const setupAppInsight = async (context: Context): Promise => { telemetryClients[context.invocationId] = telemetryClient; context.log = createAppInsightsLogger(telemetryClient); - // excluding headers because it contains sensible data - const { headers, ...includedProperties } = context.bindingData; + const { invocationId, sys } = context.bindingData; telemetryClient.commonProperties = { - ...includedProperties, + invocationId, + sys, + ...additionalProperties, ...appInsights.defaultClient.commonProperties, }; context.log('Set up AppInsights'); }; -const finalizeAppInsightForHttpTrigger = async (context: Context, req: HttpRequest): Promise => { +const setupAppInsightForHttpTrigger = async (context: Context): Promise => { + if (isDisabled) { + context.log = consoleLogger; + return; + } + + setupTelemetryClient(context, { params: context.req?.params }); +}; + +const setupAppInsightForNonHttpTrigger = async (context: Context): Promise => { + if (isDisabled) { + context.log = consoleLogger; + return; + } + + setupTelemetryClient(context, { workflowData: context.bindingData.workflowData }); +}; + +const shouldLog = (logBehavior: LogBehavior, isError: boolean) => { + switch (logBehavior) { + case 'always': + return true; + case 'on_error': + return isError; + case 'on_success': + return !isError; + case 'never': + return false; + } +}; + +const finalizeAppInsightForHttpTrigger = async ( + context: Context, + req: HttpRequest, + logBodyBehavior: LogBehavior = 'on_error', + bodySanitizer: LogDataSanitizer = noOpLogDataSanitizer, +): Promise => { if (isDisabled) { return; } @@ -84,10 +120,9 @@ const finalizeAppInsightForHttpTrigger = async (context: Context, req: HttpReque context.log.warn("context.res is empty and properly shouldn't be"); } - const properties: { [key: string]: unknown } = {}; - - if (context?.res?.status >= 400) { - properties.body = context.req?.body ?? 'NO_REQ_BODY'; + if (shouldLog(logBodyBehavior, context?.res?.status >= 400)) { + context.log('Request body:', context.req?.body ? bodySanitizer(context.req?.body) : 'NO_REQ_BODY'); + context.log('Response body:', context.res?.body ? bodySanitizer(context.res?.body) : 'NO_RES_BODY'); } telemetryClient.trackRequest({ @@ -98,7 +133,6 @@ const finalizeAppInsightForHttpTrigger = async (context: Context, req: HttpReque url: req.url, duration: 0, id: correlationContext?.operation?.id ?? 'UNDEFINED', - properties, }); telemetryClient.flush(); @@ -123,8 +157,6 @@ const finalizeAppInsightForNonHttpTrigger = async (context: Context): Promise (context: Context, req: HttpRequest) => + finalizeAppInsightForHttpTrigger(context, req, logBodyBehavior, bodySanitizer), }; const AppInsightForNoNHttpTrigger = { - setup: setupAppInsight, + setup: setupAppInsightForNonHttpTrigger, finalizeAppInsight: finalizeAppInsightForNonHttpTrigger, }; -export { AppInsightForHttpTrigger, AppInsightForNoNHttpTrigger }; +export { AppInsightForHttpTrigger, AppInsightForNoNHttpTrigger, LogBehavior, LogDataSanitizer }; From 28be25b74c7b01ad79d8613274f0ed1e7098fea7 Mon Sep 17 00:00:00 2001 From: Daniel Ziegler Date: Mon, 15 Jan 2024 16:51:54 +0100 Subject: [PATCH 2/2] feature - Improve logging --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14582ed..0b12bd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 2.3.0 (15.01.2024) +- Do not add the full content of `context.bindingData` to `customDimensions` for app insights logging anymore as it contains i.e. the request body. ++ Add `AppInsightForHttpTrigger.finalizeWithConfig` which allows you to configure when the request and response body should be logged and allows you to use a body sanitizer to remove sensitive data. + ## 2.2.2 (03.11.2023) + Added the `context` as a parameter for the `errorResponseHandler` function to enhance error handling capabilities.