Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metalog decomposition: Logger, Formatter, Console #208

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5ff30d1
Decompose FsLogger, Console, formatting from metalog.js
georgolden Aug 22, 2022
cb0db9e
Update existing tests
georgolden Aug 22, 2022
19d6940
Logger reimplemented in metalog.js. Multiple loggers handling, DI wit…
georgolden Aug 22, 2022
cf42792
Types and changelog updated
georgolden Aug 22, 2022
d2641c9
Slightly code improvement
georgolden Aug 22, 2022
958bfe5
Apply suggestions from code review
tshemsedinov Aug 23, 2022
c6c9c64
Update lib/console.js
tshemsedinov Aug 23, 2022
c6b7009
Update deps and fix style issues
georgolden Aug 24, 2022
b3fb861
Example with pluggable redis logger
georgolden Aug 24, 2022
6fbcfff
Test workflow with redis and env vars
georgolden Aug 24, 2022
4cfefca
Remove windows and macos to perform redis tests
georgolden Aug 24, 2022
b405a3a
Fix redis connection issue during test workflow
georgolden Aug 24, 2022
7aa1b4e
Example with constructor injection
georgolden Aug 24, 2022
249fa6b
Apply suggestions from code review
tshemsedinov Aug 25, 2022
834c18a
Update lib/formatter.js
tshemsedinov Aug 25, 2022
f42f678
Update lib/formatter.js
tshemsedinov Aug 25, 2022
5a14ee5
Update metautil to 3.5.24
tshemsedinov Aug 30, 2022
fae38e1
Use metautil.isError
tshemsedinov Aug 30, 2022
b6e3f27
Update metalog config in tests
tshemsedinov Aug 30, 2022
9e296ab
Write to stdout
tshemsedinov Aug 30, 2022
8f2599f
Naming improvement, plugin implementation
georgolden Sep 1, 2022
51ee4be
Fix test
georgolden Sep 1, 2022
d927773
Merge branch 'master' into feature/writable-destinations
tshemsedinov Nov 4, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,19 @@ jobs:
- 18
os:
- ubuntu-latest
- windows-latest
- macos-latest
# - windows-latest
# - macos-latest

services:
redis:
image: redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379

steps:
- uses: actions/checkout@v2
Expand All @@ -31,3 +42,6 @@ jobs:
${{ runner.os }}-node-
- run: npm ci
- run: npm test
env:
REDIS_HOST: '0.0.0.0'
REDIS_PORT: 6379
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased][unreleased]

- Decompose metalog.js in to 4 files by destiny
- Implement interface and DI container for external loggers
- Replace old Logger with new Logger - facade for multiple loggers

## [3.1.9][] - 2022-07-07

- Package maintenance
Expand Down
116 changes: 116 additions & 0 deletions lib/console.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
'use strict';

const readline = require('readline');
const util = require('util');

const INDENT = 2;

