Giter VIP home page Giter VIP logo

helix-log's Introduction

Helix Log Framework

Logging framework used within the helix project.

Status

codecov CircleCI GitHub license GitHub issues LGTM Code Quality Grade: JavaScript

Development

Build

npm install

Test

npm test

Lint

npm run lint

Usage

const { info, error } = require('@adobe/helix-log');

info('This is an info level message ', { foo: 42 }, '; dumping any javascript variable like console.log is supported');
error('You can log exceptions like this', new Error('This is a problem'));

API Reference

Classes

The default constructor can be used to get the current point in time as a BigDate.
BunyanStreamInterface

Bunyan stream that can be used to forward any bunyan messages to helix log.

CoralogixLogger

Sends log messages to the coralogix logging service.

You can customize the host, application and subsystem per log message by specifying the appropriate fields.

LoggerBase

Can be used as a base class/helper for implementing loggers.

This will first apply the filter, drop the message if the level is too low or if the filter returned undefined and will then call the subclass provided this._logImpl function for final processing.

This also wraps the entire logging logic into a promise enabled/async error handler that will log any errors using the rootLogger.

FormattedLoggerBase

Can be used as a base class/helper for implementing loggers that require a formatter.

Will do the same processing as LoggerBase and in addition call the formatter before invoking _logImpl.

ConsoleLogger

Logger that is especially designed to be used in node.js Print's to stderr; Marks errors, warns & debug messages with a colored [ERROR]/... prefix.

Formatter MUST produce strings. Default formatter is messageFormatConsole.

MultiLogger

Simple logger that forwards all messages to the underlying loggers.

This maintains an es6 map called loggers. Consumers of this API are explicitly permitted to mutate this map or replace it all together in order to add, remove or alter logger.

FileLogger

Logger specifically designed for logging to unix file descriptors.

This logger is synchronous: It uses blocking syscalls and thus guarantees that all data is written even if process.exit() is called immediately after logging. For normal files this is not a problem as linux will never block when writing to files, for sockets, pipes and ttys this might block the process for a considerable time.

Formatter MUST produce strings. Default formatter is messageFormatTechnical.

MemLogger

Logs messages to an in-memory buffer.

Formatter can be anything; default just logs the messages as-is.

InterfaceBase

Can be used as a base class/helper for implementing logging interfaces.

This will make sure that all the required fields are set to their default value, apply the filter, drop the message if the level is too low or if the filter returned undefined and will then forward the message to the logger configured.

This also wraps the entire logging logic into a promise enabled/async error handler that will log any errors using the rootLogger.

SimpleInterface

The fields interface provides the ability to conveniently specify both a message and custom fields to the underlying logger.

Secret

Special wrapper that should be used to protect secret values.

Effectively this tries to hide the actual payload so that the secret has to be explicitly extracted before using it.

const mySecret = new Secret(42); // Create a secret
mySecret.value; // => 42; extract the value
mySecret.value = 64; // assign a new value

The secret can only be accessed through the .secret property in order to make sure that the access is not accidental; the secret property cannot be iterated over and the secret will not be leaked when converted to json or when printed.

WinstonTransportInterface

Winston transport that forwards any winston log messages to the specified helix-log logger.

const winston = require('winston');
const { WinstonTransportInterface } = require('helix-log');

const myWinsonLogger = W.createLogger({ transports: [new WinstonTransportInterface()] });

// Will log 'Hello World' to the rootLogger with the fields: // {category: 'testing'}. myWinsonLogger.info('Hello World', category: 'testing');

Constants

rootLogger

The logger all other loggers attach to.

Must always contain a logger named 'default'; it is very much recommended that the default logger always be a console logger; this can serve as a good fallback in case other loggers fail.

JsonifyForLog

Trait used to serialize json objects to json. See jsonifyForLog.

Functions

eraseBunyanDefaultFields(fields)Object

Remove the default fields emitted by buyan.

These fields are added by bunyan by default but are often not of much interest (like name, bunyanLevel, or v) or can be easily added again later for data based loggers where this information might be valuable (e.g. hostname, pid).

Use like this:

numericLogLevel(name)number

This can be used to convert a string log level into it's numeric equivalent. More pressing log levels have lower numbers.

makeLogMessage([fields])Message

Supplies default values for all the required fields in log messages.

You may pass this a message that is just a string (as opposed to the usually required array of message components). The message will then automatically be wrapped in an array.

tryInspect(what, opts)

Wrapper around inspect that is extremely robust against errors during inspection.

Specifically designed to handle errors in toString() functions and custom inspect functions.

If any error is encountered a less informative string than a full inspect is returned and the error is logged using err().

serializeMessage(msg, opts)string

Turns the message field into a string.

messageFormatSimple(fields)MessageFormatter | string

Simple message format: Serializes the message and prefixes it with the log level.

This is used by the MemLogger by default for instance, because it is relatively easy to test with and contains no extra info.

messageFormatTechnical(fields)MessageFormatter | string

Message format that includes extra information; prefixes each message with the time stamp and the log level.

This is used by FileLogger by default for instance because if you work with many log files you need that sort of info.

messageFormatConsole(fields)MessageFormatter | string

Message format with coloring/formatting escape codes

Designed for use in terminals.

messageFormatJson(fields)MessageFormatter | Object

Use jsonifyForLog to turn the fields into something that can be converted to json.

messageFormatJsonString(fields)MessageFormatter | Object

Message format that produces & serialize json.

Really just an alias for JSON.stringify(messageFormatJson(fields)).

deriveLogger(logger, opts)Logger

Helper function that creates a derived logger that is derived from a given logger, merging the given options. All the properties are shallow copied to the new logger With the exception of the defaultFields, where the defaultFields object itself is shallow-copied. Thus allowing to extend the default fields.

__handleLoggingExceptions(fields, logger, code)Message

Helper to wrap any block of code and handle it's async & sync exceptions.

This will catch any exceptions/promise rejections and log them using the rootLogger.

fatal(...msg)

Log just a message to the rootLogger using the SimpleInterface.

Alias for new SimpleInterface().log(...msg).

This is not a drop in replacement for console.log, since this does not support string interpolation using %O/%f/..., but should cover most use cases.

messageFormatJsonStatic(fields)Object

Message format used for comparing logs in tests.

Pretty much just messageFormatJson, but removes the time stamp because that is hard to compare since it is different each time...

If other highly variable fields are added in the future (e.g. id = uuidgen()) these shall be removed too.

recordLogs(opts, fn)string

Record the log files with debug granularity while the given function is running.

While the logger is recording, all other loggers are disabled. If this is not your desired behaviour, you can use the MemLogger manually.

assertLogs(opts, fn, logs)

Assert that a piece of code produces a specific set of log messages.

recordAsyncLogs(opts, fn)string

Async variant of recordLogs.

Note that using this is a bit dangerous as other async procedures may also emit log messages while this procedure is running

assertAsyncLogs(opts, fn, logs)

Async variant of assertLogs

Note that using this is a bit dangerous as other async procedures may also emit logs while this async procedure is running.

