Gathering detailed insights and metrics for @dotcom-reliability-kit/logger
Gathering detailed insights and metrics for @dotcom-reliability-kit/logger
Gathering detailed insights and metrics for @dotcom-reliability-kit/logger
Gathering detailed insights and metrics for @dotcom-reliability-kit/logger
🪨 A well tested suite of tools designed to help FT.com applications be more reliable and measurable
npm install @dotcom-reliability-kit/logger
Typescript
Module System
Min. Node Version
Node Version
NPM Version
80.7
Supply Chain
99.1
Quality
90.1
Maintenance
100
Vulnerability
99.6
License
opentelemetry: v3.0.2
Updated on Mar 05, 2025
opentelemetry: v3.0.1
Updated on Feb 19, 2025
middleware-allow-request-methods: v1.0.0
Updated on Feb 12, 2025
fetch-error-handler: v1.0.0
Updated on Jan 30, 2025
opentelemetry: v3.0.0
Updated on Jan 21, 2025
serialize-request: v4.0.0
Updated on Jan 21, 2025
JavaScript (98.32%)
TypeScript (1.2%)
CSS (0.34%)
Shell (0.15%)
Verify real, reachable, and deliverable emails with instant MX records, SMTP checks, and disposable email detection.
Total Downloads
547,105
Last Day
2,689
Last Week
11,971
Last Month
54,147
Last Year
420,815
MIT License
8 Stars
1,106 Commits
1 Forks
50 Watchers
7 Branches
33 Contributors
Updated on Mar 13, 2025
Minified
Minified + Gzipped
Latest Version
4.0.0
Package Id
@dotcom-reliability-kit/logger@4.0.0
Unpacked Size
45.07 kB
Size
13.41 kB
File Count
8
NPM Version
10.9.2
Node Version
22.13.0
Published on
Jan 21, 2025
Cumulative downloads
Total Downloads
Last Day
-1.5%
2,689
Compared to previous day
Last Week
16.6%
11,971
Compared to previous week
Last Month
36.3%
54,147
Compared to previous month
Last Year
240.1%
420,815
Compared to previous year
A simple and fast logger based on Pino, with FT preferences baked in. This module is part of FT.com Reliability Kit.
Install @dotcom-reliability-kit/logger
as a dependency:
1npm install --save @dotcom-reliability-kit/logger
Include in your code:
1import logger from '@dotcom-reliability-kit/logger'; 2// or 3const logger = require('@dotcom-reliability-kit/logger');
Perform logging at different levels using methods with the same name:
1// Info-level log with a message 2logger.info('Saying hello'); 3 4// Warning-level log with a message 5logger.warn('Everything’s mostly cool'); 6 7// Error-level log with a message and additional data 8logger.error('Uh-oh', { field: 'some value' }); 9 10// Info-level log with no message but some additional data 11logger.info({ event: 'UPDATE_NOTIFICATION', data: data }); 12 13// Error-level log with an error object and additional data 14const error = new Error('Whoops!'); 15logger.error('Uh-oh', error, { extra_field: 'boo' });
Understanding log levels is key to using the logger. Setting a log level explains to the person debugging your application what level of information (or error severity) they're working with.
The valid levels are:
debug
: The lowest log level. This is for high-volume information which isn't critical to monitoring and running the app, but may be useful when debugging in local development. E.g. logging transformed article information before rendering.
info
: The highest informational log level. This is for key information which is important debugging an application in production. E.g. when the application is ready to receive HTTP traffic.
warn
: The lowest error log level. This is for when something may cause an issue but it does not have a large impact on the end user. E.g. a deprecated method is being used which may be removed the next time a dependency is updated.
error
: The general error log level. This is for when an error occurs which impacts the end user. E.g. an upstream service failed and so a page cannot be rendered.
fatal
: The highest error log level. This is for when an error occurs and is severe enough that the application cannot recover. This should always be accompanied with the application process being exited.
Logger
The default export of @dotcom-reliability-kit/logger
is an instance of the Logger
class with some sensible configurations. You can also create your own logger by importing this class and instantating it yourself with some options.
1import { Logger } from '@dotcom-reliability-kit/logger'; 2// or 3const { Logger } = require('@dotcom-reliability-kit/logger'); 4 5const myLogger = new Logger({ 6 // options go here 7});
Logger
configuration optionsConfig options can be passed into the Logger
constructor to change the behaviour of the logger.
options.baseLogData
Base log data which is added to every log output made by the logging methods. This allows you to add consistent data to a logger, e.g. information about the app. This must be an Object
but it can have any properties. E.g.
1const logger = new Logger({ 2 baseLogData: { 3 app: { 4 version: '1.2.3' 5 } 6 } 7}); 8 9logger.info('This is a log'); 10// Outputs: 11// { 12// "level": "info", 13// "message": "This is a log", 14// "app": { 15// "version": '1.2.3' 16// } 17// }
options.logLevel
The maximum log level to output during logging. Logs at levels beneath this will be ignored. This option must be a String
which is set to one of the supported log levels.
1const logger = new Logger({ 2 logLevel: 'warn' 3}); 4 5logger.info('This is some info'); // Outputs: nothing 6logger.warn('This is a warning'); // Outputs: the message
It's also possible to set this option as an environment variable, which is how you configure the default logger. The following environment variables are read from:
LOG_LEVEL
: The preferred way to set log level in an environment variableSPLUNK_LOG_LEVEL
: The legacy way to set log level, to maintain compatibility with n-loggeroptions.serializers
You can customize the way that the logger converts certain properties to JSON by specifying serializers. This allows you to extract only the information you need or to fully transform a single property value.
This option must be an object. Each key corresponds to the log property you want to serialize, and each value must be a function that performs the serialization. Expressed as a TypeScript type:
1type Serializer = (value, propertyName) => any
When you define a serializer for a property, your serializer function will be called every time we encounter that property at the top level of a log data object. E.g.
1const fruitEmoji = { 2 apple: '🍏', 3 banana: '🍌', 4 coconut: '🥥' 5}; 6function emojifyFruit(value) { 7 return fruitEmoji[value] || value; 8} 9 10const logger = new Logger({ 11 serializers: { 12 // If a "snack" property is found in a log, this function will be called 13 snack: emojifyFruit 14 } 15}); 16 17logger.info({ 18 message: 'Hello World', 19 snack: 'banana' 20}); 21// Outputs: 22// { 23// "level": "info", 24// "message": "Hello World", 25// "snack": "🍌" 26// }
Some properties cannot be serialized in this way to maintain consistent logs:
err
, error
, level
, message
, and time
are all reserved. Configured serializers for these properties will be ignored.
[!WARNING]
It's your responsibility to properly handle unexpected data in your log serializers. You should ideally type guard to avoid your logs failing to send. If an unexpected error is encountered in a serializer then you'll seeLOG_METHOD_FAILURE
errors appear in your logs.E.g. taking the example above, we would probably ensure that the property we're working with is a string:
1function emojifyFruit(value) { 2 if (typeof value === 'string' && fruitEmoji[value]) { 3 return fruitEmoji[value]; 4 } 5 // Always return the original value if you can't process it, so you don't lose log data 6 return value; 7}
options.transforms
An array of functions which are called on log data before logs are output. This allows you to apply transformations to the final log object before it's sent.
Each log transform must be a function which accepts a single object argument and returns an object. Expressed as TypeScript types:
1type LogData = {[x: string]: any}; 2type Transform = (logData: LogData) => LogData
You can pass as many transforms as you need, though you must consider performance – each function will be called on every log that's sent.
1function uppercaseProperties(logData) { 2 const entries = Object.entries(logData).map(([property, value]) => { 3 return [property.toUpperCase(), value]; 4 }); 5 return Object.fromEntries(entries); 6} 7 8const logger = new Logger({ 9 transforms: [ 10 uppercaseProperties 11 ] 12}); 13 14logger.info({ 15 message: 'Hello World', 16 example: true 17}); 18// Outputs: 19// { 20// "LEVEL": "info", 21// "MESSAGE": "This is a log", 22// "EXAMPLE": true 23// }
You can also use built-in transforms to do things like mask sensitive data.
options.withPrettifier
Whether to send prettified logs if available. This option has no effect if you have the NODE_ENV
environment variable set to either production
or if you have not installed pino-pretty. See local development usage for more information.
Must be a Boolean
and defaults to true
.
It's also possible to set this option as an environment variable, which is how you configure the default logger. Set the LOG_DISABLE_PRETTIFIER
environment variable to true
if you want to force the prettifier not to load.
logger.log()
and shortcut methodsThe log
method of a Logger
can be used to send a JSON-formatted log to stdout
with a message and additional information. This method requires a level
parameter set to a valid log level. It can accept any number of other arguments which must be either a String
, and Object
, or an instance of Error
.
1logger.log('debug', 'This is a message', { extraData: 123 }, new Error('Oops'));
It's generally easier and less error-prone to use one of the shortcut methods rather than log(level)
. There are shortcut methods for each of the log levels:
logger.debug(...logData)
: Log with a level of "debug"logger.info(...logData)
: Log with a level of "info"logger.error(...logData)
: Log with a level of "error"logger.warn(...logData)
: Log with a level of "warn"logger.fatal(...logData)
: Log with a level of "fatal"As well as the valid log levels, there are a couple of deprecated legacy levels. These are only present to maintain backwards-compatibility with n-logger.
[!WARNING]
These methods are deprecated and will log a warning message the first time they're used.
logger.data(...logData)
: Aliases logger.debug()
logger.silly(...logData)
: Aliases logger.debug()
logger.verbose(...logData)
: Aliases logger.debug()
logger.flush()
Most logs are queued up and sent asynchronously, as this keeps your application performant and minimises the impact of logging. Sometimes (very rarely) you may need to manually flush the queue of logs. You can do so by using the flush
method:
1logger.flush();
Logs are automatically flushed if the application stops running, to ensure log information is not lost in the event of an application crash.
logger.createChildLogger()
Create a new logger with the same config options as the current logger, but with additional base log data merged in. This allows you to create loggers which add extra information depending on where in the codebase you are. You must pass an Object
to set the base log data of the child logger.
Express example where you have an application logger and a child logger for use within Express routes:
1const app = express(); 2const appLogger = new Logger({ 3 baseLogData: { 4 appName: 'my-app' 5 } 6}); 7 8app.use((request, response) => { 9 request.log = appLogger.createChildLogger({ 10 requestUrl: request.url 11 }); 12}); 13 14app.get('/mock-url', (request, response) => { 15 request.log.info('We got a request'); 16 // Outputs: 17 // { 18 // "level": "info", 19 // "message": "We got a request", 20 // "appName": "my-app", 21 // "requestUrl": "/mock-url" 22 // } 23});
logger.addContext()
Add additional base log data to the logger via merging objects together. You must pass an Object
which will be merged with the existing base log data.
[!WARNING]
This method is deprecated and will log a warning message the first time it's used. This is just for compatibility with n-logger and you should use eitherbaseLogData
orcreateChildLogger
instead.
1const logger = new Logger({ 2 baseLogData: { 3 appName: 'my-app' 4 } 5}); 6 7logger.addContext({ 8 myExtraData: 'extra data' 9}); 10 11logger.info('Example'); 12// Outputs: 13// { 14// "level": "info", 15// "message": "Example", 16// "appName": "my-app", 17// "myExtraData": "extra data" 18// }
logger.setContext()
Add a context
property to the logger's base log data. You must pass an Object
which will be added as a context
property to all logs.
[!WARNING]
This method is deprecated and will log a warning message the first time it's used. This is just for compatibility with n-serverless-logger and you should use eitherbaseLogData
orcreateChildLogger
instead.
1const logger = new Logger(); 2 3logger.setContext({ 4 myExtraData: 'extra data' 5}); 6 7logger.info('Example'); 8// Outputs: 9// { 10// "level": "info", 11// "message": "Example", 12// "context": { 13// "myExtraData": "extra data" 14// } 15// }
logger.clearContext()
Remove the context
property from the logger's base log data.
[!WARNING]
This method is deprecated and will log a warning message the first time it's used. This is just for compatibility with n-serverless-logger and you should use eitherbaseLogData
orcreateChildLogger
instead.
1const logger = new Logger({ 2 baseLogData: { 3 context: { 4 appName: 'my-app' 5 } 6 } 7}); 8 9logger.clearContext(); 10 11logger.info('Example'); 12// Outputs: 13// { 14// "level": "info", 15// "message": "Example" 16// }
As well as writing your own log transforms, you can use the ones provided as part of this package. All built-in transforms are provided as properties of the transforms
object:
1const { transforms } = require('@dotcom-reliability-kit/logger');
legacyMask
transformThe legacy mask transform applies the same masking behaviour to the logger as n-mask-logger, replicating the behaviour exactly. It masks a list of fields you specify so that we don't accidentally log sensitive information in our apps. You can use it like this:
1const { Logger, transforms } = require('@dotcom-reliability-kit/logger'); 2 3const logger = new Logger({ 4 transforms: [ 5 transforms.legacyMask() 6 ] 7}); 8 9logger.info({ 10 email: 'oops@ft.com' 11}); 12// Outputs: 13// { 14// "level": "info", 15// "email": "*****" 16// }
You can configure the legacy mask transform by passing in an options object:
1const logger = new Logger({
2 transforms: [
3 transforms.legacyMask({
4 // options go here
5 })
6 ]
7});
legacyMaskOptions.denyList
An array of strings which indicate the property names in the logs which should have their values masked. Adding new items to the deny list does not alter the default set of property names: firstName
, ft-backend-key
, ft-session-id
, FTSession_s
, FTSession
, lastName
, password
, phone
, postcode
, primaryTelephone
, session
, and sessionId
:
1transforms.legacyMask({ 2 denyList: [ 'myEmailProperty', 'myPasswordProperty' ] 3})
legacyMaskOptions.allowList
An array of strings which indicate the property names in the logs which should not have their values masked. This is used to override the default fields in cases where you want to log potentially sensitive data. E.g. if your email
property actually contains a boolean
indicating whether a user is opted into email, then you might want to do this:
1transforms.legacyMask({ 2 allowList: [ 'email' ] 3})
legacyMaskOptions.maskString
A string which is used as the replacement when a property is masked. This defaults to *****
:
1transforms.legacyMask({ 2 maskString: '🙈🙉🙊' 3})
The logger can accept data in a variety of formats, and it combines all the different data you give it into a single object which is then stringified as JSON. Each logging method can accept any number of objects, strings, and errors as log data:
1logger.info('This is a string', { thisIsData: true }, { thisIsMoreData: true }); 2// Outputs: 3// { 4// "level": "info", 5// "message": "This is a string", 6// "thisIsData": true, 7// "thisIsMoreData": true 8// }
Different types of data are serialized differently before being output as JSON:
Objects are left as they are, all of their properties are extracted and logged as regular JSON. E.g.
1logger.info({ hello: 'world' }); 2// Outputs: 3// { 4// "level": "info", 5// "hello": "world" 6// }
[!WARNING]
It's important to note that only properties that are serializable as JSON can be logged. Any non-serializable properties (e.g. functions) will not be output.
Strings are moved into the message
property of the output log. E.g.
1logger.info('Hello world'); 2// Outputs: 3// { 4// "level": "info", 5// "message": "Hello World!" 6// }
Errors are moved into the error
property of the output log and serialized using the Reliability Kit serializeError
method. E.g.
1logger.info(new Error('Oops')); 2// Outputs: 3// { 4// "level": "info", 5// "error": { 6// "name": "Error", 7// "message": "Oops", 8// ...etc 9// } 10// }
Errors found in sub-properties of the log data are not serialized like this due to performance reasons: looping over every nested property to check if it's an error is expensive (do not do this). Also, be mindful of passing in errors with a custom error property e.g. myErrorProperty
, as you'll have to serialize them yourself like below:
1logger.info({ myErrorProperty: serializeError(new Error('Oops')) });
... if not, you'll get the following output:
1logger.info({ myErrorProperty: new Error('Oops') }); 2// Outputs: 3// { 4// "level": "info", 5// "myErrorProperty": {} 6// }
The exception to this is if the sub-property name is either error
or err
, as these are automatically serialized. E.g.
1logger.info({ error: new Error('Oops') }); 2// Outputs: 3// { 4// "level": "info", 5// "error": { 6// "cause": null, 7// "code": "UNKNOWN", 8// "data": {}, 9// "isOperational": false, 10// "message": "Oops", 11// "name": "Error", 12// "relatesToSystems": [], 13// "statusCode": null 14// } 15// }
The order of the log data items is meaningful, and when a property is encountered the first instance of it will be used. This is the opposite behaviour to JavaScript's Object.assign
:
1logger.info({ example: 1 }, { example: 2 }, { example: 3 }); 2// Outputs: 3// { 4// "level": "info", 5// "example": 1 6// }
In local development @dotcom-reliability-kit/logger
does not format or colourize logs by default. You may want to enable this to make reading logs in local development more easy.
To get formatted and colourised logs locally, you need to meet two conditions:
Have the NODE_ENV
environment variable set to either development
or don't have it set at all.
Install pino-pretty as a development dependency in your project. It's very important that this is a development dependency rather than a production one, otherwise you risk prettifying logs in production which makes them appear incorrectly in Splunk:
1npm install -D pino-pretty
Ensure you don't disable prettification via the withPrettifier
option.
Using @dotcom-reliability-kit/logger
in production requires that your application can handle logs to stdout
and sends these logs to somewhere more permanent. On Heroku this means you're required to have migrated to Log Drains. On AWS Lambda it means you must be sending logs to CloudWatch (see tech hub documentation).
If you're using Jest to test your code, you may encounter issues with the tests not exiting when you're mocking @dotcom-reliability-kit/logger
. You can't rely on Jest's automatic mocking:
1jest.mock('@dotcom-reliability-kit/logger');
This is because, in order to mock the logger, Jest will still load the original module which creates a fully fledged logger with bindings to process.stdout
. You can get around this by providing your own manual mock logger, either as a second argument to jest.mock
or as a file in __mocks__/@dotcom-reliability-kit/logger.js
. E.g.
1jest.mock('@dotcom-reliability-kit/logger', () => ({ 2 debug: jest.fn(), 3 error: jest.fn(), 4 fatal: jest.fn(), 5 info: jest.fn(), 6 warn: jest.fn() 7}));
@dotcom-reliability-kit/logger
is compatible with most use cases of n-logger, n-mask-logger, and n-serverless-logger. We tried hard to make migration as easy as possible from these libraries. The full list of differences are available in the Migration Guide as well as tips on migrating.
Consult the Migration Guide if you're trying to migrate to a later major version of this package.
See the central contributing guide for Reliability Kit.
Licensed under the MIT license.
Copyright © 2022, The Financial Times Ltd.
No vulnerabilities found.
No security vulnerabilities found.