Compare commits

..

1 Commits

Author SHA1 Message Date
Taylor McKinnon 37cd9dfdda add time_ns field 2020-01-15 10:49:08 -08:00
31 changed files with 2225 additions and 944 deletions

View File

@ -1,31 +0,0 @@
name: Tests
on:
push:
branches-ignore:
- development/**
- q/*/**
jobs:
tests:
runs-on: ubuntu-latest
steps:
- name: Checkokut
uses: actions/checkout@v4
- name: Install deps
run: sudo apt-get update -q
- uses: actions/setup-node@v4
with:
node-version: '16'
- name: Install Yarn
run: npm install -g yarn
- name: install dependencies
run: yarn install --frozen-lockfile
- name: run lint
run: echo "linter is disabled temporarily ()" || yarn run --silent lint -- --max-warnings 0
- name: run lint_md
run: yarn --silent lint_md
- name: run test
run: yarn test
- name: run coverage
run: yarn coverage

View File

@ -1,5 +1,6 @@
# WereLogs # WereLogs
[![CircleCI][badgepub]](https://circleci.com/gh/scality/werelogs)
[![Scality CI][badgepriv]](http://ci.ironmann.io/gh/scality/werelogs) [![Scality CI][badgepriv]](http://ci.ironmann.io/gh/scality/werelogs)
This repository provides a NodeJS Library that aims to be an efficient logging This repository provides a NodeJS Library that aims to be an efficient logging
@ -19,7 +20,7 @@ https://github.com/scality/Guidelines/blob/master/CONTRIBUTING.md).
In order to install WereLogs, you can use NPM with github's HTTP url, and save In order to install WereLogs, you can use NPM with github's HTTP url, and save
it in your own package.json: it in your own package.json:
```sh ```
$> npm i --save scality/werelogs $> npm i --save scality/werelogs
``` ```
@ -157,3 +158,11 @@ In order to find out the known issues, it is advised to take a look at the
[project's github page](http://github.com/scality/werelogs). There, you should [project's github page](http://github.com/scality/werelogs). There, you should
be able to find the issues, tagged with the releases they are impacting, be able to find the issues, tagged with the releases they are impacting,
whether they're open or closed. whether they're open or closed.
## Contributing
The contributing rules for this project are defined in the associated
CONTRIBUTING.md file.
[badgepub]: https://circleci.com/gh/scality/werelogs.svg?style=svg
[badgepriv]: http://ci.ironmann.io/gh/scality/werelogs.svg?style=svg&circle-token=a946e81ad65b99814403b5e57f017d9ecbe93f0a

23
circle.yml Normal file
View File

@ -0,0 +1,23 @@
general:
branches:
ignore:
- /^ultron\/.*/ # Ignore ultron/* branches
artifacts:
- coverage/
- doc/
machine:
node:
version: 6.13.1
test:
override:
- npm run lint_md
- npm run lint -- --max-warnings 0
- npm run gendoc
- npm run coverage
# running ft_test packs werelogs and installs it + deps into
# tests/functional. Pack is like publishing werelogs in a local tgz
# archive that can be installed.
# This step shall ensure that no issue is encountered when installing
# the package, and allows to functionally test werelogs.
- npm run ft_test

36
eve/main.yml Normal file
View File

@ -0,0 +1,36 @@
---
version: 0.2
branches:
feature/*, documentation/*, improvement/*, bugfix/*, w/*, q/*, hotfix/*:
stage: pre-merge
stages:
pre-merge:
worker: &master-worker
type: docker
path: eve/workers/master
volumes:
- '/home/eve/workspace'
steps:
- Git:
name: fetch source
repourl: '%(prop:git_reference)s'
shallow: 'True'
retryFetch: 'True'
haltOnFailure: 'True'
- ShellCommand:
name: install dependencies
command: yarn install --frozen-lockfile
- ShellCommand:
name: run lint
command: yarn run --silent lint -- --max-warnings 0
- ShellCommand:
name: run lint_md
command: yarn run --silent lint_md
- ShellCommand:
name: run test
command: yarn run test
- ShellCommand:
name: run coverage
command: yarn run coverage

View File

@ -0,0 +1,55 @@
FROM ubuntu:trusty
#
# Install apt packages needed by the buildchain
#
ENV LANG C.UTF-8
COPY buildbot_worker_packages.list vaultclient_packages.list /tmp/
RUN apt-get update -q && apt-get -qy install curl apt-transport-https \
&& apt-get install -qy software-properties-common python-software-properties \
&& curl --silent https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
&& echo "deb https://deb.nodesource.com/node_10.x trusty main" > /etc/apt/sources.list.d/nodesource.list \
&& curl -sS http://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb http://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& add-apt-repository ppa:ubuntu-toolchain-r/test \
&& apt-get update -q \
&& cat /tmp/buildbot_worker_packages.list | xargs apt-get install -qy \
&& cat /tmp/vaultclient_packages.list | xargs apt-get install -qy \
&& pip install pip==9.0.1 \
&& rm -rf /var/lib/apt/lists/* \
&& rm -f /tmp/*_packages.list
#
# Install usefull nodejs dependencies
#
RUN yarn global add mocha
#
# Add user eve
#
RUN adduser -u 1042 --home /home/eve --disabled-password --gecos "" eve \
&& adduser eve sudo \
&& echo 'eve ALL=NOPASSWD: ALL' >> /etc/sudoers
#
# Run buildbot-worker on startup
#
ARG BUILDBOT_VERSION=0.9.12
RUN pip install buildbot-worker==$BUILDBOT_VERSION
USER eve
ENV HOME /home/eve
#
# Setup nodejs environmnent
#
ENV CXX=g++-4.9
ENV LANG C.UTF-8
WORKDIR /home/eve/workspace
CMD buildbot-worker create-worker . "$BUILDMASTER:$BUILDMASTER_PORT" "$WORKERNAME" "$WORKERPASS" \
&& buildbot-worker start --nodaemon

View File

@ -0,0 +1,9 @@
ca-certificates
git
libffi-dev
libssl-dev
python2.7
python2.7-dev
python-pip
software-properties-common
sudo

View File

@ -0,0 +1,3 @@
nodejs
g++-4.9
yarn

64
index.d.ts vendored
View File

@ -1,27 +1,12 @@
interface WerelogsConfigOptions { interface config {}
level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
dump?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
streams?: object[];
}
declare class WerelogsConfig { interface LogDictionnary {
constructor(config?: WerelogsConfigOptions);
reset(): WerelogsConfig;
update(config: WerelogsConfig): WerelogsConfig;
logger: any;
level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
dump: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
end: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
}
interface LogDictionary {
httpMethod?: string; httpMethod?: string;
httpURL?: string; httpURL?: string;
[field: string]: any; [field: string]: any;
} }
declare module 'werelogs' { declare class RequestLogger {
export class RequestLogger {
constructor( constructor(
logger: any, logger: any,
logLevel: string, logLevel: string,
@ -31,35 +16,30 @@ declare module 'werelogs' {
); );
getUids(): Array<string>; getUids(): Array<string>;
getSerializedUids(): string; getSerializedUids(): string;
addDefaultFields(fields: LogDictionary): LogDictionary; addDefaultFields(fields: LogDictionnary): LogDictionnary;
trace(msg: string, data?: LogDictionary): void; trace(msg: string, data?: LogDictionnary): void;
debug(msg: string, data?: LogDictionary): void; debug(msg: string, data?: LogDictionnary): void;
info(msg: string, data?: LogDictionary): void; info(msg: string, data?: LogDictionnary): void;
warn(msg: string, data?: LogDictionary): void; warn(msg: string, data?: LogDictionnary): void;
error(msg: string, data?: LogDictionary): void; error(msg: string, data?: LogDictionnary): void;
fatal(msg: string, data?: LogDictionary): void; fatal(msg: string, data?: LogDictionnary): void;
end(msg: string, data?: LogDictionary): void; end(msg: string, data?: LogDictionnary): void;
errorEnd(msg: string, data?:LogDictionary): void;
} }
declare module 'werelogs' {
export class Logger { export class Logger {
name: string; name: string;
constructor(name: string);
constructor(name: string, config?: config);
setLevel(levelName: string): void;
setDumpLevelThreshold(levelName: string): void;
newRequestLogger(uids?: string|Array<string>): RequestLogger; newRequestLogger(uids?: string|Array<string>): RequestLogger;
newRequestLoggerFromSerializedUids(uids: string): RequestLogger; newRequestLoggerFromSerializedUids(uids: string): RequestLogger;
trace(msg: string, data?: LogDictionary): void; trace(msg: string, data?: LogDictionnary): void;
debug(msg: string, data?: LogDictionary): void; debug(msg: string, data?: LogDictionnary): void;
info(msg: string, data?: LogDictionary): void; info(msg: string, data?: LogDictionnary): void;
warn(msg: string, data?: LogDictionary): void; warn(msg: string, data?: LogDictionnary): void;
error(msg: string, data?: LogDictionary): void; error(msg: string, data?: LogDictionnary): void;
fatal(msg: string, data?: LogDictionary): void; fatal(msg: string, data?: LogDictionnary): void;
}
export function configure(config: WerelogsConfigOptions): void;
export class API {
constructor(config: WerelogsConfigOptions);
reconfigure(config: WerelogsConfigOptions): void;
Logger: Logger;
} }
} }

View File

@ -1,5 +1,4 @@
const API = require('./lib/api.js'); const API = require('./lib/api.js');
const stderrUtils = require('./lib/stderrUtils');
/* /*
* For convenience purposes, we provide an already instanciated API; so that * For convenience purposes, we provide an already instanciated API; so that
@ -12,40 +11,4 @@ module.exports = {
Logger: werelogs.Logger, Logger: werelogs.Logger,
configure: werelogs.reconfigure.bind(werelogs), configure: werelogs.reconfigure.bind(werelogs),
Werelogs: API, Werelogs: API,
/**
* Timestamp logs going to stderr
*
* @example <caption>Simplest usage</caption>
* ```
* const { stderrUtils } = require('werelogs');
* stderrUtils.catchAndTimestampStderr();
* ```
*
* @example <caption>Manage process exit</caption>
* ```
* const { stderrUtils } = require('werelogs');
* // set exitCode to null to keep process running on uncaughtException
* stderrUtils.catchAndTimestampStderr(undefined, null);
* // application init
* process.on('uncaughtException', (err) => {
* // custom handling, close connections, files
* this.worker.kill(); // or process.exit(1);
* });
* // Note you could use prependListener to execute your callback first
* // and then let stderrUtils exit the process.
* ```
*
* @example <caption>Custom listener</caption>
* ```
* const { stderrUtils } = require('werelogs');
* stderrUtils.catchAndTimestampWarning();
* // application init
* process.on('uncaughtException', (err, origin) => {
* stderrUtils.printErrorWithTimestamp(err, origin);
* // close and stop everything
* process.exit(1);
* });
* ```
*/
stderrUtils,
}; };

View File

@ -1,5 +1,4 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const LogLevel = require('./LogLevel.js'); const LogLevel = require('./LogLevel.js');
const SimpleLogger = require('./SimpleLogger.js'); const SimpleLogger = require('./SimpleLogger.js');
@ -98,11 +97,10 @@ class Config {
// for check log level vs. log dump level // for check log level vs. log dump level
const newLogLevel = checkedConfig.level || this.logLevel; const newLogLevel = checkedConfig.level || this.logLevel;
const newLogDumpLevel = checkedConfig.dump || this.dumpThreshold; const newLogDumpLevel = checkedConfig.dump || this.dumpThreshold;
if (newLogDumpLevel if (newLogDumpLevel &&
&& !LogLevel.shouldLog(newLogDumpLevel, newLogLevel)) { !LogLevel.shouldLog(newLogDumpLevel, newLogLevel)) {
throw new Error( throw new Error(
'Logging level should be at most logging dump level', 'Logging level should be at most logging dump level');
);
} }
if (Object.prototype.hasOwnProperty.call(checkedConfig, 'level')) { if (Object.prototype.hasOwnProperty.call(checkedConfig, 'level')) {
@ -124,8 +122,8 @@ class Config {
+ 'of Writeable Streams.'); + 'of Writeable Streams.');
} }
if (!checkedConfig.streams.length) { if (!checkedConfig.streams.length) {
throw new Error('Werelogs config.streams must contain at ' throw new Error('Werelogs config.streams must contain at ' +
+ 'least one stream.'); 'least one stream.');
} }
this.streams = checkedConfig.streams.map(stream => { this.streams = checkedConfig.streams.map(stream => {
stream.level = 'trace'; // eslint-disable-line no-param-reassign stream.level = 'trace'; // eslint-disable-line no-param-reassign

View File

@ -1,5 +1,4 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const logLevels = [ const logLevels = [
'trace', 'trace',

View File

@ -1,12 +1,12 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const LogLevel = require('./LogLevel.js'); const LogLevel = require('./LogLevel.js');
const RequestLogger = require('./RequestLogger.js'); const RequestLogger = require('./RequestLogger.js');
const { unserializeUids } = require('./Utils.js'); const unserializeUids = require('./Utils.js').unserializeUids;
const Config = require('./Config.js'); const Config = require('./Config.js');
class Logger { class Logger {
/** /**
* This is the constructor of the Logger class. It takes optional * This is the constructor of the Logger class. It takes optional
* configuration parameters, that allow to modify its behavior. * configuration parameters, that allow to modify its behavior.
@ -74,8 +74,7 @@ class Logger {
callparams: [msg, data], callparams: [msg, data],
}, },
'Werelogs API was mis-used.' 'Werelogs API was mis-used.'
+ ' This development error should be fixed ASAP.', + ' This development error should be fixed ASAP.');
);
return; return;
} }
if (data) { if (data) {

View File

@ -1,15 +1,17 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict const assert = require('assert');
const LogLevel = require('./LogLevel.js'); const LogLevel = require('./LogLevel.js');
const Utils = require('./Utils.js'); const Utils = require('./Utils.js');
const serializeUids = Utils.serializeUids;
const { serializeUids, generateUid, objectCopy } = Utils; const generateUid = Utils.generateUid;
const objectCopy = Utils.objectCopy;
function ensureUidValidity(uid) { function ensureUidValidity(uid) {
if (uid.indexOf(':') !== -1) { if (uid.indexOf(':') !== -1) {
throw new Error(`RequestLogger UID "${uid}" contains an illegal ` throw new Error(`RequestLogger UID "${uid}" contains an illegal ` +
+ 'character: \':\'.'); 'character: \':\'.');
} }
return uid; return uid;
} }
@ -21,6 +23,9 @@ class EndLogger {
} }
augmentedLog(level, msg, data) { augmentedLog(level, msg, data) {
assert.strictEqual(this.logger.elapsedTime, null, 'The logger\'s'
+ 'end() wrapper should not be called more than'
+ ' once.');
// We can alter current instance, as it won't be usable after this // We can alter current instance, as it won't be usable after this
// call. // call.
this.fields = objectCopy(this.fields, data || {}); this.fields = objectCopy(this.fields, data || {});
@ -138,6 +143,7 @@ class EndLogger {
* request. * request.
*/ */
class RequestLogger { class RequestLogger {
/** /**
* Constructor of the WereLogs Request Logger. * Constructor of the WereLogs Request Logger.
* This function takes a logger instance, a logging level, and a last * This function takes a logger instance, a logging level, and a last
@ -173,7 +179,7 @@ class RequestLogger {
* @returns {undefined} * @returns {undefined}
*/ */
constructor(logger, logLevel, dumpThreshold, endLevel, uids) { constructor(logger, logLevel, dumpThreshold, endLevel, uids) {
let uidList; let uidList = undefined;
if (!LogLevel.shouldLog(dumpThreshold, logLevel)) { if (!LogLevel.shouldLog(dumpThreshold, logLevel)) {
throw new Error('Logging Dump level should be equal or' throw new Error('Logging Dump level should be equal or'
@ -365,6 +371,8 @@ class RequestLogger {
if (msg === undefined && data === undefined) { if (msg === undefined && data === undefined) {
return this.endLogger; return this.endLogger;
} }
assert.strictEqual(this.elapsedTime, null, 'The "end()" logging method '
+ 'should not be called more than once.');
return this.log(this.endLevel, msg, data, true); return this.log(this.endLevel, msg, data, true);
} }
@ -381,6 +389,8 @@ class RequestLogger {
* @returns {undefined} * @returns {undefined}
*/ */
errorEnd(msg, data) { errorEnd(msg, data) {
assert.strictEqual(this.elapsedTime, null, 'The "end()" logging method '
+ 'should not be called more than once.');
return this.log('error', msg, data, true); return this.log('error', msg, data, true);
} }
@ -422,8 +432,7 @@ class RequestLogger {
+ ' This development error should be fixed ASAP.', + ' This development error should be fixed ASAP.',
{ {
callparams: [msg, logFields], callparams: [msg, logFields],
}, });
);
return; return;
} }
const fields = objectCopy({}, this.fields, logFields || {}); const fields = objectCopy({}, this.fields, logFields || {});
@ -434,18 +443,14 @@ class RequestLogger {
* uses this field to generate ISO 8601 timestamp * uses this field to generate ISO 8601 timestamp
*/ */
if (fields.time === undefined) { if (fields.time === undefined) {
fields.time = Date.now(); const now = process.hrtime();
fields.time = now[0] * 1000;
fields.time_ns = now[1];
} }
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
fields.req_id = serializeUids(this.uids); fields.req_id = serializeUids(this.uids);
if (endFlag) { if (endFlag) {
if (this.elapsedTime !== null) {
// reset elapsedTime to avoid an infinite recursion
// while logging the error
this.elapsedTime = null;
this.error('RequestLogger.end() has been called more than once');
}
this.elapsedTime = process.hrtime(this.startTime); this.elapsedTime = process.hrtime(this.startTime);
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
fields.elapsed_ms = this.elapsedTime[0] * 1000 fields.elapsed_ms = this.elapsedTime[0] * 1000

View File

@ -1,17 +1,7 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const os = require('os'); const os = require('os');
const safeJSONStringify = require('safe-json-stringify'); const safeJSONStringify = require('safe-json-stringify');
const fastJSONStringify = require('fast-safe-stringify')
function errorStackReplacer(key, value) {
if (value instanceof Error) {
return value.stack;
}
return value;
}
/* /*
* This function safely stringifies JSON. If an exception occcurs (due to * This function safely stringifies JSON. If an exception occcurs (due to
* circular references, exceptions thrown from object getters etc.), the module * circular references, exceptions thrown from object getters etc.), the module
@ -23,13 +13,12 @@ function errorStackReplacer(key, value) {
function safeStringify(obj) { function safeStringify(obj) {
let str; let str;
try { try {
// Try to stringify the object (fast version) str = JSON.stringify(obj);
str = fastJSONStringify(obj, errorStackReplacer);
} catch (e) { } catch (e) {
// fallback to remove circular object references or other exceptions // fallback to remove circular object references or other exceptions
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
obj.unsafeJSON = true; obj.unsafeJSON = true;
return safeJSONStringify(obj, errorStackReplacer); return safeJSONStringify(obj);
} }
return str; return str;
} }
@ -48,8 +37,8 @@ class SimpleLogger {
this.streams = [{ level: 'trace', stream: process.stdout }]; this.streams = [{ level: 'trace', stream: process.stdout }];
if (streams) { if (streams) {
if (!Array.isArray(streams)) { if (!Array.isArray(streams)) {
throw new Error('Invalid streams. streams must be an array' throw new Error('Invalid streams. streams must be an array' +
+ ' list of writeable streams'); ' list of writeable streams');
} }
/* /*
* This is for backwards compatibility. current config in projects * This is for backwards compatibility. current config in projects
@ -79,9 +68,8 @@ class SimpleLogger {
logFields.hostname = this.hostname; logFields.hostname = this.hostname;
logFields.pid = process.pid; logFields.pid = process.pid;
const safeString = safeStringify(logFields);
this.streams.forEach(s => s.stream this.streams.forEach(s => s.stream
.write(`${safeString}\n`)); .write(`${safeStringify(logFields)}\n`));
} }
info(fields, message) { info(fields, message) {

View File

@ -1,5 +1,4 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
/** /**
* @constant * @constant

View File

@ -1,10 +1,10 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const Config = require('./Config.js'); const Config = require('./Config.js');
const Logger = require('./Logger.js'); const Logger = require('./Logger.js');
class API { class API {
/** /**
* This is the constructor of the Logger class. It takes optional * This is the constructor of the Logger class. It takes optional
* configuration parameters, that allow to modify its behavior. * configuration parameters, that allow to modify its behavior.

View File

@ -1,106 +0,0 @@
/**
* @returns {string} a timestamp in ISO format YYYY-MM-DDThh:mm:ss.sssZ
*/
const defaultTimestamp = () => new Date().toISOString();
/**
* Prints on stderr a timestamp, the origin and the error
*
* If no other instructions are needed on uncaughtException,
* consider using `catchAndTimestampStderr` directly.
*
* @example
* process.on('uncaughtException', (err, origin) => {
* printErrorWithTimestamp(err, origin);
* // server.close();
* // file.close();
* process.nextTick(() => process.exit(1));
* });
* // Don't forget to timestamp warning
* catchAndTimestampWarning();
* @param {Error} err see process event uncaughtException
* @param {uncaughtException|unhandledRejection} origin see process event
* @param {string} [date=`defaultTimestamp()`] Date to print
* @returns {boolean} see process.stderr.write
*/
function printErrorWithTimestamp(
err, origin, date = defaultTimestamp(),
) {
return process.stderr.write(`${date}: ${origin}:\n${err.stack}\n`);
}
/**
* Prefer using `catchAndTimestampStderr` instead of this function.
*
* Adds listener for uncaughtException to print with timestamp.
*
* If you want to manage the end of the process, you can set exitCode to null.
* Or use `printErrorWithTimestamp` in your own uncaughtException listener.
*
* @param {Function} [dateFct=`defaultTimestamp`] Fct returning a formatted date
* @param {*} [exitCode=1] On uncaughtException, if not null, `process.exit`
* will be called with this value
* @returns {undefined}
*/
function catchAndTimestampUncaughtException(
dateFct = defaultTimestamp, exitCode = 1,
) {
process.on('uncaughtException', (err, origin) => {
printErrorWithTimestamp(err, origin, dateFct());
if (exitCode !== null) {
process.nextTick(() => process.exit(exitCode));
}
});
}
/**
* Forces the use of `--trace-warnings` and adds a date in warning.detail
* The warning will be printed by the default `onWarning`
*
* @param {string} [dateFct=`defaultTimestamp`] Fct returning a formatted date
* @returns {undefined}
*/
function catchAndTimestampWarning(dateFct = defaultTimestamp) {
process.traceProcessWarnings = true;
// must be executed first, before the default `onWarning`
process.prependListener('warning', warning => {
if (warning.detail) {
// eslint-disable-next-line no-param-reassign
warning.detail += `\nAbove Warning Date: ${dateFct()}`;
} else {
// eslint-disable-next-line no-param-reassign
warning.detail = `Above Warning Date: ${dateFct()}`;
}
});
}
/**
* Adds listener for uncaughtException and warning to print them with timestamp.
*
* If you want to manage the end of the process, you can set exitCode to null.
* Or use `printErrorWithTimestamp` in your own uncaughtException listener.
*
* @example
* const { stderrUtils } = require('werelogs');
* // first instruction in your index.js or entrypoint
* stderrUtils.catchAndTimestampStderr();
*
* @param {Function} [dateFct=`defaultTimestamp`] Fct returning a formatted date
* @param {*} [exitCode=1] On uncaughtException, if not null, `process.exit`
* will be called with this value
* @returns {undefined}
*/
function catchAndTimestampStderr(
dateFct = defaultTimestamp, exitCode = 1,
) {
catchAndTimestampUncaughtException(dateFct, exitCode);
catchAndTimestampWarning(dateFct);
}
module.exports = {
defaultTimestamp,
printErrorWithTimestamp,
catchAndTimestampUncaughtException,
catchAndTimestampWarning,
catchAndTimestampStderr,
};

View File

@ -3,16 +3,16 @@
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
"version": "8.1.5", "version": "8.0.0",
"description": "An efficient raw JSON logging library aimed at micro-services architectures.", "description": "An efficient raw JSON logging library aimed at micro-services architectures.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"gendoc": "jsdoc $(git ls-files 'lib/*.js') -d doc", "gendoc": "jsdoc $(git ls-files 'lib/*.js') -d doc",
"lint": "eslint $(git ls-files '*.js')", "lint": "eslint $(git ls-files '*.js')",
"lint_md": "markdownlint $(git ls-files '*.md')", "lint_md": "mdlint $(git ls-files '*.md')",
"test": "mocha tests/unit/", "test": "mocha tests/unit/",
"ft_test": "(npm pack && cp werelogs-*.tgz tests/functional && cd tests/functional && cp -R ../../node_modules/ node_modules/ && npm install werelogs-*.tgz && ./node_modules/.bin/mocha . multi-modules/ && rm -rf tests/functional/node_modules tests/functional/werelogs-*.tgz tests/functional/*lock*)", "ft_test": "rm -rf tests/functional/node_modules && npm pack && cp -R node_modules werelogs-*.tgz tests/functional && cd tests/functional && npm install werelogs-*.tgz && ./node_modules/.bin/mocha . multi-modules/ && cd -",
"coverage": "nyc ./node_modules/.bin/_mocha tests/unit" "coverage": "istanbul cover ./node_modules/.bin/_mocha tests/unit"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -33,20 +33,17 @@
}, },
"homepage": "https://github.com/scality/werelogs#readme", "homepage": "https://github.com/scality/werelogs#readme",
"dependencies": { "dependencies": {
"fast-safe-stringify": "^2.1.1", "safe-json-stringify": "1.0.3"
"safe-json-stringify": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^7.32.0", "eslint-plugin-react": "^4.3.0",
"eslint-config-airbnb": "^18.2.1", "eslint": "^2.13.1",
"eslint-config-scality": "git+https://git.yourcmc.ru/vitalif/zenko-eslint-config-scality.git", "eslint-config-airbnb": "^6.2.0",
"eslint-plugin-import": "^2.22.1", "eslint-config-scality": "scality/Guidelines#71a059ad",
"eslint-plugin-jsx-a11y": "^6.4.1", "istanbul": "^1.0.0-alpha.2",
"eslint-plugin-react": "^7.26.0", "istanbul-api": "==1.0.0-alpha.9",
"eslint-plugin-react-hooks": "^4.2.0",
"jsdoc": "^3.4.3", "jsdoc": "^3.4.3",
"markdownlint-cli": "^0.27.1", "mdlint": "^0.1.0",
"mocha": ">=3.1.2", "mocha": "^3.2.0"
"nyc": "^15.1.0"
} }
} }

View File

@ -1,11 +1,11 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const assert = require('assert'); const assert = require('assert');
const LogLevel = require('../lib/LogLevel.js'); const LogLevel = require('../lib/LogLevel.js');
class DummyLogger { class DummyLogger {
constructor() { constructor() {
this.ops = []; this.ops = [];
this.counts = { this.counts = {
@ -57,8 +57,8 @@ function computeBehavior(filterLevel, logLevel, testLevel) {
return { return {
value, value,
msg: `Expected ${logLevel} to be called ${value} times with ` msg: `Expected ${logLevel} to be called ${value} times with ` +
+ `filter level ${filterLevel}.`, `filter level ${filterLevel}.`,
}; };
} }
@ -107,8 +107,7 @@ function loggingMisuseGenerator(test, createLogger) {
logger.info.apply(logger, test.args); logger.info.apply(logger, test.args);
}, },
Error, Error,
`Werelogs should not throw with ${test.desc}`, `Werelogs should not throw with ${test.desc}`);
);
assert(dummyLogger.ops[0][0], 'fatal', assert(dummyLogger.ops[0][0], 'fatal',
'Expected the Module Logger to have logged a fatal message.'); 'Expected the Module Logger to have logged a fatal message.');
done(); done();

View File

@ -1,10 +1,8 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const assert = require('assert'); const assert = require('assert');
const { PassThrough } = require('stream'); const PassThrough = require('stream').PassThrough;
const pass = new PassThrough;
const pass = new PassThrough();
const werelogs = require('werelogs'); // eslint-disable-line const werelogs = require('werelogs'); // eslint-disable-line
@ -52,7 +50,7 @@ describe('Werelogs is usable as a dependency', () => {
assert.doesNotThrow( assert.doesNotThrow(
createModuleLogger, createModuleLogger,
Error, Error,
'Werelogs threw an exception trying to create a ModuleLogger.', 'Werelogs threw an exception trying to create a ModuleLogger.'
); );
done(); done();
}); });
@ -68,11 +66,8 @@ describe('Werelogs is usable as a dependency', () => {
it('Should be able to log a message and additional fields', done => { it('Should be able to log a message and additional fields', done => {
const logger = createModuleLogger(); const logger = createModuleLogger();
const msg = 'This is a message with added fields'; const msg = 'This is a message with added fields';
const fields = { const fields = { errorCode: 9, description: 'TestError',
errorCode: 9, options: { dump: false } };
description: 'TestError',
options: { dump: false },
};
logger.info(msg, fields); logger.info(msg, fields);
assert.strictEqual(parseLogEntry().message, msg); assert.strictEqual(parseLogEntry().message, msg);
checkFields(fields); checkFields(fields);
@ -88,7 +83,7 @@ describe('Werelogs is usable as a dependency', () => {
assert.doesNotThrow( assert.doesNotThrow(
() => createModuleLogger().newRequestLogger(), () => createModuleLogger().newRequestLogger(),
Error, Error,
'Werelogs threw an exception trying to create a ModuleLogger.', 'Werelogs threw an exception trying to create a ModuleLogger.'
); );
done(); done();
}); });
@ -104,11 +99,8 @@ describe('Werelogs is usable as a dependency', () => {
it('Should be able to log a message and additional fields', done => { it('Should be able to log a message and additional fields', done => {
const logger = createModuleLogger().newRequestLogger(); const logger = createModuleLogger().newRequestLogger();
const msg = 'This is a message with added fields'; const msg = 'This is a message with added fields';
const fields = { const fields = { errorCode: 9, description: 'TestError',
errorCode: 9, options: { dump: false } };
description: 'TestError',
options: { dump: false },
};
logger.info(msg, fields); logger.info(msg, fields);
assert.strictEqual(parseLogEntry().message, msg); assert.strictEqual(parseLogEntry().message, msg);
checkFields(fields); checkFields(fields);

View File

@ -1,5 +1,5 @@
const assert = require('assert'); const assert = require('assert');
const { PassThrough } = require('stream'); const PassThrough = require('stream').PassThrough;
const Werelogs = require('werelogs'); // eslint-disable-line const Werelogs = require('werelogs'); // eslint-disable-line
const modules = [ const modules = [
@ -7,8 +7,7 @@ const modules = [
require('./module2.js'), require('./module2.js'),
require('./module3.js'), require('./module3.js'),
]; ];
const pass = new PassThrough;
const pass = new PassThrough();
const logBuffer = { const logBuffer = {
records: [], records: [],
@ -18,8 +17,8 @@ pass.on('data', data => {
}); });
describe('Config is shared and unique within one API', () => { describe('Config is shared and unique within one API', () => {
it('should find all log entries in the RingBuffer with the right ' it('should find all log entries in the RingBuffer with the right ' +
+ 'module name', done => { 'module name', done => {
Werelogs.configure({ Werelogs.configure({
level: 'debug', level: 'debug',
dump: 'fatal', dump: 'fatal',

View File

@ -16,8 +16,7 @@ describe('Config', () => {
() => { () => {
config.logger.info('test message'); config.logger.info('test message');
}, },
Error, Error);
);
done(); done();
}); });

View File

@ -1,5 +1,4 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const assert = require('assert'); const assert = require('assert');
@ -13,8 +12,7 @@ function generateValidThrowTest(level) {
}, },
Error, Error,
'Expected level to be valid and ' 'Expected level to be valid and '
+ 'the function not to throw an Error.', + 'the function not to throw an Error.');
);
done(); done();
}; };
} }
@ -28,8 +26,7 @@ describe('LogLevel', () => {
}, },
RangeError, RangeError,
'Expected function to throw an Error instance due to ' 'Expected function to throw an Error instance due to '
+ 'invalid log level.', + 'invalid log level.');
);
done(); done();
}); });
@ -57,8 +54,7 @@ describe('LogLevel', () => {
assert.strictEqual( assert.strictEqual(
LogLevel.shouldLog('trace', 'trace'), LogLevel.shouldLog('trace', 'trace'),
true, true,
'Expected trace floor to allow logging trace level.', 'Expected trace floor to allow logging trace level.');
);
done(); done();
}); });
@ -66,8 +62,7 @@ describe('LogLevel', () => {
assert.strictEqual( assert.strictEqual(
LogLevel.shouldLog('debug', 'debug'), LogLevel.shouldLog('debug', 'debug'),
true, true,
'Expected debug floor to allow logging debug level.', 'Expected debug floor to allow logging debug level.');
);
done(); done();
}); });
@ -75,8 +70,7 @@ describe('LogLevel', () => {
assert.strictEqual( assert.strictEqual(
LogLevel.shouldLog('info', 'info'), LogLevel.shouldLog('info', 'info'),
true, true,
'Expected info floor to allow logging info level.', 'Expected info floor to allow logging info level.');
);
done(); done();
}); });
@ -84,8 +78,7 @@ describe('LogLevel', () => {
assert.strictEqual( assert.strictEqual(
LogLevel.shouldLog('warn', 'warn'), LogLevel.shouldLog('warn', 'warn'),
true, true,
'Expected warn floor to allow logging warn level.', 'Expected warn floor to allow logging warn level.');
);
done(); done();
}); });
@ -93,8 +86,7 @@ describe('LogLevel', () => {
assert.strictEqual( assert.strictEqual(
LogLevel.shouldLog('error', 'error'), LogLevel.shouldLog('error', 'error'),
true, true,
'Expected error floor to allow logging error level.', 'Expected error floor to allow logging error level.');
);
done(); done();
}); });
@ -102,8 +94,7 @@ describe('LogLevel', () => {
assert.strictEqual( assert.strictEqual(
LogLevel.shouldLog('fatal', 'fatal'), LogLevel.shouldLog('fatal', 'fatal'),
true, true,
'Expected fatal floor to allow logging fatal level.', 'Expected fatal floor to allow logging fatal level.');
);
done(); done();
}); });
}); });

View File

@ -1,9 +1,11 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const assert = require('assert'); const assert = require('assert');
const { genericFilterGenerator, loggingMisuseGenerator, DummyLogger } = require('../Utils'); const Utils = require('../Utils.js');
const genericFilterGenerator = Utils.genericFilterGenerator;
const loggingMisuseGenerator = Utils.loggingMisuseGenerator;
const DummyLogger = Utils.DummyLogger;
const Config = require('../../lib/Config.js'); const Config = require('../../lib/Config.js');
const RequestLogger = require('../../lib/RequestLogger.js'); const RequestLogger = require('../../lib/RequestLogger.js');
@ -52,8 +54,7 @@ describe('Logger is usable:', () => {
assert.throws( assert.throws(
() => new Logger(), () => new Logger(),
TypeError, TypeError,
'Logger Instanciation should not succeed without parameter.', 'Logger Instanciation should not succeed without parameter.');
);
done(); done();
}); });
@ -61,8 +62,7 @@ describe('Logger is usable:', () => {
assert.throws( assert.throws(
() => new Logger(config), () => new Logger(config),
TypeError, TypeError,
'Logger Instanciation should not be succeed without a name.', 'Logger Instanciation should not be succeed without a name.');
);
done(); done();
}); });
@ -70,8 +70,7 @@ describe('Logger is usable:', () => {
assert.throws( assert.throws(
() => new Logger({ level: 'info' }, 'WereLogsTest'), () => new Logger({ level: 'info' }, 'WereLogsTest'),
TypeError, TypeError,
'Logger Instanciation should not succeed with a bad config type.', 'Logger Instanciation should not succeed with a bad config type.');
);
done(); done();
}); });
@ -79,8 +78,7 @@ describe('Logger is usable:', () => {
assert.throws( assert.throws(
() => new Logger('WereLogsTest'), () => new Logger('WereLogsTest'),
TypeError, TypeError,
'Logger Instanciation should not succeed with only a name.', 'Logger Instanciation should not succeed with only a name.');
);
done(); done();
}); });
@ -91,8 +89,7 @@ describe('Logger is usable:', () => {
logger.newRequestLogger(); logger.newRequestLogger();
}, },
Error, Error,
'Werelogs should not throw when creating a request logger.', 'Werelogs should not throw when creating a request logger.');
);
done(); done();
}); });
@ -104,11 +101,9 @@ describe('Logger is usable:', () => {
}, },
Error, Error,
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
'Werelogs should not throw when creating a request logger from a Serialized UID Array.', 'Werelogs should not throw when creating a request logger from a Serialized UID Array.');
);
const reqLogger = logger.newRequestLoggerFromSerializedUids( const reqLogger = logger.newRequestLoggerFromSerializedUids(
'OneUID:SecondUID:TestUID:YouWinUID', 'OneUID:SecondUID:TestUID:YouWinUID');
);
assert(reqLogger instanceof RequestLogger, 'RequestLogger'); assert(reqLogger instanceof RequestLogger, 'RequestLogger');
assert.deepStrictEqual(reqLogger.getUids().slice(0, -1), assert.deepStrictEqual(reqLogger.getUids().slice(0, -1),
['OneUID', 'SecondUID', 'TestUID', 'YouWinUID']); ['OneUID', 'SecondUID', 'TestUID', 'YouWinUID']);
@ -134,7 +129,7 @@ describe('Logger is usable:', () => {
describe('Does not crash and logs a fatal message when mis-using its logging API', () => { describe('Does not crash and logs a fatal message when mis-using its logging API', () => {
const testValues = [ const testValues = [
{ desc: 'a string as second argument', args: ['test', 'second-param-string'] }, { desc: 'a string as second argument', args: ['test', 'second-param-string'] },
{ desc: 'a function as second argument', args: ['test', () => { }] }, // eslint-disable-line arrow-body-style { desc: 'a function as second argument', args: ['test', () => { return; }] }, // eslint-disable-line arrow-body-style
{ desc: 'a Number as second argument', args: ['test', 1] }, { desc: 'a Number as second argument', args: ['test', 1] },
{ desc: 'more than 2 arguments', args: ['test', 2, 3, 4] }, { desc: 'more than 2 arguments', args: ['test', 2, 3, 4] },
]; ];

View File

@ -1,9 +1,11 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const assert = require('assert'); const assert = require('assert');
const { DummyLogger, genericFilterGenerator, loggingMisuseGenerator } = require('../Utils.js'); const Utils = require('../Utils.js');
const DummyLogger = Utils.DummyLogger;
const genericFilterGenerator = Utils.genericFilterGenerator;
const loggingMisuseGenerator = Utils.loggingMisuseGenerator;
const RequestLogger = require('../../lib/RequestLogger.js'); const RequestLogger = require('../../lib/RequestLogger.js');
@ -60,8 +62,7 @@ describe('RequestLogger', () => {
assert.throws( assert.throws(
() => new RequestLogger(undefined, 'fatal', 'debug', 'info'), () => new RequestLogger(undefined, 'fatal', 'debug', 'info'),
Error, Error,
'Dump level "debug" should not be valid with logging level "fatal".', 'Dump level "debug" should not be valid with logging level "fatal".');
);
done(); done();
}); });
@ -69,8 +70,7 @@ describe('RequestLogger', () => {
assert.doesNotThrow( assert.doesNotThrow(
() => new RequestLogger(undefined, 'debug', 'fatal', 'info'), () => new RequestLogger(undefined, 'debug', 'fatal', 'info'),
Error, Error,
'Dump level "fatal" should be valid with logging level "debug".', 'Dump level "fatal" should be valid with logging level "debug".');
);
done(); done();
}); });
}); });
@ -98,8 +98,7 @@ describe('RequestLogger', () => {
assert.throws( assert.throws(
() => new RequestLogger(undefined, 'debug', 'fatal', 'info', 'pouet:tata'), () => new RequestLogger(undefined, 'debug', 'fatal', 'info', 'pouet:tata'),
Error, Error,
'UID string "pouet:tata" should be rejected by the RequestLogger constructor.', 'UID string "pouet:tata" should be rejected by the RequestLogger constructor.');
);
done(); done();
}); });
@ -117,8 +116,7 @@ describe('RequestLogger', () => {
assert.throws( assert.throws(
() => new RequestLogger(undefined, 'debug', 'fatal', 'info', ['OneUID', 'SecondUID', 'Test:DashUID']), () => new RequestLogger(undefined, 'debug', 'fatal', 'info', ['OneUID', 'SecondUID', 'Test:DashUID']),
Error, Error,
'UID string "Test:DashUID" should be rejected by the RequestLogger constructor.', 'UID string "Test:DashUID" should be rejected by the RequestLogger constructor.');
);
done(); done();
}); });
}); });
@ -188,7 +186,7 @@ describe('RequestLogger', () => {
describe('Does not crash when mis-using its logging API', () => { describe('Does not crash when mis-using its logging API', () => {
const testValues = [ const testValues = [
{ desc: 'a string as second argument', args: ['test', 'second-param-string'] }, { desc: 'a string as second argument', args: ['test', 'second-param-string'] },
{ desc: 'a function as second argument', args: ['test', function f() { }] }, { desc: 'a function as second argument', args: ['test', function f() { return; }] },
{ desc: 'a Number as second argument', args: ['test', 1] }, { desc: 'a Number as second argument', args: ['test', 1] },
{ desc: 'more than 2 arguments', args: ['test', 2, 3, 4] }, { desc: 'more than 2 arguments', args: ['test', 2, 3, 4] },
]; ];
@ -255,7 +253,7 @@ describe('RequestLogger', () => {
const reqLogger = new RequestLogger(dummyLogger, const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info'); 'info', 'fatal', 'info');
const refFields = { hits: 45, count: 32 }; const refFields = { hits: 45, count: 32 };
const usedFields = { ...refFields }; const usedFields = Object.assign({}, refFields);
reqLogger.debug('test', usedFields); reqLogger.debug('test', usedFields);
assert.deepStrictEqual(usedFields, refFields); assert.deepStrictEqual(usedFields, refFields);
done(); done();
@ -267,7 +265,7 @@ describe('RequestLogger', () => {
const reqLogger = new RequestLogger(dummyLogger, const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info'); 'info', 'fatal', 'info');
const refFields = { hits: 45, count: 32 }; const refFields = { hits: 45, count: 32 };
const usedFields = { ...refFields }; const usedFields = Object.assign({}, refFields);
reqLogger.info('test', usedFields); reqLogger.info('test', usedFields);
assert.deepStrictEqual(usedFields, refFields); assert.deepStrictEqual(usedFields, refFields);
done(); done();
@ -278,7 +276,7 @@ describe('RequestLogger', () => {
const reqLogger = new RequestLogger(dummyLogger, const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info'); 'info', 'fatal', 'info');
const refFields = { hits: 45, count: 32 }; const refFields = { hits: 45, count: 32 };
const usedFields = { ...refFields }; const usedFields = Object.assign({}, refFields);
reqLogger.error('test', usedFields); reqLogger.error('test', usedFields);
assert.deepStrictEqual(usedFields, refFields); assert.deepStrictEqual(usedFields, refFields);
done(); done();
@ -414,35 +412,14 @@ describe('RequestLogger', () => {
assert.strictEqual(dummyLogger.ops[0][1][0].endValue, 42); assert.strictEqual(dummyLogger.ops[0][1][0].endValue, 42);
done(); done();
}); });
it('should log an error in addition to request logs when end() called more than once',
done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'trace', 'fatal');
reqLogger.end().info('after first call to end()');
reqLogger.end().debug('after second call to end()');
assert.strictEqual(dummyLogger.ops.length, 3);
assert.strictEqual(dummyLogger.ops[0][0], 'info');
assert.strictEqual(dummyLogger.ops[0][1][1], 'after first call to end()');
assert.strictEqual(dummyLogger.ops[1][0], 'error');
assert.strictEqual(dummyLogger.ops[2][0], 'debug');
assert.strictEqual(dummyLogger.ops[2][1][1], 'after second call to end()');
done();
});
}); });
describe('Log History dumped when logging floor level reached', () => { describe('Log History dumped when logging floor level reached', () => {
it('Dumping duplicates log entries', done => { it('Dumping duplicates log entries', done => {
const commandHistory = ['info', 'error']; const commandHistory = ['info', 'error'];
const expectedHistory = [['info', 0], ['info', 0], ['error', 1]]; const expectedHistory = [['info', 0], ['info', 0], ['error', 1]];
const expectedCounts = { const expectedCounts = { trace: 0, debug: 0, info: 2, warn: 0,
trace: 0, error: 1, fatal: 0 };
debug: 0,
info: 2,
warn: 0,
error: 1,
fatal: 0,
};
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts,
done); done);
@ -454,14 +431,8 @@ describe('RequestLogger', () => {
const expectedHistory = [['trace', 0], ['info', 1], ['debug', 2], const expectedHistory = [['trace', 0], ['info', 1], ['debug', 2],
['trace', 0], ['info', 1], ['debug', 2], ['trace', 0], ['info', 1], ['debug', 2],
['error', 3]]; ['error', 3]];
const expectedCounts = { const expectedCounts = { trace: 2, debug: 2, info: 2, warn: 0,
trace: 2, error: 1, fatal: 0 };
debug: 2,
info: 2,
warn: 0,
error: 1,
fatal: 0,
};
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts,
done); done);
@ -477,14 +448,8 @@ describe('RequestLogger', () => {
['error', 3], ['warn', 4], ['debug', 5], ['error', 3], ['warn', 4], ['debug', 5],
['warn', 4], ['debug', 5], ['warn', 4], ['debug', 5],
['fatal', 6]]; ['fatal', 6]];
const expectedCounts = { const expectedCounts = { trace: 2, debug: 4, info: 2, warn: 2,
trace: 2, error: 1, fatal: 1 };
debug: 4,
info: 2,
warn: 2,
error: 1,
fatal: 1,
};
runLoggingDumpTest(commandHistory, expectedHistory, runLoggingDumpTest(commandHistory, expectedHistory,
expectedCounts, done); expectedCounts, done);

View File

@ -1,11 +1,11 @@
'use strict'; // eslint-disable-line strict
// eslint-disable-line strict
const assert = require('assert'); const assert = require('assert');
const Utils = require('../../lib/Utils.js');
const { const generateUid = Utils.generateUid;
generateUid, serializeUids, unserializeUids, objectCopy, const serializeUids = Utils.serializeUids;
} = require('../../lib/Utils'); const unserializeUids = Utils.unserializeUids;
const objectCopy = Utils.objectCopy;
describe('Utils: generateUid', () => { describe('Utils: generateUid', () => {
it('generates a string-typed ID', done => { it('generates a string-typed ID', done => {
@ -24,8 +24,8 @@ describe('Utils: generateUid', () => {
} }
Object.keys(generated).every(uid => { Object.keys(generated).every(uid => {
assert.strictEqual(generated[uid], 1, assert.strictEqual(generated[uid], 1,
`Uid ${uid} was generated ${generated[uid]} ` `Uid ${uid} was generated ${generated[uid]} ` +
+ 'times: It is not even remotely unique.'); 'times: It is not even remotely unique.');
return {}; return {};
}); });
done(); done();
@ -54,12 +54,8 @@ describe('Utils: objectCopy', () => {
it('copies all the properties from source to target object', done => { it('copies all the properties from source to target object', done => {
const target = { foo: 'bar' }; const target = { foo: 'bar' };
const source = { id: 1, name: 'demo', value: { a: 1, b: 2, c: 3 } }; const source = { id: 1, name: 'demo', value: { a: 1, b: 2, c: 3 } };
const result = { const result = { foo: 'bar', id: 1, name: 'demo',
foo: 'bar', value: { a: 1, b: 2, c: 3 } };
id: 1,
name: 'demo',
value: { a: 1, b: 2, c: 3 },
};
objectCopy(target, source); objectCopy(target, source);
assert.deepStrictEqual(target, result, assert.deepStrictEqual(target, result,
'target should have the same properties as source'); 'target should have the same properties as source');
@ -69,27 +65,16 @@ describe('Utils: objectCopy', () => {
it('copies all the properties from multiple sources to target object', it('copies all the properties from multiple sources to target object',
done => { done => {
const target = { foo: 'bar' }; const target = { foo: 'bar' };
const source1 = { const source1 = { id: 1, name: 'demo1',
id: 1, value: { a: 1, b: 2, c: 3 } };
name: 'demo1',
value: { a: 1, b: 2, c: 3 },
};
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
const source2 = { const source2 = { req_id: 2, method: 'test',
req_id: 2, err: { code: 'error', msg: 'test' } };
method: 'test', const result = { foo: 'bar', id: 1, name: 'demo1',
err: { code: 'error', msg: 'test' },
};
const result = {
foo: 'bar',
id: 1,
name: 'demo1',
value: { a: 1, b: 2, c: 3 }, value: { a: 1, b: 2, c: 3 },
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
req_id: 2, req_id: 2, method: 'test',
method: 'test', err: { code: 'error', msg: 'test' } };
err: { code: 'error', msg: 'test' },
};
objectCopy(target, source1, source2); objectCopy(target, source1, source2);
assert.deepStrictEqual(target, result, assert.deepStrictEqual(target, result,
'target should have the same properties as source'); 'target should have the same properties as source');

View File

@ -1,17 +0,0 @@
#!/usr/bin/env node
// Convert string args into primitive value
const fromStr = (str, primitive) => (str === `${primitive}` ? primitive : str);
const date = fromStr(process.argv[2], undefined);
const exitCode = fromStr(fromStr(process.argv[3], null), undefined);
const { stderrUtils } = require('../../../../index');
stderrUtils.catchAndTimestampStderr(
date ? () => date : undefined,
exitCode,
);
process.emitWarning('TestWarningMessage');
// This will print warning after printing error before exit
throw new Error('TestingError');

View File

@ -1,23 +0,0 @@
#!/usr/bin/env node
// Convert string args into primitive value
const fromStr = (str, primitive) => (str === `${primitive}` ? primitive : str);
const date = fromStr(process.argv[2], undefined);
const exitCode = fromStr(fromStr(process.argv[3], null), undefined);
const promise = fromStr(process.argv[4], true);
const { stderrUtils } = require('../../../../index');
stderrUtils.catchAndTimestampUncaughtException(
date ? () => date : undefined,
exitCode,
);
// Executed if process does not exit, process is in undefined behavior (bad)
// eslint-disable-next-line no-console
setTimeout(() => console.log('EXECUTED AFTER UNCAUGHT EXCEPTION'), 1);
if (promise === true) {
Promise.reject();
} else {
throw new Error('TestingError');
}

View File

@ -1,38 +0,0 @@
#!/usr/bin/env node
// Convert string args into primitive value
const fromStr = (str, primitive) => (str === `${primitive}` ? primitive : str);
const date = fromStr(process.argv[2], undefined);
const name = fromStr(process.argv[3], undefined);
const code = fromStr(process.argv[4], undefined);
const detail = fromStr(process.argv[5], undefined);
const { stderrUtils } = require('../../../../index');
stderrUtils.catchAndTimestampWarning(
date ? () => date : undefined,
);
const warning = new Error('TestWarningMessage');
if (name) warning.name = name;
if (code) warning.code = code;
if (detail) warning.detail = detail;
process.emitWarning(warning);
/*
Examples:
(node:203831) Error: TestWarningMessage
at Object.<anonymous> (catchWarning.js:15:17)
...
at node:internal/main/run_main_module:22:47
Above Warning Date: 2024-06-26T16:32:55.505Z
(node:205151) [TEST01] CUSTOM: TestWarningMessage
at Object.<anonymous> (catchWarning.js:15:17)
...
at node:internal/main/run_main_module:22:47
Some additional detail
Above Warning Date: Tue, 31 Dec 2024 10:20:30 GMT
*/

View File

@ -1,309 +0,0 @@
const assert = require('assert');
const { execFile } = require('child_process');
const stderrUtils = require('../../lib/stderrUtils');
/** Simple regex for ISO YYYY-MM-DDThh:mm:ss.sssZ */
// eslint-disable-next-line max-len
const defaultDateRegex = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)/;
// eslint-disable-next-line valid-jsdoc
/** another format: Tue, 31 Dec 2024 10:20:30 GMT */
const customDate = () => new Date('2024-12-31T10:20:30.444Z').toUTCString();
describe('stderrUtils', () => {
const errStackRegex = /Error: TestingError\n(?:.*\sat\s.*\n)+/;
describe('defaultTimestamp', () => {
it('should match ISO format', () => {
assert.match(stderrUtils.defaultTimestamp(), defaultDateRegex);
});
});
describe('printErrorWithTimestamp', () => {
let stderrText;
const originalStderrWrite = process.stderr.write;
const mockedStderrWrite = text => { stderrText = text; return true; };
const err = new Error('TestingError');
const origin = 'uncaughtException';
beforeEach(() => {
stderrText = undefined;
process.stderr.write = mockedStderrWrite;
});
afterEach(() => {
process.stderr.write = originalStderrWrite;
stderrText = undefined;
});
it(
'should write to stderr with current date, origin and stacktrace',
() => {
const written = stderrUtils
.printErrorWithTimestamp(err, origin);
assert.strictEqual(written, true);
const [firstLine, errStack] = stderrText.split(':\n');
const [errDate, errOrigin] = firstLine.split(': ');
assert.match(errDate, defaultDateRegex);
assert.strictEqual(errOrigin, origin);
assert.strictEqual(errStack, `${err.stack}\n`);
},
);
it(
'should write to stderr with custom date, origin and stacktrace',
() => {
const written = stderrUtils
.printErrorWithTimestamp(err, origin, customDate());
assert.strictEqual(written, true);
const [firstLine, errStack] = stderrText.split(':\n');
const [errDate, errOrigin] = firstLine.split(': ');
assert.strictEqual(errDate, customDate());
assert.strictEqual(errOrigin, origin);
assert.strictEqual(errStack, `${err.stack}\n`);
},
);
});
const execOptions = {
cwd: __dirname,
// Subprocess should always stop alone
// But just in case, kill subprocess after 500ms.
// Leave enough time for `nyc` that runs slower.
timeout: 500,
};
// Execute in another process to notice the process exit
// Therefore, looks more like a functional test
const timeoutHint = (ms, retries) =>
`Test fixture process timed out after ${ms}ms with ${retries} retries.\n` +
'Due to nyc coverage first run slowing down process.\nIncrease execOptions.timeout to fix';
describe('catchAndTimestampUncaughtException', () => {
[
{ desc: 'with default date' },
{ desc: 'with custom date', date: customDate() },
{ desc: 'with custom exitCode 42', exitCode: 42 },
{ desc: 'without exit on uncaught exception', exitCode: null },
{ desc: 'for unhandled promise', promise: true },
].forEach(({
desc, date, exitCode, promise,
}) => describe(desc, () => {
/** for before all hook that doesn't support this.retries */
let retries = 4;
let err;
let stdout;
let stderr;
let errStack;
let errDate;
let errOrigin;
before('run process catchUncaughtException', function beforeAllHook(done) {
execFile(
'./fixtures/stderrUtils/catchUncaughtException.js',
[`${date}`, `${exitCode}`, `${promise}`],
execOptions,
(subErr, subStdout, subStderr) => {
if (subErr?.killed) {
retries--;
if (retries <= 0) {
assert.fail(timeoutHint(execOptions.timeout, retries));
}
execOptions.timeout *= 2;
return beforeAllHook(done);
}
err = subErr;
stdout = subStdout;
stderr = subStderr;
let firstLine;
[firstLine, errStack] = stderr.split(':\n');
[errDate, errOrigin] = firstLine.split(': ');
done();
},
);
});
if (exitCode === null) {
it('should not be an error (or timeout)',
() => assert.ifError(err));
it('should have stdout (printed after uncaught exception)',
() => assert.match(stdout,
/^.*EXECUTED AFTER UNCAUGHT EXCEPTION(?:.|\n)*$/));
} else {
it('should be an error',
() => assert.ok(err));
it(`should have exitCode ${exitCode || 1}`,
() => assert.strictEqual(err.code, exitCode || 1));
it('should have empty stdout',
() => assert.strictEqual(stdout, ''));
}
it('should have stderr',
() => assert.ok(stderr));
it('should have date in stderr first line',
() => (date
? assert.strictEqual(errDate, date)
: assert.match(errDate, defaultDateRegex)));
it('should have origin in stderr first line',
() => (promise === true
? assert.strictEqual(errOrigin, 'unhandledRejection')
: assert.strictEqual(errOrigin, 'uncaughtException')));
if (!promise) {
it('should have stack trace on stderr',
() => assert.match(errStack, errStackRegex));
}
}));
});
describe('catchAndTimestampWarning (also tests node onWarning)', () => {
[
{ desc: 'with default date' },
{ desc: 'with custom date', date: customDate() },
{ desc: 'with deprecation warning', name: 'DeprecationWarning' },
{
desc: 'with custom warning',
name: 'CUSTOM',
code: 'TEST01',
detail: 'Some additional detail',
},
].forEach(({
desc, date, name, code, detail,
}) => describe(desc, () => {
/** for before all hook that doesn't support this.retries */
let retries = 4;
let err;
let stdout;
let stderr;
before('run process catchWarning', function beforeAllHook(done) {
execFile(
'./fixtures/stderrUtils/catchWarning.js',
[`${date}`, `${name}`, `${code}`, `${detail}`],
execOptions,
(subErr, subStdout, subStderr) => {
if (subErr?.killed) {
retries--;
if (retries <= 0) {
assert.fail(timeoutHint(execOptions.timeout, retries));
}
execOptions.timeout *= 2;
return beforeAllHook(done);
}
err = subErr;
stdout = subStdout;
stderr = subStderr;
done();
},
);
});
it('should not be an error (or timeout)',
() => assert.ifError(err));
it('should have empty stdout',
() => assert.strictEqual(stdout, ''));
it('should have stderr',
() => assert.ok(stderr));
it('should have message on stderr first line, then stack trace',
() => assert.match(stderr,
/^.*TestWarningMessage\n(?:\s+at\s.*\n)+/));
if (code) {
it('should have code on stderr first line',
() => assert.match(stderr, new RegExp(`^.*[${code}]`)));
}
if (name) {
it('should have name on stderr first line',
() => assert.match(stderr, new RegExp(`^.*${name}:`)));
}
if (detail) {
it('should have detail on stderr',
() => assert.match(stderr, new RegExp(`.*${detail}.*`)));
}
it(`should have ${date ? 'custom' : 'default'} date on stderr`,
() => assert.match(stderr, new RegExp(
`\nAbove Warning Date: ${
date || defaultDateRegex.source}\n`,
)));
}));
});
describe('catchAndTimestampStderr', () => {
[
{ desc: 'with default date' },
{ desc: 'with custom date', date: customDate() },
{ desc: 'with exit code', exitCode: 42 },
].forEach(({
desc, date, exitCode,
}) => describe(desc, () => {
/** for before all hook that doesn't support this.retries */
let retries = 4;
let err;
let stdout;
let stderr;
before('run process catchStderr', function beforeAllHook(done) {
execFile(
'./fixtures/stderrUtils/catchStderr.js',
[`${date}`, `${exitCode}`],
execOptions,
(subErr, subStdout, subStderr) => {
if (subErr?.killed) {
retries--;
if (retries <= 0) {
assert.fail(timeoutHint(execOptions.timeout, retries));
}
execOptions.timeout *= 2;
return beforeAllHook(done);
}
err = subErr;
stdout = subStdout;
stderr = subStderr;
done();
},
);
});
it('should be an error',
() => assert.ok(err));
it(`should have exitCode ${exitCode || 1}`,
() => assert.strictEqual(err.code, exitCode || 1));
it('should have empty stdout',
() => assert.strictEqual(stdout, ''));
it('should have stderr',
() => assert.ok(stderr));
// 2024-06-26T15:04:55.364Z: uncaughtException:
// Error: TestingError
// at Object.<anonymous> (catchStderr.js:16:7)
// at node:internal/main/run_main_module:22:47
it('should have error date, origin and stacktrace in stderr',
() => assert.match(stderr,
new RegExp(`${date || defaultDateRegex.source
}: uncaughtException:\n${errStackRegex.source}`)));
// (node:171245) Warning: TestWarningMessage
// at Object.<anonymous> (catchStderr.js:14:9)
// at node:internal/main/run_main_module:22:47
// Above Warning Date: 2024-06-26T15:04:55.365Z
it('should have warning with stacktrace in stderr', () => {
const trace = 'Warning: TestWarningMessage\n(?:\\s+at\\s.*\n)+';
const detail = `(?:.|\n)*?(?<=\n)Above Warning Date: ${
date || defaultDateRegex.source}\n`;
assert.match(stderr,
new RegExp(`${trace}${detail}`));
});
}));
});
});

1817
yarn.lock Normal file

File diff suppressed because it is too large Load Diff