jsonifyForLog(what)*

jsonify the given data using the JsonifyForLog trait.

Takes any javascript object and produces an object tree that only contains json compatible objects (objects, arrays, numbers, bools, strings and such).

This is a no-op if the input is already json compatible.

Note that this is specifically designed to serialize data for structured logging. This is NOT suitable for proper serialization of json; specifically this may loose information in cases where that makes sense.

Features a default converter for any exception/subclass of Error.

Typedefs

MessageFormatter*

Most loggers take a message with log(), encode it and write it to some external resource.

E.g. most Loggers (like ConsoleLogger, FileLogger, ...) write to a text oriented resource, so the message needs to be converted to text. This is what the formatter is for.

Not all Loggers require text; some are field oriented (working with json compatible data), others like the MemLogger can handle arbitrary javascript objects, but still provide an optional formatter (in this case defaulted to the identity function – doing nothing) in case the user explicitly wishes to perform formatting.

Interfaces

Message

Internally helix log passe these messages around.

Messages are just plain objects with some conventions regarding their fields:

Logger

Loggers are used to write log message.

These receive a message via their log() method and forward the message to some external resource or other loggers in the case of MultiLogger.

Loggers MUST provide a log(message) method accepting log messages.

Loggers SHOULD provide a constructor that can accept options as the last argument new MyLogger(..args, { ...options });

Loggers MAY support any arguments or options in addition to the ones described here.

Loggers SHOULD NOT throw exceptions; instead they should log an error.

Loggers MUST use the optional fields & named options described in this interface either as specified here, or not at all.

Loggers SHOULD provide a named constructor option 'level' and associated field that can be used to limit logging to messages to those with a sufficiently high log level.

Loggers SHOULD provide a named constructor option 'filter' and associated field that can be used to transform messages arbitrarily. This option should default to the identity() function from ferrum. If the filter returns undefined the message MUST be discarded.

Loggers SHOULD provide a named constructor option 'defaultFields'; if they do support the property they MUST perform a shallow merge/setdefault into the message AFTER applying the filters.

If loggers send messages to some external resource not supporting the Message format, they SHOULD also provide an option 'formatter' and associated field that is used to produce the external format. This formatter SHOULD be set to a sane default.

Helix-log provides some built-in formatters e.g. for plain text, json and for consoles supporting ANSI escape sequences.

LoggingInterface

Helix-Log LoggingInterfaces take messages as defined by some external interface, convert them to the internal Message format and forward them to a logger.

Some use cases include:

  • Providing a Console.log/warn/error compatible interface
  • Providing winston or bunyan compatible logging API
  • Providing a backend for forwarding bunyan or winston logs to helix log
  • Receiving log messages over HTTP
  • SimpleInterface and SimpleInterface are used to provide the info("My", "Message") and info.fields("My", "Message", { cutsom: "fields" }) interfaces.

LoggingInterfaces SHOULD provide a constructor that can accept options as the last argument new MyInterface(..args, { ...options });

LoggingInterfaces MAY support any arguments or options in addition to the ones described here.a

LoggingInterfaces MUST use the optional fields & named options described in this LoggingInterface either as specified here, or not at all.

LoggingInterfaces SHOULD NOT throw exceptions; instead they should log errors using the global logger.

LoggingInterfaces SHOULD provide a named constructor option/field 'logger' that indicates which destination logs are sent to. This option SHOULD default to the rootLogger.

LoggingInterfaces SHOULD provide a named constructor option 'level' and associated field that can be used to limit logging to messages to those with a sufficiently high log level.

LoggingInterfaces SHOULD provide a named constructor option 'filter' and associated field that can be used to transform messages arbitrarily. This option should default to the identity() function from ferrum. If the filter returns undefined the message MUST be discarded.

LoggingInterfaces SHOULD provide a named constructor option 'defaultFields'; if they do support the property they MUST perform a shallow merge/setdefault into the message AFTER applying the filters.

Message

Internally helix log passe these messages around.

Messages are just plain objects with some conventions regarding their fields:

Kind: global interface
Example

const myMessage = {
  // REQUIRED

  level: 'info',
  timestamp: new BigDate(), // Can also be a normal Date

  // OPTIONAL

  // This is what constitutes the actual log message an array
  // of any js objects which are usually later converted to text
  // using `tryInspect()`; we defer this text conversion step so
  // formatters can do more fancy operations (like colorizing certain
  // types; or we could)
  message: ['Print ', 42, ' deep thoughts!']
  exception: {

    // REQUIRED
    $type: 'MyCustomException',
    name; 'MyCustomException',
    message: 'Some custom exception ocurred',
    stack: '...',

    // OPTIONAL
    code: 42,
    causedBy: <nested exception>

    // EXCEPTION MAY CONTAIN ARBITRARY OTHER FIELDS
    ...fields,
  }

  // MESSAGE MAY CONTAIN ARBITRARY OTHER FIELDS
  ...fields,
}

Logger

Loggers are used to write log message.

These receive a message via their log() method and forward the message to some external resource or other loggers in the case of MultiLogger.

Loggers MUST provide a log(message) method accepting log messages.

Loggers SHOULD provide a constructor that can accept options as the last argument new MyLogger(..args, { ...options });

Loggers MAY support any arguments or options in addition to the ones described here.

Loggers SHOULD NOT throw exceptions; instead they should log an error.

Loggers MUST use the optional fields & named options described in this interface either as specified here, or not at all.

Loggers SHOULD provide a named constructor option 'level' and associated field that can be used to limit logging to messages to those with a sufficiently high log level.

Loggers SHOULD provide a named constructor option 'filter' and associated field that can be used to transform messages arbitrarily. This option should default to the identity() function from ferrum. If the filter returns undefined the message MUST be discarded.

Loggers SHOULD provide a named constructor option 'defaultFields'; if they do support the property they MUST perform a shallow merge/setdefault into the message AFTER applying the filters.

If loggers send messages to some external resource not supporting the Message format, they SHOULD also provide an option 'formatter' and associated field that is used to produce the external format. This formatter SHOULD be set to a sane default.

Helix-log provides some built-in formatters e.g. for plain text, json and for consoles supporting ANSI escape sequences.

Kind: global interface

logger.log(fields)

Actually print a log message

Implementations of this MUST NOT throw exceptions. Instead implementors ARE ADVISED to attempt to log the error using err() while employing some means to avoid recursively triggering the error. Loggers SHOULD fall back to logging with console.error.

Even though loggers MUST NOT throw exceptions; users of this method SHOULD still catch any errors and handle them appropriately.

Kind: instance method of Logger

Param Type
fields Message

logger.flush()

Flush the internal buffer.

Implementations of this SHOULD try to flush the underlying log sink if possible. The returned promise SHOULD only fulfill if the flushing was done (best effort).

Note that implementations SHOULD use best effort to avoid buffering or the need for flushing. However, there might be cases where this is not possible, for example when sending log messages over the network.

Kind: instance method of Logger

LoggingInterface

