diff --git a/docs/morgan.asciidoc b/docs/morgan.asciidoc index 1166da5..51b9606 100644 --- a/docs/morgan.asciidoc +++ b/docs/morgan.asciidoc @@ -204,6 +204,35 @@ Integration with Elastic APM can be explicitly disabled via the app.use(morgan(ecsFormat({ apmIntegration: false }))); ---- +[float] +[[morgan-logHook]] +=== Customizing Log Records + +To add custom fields to log records or remove sensitive data, you can use the `logHook` option. This option is a function that takes the formatted log record and returns a new log record. Here's an example that adds a label to the log record and removes the `authorization` and `cookie` headers: + +[source,js] +---- +app.use(morgan(ecsFormat({ + logHook: ({ record, req, res }) => { + // Omit sensitive headers + const { authorization, cookie, ...headers } = record.http.request.headers; + return { + ...record, + labels: { + ...record.labels, + customLabel: res.locals.someValue + }, + http: { + ...record.http, + request: { + ...record.http.request, + headers + } + } + }; + }, +}))); +---- [float] [[morgan-ref]] @@ -222,5 +251,6 @@ app.use(morgan(ecsFormat({ apmIntegration: false }))); ** `serviceEnvironment` +{type-string}+ A "service.environment" value. If specified this overrides any value from an active APM agent. ** `serviceNodeName` +{type-string}+ A "service.node.name" value. If specified this overrides any value from an active APM agent. ** `eventDataset` +{type-string}+ A "event.dataset" value. If specified this overrides the default of using `${serviceVersion}`. +** `logHook` +{type-function}+ A function that takes the formatted log record and returns a new log record. This can be used to add custom fields to the log record or remove sensitive data. It is passed an object with three properties: `record`, `req`, and `res`. Create a formatter for morgan that emits in ECS Logging format. diff --git a/packages/ecs-morgan-format/index.d.ts b/packages/ecs-morgan-format/index.d.ts index 3ca96a8..33cd253 100644 --- a/packages/ecs-morgan-format/index.d.ts +++ b/packages/ecs-morgan-format/index.d.ts @@ -1,3 +1,4 @@ +import type { IncomingMessage, ServerResponse } from 'http'; import type { FormatFn } from "morgan"; interface Config { @@ -42,6 +43,8 @@ interface Config { /** Specify "event.dataset" field. Defaults `${serviceName}`. */ eventDataset?: string; + /** Callback for custom modification of the fields */ + logHook: (event: { record: any, req: IncomingMessage, res: ServerResponse }) => void; } declare function ecsFormat(config?: Config): FormatFn; diff --git a/packages/ecs-morgan-format/index.js b/packages/ecs-morgan-format/index.js index b3d2da7..e4e402a 100644 --- a/packages/ecs-morgan-format/index.js +++ b/packages/ecs-morgan-format/index.js @@ -175,6 +175,8 @@ function ecsFormat (opts) { formatHttpRequest(ecsFields, req) formatHttpResponse(ecsFields, res) + opts.logHook && opts.logHook({ record: ecsFields, req, res }) + return stringify(ecsFields) } } diff --git a/packages/ecs-morgan-format/test/basic.test.js b/packages/ecs-morgan-format/test/basic.test.js index 090986e..dc1c1f3 100644 --- a/packages/ecs-morgan-format/test/basic.test.js +++ b/packages/ecs-morgan-format/test/basic.test.js @@ -235,3 +235,26 @@ test('can configure correlation fields', t => { t.end() }) }) + +test('can provide custom fields', t => { + t.plan(2) + + const stream = split().on('data', line => { + const rec = JSON.parse(line) + t.equal(rec.labels.custom, 'customValue') + }) + const logger = morgan( + ecsFormat({ + logHook ({ record, req, res }) { + record.labels = record.labels || {} + record.labels.custom = 'customValue' + } + }), + { stream } + ) + + makeExpressServerAndRequest(logger, '/', {}, null, function (err) { + t.error(err) + t.end() + }) +})