Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 39 additions & 51 deletions lib/config/normalizers.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,17 @@ const {
CONTEXT_MANAGER_ASYNCLOCALSTORAGE,
} = require('./schema');

// TODO: move this typedef to logger.js
/**
* @typedef {Object} Logger
* @property {function(Object | String, any, any, any): undefined} log
* @property {function(Object | String, any, any, any): undefined} info
* @property {function(Object | String, any, any, any): undefined} warn
* @property {function(Object | String, any, any, any): undefined} error
* @property {function(Object | String, any, any, any): undefined} fatal
* @property {function(Object | String, any, any, any): undefined} debug
* @property {function(Object | String, any, any, any): undefined} trace
*/

/**
* Normalizes the key/value pairs properties of the config options object
* KeyValuePairs config vars are either an object or a comma-separated string
* of key=value pairs (whitespace around the "key=value" strings is trimmed):
* {'foo': 'bar', 'eggs': 'spam'} => [['foo', 'bar'], ['eggs', 'spam']]
* foo=bar, eggs=spam => [['foo', 'bar'], ['eggs', 'spam']]
*
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
* @param {String[]} fields the list of fields to normalize as key/value pair
* @param {Record<string, unknown>} defaults the configuration defaults
* @param {Logger} logger
* @param {Record<String, unknown>} defaults the configuration defaults
* @param {import('../logging.js').Logger} logger
*/
function normalizeKeyValuePairs(opts, fields, defaults, logger) {
for (const key of fields) {
Expand Down Expand Up @@ -69,10 +57,10 @@ function normalizeKeyValuePairs(opts, fields, defaults, logger) {
/**
* Normalizes the number properties of the config options object
*
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
* @param {String[]} fields the list of fields to normalize as number
* @param {Record<string, unknown>} defaults the configuration defaults
* @param {Logger} logger
* @param {Record<String, unknown>} defaults the configuration defaults
* @param {import('../logging.js').Logger} logger
*/
function normalizeNumbers(opts, fields, defaults, logger) {
for (const key of fields) {
Expand All @@ -83,10 +71,10 @@ function normalizeNumbers(opts, fields, defaults, logger) {
/**
* Normalizes the number properties of the config options object
*
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
* @param {String[]} fields the list of fields to normalize as number
* @param {Record<string, unknown>} defaults the configuration defaults
* @param {Logger} logger
* @param {Record<String, unknown>} defaults the configuration defaults
* @param {import('../logging.js').Logger} logger
*/
function normalizeInfinity(opts, fields, defaults, logger) {
for (const key of fields) {
Expand All @@ -97,8 +85,8 @@ function normalizeInfinity(opts, fields, defaults, logger) {
/**
* Translates a string byte size, e.g. '10kb', into an integer number of bytes.
*
* @param {string} input
* @returns {number|undefined}
* @param {String} input
* @returns {Number|undefined}
*/
function bytes(input) {
const matches = input.match(/^(\d+)(b|kb|mb|gb)$/i);
Expand Down Expand Up @@ -130,10 +118,10 @@ function bytes(input) {
/**
* Normalizes the byte properties of the config options object
*
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
* @param {String[]} fields the list of fields to normalize as bytes
* @param {Record<string, unknown>} defaults the configuration defaults
* @param {Logger} logger
* @param {Record<String, unknown>} defaults the configuration defaults
* @param {import('../logging.js').Logger} logger
*/
function normalizeBytes(opts, fields, defaults, logger) {
for (const key of fields) {
Expand All @@ -152,12 +140,12 @@ function normalizeBytes(opts, fields, defaults, logger) {
* secondsFromDuration('-1ms', 's', ['ms', 's', 'm'], true) // => -0.001
* secondsFromDuration(500, 'ms', ['us', 'ms', 's', 'm'], false) // => 0.5
*
* @param {string|number} duration - Typically a string of the form `<num><unit>`,
* @param {String|Number} duration - Typically a string of the form `<num><unit>`,
* for example `30s`, `-1ms`, `2m`. The `defaultUnit` is used if a unit is
* not part of the string, or if duration is a number. If given as a string,
* decimal ('1.5s') and exponential-notation ('1e-3s') values are not allowed.
* @param {string} defaultUnit
* @param {Array<string>} allowedUnits - An array of the allowed unit strings. This
* @param {String} defaultUnit
* @param {Array<String>} allowedUnits - An array of the allowed unit strings. This
* array may include any number of `us`, `ms`, `s`, and `m`.
* @param {Boolean} allowNegative - Whether a negative number is allowed.
* @param {string} key - the config option key
Expand Down Expand Up @@ -236,10 +224,10 @@ function secondsFromDuration(
/**
* Normalizes the duration properties of the config options object
*
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
* @param {Array<Object>} fields the list of fields to normalize as duration (with name, defaultUnit, allowedUnits, allowNegative)
* @param {Record<string, unknown>} defaults the configuration defaults
* @param {Logger} logger
* @param {Record<String, unknown>} defaults the configuration defaults
* @param {import('../logging.js').Logger} logger
*/
function normalizeDurationOptions(opts, fields, defaults, logger) {
for (const optSpec of fields) {
Expand Down Expand Up @@ -291,10 +279,10 @@ function normalizeDurationOptions(opts, fields, defaults, logger) {
* comma-separated string (whitespace is trimmed):
* 'foo, bar' => ['foo', 'bar']
*
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
* @param {String[]} fields the list of fields to normalize as arrays
* @param {Record<string, unknown>} defaults the configuration defaults
* @param {Logger} logger
* @param {Record<String, unknown>} defaults the configuration defaults
* @param {import('../logging.js').Logger} logger
*/
function normalizeArrays(opts, fields, defaults, logger) {
for (const key of fields) {
Expand All @@ -307,10 +295,10 @@ function normalizeArrays(opts, fields, defaults, logger) {
/**
* Parses "true"|"false" to boolean if not a boolean already and returns it. Returns undefined otherwise
*
* @param {Logger} logger
* @param {string} key
* @param {import('../logging.js').Logger} logger
* @param {String} key
* @param {any} value
* @returns {boolean|undefined}
* @returns {Boolean|undefined}
*/
function strictBool(logger, key, value) {
if (typeof value === 'boolean') {
Expand All @@ -333,10 +321,10 @@ function strictBool(logger, key, value) {
* Boolean config vars are either already a boolean, or a string
* representation of the boolean value: `true` or `false`
*
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
* @param {String[]} fields the list of fields to normalize as boolean
* @param {Record<string, unknown>} defaults the configuration defaults
* @param {Logger} logger
* @param {Record<String, unknown>} defaults the configuration defaults
* @param {import('../logging.js').Logger} logger
*/
function normalizeBools(opts, fields, defaults, logger) {
for (const key of fields) {
Expand All @@ -347,10 +335,10 @@ function normalizeBools(opts, fields, defaults, logger) {
/**
* Checks validity of the URL properties of the config options object.
*
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
* @param {String[]} fields the list of fields to normalize as boolean
* @param {Record<string, unknown>} defaults the configuration defaults
* @param {Logger} logger
* @param {Record<String, unknown>} defaults the configuration defaults
* @param {import('../logging.js').Logger} logger
*/
function normalizeUrls(opts, fields, defaults, logger) {
for (const key of fields) {
Expand Down Expand Up @@ -378,10 +366,10 @@ function normalizeUrls(opts, fields, defaults, logger) {
* ['foo', /url/pathname$/] => ['foo'] (strings are placed into a specific config option)
* => [/url/pathname$/] (RegExps are placed into a specific config option)
*
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
* @param {String[]} fields the list of fields to normalize as boolean
* @param {Record<string, unknown>} defaults the configuration defaults
* @param {Logger} logger
* @param {Record<String, unknown>} defaults the configuration defaults
* @param {import('../logging.js').Logger} logger
*/
function normalizeIgnoreOptions(opts, fields, defaults, logger) {
// Params are meant to be used in upcoming changes
Expand Down Expand Up @@ -434,7 +422,7 @@ function normalizeIgnoreOptions(opts, fields, defaults, logger) {
* Normalizes the wildcard matchers of sanitizeFieldNames and thansforms the into RegExps
*
* TODO: we are doing the same to some ignoreOptions
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
*/
function normalizeSanitizeFieldNames(opts) {
if (opts.sanitizeFieldNames) {
Expand Down Expand Up @@ -475,10 +463,10 @@ function normalizeElasticsearchCaptureBodyUrls(opts) {
/**
* Makes sure the cloudProvider options is valid othherwise it set the default value.
*
* @param {Record<string, unknown>} opts the configuration options to normalize
* @param {Record<String, unknown>} opts the configuration options to normalize
* @param {String[]} fields the list of fields to normalize as duration
* @param {Record<string, unknown>} defaults the configuration defaults
* @param {Logger} logger
* @param {Record<String, unknown>} defaults the configuration defaults
* @param {import('../logging.js').Logger} logger
*/
function normalizeCloudProvider(opts, fields, defaults, logger) {
if ('cloudProvider' in opts) {
Expand Down
67 changes: 47 additions & 20 deletions lib/logging.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@

'use strict';

/**
* @typedef {Object} Logger
* @property {function(Record<string, any> | string, ...any): undefined} fatal
* @property {function(Record<string, any> | string, ...any): undefined} error
* @property {function(Record<string, any> | string, ...any): undefined} warn
* @property {function(Record<string, any> | string, ...any): undefined} info
* @property {function(Record<string, any> | string, ...any): undefined} debug
* @property {function(Record<string, any> | string, ...any): undefined} trace
*/

// Internal logging for the Elastic APM Node.js Agent.
//
// Promised interface:
Expand Down Expand Up @@ -81,25 +91,31 @@ class SafePinoDestWrapper {
}
}

// Create a pino logger for the agent.
//
// By default `createLogger()` will return a pino logger that logs to stdout
// in ecs-logging format, set to the "info" level.
//
// @param {String} levelName - Optional, default "info". It is meant to be one
// of the log levels specified in the top of file comment. For backward
// compatibility it falls back to "trace".
// @param {Object} customLogger - Optional. A custom logger object to which
// log messages will be passed. It must provide
// trace/debug/info/warn/error/fatal methods that take a string argument.
//
// Internally the agent uses structured logging using the pino API
// (https://getpino.io/#/docs/api?id=logger). However, with a custom logger,
// log record fields other than the *message* are dropped, to avoid issues
// with incompatible logger APIs.
//
// As a special case, if the provided logger is a *pino logger instance*,
// then it will be used directly.
/**
* Creates a pino logger for the agent.
*
* By default `createLogger()` will return a pino logger that logs to stdout
* in ecs-logging format, set to the "info" level.
*
* @param {String} levelName - Optional, default "info". It is meant to be one
* of the log levels specified in the top of file comment. For backward
* compatibility it falls back to "trace".
* @param {Object} customLogger - Optional. A custom logger object to which
* log messages will be passed. It must provide
* trace/debug/info/warn/error/fatal methods that take a string argument.
*
* Internally the agent uses structured logging using the pino API
* (https://getpino.io/#/docs/api?id=logger). However, with a custom logger,
* log record fields other than the *message* are dropped, to avoid issues
* with incompatible logger APIs.
*
* As a special case, if the provided logger is a *pino logger instance*,
* then it will be used directly.
*
* @param {string} [levelName=info] log level we want for the created logger
* @param {Logger} [customLogger] custom logger object provided by the user
* @returns {Logger}
*/
function createLogger(levelName, customLogger) {
let dest;
const serializers = {
Expand Down Expand Up @@ -163,11 +179,22 @@ function createLogger(levelName, customLogger) {
return logger;
}

/**
* Returns true if the logger is not ours
*
* @param {Logger} logger
* @returns {boolean}
*/
function isLoggerCustom(logger) {
return !logger[LOGGER_IS_OURS_SYM];
}

// Adjust the level on the given logger.
/**
* Adjust the level on the given logger.
*
* @param {Logger} logger
* @param {string} levelName
*/
function setLogLevel(logger, levelName) {
const pinoLevel = PINO_LEVEL_FROM_LEVEL_NAME[levelName];
if (!pinoLevel) {
Expand Down