Helix-Log LoggingInterfaces take messages as defined by some external interface, convert them to the internal Message format and forward them to a logger.

Some use cases include:

  • Providing a Console.log/warn/error compatible interface
  • Providing winston or bunyan compatible logging API
  • Providing a backend for forwarding bunyan or winston logs to helix log
  • Receiving log messages over HTTP
  • SimpleInterface and SimpleInterface are used to provide the info("My", "Message") and info.fields("My", "Message", { cutsom: "fields" }) interfaces.

LoggingInterfaces SHOULD provide a constructor that can accept options as the last argument new MyInterface(..args, { ...options });

LoggingInterfaces MAY support any arguments or options in addition to the ones described here.a

LoggingInterfaces MUST use the optional fields & named options described in this LoggingInterface either as specified here, or not at all.

LoggingInterfaces SHOULD NOT throw exceptions; instead they should log errors using the global logger.

LoggingInterfaces SHOULD provide a named constructor option/field 'logger' that indicates which destination logs are sent to. This option SHOULD default to the rootLogger.

LoggingInterfaces SHOULD provide a named constructor option 'level' and associated field that can be used to limit logging to messages to those with a sufficiently high log level.

LoggingInterfaces SHOULD provide a named constructor option 'filter' and associated field that can be used to transform messages arbitrarily. This option should default to the identity() function from ferrum. If the filter returns undefined the message MUST be discarded.

LoggingInterfaces SHOULD provide a named constructor option 'defaultFields'; if they do support the property they MUST perform a shallow merge/setdefault into the message AFTER applying the filters.

Kind: global interface

The default constructor can be used to get the current point in time as a BigDate.

Kind: global class
Implements: Equals, Deepclone, Shallowclone

new The default constructor can be used to get the current point in time as a BigDate.(Converts, Construct, Construct, ...Can)

A Date class capable of storing timestamps at arbitrary precisions that can be used as a drop-in replacement for date.

When generating timestamps at quick succession Date will often yield the same result:

const assert = require('assert');
const a = new Date();
const b = new Date();
assert.strictEqual(a.toISOString(), b.toISOString())

This is often problematic. E.g. in the case of helix-log this can lead to log messages being displayed out of order. Using process.hrtime() is not an option either because it's time stamps are only really valid on the same process.

In order to remedy this problem, helix-log was created: It measures time at a very high precision (nano seconds) while maintaining a reference to corordinated universal time

const assert = require('assert');
const { BigDate } = require('@adobe/helixLog')
const a = new BigDate();
const b = new BigDate();
assert.notStrictEqual(a.toISOString(), b.toISOString());

Precision in relation to Corordinated Universal Time

Mostly depends on how well synchronized the system clock is…usually between 20ms and 200ms. This goes for both Date as well as BigDate, although BigDate can add up to 1ms of extra error.

Precision in relation to Date

BigDate can add up to 1ms out of sync with Date.

When measuring comparing BigDate.preciseTime() and Date.getTime() you may find differences of up to 2.5ms due to the imprecision of measurement.

Precision in relation to hrtime()

Using BigDate::fromHrtime() you can convert hrtime timestamps to BigDate. The conversion should be 1 to 1 (BigDate uses hrtime internally), but the internal reference will be recalculated every time the clock jumps (e.g on hibernation or when the administrator adjusts the time). This can lead to fromHrtime interpreting timestamps vastly different before and after the jump.

For benchmarking/measurement overhead

BigDate can be used for benchmarking and is better at it than Date and worse at it than hrtime.

Measuring the actual overhead proofed difficult, because results where vastly different depending on how often hrtime was called.

     | Worst | Cold  |  Hot  | Best

-------- | ----- | ----- | ----- | ----- Hrtime | 10µs | 20µs | 1.5µs | 80ns BigDate | 500µs | 80µs | 4µs | 250ns

Worst: First few invocations, bad luck Cold: Typical first few invocations. Hot: After tens to hundreds of invocations Best: After millions of invocations

Param Type Description
Converts Date | BigDate a Date to a BigDate or copies a BigDate
Construct String a BigDate from a string (like date, but supports arbitrary precision in the ISO 8601 String decimal places)
Construct Number | Big | Bigint a BigDate from the epoch value (unix time/number of milliseconds elapsed sinc 1970-01-01). Decimal places are honored and can be used to supply an epoch value of arbitrary precision.
...Can * be used to construct a BigDate from components (year, month, day, hours, minutes, seconds, milliseconds with decimal places)

BunyanStreamInterface

Bunyan stream that can be used to forward any bunyan messages to helix log.

Kind: global class
Implements: LoggingInterface

CoralogixLogger

Sends log messages to the coralogix logging service.

You can customize the host, application and subsystem per log message by specifying the appropriate fields.

Kind: global class
Implements: Logger

new CoralogixLogger(apikey, app, subsystem)

Param Type Default Description
apikey string | Secret – Your coralogix api key
app string – Name of the app under which the log messages should be categorized
subsystem string – Name of the subsystem under which
[opts.host] string "os.hostname()" The hostname under which to categorize the messages
[opts.apiurl] string "'https://api.coralogix.com/api/v1/&#x27;" where the coralogix api can be found; for testing; where the coralogix api can be found; for testing

coralogixLogger.apikey : Secret

Name of the app under which the log messages should be categorized

Kind: instance property of CoralogixLogger

coralogixLogger.app : string

Name of the app under which the log messages should be categorized

Kind: instance property of CoralogixLogger

coralogixLogger.subsystem : string

Name of the subsystem under which the log messages should be categorized

Kind: instance property of CoralogixLogger

coralogixLogger.host : string

The hostname under which to categorize the messages

Kind: instance property of CoralogixLogger

coralogixLogger.flush()

Flush the internal buffer.

Implementations of this SHOULD try to flush the underlying log sink if possible. The returned promise SHOULD only fulfill if the flushing was done (best effort).

Note that implementations SHOULD use best effort to avoid buffering or the need for flushing. However, there might be cases where this is not possible, for example when sending log messages over the network.

Kind: instance method of CoralogixLogger
Implements: flush

LoggerBase

Can be used as a base class/helper for implementing loggers.

This will first apply the filter, drop the message if the level is too low or if the filter returned undefined and will then call the subclass provided this._logImpl function for final processing.

This also wraps the entire logging logic into a promise enabled/async error handler that will log any errors using the rootLogger.

Kind: global class

new LoggerBase(opts)

Param Type Default Description
opts Object – Optional, named parameters
[opts.level] string "'silly'" The minimum log level to sent to loggly
[opts.filter] function identity Will be given every log message to perform arbitrary transformations; must return either another valid message object or undefined (in which case the message will be dropped).

Example

class MyConsoleLogger extends LoggerBase {
  _logImpl(fields) {
    console.log(fields);
  }
}

loggerBase.level : string

The minimum log level for messages to be printed. Feel free to change to one of the available levels.

Kind: instance property of LoggerBase

loggerBase.filter : function

Used to optionally transform all messages. Takes a message and returns a transformed message or undefined (in which case the message will be dropped).

