HEX
Server: nginx/1.24.0
System: Linux nowruzgan 6.8.0-57-generic #59-Ubuntu SMP PREEMPT_DYNAMIC Sat Mar 15 17:40:59 UTC 2025 x86_64
User: babak (1000)
PHP: 8.3.6
Disabled: NONE
Upload Files
File: //usr/share/opensearch-dashboards/node_modules/@elastic/ecs-pino-format/index.js
// 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')

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.
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
    }
  }

  let apm = null
  let apmServiceName = 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
      // 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

  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,
          // Warning: silently drop any "ecs" value from `base`. See
          // "ecs.version" comment below.
          ecs,
          ...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 }
        }

        // 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
        }

        return ecsBindings
      },

      log (obj) {
        const {
          req,
          res,
          err,
          ...ecsObj
        } = obj

        // 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 (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
            const span = apm.currentSpan
            // istanbul ignore else
            if (span) {
              ecsObj.span = ecsObj.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
}

// 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