@elastic/ecs-pino-format
Advanced tools
Comparing version 1.3.0 to 1.4.0
@@ -8,2 +8,3 @@ import type { LoggerOptions } from "pino"; | ||
*/ | ||
convertErr?: boolean; | ||
@@ -14,3 +15,5 @@ /** | ||
*/ | ||
convertReqRes?: boolean; | ||
/** | ||
@@ -22,10 +25,28 @@ * Whether to automatically integrate with | ||
* | ||
* - "service.name" - the configured serviceName in the agent | ||
* - "event.dataset" - set to "$serviceName.log" for correlation in Kibana | ||
* - "trace.id", "transaction.id", and "span.id" - if there is a current | ||
* active trace when the log call is made | ||
* | ||
* and also the following fields, if not already specified in this config: | ||
* | ||
* - "service.name" - the configured `serviceName` in the agent | ||
* - "service.version" - the configured `serviceVersion` in the agent | ||
* - "service.environment" - the configured `environment` in the agent | ||
* - "service.node.name" - the configured `serviceNodeName` in the agent | ||
* - "event.dataset" - set to `${serviceName}` for correlation in Kibana | ||
* | ||
* Default true. | ||
*/ | ||
apmIntegration?: boolean; | ||
/** Specify "service.name" field. Defaults to a value from the APM agent, if available. */ | ||
serviceName?: string; | ||
/** Specify "service.version" field. Defaults to a value from the APM agent, if available. */ | ||
serviceVersion?: string; | ||
/** Specify "service.environment" field. Defaults to a value from the APM agent, if available. */ | ||
serviceEnvironment?: string; | ||
/** Specify "service.node.name" field. Defaults to a value from the APM agent, if available. */ | ||
serviceNodeName?: string; | ||
/** Specify "event.dataset" field. Defaults `${serviceName}`. */ | ||
eventDataset?: string; | ||
} | ||
@@ -32,0 +53,0 @@ |
181
index.js
@@ -27,42 +27,18 @@ // Licensed to Elasticsearch B.V. under one or more contributor | ||
const { hasOwnProperty } = Object.prototype | ||
let triedElasticApmImport = false | ||
let elasticApm = null | ||
// Create options for `pino(...)` that configure it for ecs-logging output. | ||
// | ||
// @param {Object} opts - Optional. | ||
// - {Boolean} opts.convertErr - Whether to convert a logged `err` field | ||
// to ECS error fields. Default true, to match Pino's default of having | ||
// an `err` serializer. | ||
// - {Boolean} opts.convertReqRes - Whether to convert logged `req` and `res` | ||
// HTTP request and response fields to ECS HTTP, User agent, and URL | ||
// fields. Default false. | ||
// - {Boolean} opts.apmIntegration - Whether to automatically integrate with | ||
// Elastic APM (https://github.com/elastic/apm-agent-nodejs). If a started | ||
// APM agent is detected, then log records will include the following | ||
// fields: | ||
// - "service.name" - the configured serviceName in the agent | ||
// - "event.dataset" - set to "$serviceName.log" for correlation in Kibana | ||
// - "trace.id", "transaction.id", and "span.id" - if there is a current | ||
// active trace when the log call is made | ||
// Default true. | ||
/** | ||
* Create options for `pino(...)` that configure it for ecs-logging output. | ||
* | ||
* @param {Config} [opts] - See index.d.ts. | ||
*/ | ||
function createEcsPinoOptions (opts) { | ||
let convertErr = true | ||
let convertReqRes = false | ||
let apmIntegration = true | ||
if (opts) { | ||
if (hasOwnProperty.call(opts, 'convertErr')) { | ||
convertErr = opts.convertErr | ||
} | ||
if (hasOwnProperty.call(opts, 'convertReqRes')) { | ||
convertReqRes = opts.convertReqRes | ||
} | ||
if (hasOwnProperty.call(opts, 'apmIntegration')) { | ||
apmIntegration = opts.apmIntegration | ||
} | ||
} | ||
// istanbul ignore next | ||
opts = opts || {} | ||
const convertErr = opts.convertErr != null ? opts.convertErr : true | ||
const convertReqRes = opts.convertReqRes != null ? opts.convertReqRes : false | ||
const apmIntegration = opts.apmIntegration != null ? opts.apmIntegration : true | ||
let apm = null | ||
let apmServiceName = null | ||
if (apmIntegration) { | ||
@@ -89,13 +65,52 @@ // istanbul ignore if | ||
apm = elasticApm | ||
// Elastic APM v3.11.0 added getServiceName(). Fallback to private `apm._conf`. | ||
// istanbul ignore next | ||
apmServiceName = apm.getServiceName | ||
? apm.getServiceName() | ||
: apm._conf.serviceName | ||
} | ||
} | ||
let isServiceNameInBindings = false | ||
let isEventDatasetInBindings = false | ||
let serviceName = opts.serviceName | ||
if (serviceName == null && apm) { | ||
// istanbul ignore next | ||
serviceName = (apm.getServiceName | ||
? apm.getServiceName() // added in elastic-apm-node@3.11.0 | ||
: apm._conf.serviceName) // fallback to private `_conf` | ||
} | ||
let serviceVersion = opts.serviceVersion | ||
if (serviceVersion == null && apm) { | ||
// istanbul ignore next | ||
serviceVersion = (apm.getServiceVersion | ||
? apm.getServiceVersion() // added in elastic-apm-node@... | ||
: apm._conf.serviceVersion) // fallback to private `_conf` | ||
} | ||
let serviceEnvironment = opts.serviceEnvironment | ||
if (serviceEnvironment == null && apm) { | ||
// istanbul ignore next | ||
serviceEnvironment = (apm.getServiceEnvironment | ||
? apm.getServiceEnvironment() // added in elastic-apm-node@... | ||
: apm._conf.environment) // fallback to private `_conf` | ||
} | ||
let serviceNodeName = opts.serviceNodeName | ||
if (serviceNodeName == null && apm) { | ||
// istanbul ignore next | ||
serviceNodeName = (apm.getServiceNodeName | ||
? apm.getServiceNodeName() // added in elastic-apm-node@... | ||
: apm._conf.serviceNodeName) // fallback to private `_conf` | ||
} | ||
let eventDataset = opts.eventDataset | ||
if (eventDataset == null && serviceName) { | ||
eventDataset = serviceName | ||
} | ||
let wasBindingsCalled = false | ||
function addStaticEcsBindings (obj) { | ||
obj['ecs.version'] = version | ||
if (serviceName) { obj['service.name'] = serviceName } | ||
if (serviceVersion) { obj['service.version'] = serviceVersion } | ||
if (serviceEnvironment) { obj['service.environment'] = serviceEnvironment } | ||
if (serviceNodeName) { obj['service.node.name'] = serviceNodeName } | ||
if (eventDataset) { obj['event.dataset'] = eventDataset } | ||
} | ||
const ecsPinoOptions = { | ||
@@ -117,5 +132,2 @@ messageKey: 'message', | ||
name, | ||
// Warning: silently drop any "ecs" value from `base`. See | ||
// "ecs.version" comment below. | ||
ecs, | ||
...ecsBindings | ||
@@ -126,20 +138,18 @@ } = bindings | ||
// https://www.elastic.co/guide/en/ecs/current/ecs-process.html#field-process-pid | ||
ecsBindings.process = { pid: pid } | ||
ecsBindings['process.pid'] = pid | ||
} | ||
if (hostname !== undefined) { | ||
// https://www.elastic.co/guide/en/ecs/current/ecs-host.html#field-host-hostname | ||
ecsBindings.host = { hostname: hostname } | ||
ecsBindings['host.hostname'] = hostname | ||
} | ||
if (name !== undefined) { | ||
// https://www.elastic.co/guide/en/ecs/current/ecs-log.html#field-log-logger | ||
ecsBindings.log = { logger: name } | ||
ecsBindings['log.logger'] = name | ||
} | ||
// Note if service.name & event.dataset are set, to not do so again below. | ||
if (bindings.service && bindings.service.name) { | ||
isServiceNameInBindings = true | ||
} | ||
if (bindings.event && bindings.event.dataset) { | ||
isEventDatasetInBindings = true | ||
} | ||
// With `pino({base: null, ...})` the `formatters.bindings` is *not* | ||
// called. In this case we need to make sure to add our static bindings | ||
// in `log()` below. | ||
wasBindingsCalled = true | ||
addStaticEcsBindings(ecsBindings) | ||
@@ -157,52 +167,16 @@ return ecsBindings | ||
// https://www.elastic.co/guide/en/ecs/current/ecs-ecs.html | ||
// For "ecs.version" we take a heavier-handed approach, because it is | ||
// a require ecs-logging field: overwrite any possible "ecs" value from | ||
// the log statement. This means we don't need to spend the time | ||
// guarding against "ecs" being null, Array, Buffer, Date, etc. | ||
ecsObj.ecs = { version } | ||
if (!wasBindingsCalled) { | ||
addStaticEcsBindings(ecsObj) | ||
} | ||
if (apm) { | ||
// A mis-configured APM Agent can be "started" but not have a | ||
// "serviceName". | ||
if (apmServiceName) { | ||
// Per https://github.com/elastic/ecs-logging/blob/master/spec/spec.json | ||
// "service.name" and "event.dataset" should be automatically set | ||
// if not already by the user. | ||
if (!isServiceNameInBindings) { | ||
const service = ecsObj.service | ||
if (service === undefined) { | ||
ecsObj.service = { name: apmServiceName } | ||
} else if (!isVanillaObject(service)) { | ||
// Warning: "service" type conflicts with ECS spec. Overwriting. | ||
ecsObj.service = { name: apmServiceName } | ||
} else if (typeof service.name !== 'string') { | ||
ecsObj.service.name = apmServiceName | ||
} | ||
} | ||
if (!isEventDatasetInBindings) { | ||
const event = ecsObj.event | ||
if (event === undefined) { | ||
ecsObj.event = { dataset: apmServiceName + '.log' } | ||
} else if (!isVanillaObject(event)) { | ||
// Warning: "event" type conflicts with ECS spec. Overwriting. | ||
ecsObj.event = { dataset: apmServiceName + '.log' } | ||
} else if (typeof event.dataset !== 'string') { | ||
ecsObj.event.dataset = apmServiceName + '.log' | ||
} | ||
} | ||
} | ||
// https://www.elastic.co/guide/en/ecs/current/ecs-tracing.html | ||
const tx = apm.currentTransaction | ||
if (tx) { | ||
ecsObj.trace = ecsObj.trace || {} | ||
ecsObj.trace.id = tx.traceId | ||
ecsObj.transaction = ecsObj.transaction || {} | ||
ecsObj.transaction.id = tx.id | ||
ecsObj['trace.id'] = tx.traceId | ||
ecsObj['transaction.id'] = tx.id | ||
const span = apm.currentSpan | ||
// istanbul ignore else | ||
if (span) { | ||
ecsObj.span = ecsObj.span || {} | ||
ecsObj.span.id = span.id | ||
ecsObj['span.id'] = span.id | ||
} | ||
@@ -245,17 +219,2 @@ } | ||
// Return true if the given arg is a "vanilla" object. Roughly the intent is | ||
// whether this is basic mapping of string keys to values that will serialize | ||
// as a JSON object. | ||
// | ||
// Currently, it excludes Map. The uses above don't really expect a user to: | ||
// service = new Map([["foo", "bar"]]) | ||
// log.info({ service }, '...') | ||
// | ||
// There are many ways tackle this. See some attempts and benchmarks at: | ||
// https://gist.github.com/trentm/34131a92eede80fd2109f8febaa56f5a | ||
function isVanillaObject (o) { | ||
return (typeof o === 'object' && | ||
(!o.constructor || o.constructor.name === 'Object')) | ||
} | ||
module.exports = createEcsPinoOptions |
{ | ||
"name": "@elastic/ecs-pino-format", | ||
"version": "1.3.0", | ||
"version": "1.4.0", | ||
"description": "A formatter for the pino logger compatible with Elastic Common Schema.", | ||
@@ -31,5 +31,7 @@ "main": "index.js", | ||
}, | ||
"homepage": "https://github.com/elastic/ecs-logging-nodejs/blob/master/loggers/pino/README.md", | ||
"homepage": "https://github.com/elastic/ecs-logging-nodejs/blob/main/loggers/pino/README.md", | ||
"scripts": { | ||
"test": "standard && tap --100 --timeout ${TAP_TIMEOUT:-10} test/*.test.js" | ||
"lint": "standard", | ||
"lint:fix": "standard --fix", | ||
"test": "tap --timeout ${TAP_TIMEOUT:-10} test/*.test.js" | ||
}, | ||
@@ -46,3 +48,3 @@ "engines": { | ||
"ajv-formats": "^1.5.1", | ||
"elastic-apm-node": "^3.14.0", | ||
"elastic-apm-node": "^3.23.0", | ||
"express": "^4.17.1", | ||
@@ -53,4 +55,4 @@ "pino": "^6.0.0", | ||
"standard": "16.x", | ||
"tap": "^14.x" | ||
"tap": "^15.0.10" | ||
} | ||
} |
@@ -5,3 +5,3 @@ <img align="right" width="auto" height="auto" src="https://www.elastic.co/static-res/images/elastic-logo-200.png"> | ||
[![Build Status](https://apm-ci.elastic.co/buildStatus/icon?job=apm-agent-nodejs%2Fecs-logging-nodejs-mbp%2Fmaster)](https://apm-ci.elastic.co/job/apm-agent-nodejs/job/ecs-logging-nodejs-mbp/job/master/) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) | ||
[![Build Status](https://apm-ci.elastic.co/buildStatus/icon?job=apm-agent-nodejs%2Fecs-logging-nodejs-mbp%2Fmain)](https://apm-ci.elastic.co/job/apm-agent-nodejs/job/ecs-logging-nodejs-mbp/job/main/) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) | ||
@@ -32,3 +32,3 @@ This Node.js package provides a formatter for the [pino](https://www.npmjs.com/package/pino) | ||
const log = pino(ecsFormat()) | ||
const log = pino(ecsFormat(/* options */)) | ||
log.info('Hello world') | ||
@@ -43,4 +43,4 @@ | ||
```sh | ||
{"log.level":"info","@timestamp":"2021-01-19T22:51:12.142Z","ecs":{"version":"1.6.0"},"process":{"pid":82240},"host":{"hostname":"pink.local"},"message":"Hello world"} | ||
{"log.level":"warn","@timestamp":"2021-01-19T22:51:12.143Z","ecs":{"version":"1.6.0"},"process":{"pid":82240},"host":{"hostname":"pink.local"},"module":"foo","message":"From child"} | ||
{"log.level":"info","@timestamp":"2023-10-16T18:08:02.601Z","process.pid":74325,"host.hostname":"pink.local","ecs.version":"1.6.0","message":"Hello world"} | ||
{"log.level":"warn","@timestamp":"2023-10-16T18:08:02.602Z","process.pid":74325,"host.hostname":"pink.local","ecs.version":"1.6.0","module":"foo","message":"From child"} | ||
``` | ||
@@ -47,0 +47,0 @@ |
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
23586
5
235