Kind: instance property of LoggerBase

FormattedLoggerBase

Can be used as a base class/helper for implementing loggers that require a formatter.

Will do the same processing as LoggerBase and in addition call the formatter before invoking _logImpl.

Kind: global class

new FormattedLoggerBase()

Param Type Description
[opts.formatter] function In addition to the filter, the formatter will be used to convert the message into a format compatible with the external resource.

Example

class MyConsoleLogger extends FormattedLoggerBase {
  _logImpl(payload, fields) {
    console.log(`[${fields.level}]`, payload);
  }
}

formattedLoggerBase.formatter : MessageFormatter

Formatter used to format all the messages. Must yield an object suitable for the external resource this logger writes to.

Kind: instance property of FormattedLoggerBase

ConsoleLogger

Logger that is especially designed to be used in node.js Print's to stderr; Marks errors, warns & debug messages with a colored [ERROR]/... prefix.

Formatter MUST produce strings. Default formatter is messageFormatConsole.

Kind: global class
Implements: Logger

new ConsoleLogger()

Param Type Default Description
[opts.stream] Writable console._stderr A writable stream to log to.

consoleLogger.stream : Writable

Writable stream to write log messages to. Usually console._stderr.

Kind: instance property of ConsoleLogger

MultiLogger

Simple logger that forwards all messages to the underlying loggers.

This maintains an es6 map called loggers. Consumers of this API are explicitly permitted to mutate this map or replace it all together in order to add, remove or alter logger.

Kind: global class
Implements: Logger
Parameter: Sequence<Loggers> loggers – The loggers to forward to.

multiLogger.flush()

Flush the internal buffer.

Implementations of this SHOULD try to flush the underlying log sink if possible. The returned promise SHOULD only fulfill if the flushing was done (best effort).

Note that implementations SHOULD use best effort to avoid buffering or the need for flushing. However, there might be cases where this is not possible, for example when sending log messages over the network.

Kind: instance method of MultiLogger
Implements: flush

FileLogger

Logger specifically designed for logging to unix file descriptors.

This logger is synchronous: It uses blocking syscalls and thus guarantees that all data is written even if process.exit() is called immediately after logging. For normal files this is not a problem as linux will never block when writing to files, for sockets, pipes and ttys this might block the process for a considerable time.

Formatter MUST produce strings. Default formatter is messageFormatTechnical.

Kind: global class
Implements: Logger

new FileLogger(name)

Param Type Description
name string | Integer The path of the file to log to OR the unix file descriptor to log to.

fileLogger.fd : Integer

The underlying operating system file descriptor.

Kind: instance property of FileLogger

MemLogger

Logs messages to an in-memory buffer.

Formatter can be anything; default just logs the messages as-is.

Kind: global class
Implements: Logger

memLogger.buf : Array

An array that holds all the messages logged thus far. May be modified An array that holds all the messages logged thus far. May be modified.

Kind: instance property of MemLogger

InterfaceBase

Can be used as a base class/helper for implementing logging interfaces.

This will make sure that all the required fields are set to their default value, apply the filter, drop the message if the level is too low or if the filter returned undefined and will then forward the message to the logger configured.

This also wraps the entire logging logic into a promise enabled/async error handler that will log any errors using the rootLogger.

Kind: global class

new InterfaceBase(opts)

Param Type Default Description
opts Object – Optional, named parameters
[opts.level] string "'silly'" The minimum log level to sent to the logger
[opts.logger] Logger rootLogger The helix logger to use
[opts.filter] function identity Will be given every log message to perform arbitrary transformations; must return either another valid message object or undefined (in which case the message will be dropped).
[opts.defaultFields] object Additional log fields to add to every log message.

Example

class MyTextInterface extends InterfaceBase {
  logText(str) {
    this._logImpl({ message: [str] });
  }
};

const txt = new MyTextInterface({ logger: rootLogger });
txt.logText("Hello World");

SimpleInterface

The fields interface provides the ability to conveniently specify both a message and custom fields to the underlying logger.

Kind: global class
Implements: LoggingInterface

simpleInterface.fatal(...msg)

These methods are used to log just a message with no custom fields to the underlying logger; similar to console.log.

This is not a drop in replacement for console.log, since this does not support string interpolation using %O/%f/..., but should cover most use cases.

Kind: instance method of SimpleInterface

Param Type Description
...msg * The message to write

Secret

Special wrapper that should be used to protect secret values.

Effectively this tries to hide the actual payload so that the secret has to be explicitly extracted before using it.

const mySecret = new Secret(42); // Create a secret
mySecret.value; // => 42; extract the value
mySecret.value = 64; // assign a new value

The secret can only be accessed through the .secret property in order to make sure that the access is not accidental; the secret property cannot be iterated over and the secret will not be leaked when converted to json or when printed.

Kind: global class

WinstonTransportInterface

Winston transport that forwards any winston log messages to the specified helix-log logger.

const winston = require('winston');
const { WinstonTransportInterface } = require('helix-log');

const myWinsonLogger = W.createLogger({
  transports: [new WinstonTransportInterface()]
});

// Will log 'Hello World' to the rootLogger with the fields:
// {category: 'testing'}.
myWinsonLogger.info('Hello World', category: 'testing');

Kind: global class
Implements: WinstonTransport, LoggingInterface

new WinstonTransportInterface(opts)

Param Type Description
opts Object – Options as specified by WinstonTransport AND by LoggingInterface; both sets of options are supported
opts.level String – This is the level as specified by WinstonTransport. If your winston instance uses custom log levels not supported by helix-log you can use the filter to map winston log levels to helix log ones.

rootLogger

The logger all other loggers attach to.

Must always contain a logger named 'default'; it is very much recommended that the default logger always be a console logger; this can serve as a good fallback in case other loggers fail.

Kind: global constant
Example

// Change the default logger
rootLogger.loggers.set('default', new ConsoleLogger({level: 'debug'}));

You should not log to the root logger directly; instead use one of the wrapper functions log, fatal, err, warn, info, verbose, debug; they perform some additional

JsonifyForLog

Trait used to serialize json objects to json. See jsonifyForLog.

Kind: global constant

eraseBunyanDefaultFields(fields) ⇒ Object

Remove the default fields emitted by buyan.

These fields are added by bunyan by default but are often not of much interest (like name, bunyanLevel, or v) or can be easily added again later for data based loggers where this information might be valuable (e.g. hostname, pid).

Use like this:

Kind: global function

Param Type
fields Object

Example

const bunyan = require('bunyan');
const { BunyanStreamInterface, eraseBunyanDefaultFields } = require('helix-log');

const logger = bunyan.createLogger({name: 'helixLogger'});
logger.addStream(BunyanStreamInterface.createStream({
  filter: eraseBunyanDefaultFields
}));

numericLogLevel(name) ⇒ number

This can be used to convert a string log level into it's numeric equivalent. More pressing log levels have lower numbers.

Kind: global function
Returns: number - The numeric log level
Throws:

  • Error If the given log level name is invalid.
Param Type Description
name string Name of the log level

