fix: compatibility with log aggregators

This commit is contained in:
Rainer Killinger
2022-11-01 18:35:26 +01:00
parent 107d94d499
commit 8aef5b8d5b
8 changed files with 54 additions and 26 deletions

View File

@@ -22,8 +22,7 @@ To select your desired log levels add the corresponding numbers and set the valu
For example `STAPPS_LOG_LEVEL=17` is 16 + 1 and would log everything that is `OK` or `INFO`.
If you want to use logger in production (`NODE_ENV=production`) and allow all transports to fail set
`ALLOW_NO_TRANSPORT` to `true`.
If you want to use logger in production (`NODE_ENV=production`) and allow all transports to fail set `ALLOW_NO_TRANSPORT` to `true`.
Additionally setting the environment variable `STAPPS_EXIT_LEVEL` which works in the same manner as `STAPPS_LOG_LEVEL` will terminate your process after logging at the selected level(s) (usefull for integration tests). It will be ignored in afore mentioned productive environments.
@@ -52,7 +51,7 @@ Environment variables are:
## Transformations
By default the logger will only add the log level to the message.
By default the logger will only add the log level to the message. It will replace newlines with spaces and might skip some of your choosen Transformers when in production (`NODE_ENV=production`) for compatibility reasons with existing log aggregators and analyzers.
You can change this behavior by setting other Transformers via `Logger.setTransformations`. If you do so, mind the order of the transformers.

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {stringify} from 'flatted';
import {isNodeEnvironment, isProductiveNodeEnvironment} from './common';
import {isNodeEnvironment, isProductiveEnvironment, isProductiveNodeEnvironment} from './common';
import {Transformation} from './transformation';
import {AddLogLevel} from './transformations/add-log-level';
import {Transport} from './transport';
@@ -91,17 +91,24 @@ export class Logger {
/**
* Apply transformations to an output
* Will strip newlines in production environment
*
* @param logLevel Log level of the output
* @param output Output to apply transformations to
*/
private static applyTransformers(logLevel: LogLevel, output: string): string {
if (isProductiveEnvironment()) {
output = output.replace(/[\n\r]/g, ' ');
}
if (!Array.isArray(Logger.transformations) || Logger.transformations.length === 0) {
return output;
}
let transformedOutput = output;
for (const transformation of Logger.transformations) {
for (const transformation of Logger.transformations.filter(transform =>
!isProductiveEnvironment() ? true : transform.useInProduction === isProductiveEnvironment(),
)) {
transformedOutput = transformation.transform(logLevel, transformedOutput);
}

View File

@@ -18,6 +18,11 @@ import {LogLevel} from './logger';
* A transformer for log output
*/
export interface Transformation {
/**
* Indicates if this transformation is stripped in production environments
*/
useInProduction: boolean;
/**
* Transform an output
*

View File

@@ -19,6 +19,11 @@ import {Transformation} from '../transformation';
* Transformation that adds the log level to output
*/
export class AddLogLevel implements Transformation {
/**
* Keep this transformation in production environments
*/
useInProduction = true;
/**
* Add log level to output
*

View File

@@ -20,6 +20,11 @@ import {Transformation} from '../transformation';
* Transformation that colorizes log output
*/
export class Colorize implements Transformation {
/**
* Skip this transformation in production environments
*/
useInProduction = false;
/**
* Instantiate a new colorize transformation
*

View File

@@ -14,21 +14,15 @@
*/
import {LogLevel} from '../logger';
import {Transformation} from '../transformation';
import moment from 'moment';
/**
* Transformation that adds a timestamp to output
*/
export class Timestamp implements Transformation {
/**
* Instantiate a new timestamp transformation
*
* @see https://momentjs.com/docs/#/displaying/format/
* @param format Format for timestamps
* Keep this transformation in production environments
*/
constructor(private readonly format = 'LLLL') {
// noop
}
useInProduction = true;
/**
* Add timestamp to output
@@ -37,8 +31,6 @@ export class Timestamp implements Transformation {
* @param output Output to add timestamp to
*/
transform(_logLevel: LogLevel, output: string): string {
const now = moment();
return `[${now.format(this.format)}] ${output}`;
return `[${new Date().toISOString()}] ${output}`;
}
}

View File

@@ -19,6 +19,7 @@ import chaiSpies from 'chai-spies';
import {suite, test} from '@testdeck/mocha';
import {Logger} from '../src/logger';
import {AddLogLevel} from '../src/transformations/add-log-level';
import {Colorize} from '../src/transformations/colorize';
import {DummyTransport} from './dummyTransport';
chai.should();
@@ -236,6 +237,27 @@ export class LoggerSpec {
process.env.NODE_ENV = nodeEnv;
}
@test
'is compatible with log aggregation in productive environment'() {
const nodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
process.env.ALLOW_NO_TRANSPORT = 'true';
Logger.setTransformations([new AddLogLevel(), new Colorize()]);
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
// noop
});
Logger.log('Foo\nbar');
expect(spy).to.have.been.called.once;
expect(spy.__spy.calls[0][0]).to.equal('[LOG] Foo bar');
process.env.NODE_ENV = nodeEnv;
delete process.env.ALLOW_NO_TRANSPORT;
}
@test
log() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {

View File

@@ -15,21 +15,14 @@
import {expect} from 'chai';
import {suite, test} from '@testdeck/mocha';
import {Timestamp} from '../../src/transformations/timestamp';
import moment = require('moment');
@suite()
export class ColorizeSpec {
export class TimeStampSpec {
@test
'default'() {
const transformation = new Timestamp();
expect(transformation.transform('ERROR', 'Foobar')).to.be.equal(`[${moment().format('LLLL')}] Foobar`);
}
@test
'different format'() {
const transformation = new Timestamp('DD.MM.YYYY');
expect(transformation.transform('ERROR', 'Foobar')).to.be.equal(`[${moment().format('DD.MM.YYYY')}] Foobar`);
expect(transformation.transform('ERROR', 'Foobar')).to.be.contain(`Z`);
}
}