packages/ecs-pino-format/index.js (144 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. 'use strict' const { version, formatError, formatHttpRequest, formatHttpResponse } = require('@elastic/ecs-helpers') let triedElasticApmImport = false let elasticApm = null /** * Create options for `pino(...)` that configure it for ecs-logging output. * * @param {Config} [opts] - See index.d.ts. */ function ecsFormat (opts) { // 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 if (apmIntegration) { // istanbul ignore if if (opts && opts._elasticApm) { // `opts._elasticApm` is an internal/testing-only option to be used // for testing in the APM agent where the import is a local path // rather than "elastic-apm-node". elasticApm = opts._elasticApm } else if (!triedElasticApmImport) { triedElasticApmImport = true // We lazily require this module here instead of at the top-level to // avoid a possible circular-require if the user code does // `require('@elastic/ecs-pino-format')` and has a "node_modules/" // where 'elastic-apm-node' shares the same ecs-pino-format install. try { elasticApm = require('elastic-apm-node') } catch (ex) { // Silently ignore. } } if (elasticApm && elasticApm.isStarted && elasticApm.isStarted()) { apm = elasticApm } } 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 // istanbul ignore next if (serviceVersion == null && apm) { 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 = { messageKey: 'message', timestamp: () => `,"@timestamp":"${new Date().toISOString()}"`, formatters: { level (label, number) { return { 'log.level': label } }, bindings (bindings) { const { // `pid` and `hostname` are default bindings, unless overriden by // a `base: {...}` passed to logger creation. pid, hostname, // name is defined if `log = pino({name: 'my name', ...})` name, ...ecsBindings } = bindings if (pid !== undefined) { // https://www.elastic.co/guide/en/ecs/current/ecs-process.html#field-process-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 } if (name !== undefined) { // https://www.elastic.co/guide/en/ecs/current/ecs-log.html#field-log-logger ecsBindings['log.logger'] = name } // 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) return ecsBindings }, log (obj) { const { req, res, err, ...ecsObj } = obj if (!wasBindingsCalled) { addStaticEcsBindings(ecsObj) } if (apm) { // https://www.elastic.co/guide/en/ecs/current/ecs-tracing.html const tx = apm.currentTransaction if (tx) { ecsObj['trace.id'] = tx.traceId ecsObj['transaction.id'] = tx.id const span = apm.currentSpan // istanbul ignore else if (span) { ecsObj['span.id'] = span.id } } } // https://www.elastic.co/guide/en/ecs/current/ecs-http.html if (err !== undefined) { if (!convertErr) { ecsObj.err = err } else { formatError(ecsObj, err) } } // https://www.elastic.co/guide/en/ecs/current/ecs-http.html if (req !== undefined) { if (!convertReqRes) { ecsObj.req = req } else { formatHttpRequest(ecsObj, req) } } if (res !== undefined) { if (!convertReqRes) { ecsObj.res = res } else { formatHttpResponse(ecsObj, res) } } return ecsObj } } } return ecsPinoOptions } // Exports to support the following import-styles from JS and TS code: // 1. `const { ecsFormat } = require('@elastic/ecs-pino-format)` in JS and TS. // The preferred import style for JS code using CommonJS. // 2. `import { ecsFormat } from '@elastic/ecs-pino-format'` in JS and TS. // ES module (ESM) import style. This is the preferred style for TypeScript // code and for JS developers using ESM. // 3. `const ecsFormat = require('@elastic/ecs-pino-format')` in JS. // The old, deprecated import method. Still supported for backward compat. // 4. `import ecsFormat from '@elastic/ecs-pino-format'` in JS and TS. // This works, but is deprecated. Prefer #2 style. // 5. `import * as EcsPinoFormat from '@elastic/ecs-pino-format'` in TS. // One must then use `EcsPinoFormat.ecsFormat()`. module.exports = ecsFormat // Required to support style 3. module.exports.ecsFormat = ecsFormat module.exports.default = ecsFormat // Required to support style 4.