makeLogMessage([fields]) ⇒ Message

Supplies default values for all the required fields in log messages.

You may pass this a message that is just a string (as opposed to the usually required array of message components). The message will then automatically be wrapped in an array.

Kind: global function

Param Type Default Description
[fields] Object {} User supplied field values; can overwrite any default values

tryInspect(what, opts)

Wrapper around inspect that is extremely robust against errors during inspection.

Specifically designed to handle errors in toString() functions and custom inspect functions.

If any error is encountered a less informative string than a full inspect is returned and the error is logged using err().

Kind: global function

Param Type Description
what * The object to inspect
opts Object Options will be passed through to inspect. Note that these may be ignored if there is an error during inspect().

serializeMessage(msg, opts) ⇒ string

Turns the message field into a string.

Kind: global function

Param Type Description
msg Array.<*> | undefined – Message components to serialize
opts Object – Parameters are forwarded to tryInspect()

messageFormatSimple(fields) ⇒ MessageFormatter | string

Simple message format: Serializes the message and prefixes it with the log level.

This is used by the MemLogger by default for instance, because it is relatively easy to test with and contains no extra info.

Kind: global function

Param Type
fields Message

messageFormatTechnical(fields) ⇒ MessageFormatter | string

Message format that includes extra information; prefixes each message with the time stamp and the log level.

This is used by FileLogger by default for instance because if you work with many log files you need that sort of info.

Kind: global function

Param Type
fields Message

messageFormatConsole(fields) ⇒ MessageFormatter | string

Message format with coloring/formatting escape codes

Designed for use in terminals.

Kind: global function

Param Type
fields Message

messageFormatJson(fields) ⇒ MessageFormatter | Object

Use jsonifyForLog to turn the fields into something that can be converted to json.

Kind: global function
Oaram: Message message the log message

Param Type Description
fields * additional log fields

messageFormatJsonString(fields) ⇒ MessageFormatter | Object

Message format that produces & serialize json.

Really just an alias for JSON.stringify(messageFormatJson(fields)).

Kind: global function

Param Type
fields Message

deriveLogger(logger, opts) ⇒ Logger

Helper function that creates a derived logger that is derived from a given logger, merging the given options. All the properties are shallow copied to the new logger With the exception of the defaultFields, where the defaultFields object itself is shallow-copied. Thus allowing to extend the default fields.

Kind: global function
Returns: Logger - A new logger with updated options.

Param Type Description
logger Logger the logger to derive from.
opts object Options to merge with this logger

__handleLoggingExceptions(fields, logger, code) ⇒ Message

Helper to wrap any block of code and handle it's async & sync exceptions.

This will catch any exceptions/promise rejections and log them using the rootLogger.

Kind: global function
Access: package

Param Type Description
fields Object Fields to set in any error message (usually indicates which logger was used)
logger Logger the logger to wrap
code function The code to wrap

fatal(...msg)

Log just a message to the rootLogger using the SimpleInterface.

Alias for new SimpleInterface().log(...msg).

This is not a drop in replacement for console.log, since this does not support string interpolation using %O/%f/..., but should cover most use cases.

Kind: global function

Param Type Description
...msg * The message to write

messageFormatJsonStatic(fields) ⇒ Object

Message format used for comparing logs in tests.

Pretty much just messageFormatJson, but removes the time stamp because that is hard to compare since it is different each time...

If other highly variable fields are added in the future (e.g. id = uuidgen()) these shall be removed too.

Kind: global function

Param Type
fields Message

recordLogs(opts, fn) ⇒ string

Record the log files with debug granularity while the given function is running.

While the logger is recording, all other loggers are disabled. If this is not your desired behaviour, you can use the MemLogger manually.

Kind: global function
Returns: string - The logs that where produced by the codee

Param Type Description
opts Object – optional first parameter; options passed to MemLogger
fn function The logs that this code emits will be recorded.

Example

recordLogs(() => {
  info('Hello World');
  err('Nooo');
});

will return something like this:

[
  { level: 'info', message: 'Hello World', timestamp: '...' },
  { level: 'error', message: 'Noo', timestamp: '...' }
]

assertLogs(opts, fn, logs)

Assert that a piece of code produces a specific set of log messages.

Kind: global function

Param Type Description
opts Object – optional first parameter; options passed to MemLogger
fn function The logs that this code emits will be recorded.
logs string

Example

const { assertLogs, info, err } = require('@adobe/helix-shared').log;

assertLogs(() => {
  info('Hello World');
  err('Nooo');
}, [
  { level: 'info', message: 'Hello World' },
  { level: 'error', message: 'Noo' }
]);

recordAsyncLogs(opts, fn) ⇒ string

Async variant of recordLogs.

Note that using this is a bit dangerous as other async procedures may also emit log messages while this procedure is running

Kind: global function
Returns: string - The logs that where produced by the code

Param Type Description
opts Object – optional first parameter; options passed to MemLogger
fn function The logs that this code emits will be recorded.

assertAsyncLogs(opts, fn, logs)

Async variant of assertLogs

Note that using this is a bit dangerous as other async procedures may also emit logs while this async procedure is running.

Kind: global function

Param Type Description
opts Object – optional first parameter; options passed to MemLogger
fn function The logs that this code emits will be recorded.
logs string

jsonifyForLog(what) ⇒ *

jsonify the given data using the JsonifyForLog trait.

Takes any javascript object and produces an object tree that only contains json compatible objects (objects, arrays, numbers, bools, strings and such).

This is a no-op if the input is already json compatible.

Note that this is specifically designed to serialize data for structured logging. This is NOT suitable for proper serialization of json; specifically this may loose information in cases where that makes sense.

Features a default converter for any exception/subclass of Error.

Kind: global function
Returns: * - Json compatible object
Throws:

  • TraitNotImplemented If any object in the given object tree can not be converted to json-compatible
Param Type Description
what * The object to convert

MessageFormatter ⇒ *

Most loggers take a message with log(), encode it and write it to some external resource.

E.g. most Loggers (like ConsoleLogger, FileLogger, ...) write to a text oriented resource, so the message needs to be converted to text. This is what the formatter is for.

Not all Loggers require text; some are field oriented (working with json compatible data), others like the MemLogger can handle arbitrary javascript objects, but still provide an optional formatter (in this case defaulted to the identity function – doing nothing) in case the user explicitly wishes to perform formatting.

Kind: global typedef
Returns: * - Whatever kind of type the Logger needs. Usually a string.

Param Type
fields Message

helix-log's People

Contributors

dependabot[bot] avatar dominique-pfister avatar koraa avatar lgtm-com[bot] avatar renovate-bot avatar renovate[bot] avatar rofe avatar semantic-release-bot avatar silvia-odwyer avatar trieloff avatar tripodsan avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

helix-log's Issues

Bump up log messages timestampto microsecond precision

AFAICS, the CoralogixLogger uses phin to POST the log message to the coralogix API, but does not wait for them to complete. they might arrive out of order at their backend.

see