class Console {
#write;
#groupIndent;
#counters;
#times;

constructor(write) {
this.#write = write;
this.#groupIndent = 0;
this.#counters = new Map();
this.#times = new Map();
}

assert(assertion, ...args) {
try {
console.assert(assertion, ...args);
} catch (err) {
this.#write('error', this.#groupIndent, err.stack);
}
}

clear() {
readline.cursorTo(process.stdout, 0, 0);
readline.clearScreenDown(process.stdout);
}

count(label = 'default') {
let counter = this.#counters.get(label) || 0;
counter++;
this.#counters.set(label, counter);
this.#write('debug', this.#groupIndent, `${label}: ${counter}`);
}

countReset(label = 'default') {
this.#counters.delete(label);
}

debug(...args) {
this.#write('debug', this.#groupIndent, ...args);
}

dir(...args) {
this.#write('debug', this.#groupIndent, ...args);
}

trace(...args) {
const msg = util.format(...args);
const err = new Error(msg);
this.#write('debug', this.#groupIndent, `Trace${err.stack}`);
tshemsedinov marked this conversation as resolved.
Show resolved Hide resolved
}

info(...args) {
this.#write('info', this.#groupIndent, ...args);
}

log(...args) {
this.#write('log', this.#groupIndent, ...args);
}

warn(...args) {
this.#write('warn', this.#groupIndent, ...args);
}

error(...args) {
this.#write('error', this.#groupIndent, ...args);
}

group(...args) {
if (args.length !== 0) this.log(...args);
this.#groupIndent += INDENT;
}

groupCollapsed(...args) {
this.group(...args);
}

groupEnd() {
if (this.#groupIndent.length === 0) return;
this.#groupIndent -= INDENT;
}

table(tabularData) {
this.#write('log', 0, JSON.stringify(tabularData));
}

time(label = 'default') {
this.#times.set(label, process.hrtime());
}

timeEnd(label = 'default') {
const startTime = this.#times.get(label);
const totalTime = process.hrtime(startTime);
const totalTimeMs = totalTime[0] * 1e3 + totalTime[1] / 1e6;
this.timeLog(label, `${label}: ${totalTimeMs}ms`);
this.#times.delete(label);
}

timeLog(label, ...args) {
const startTime = this.#times.get(label);
if (startTime === undefined) {
const msg = `Warning: No such label '${label}'`;
this.#write('warn', this.#groupIndent, msg);
return;
}
this.#write('debug', this.#groupIndent, ...args);
}
}

module.exports = { Console };
98 changes: 98 additions & 0 deletions lib/formatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use strict';

const util = require('util');
const concolor = require('concolor');
const metautil = require('metautil');

const STACK_AT = ' at ';
const TYPE_LENGTH = 6;
const LINE_SEPARATOR = ';';
const DATE_LEN = 'YYYY-MM-DD'.length;
const TIME_START = DATE_LEN + 1;
const TIME_END = TIME_START + 'HH:MM:SS'.length;

const TYPE_COLOR = concolor({
log: 'b,black/white',
info: 'b,white/blue',
warn: 'b,black/yellow',
debug: 'b,white/green',
error: 'b,yellow/red',
});

const TEXT_COLOR = concolor({
log: 'white',
info: 'white',
warn: 'b,yellow',
debug: 'b,green',
error: 'red',
});

const isError = (val) => val?.constructor?.name?.endsWith('Error');

class Formatter {
#home;
#workerId;

constructor(home, workerId) {
this.#home = home;
this.#workerId = workerId;
}

pretty(type, indent, ...args) {
const dateTime = new Date().toISOString();
const message = this.format(type, indent, ...args);
const normalColor = TEXT_COLOR[type];
const markColor = TYPE_COLOR[type];
const time = normalColor(dateTime.substring(TIME_START, TIME_END));
const id = normalColor(this.#workerId);
const mark = markColor(' ' + type.padEnd(TYPE_LENGTH));
const msg = normalColor(message);
return `${time} ${id} ${mark} ${msg}`;
}

file(type, indent, ...args) {
const dateTime = new Date().toISOString();
const message = this.format(type, indent, ...args);
const msg = metautil.replace(message, '\n', LINE_SEPARATOR);
return `${dateTime} [${type}] ${msg}`;
}

json(type, indent, ...args) {
const timestamp = new Date().toISOString();
const { workerId } = this;
const log = { timestamp, workerId, level: type, message: null };
const first = args.shift();
if (isError(first)) {
log.err = this.expandError(first);
} else if (typeof first === 'object') {
Object.assign(log, first);
if (isError(log.err)) log.err = this.expandError(log.err);
if (isError(log.error)) log.error = this.expandError(log.error);
}
log.message = util.format(...args);
return JSON.stringify(log);
}

format(type, indent, ...args) {
const normalize = type === 'error' || type === 'debug';
const s = `${' '.repeat(indent)}${util.format(...args)}`;
return normalize ? this.normalizeStack(s) : s;
}

normalizeStack(stack) {
if (!stack) return 'no data to log';
let res = metautil.replace(stack, STACK_AT, '');
if (this.#home) res = metautil.replace(res, this.#home, '');
return res;
}

expandError(err) {
return {
...err,
georgolden marked this conversation as resolved.
Show resolved Hide resolved
message: err.message,
stack: this.normalizeStack(err.stack),
};
}
}

module.exports = { Formatter };
Loading