phin({

since the timestamp is only millisecond precise, this could result in a garbled log stream.
suggest to either ensure that the log messages are sent in sequence or increase the precision of the timestamp.

or maybe it would be better to use the coralogix client which buffers and sends log messages in bulk.

Silence or shorten exceptions thrown by failing BigDate.preciseTime(fields.timestamp)

On each day, Coralogix's daily report lists a couple of errors with a message reading [big.js] Invalid number followed by a very large serialisation of the error thrown. These are caused by a failing parse of the timestamp field:

timestamp: Number(BigDate.preciseTime(fields.timestamp)),

The stack trace of the error:

"stack"  :  "Error: [big.js] Invalid number
    at parse (/nodejsAction/9jzrzLJQ/main.js:72210:13)
    at new Big (/nodejsAction/9jzrzLJQ/main.js:72181:9)
    at Big (/nodejsAction/9jzrzLJQ/main.js:72164:67)
    at Function.preciseTime (/nodejsAction/9jzrzLJQ/main.js:7360:9)
    at CoralogixLogger._sendRequest (/nodejsAction/9jzrzLJQ/main.js:7711:37)
    at CoralogixLogger._logImpl (/nodejsAction/9jzrzLJQ/main.js:7687:17)" 

I'd suggest to:

  • either surround that parse with a try/catch block and eventually replace the bad timestamp with the current date
  • or not serialise the complete exception thrown as most of it is not relevant but just bloats Coralogix's daily report

Move interfaces to own files and provide type declarations

The current log.js is quite large and contains a log of functions and classes.
IMO, at least the interfaces that are not tried to the log module should be refactored in their own files.

An additional bonus would be to provide typescript like type declarations.

add timeout when sending logs to coralogix

slow connections to coralogix should not stall execution / termination of functions.
as seen in production, it might happen that sending logs might take forever and the serverless functions max duration is used up:

image

suggest to add a timeout of 5 seconds for sending logs and abort flushing if it's not possible to send them.

Coralogix API_KEY leaked in error message

in case of an error in a formatter, the multilogger reports an error, leaking the API_KEY:

MultiLogger encountered exception while logging to CoralogixLogger - CoralogixLogger { apikey: 'hidden-xxxx-f8dd-b196-b7b7b83fe38f', apiurl: 'https://api.coralogix.com/api/v1/', app: 'myapp', subsystem: 'bot', host: '3924f26b366d', level: 'info', formatter: [Function: formatter], fmtOpts: {} } :

ConsoleLogger logs to stderr

Following the intention that logging should behave similar than console.log, it is suprising to see that the ConsoleLogger uses console.error() to log the messages; which effectively logs to stderr.

Especially for CLI programs, where CLI output might be interspersed with log messages, logging to different streams might be a problem.

Of course, when building a pipe-able program, one might explicitely only log to stderr.

Suggestion:

  • make it configurable in the ConsoleLogger, which stream to use.
  • default to stdout

ps: I would use stdout/stderr directly, and not using console.[log|error]. it is unlikely ATM that helix-log is used in the browser (also, since it tries to load the package.json :-).

BigDate tests failing

Description
When I run the tests, I get:

  1) BigDate
       construction, toISOString(), preciseTime()
         from sparse components:

      AssertionError [ERR_ASSERTION]: The values are not equal!
      + expected - actual

      -'2019-01-31T15:00:00.000000000Z'
      +'2019-01-31T23:00:00.000000000Z'

      at Context.it (test/big-date.js:32:9)

(maybe because I have a different timezone or locale?)

$ date
Fri Nov 15 15:08:03 JST 2019

serializeMessage should support format characters

serializeMessage should support string format characters, similar to console.log:

example:

info('creating %d tasks for %s', 42, 'test');

as a consequence, I think the messageFormatJson should be smart about how many parameters it consumes from the arguments during serializing the mesage, eg:

info('creating %d tasks for %s', 42, 'test', { 'taskId': 33));

would generate:

{
  message: 'creating 42 tasks for test',
  taskId: 33,
}

define 'LogFacade' interface that acts like a winston / bunyan logger

problem 1 - code style

helix-log promises to be very simple to use, by setting up global default logging.
however, when trying to keep the code clean (eslint), one tends to avoid unused imports (requires).

so imagine my code uses debug and info so far. so I have this import:

const { debug, info } = require('@adobe/helix-log');

now, I want to use a warn(), so I need to add it to the imports. later I decided to change the warn to an error, so I need to change (and clean up) the imports again.

for me, it would be way easier to just have log.* object, like console, that offers all levels. this would also let my IDE offer autocomplete. Furthermore, a problem that can happen during refactoring is that a info variable might already be defined locally and will clash with the global info() function.

problem 2 - sub loggers

a cool feature of bunyan are the child loggers, especially when dealing with json based log formats. they allow to bind a set of log fields to a logger instance. see https://github.com/trentm/node-bunyan#logchild.

AFAICS, the current logic is:

  1. capture the log message parameters
  2. use the formatter to convert it to a string or object
  3. send it to the destination (file, console, http)

I think, having fields as a first class concept would allow for more versatile logging:

  1. capture the log message parameters
  2. create a log-data object using the message parameters, level, etc.
  3. use the formatter to serialize a message field.
  4. send the data to the destination. the destination can still decide either to include the entire log-data or just parts of it.

problem 3 - log level sensitive logging

currently, each logger has its own log-level setting. this is good, because I might want to send debug logs to the file logger, but not the console logger.
on the other hand, the developer might want to not create certain logs at all, if a the log level is low. eg. don't include stack traces if not debug log-level. somehow there should be a way to accomplish both. maybe this is not so important, though.


Suggestion

  • Create a new class LogFacade (or something) that holds a map of fields
  • the LogFacade.logWithOpts() would generate a log-data object with the message (array) and the opts. it would also add the fields to the log-data
  • the LogFacade would also contain methods for debug|info|warn|error|etc, which just delegate to the logWithOpts and the respective level.
  • the method LogFacade.child(subFields) would create a sub-facade, overlaying the parent fields with the subFields for this instance.
  • the rootFacade would be a default instance of LogFacade, using the rootLogger.
  • helix-log would export log as the rootFacade

Alternative

follow bunyans naming and:

  • rename the Logger interface to LogStream
  • rename the LogFacade to Logger

Provide context local/promise local logging configs with cls-hooked

https://www.npmjs.com/package/cls-hooked can be used to provide variables local to the current promise/async function context.

Current/Possible use case:

  • Providing the activationId field in openwhisk for logging (multiple activations can run at the same time) – this is already possible with a custom filter
  • As a better foundation to implement recordLogs and magically make it work like expected. (This would be a really nice feature)

"Async awareness" could serve as another really nice unique selling point compared to other logging frameworks if we get this right!

Multiple API designs spring to my mind:

  • Making rootLogger context dependent (or making it a getter so it can be customized by the library user)
  • Making filters/formatters context dependant

To be honest I don't really like any of those ideas…

ConsoleLogger spreads messages into multiple lines in CloudWatch

Adding the following code to a universal action that is deployed in AWS:

const obj = {
  a: '1',
  b: '2',
  c: '3',
};
log.info(`This is obj in log.info: ${JSON.stringify(obj, undefined, 2)}`);
console.log(`This is obj in console.log: ${JSON.stringify(obj, undefined, 2)}`);

I get the following log output in CloudWatch:
Screenshot 2021-05-06 at 18 35 58
where the stringified JSON is spread over multiple lines, which is not handy for larger objects.

If I replace ConsoleLogger's log implementation in https://github.com/adobe/helix-log/blob/586ee3aa4e3734dc3cf88797ed7ef04dd9c80b9b/src/log.js#L598-L590 with the following:

  _logImpl(str) {
    console.log(str);
  }

I get the expected output:
Screenshot 2021-05-06 at 18 39 00

coralogix: severity does not match log level

Description
Coralogix log entries with "level": "info"have Severity: ERROR. An example:

Timestamp: 18/10/2019 14:36:29.894 pm
Severity: 5 (ERROR)
{
   "level"  :  "info" ,
   "ow"  : {
     "activationId"  :  "??????" ,
     "actionName"  :  "/helix/helix-services-private/[email protected]" ,
     "transactionId"  :  "??????" 
  },
   "message"  :  "delivering error /404.html 404" ,
   "timestamp"  :  "2019-10-18T12:36:29.894Z" 
}

To Reproduce
Go to https://helix.coralogix.com/#/query/logs?id=1Xf1jU2SH7a and compare log level and severity.

Expected behavior
Log entries with info level should have severity 3: info

Additional context
[email protected] creates coralogix log entries with level info and severity 5: error. [email protected] OTOH is creating coralogix log entries with level info and severity 3: info. Both are using [email protected]...

Add serialize option to ignore log-data for simple message format

when using the default console logger, any log-data is serialized. this is a problem when using the logger as bunyan stream. eg:

2019-09-19T05:53:10.702Z       stderr: testing invocation via bunyan. { timestamp: 2019-09-19T05:53:10.701Z, name: 'bot/[email protected]', hostname: '7ce869d8ec3a', pid: 1, ow: { activationId: '77b3898c54e24ea1b3898c54e23ea198', actionName: '/tripod/bot/[email protected]' }, v: 0, bunyanLevel: 30 }
2019-09-19T05:53:10.703Z       stderr: [WARN] testing invocation via bunyan. { timestamp: 2019-09-19T05:53:10.702Z, name: 'bot/[email protected]', hostname: '7ce869d8ec3a', pid: 1, ow: { activationId: '77b3898c54e24ea1b3898c54e23ea198', actionName: '/tripod/bot/[email protected]' }, v: 0, bunyanLevel: 40 }

it would be nice, if we could just disable the serialization of the non-string message parts.

eg:

rootLogger.loggers.set('default', new ConsoleLogger({
  level: 'info',
  ignoreObjectSerialization: true,
}));

ConsoleLogger doesn't work in combination with VSCode's Debug Console

When starting a debugging session in Visual Studio Code, messages logged via ConsoleLogger do not appear in VSCode's standard Debug Console, indeed they don't appear anywhere.

Reason is that ConsoleLogger writes to console._stderr which is a socket that doesn't automatically flush its contents.
EDIT: no, that's not the reason

If I change:

this.stream.write(`${str}\n`);

to:

console.log(`${str}\n`);

log messages immediately appear in the Debug Console.

There is a workaround: one has to create a launch configuration in VSCode and change its console setting to integratedTerminal (see https://code.visualstudio.com/docs/editor/debugging#_launchjson-attributes)

Note, though, that this can't be set on a global level, so you'd have to create a configuration to change the console setting over and over, which isn't practical.

lowercase all jsdoc native types

as per:
https://jsdoc.app/tags-type.html
which refers to:
https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System

all the native types should be lowercase:

eg:

 * @param {String|Integer} name - The path of the file to log to
 *   OR the unix file descriptor to log to.
 * @param {Object} opts – Configuration object

should be

 * @param {string|number} name - The path of the file to log to
 *   OR the unix file descriptor to log to.
 * @param {Object} opts – Configuration object

also, instead of any we should use *.

Suppress multiple versions warning for same version

In project adobe/helix-index-file, I'm currently seeing a warning when running the tests:

Multiple versions of adobe/helix-log in the same process! Using one loaded first!
{ used: { version: '4.5.0', filename: './helix-index-files/node_modules/@adobe/helix-log/src/index.js' },
discarded: { version: '4.5.0', filename: './helix-index-files/node_modules/@adobe/helix-status/node_modules/@adobe/helix-log/src/index.js' } }

Can't we suppress that message when the 2 versions clashing are actually the same?

Retry sending messages to coralogix on intermittent network outages

Description
Errors like the one below are visible in Coralogix on a daily basis (I masked some of the data I deemed to be sensitive):

{
   "exception"  : {
     "$type"  :  "Error" ,
     "name"  :  "Error" ,
     "message"  :  "connect ECONNREFUSED 54.229.116.198:443" ,
     "stack"  :  "Error: connect ECONNREFUSED 54.229.116.198:443
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1104:14)" ,
     "code"  :  "ECONNREFUSED" 
  },
   "level"  :  "error" ,
   "logger"  : {
     "$type"  :  "CoralogixLogger" ,
     "level"  :  "info" ,
     "filter"  : {
       "$type"  :  "Function" 
    },
     "formatter"  : {
       "$type"  :  "Function" 
    },
     "apikey"  : {
       "$type"  :  "Secret" 
    },
     "apiurl"  :  "https://api.coralogix.com/api/v1/" ,
     "app"  :  "..." ,
     "subsystem"  :  "..." ,
     "host"  :  "..." 
  },
   "ow"  : { ... },
   "message"  :  "Encountered exception while logging!" ,
   "timestamp"  :  "2019-10-27T20:39:15.530Z" 
}

To Reproduce
Log into Coralogix and search for this type of error, or click the link in the daily error report email.

Expected behavior
No errors during logging.

@koraa

rethink signature of SimpleInterface.info() and make fields optional first argument

Is your feature request related to a problem? Please describe.

the current signature of the SimpleInterface logging methods are:

info(string: message)
infoFields(string: message, object: fields)

It would be more aligned with the bunyan logger, and would allow for potential string interpolation, if the signature would be:

info(string: message)
info(object: fields, string:message) // overload

Describe the solution you'd like
create a breaking change and:

  • remove the SimpleInterface.*Fields() methods
  • change the signature so that the first argument is either a fields object or a string message.

Improve logging framework

Overview

with adobe/helix-shared#134, we added a new logging framework, that gets rid of any 3rd party dependencies, which reduces the amount to dependencies drastically. but it is also changing the way the loggers work and thus renders many upstream assumptions invalid.

Details

AFAICS, it specifically create a global rootLogger in the module scope and offers no obvious way to create instances of loggers, that adhere to the console API (e.g. missing debug, info, warn, etc. methods).

Potential problem will arise, if not all upstream packages use the same module versions and might use localized versions of the module.

Additionally, it might be beneficial to move the logging framework out of helix-shared, in order to have less tight coupling (eg. git-server might want to use helix-logger, but not helix-shared).

Proposed Actions

  • remove the global rootLogger and just export classes to create logger instances.
  • additionally, refactor all logging into a new helix-logger package.

json log formatter cannot handle data and exception

test example:

  const ex = new Error('Hello World');
  ex.stack = 'Not a stack';
  ex.code = 42;
  ck('error', ['Wubalubadubdub', { bar: 32 }, ex], {
    bar: 32.
    message: 'Wubalubadubdub',
    level: 'error',
    exception: {
      $type: 'Error',
      name: 'Error',
      message: 'Hello World',
      stack: 'Not a stack',
      code: 42,
    },
  });

but generates:

{
  exception: {
    '$type': 'Error',
    code: 42,
    message: 'Hello World',
    name: 'Error',
    stack: 'Not a stack'
  },
  level: 'error',
  message: 'Wubalubadubdub { ' +
    'bar: 32 }'
}

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: undefined. Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.

Unserializable field should not throw error

when a field is not serializable, it should not throw an error but rather show an empty object or [unserializable object] in the data.

eg:

 "exception"  : {
     "$type"  :  "TypeError" ,
     "name"  :  "TypeError" ,
     "message"  :  "Cannot convert object to primitive value" ,
     "stack"  :  "TypeError: Cannot convert object to primitive value
    at Trait.invoke (/nodejsAction/XXJKBPJP/main.js:11351:71)
    at jsonifyForLog (/nodejsAction/XXJKBPJP/main.js:3199:47)
    at map (/nodejsAction/XXJKBPJP/main.js:9323:11)
    at map.next (<anonymous>)
    at each (/nodejsAction/XXJKBPJP/main.js:9151:26)
    at ./node_modules/ferrum/src/sequence.js.curry (/nodejsAction/XXJKBPJP/main.js:8911:5)
    at each [CURRY] (/nodejsAction/XXJKBPJP/main.js:7973:12)
    at obj (/nodejsAction/XXJKBPJP/main.js:9150:3)
    at ./node_modules/@adobe/openwhisk-action-utils/node_modules/@adobe/helix-log/src/serialize-json.js.JsonifyForLog.impl (/nodejsAction/XXJKBPJP/main.js:3209:38)
    at Trait.invoke (/nodejsAction/XXJKBPJP/main.js:11356:12)"

json log formatter fails (silently) when a data field is undefined

if trying to log json data with an undefined property, the json formatter ignores the data, due to a cought serialization error and just uses the normal serialize mesage. this is very hard to debug :-)

eg:

{ Error: No implementation of trait JsonifyForLog for undefined of type undefined.
    at Trait.invoke (/nodejsAction/XXOeiOAf/main.js:9952:17)
    at jsonifyForLog (/nodejsAction/XXOeiOAf/main.js:1532:47)
    at map (/nodejsAction/XXOeiOAf/main.js:7923:11)
    at map.next (<anonymous>)
    at each (/nodejsAction/XXOeiOAf/main.js:7751:26)
    at ./node_modules/ferrum/src/sequence.js.curry (/nodejsAction/XXOeiOAf/main.js:7511:5)
    at each [CURRY] (/nodejsAction/XXOeiOAf/main.js:6573:12)
    at obj (/nodejsAction/XXOeiOAf/main.js:7750:3)
    at ./node_modules/@adobe/helix-log/src/serialize-json.js.JsonifyForLog.impl (/nodejsAction/XXOeiOAf/main.js:1542:38)
    at Trait.invoke (/nodejsAction/XXOeiOAf/main.js:9956:12)

suggest to just ignore undefined fields.

ps: Having a distinct separation from data and message would really be good.

log filter should operate on the default fields.

Description
the log filter in InterfaceBase is applied before the merged defaultFields / fields is sent to the logger. this way the filter cannot react to the default fields.

To Reproduce

Example:

    const logger = new SimpleInterface({
        level: 'debug',
        defaultFields: {
          suppress: true,
        },
        filter: (fields) => {
          if (fields.suppress) {
            return undefined;
          }
          return fields;
        },
      });

Version:
4.4.1

helix-log does not initialize if package.json is at a wrong place

the following line is causing an error, when bundling helix-log with webpack:

    package: JSON.parse(readFileSync(`${__dirname}/../package.json`, { encoding: 'utf-8' })),

error:

Error: ENOENT: no such file or directory, open '../openwhisk-action-utils/node_modules/@adobe/helix-log/src/../package.json'\n

Provide human readable tests

I tried to use helix-log and I could not find some basic examples on how to create a simple console or file logger and log something. Same for the MultiLogger.
There is a lot of JSDoc, which is great, but this is not enough to understand how things are supposed to be used.
Usually, browsing the tests is the best way to find how to use an API and same here, I could not find things I expected like basic API tests to inspire from.
https://github.com/adobe/helix-log/blob/master/test/log.test.js contains a lot of tests which is great but it looks like an obfuscated file and it seems to be testing a lot of internal logic (am I really supposed to do things like logger.buf = []; ?).

Reading external code like https://github.com/adobe/openwhisk-action-utils/blob/master/src/logger.js#L67-L83 tells me we can do a lot with helix-log, which is great, we just need simpler human readable samples.

Prevent deep serialisation of non plain objects

Is your feature request related to a problem? Please describe.
it is very convenient to pass objects for logging, like:

} catch (e) {
  error('something happened!', e);
}

but when the object is large (and deep), it could produce quite some log text.
for example request throws a http.clientrequest as error, which produces a 400kb log entry.

Describe the solution you'd like

  • provide good serializers for non-plain objects, like Error
  • if no serializer exists, only use inspect if the object has either a @@toStringTag or a util.inspect.custom method.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

This repository currently has no open or pending branches.

Detected dependencies

circleci
.circleci/config.yml
  • cimg/node 20.12.1
github-actions
.github/workflows/codeql.yml
  • actions/checkout v4
  • github/codeql-action v3
  • github/codeql-action v3
  • github/codeql-action v3
.github/workflows/semantic-release.yaml
  • actions/checkout v4
  • actions/setup-node v4
.github/workflows/semver-check.yaml
npm
package.json
  • big.js ^6.1.1
  • colorette ^2.0.2
  • ferrum ^1.9.3
  • phin ^3.7.0
  • polka ^0.5.2
  • @adobe/eslint-config-helix 2.0.6
  • @semantic-release/changelog 6.0.3
  • @semantic-release/git 10.0.1
  • ajv 8.13.0
  • bunyan 1.8.15
  • codecov 3.8.3
  • eslint 8.57.0
  • jsdoc-to-markdown 8.0.1
  • junit-report-builder 3.2.1
  • mocha 10.4.0
  • mocha-junit-reporter 2.2.1
  • mocha-multi-reporters 1.5.1
  • nyc 15.1.0
  • semantic-release 23.1.1
  • uuid 9.0.1

  • Check this box to trigger a request for Renovate to run again on this repository

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.