@hg-ts/logger 0.5.17 → 0.5.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hg-ts/logger",
3
- "version": "0.5.17",
3
+ "version": "0.5.19",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -16,11 +16,11 @@
16
16
  "lint:ts:fix": "lint-ts --fix"
17
17
  },
18
18
  "devDependencies": {
19
- "@hg-ts-config/typescript": "0.5.17",
20
- "@hg-ts/async-context": "0.5.17",
21
- "@hg-ts/execution-mode": "0.5.17",
22
- "@hg-ts/linter": "0.5.17",
23
- "@hg-ts/types": "0.5.17",
19
+ "@hg-ts-config/typescript": "0.5.19",
20
+ "@hg-ts/async-context": "0.5.19",
21
+ "@hg-ts/execution-mode": "0.5.19",
22
+ "@hg-ts/linter": "0.5.19",
23
+ "@hg-ts/types": "0.5.19",
24
24
  "@nestjs/common": "11.1.0",
25
25
  "@nestjs/core": "11.1.0",
26
26
  "@types/node": "22.19.1",
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export {
2
+ Logger,
3
+ ConsoleLogger,
4
+ JSONLogger,
5
+ MockLogger,
6
+ } from './logger/index.js';
7
+ export * from './logger.module.js';
8
+ export * from './trace.context.js';
@@ -0,0 +1,63 @@
1
+ import * as assert from 'node:assert';
2
+ import winston from 'winston';
3
+ import {
4
+ LogLevel,
5
+ syslogToLoggerMap,
6
+ } from './base.logger.js';
7
+
8
+ export type LogMessage = winston.Logform.TransformableInfo & {
9
+ context: Nullable<string>;
10
+ message: string;
11
+ level: LogLevel;
12
+ process: {
13
+ pid: number;
14
+ title: Nullable<string>;
15
+ };
16
+ timestamp: Date;
17
+ items: unknown[];
18
+ };
19
+
20
+ function getLogData(info: winston.Logform.TransformableInfo): LogMessage {
21
+ const level = syslogToLoggerMap.get(info.level) ?? info.level as LogLevel;
22
+ const context = info['context'] ?? null;
23
+
24
+ assert.ok(typeof context === 'string' || context === null);
25
+
26
+ return {
27
+ level,
28
+ context,
29
+ process: {
30
+ pid: process.pid,
31
+ title: process.title.endsWith('node') ? null : process.title,
32
+ },
33
+ message: info.message as string,
34
+ items: Array.isArray(info['items']) ? info['items'] : [info['items']],
35
+ timestamp: new Date(),
36
+ };
37
+ }
38
+
39
+ export const baseFormatter = winston.format(info => {
40
+ const logData = getLogData(info);
41
+
42
+ logData['items'] = logData.items
43
+ .map(item => {
44
+ if (typeof item === 'bigint') {
45
+ return `${item}n`;
46
+ }
47
+
48
+ if (item instanceof Error && !('toJSON' in item)) {
49
+ return {
50
+ name: item.name,
51
+ message: item.message,
52
+ stack: item.stack,
53
+ };
54
+ }
55
+
56
+ return item;
57
+ });
58
+ return logData;
59
+ });
60
+
61
+ export function createFormatter(formatter: (info: LogMessage) => LogMessage): winston.Logform.FormatWrap {
62
+ return winston.format(formatter as any);
63
+ }
@@ -0,0 +1,83 @@
1
+ import { format } from 'node:util';
2
+ import winston from 'winston';
3
+ import { TraceContext } from '../trace.context.js';
4
+
5
+ import { Logger } from './logger.js';
6
+
7
+ type Transport = winston.Logger['add'] extends (transport: infer R) => void ? R : never;
8
+
9
+ export type LogLevel = keyof Omit<Logger, 'setContext'>;
10
+
11
+ export const syslogToLoggerMap = new Map<string, LogLevel>()
12
+ .set('emerg', 'emergency')
13
+ .set('crit', 'critical');
14
+
15
+ export const loggerToSyslogMap = new Map<LogLevel, string>()
16
+ .set('emergency', 'emerg')
17
+ .set('critical', 'crit');
18
+
19
+
20
+ export abstract class BaseLogger implements Logger {
21
+ protected context: Nullable<string> = null;
22
+ private readonly logger: winston.Logger;
23
+ private readonly traceContext: TraceContext;
24
+
25
+ protected constructor(logLevel: LogLevel, context: string, traceContext: TraceContext) {
26
+ this.context = context;
27
+ this.traceContext = traceContext;
28
+
29
+ this.logger = winston.createLogger({
30
+ level: logLevel,
31
+ levels: winston.config.syslog.levels,
32
+ transports: [],
33
+ handleExceptions: true,
34
+ handleRejections: true,
35
+ exitOnError: false,
36
+ });
37
+ }
38
+
39
+ public debug(...messages: unknown[]): void {
40
+ this.log('debug', messages);
41
+ }
42
+ public info(...messages: unknown[]): void {
43
+ this.log('info', messages);
44
+ }
45
+ public notice(...messages: unknown[]): void {
46
+ this.log('notice', messages);
47
+ }
48
+ public warning(...messages: unknown[]): void {
49
+ this.log('warning', messages);
50
+ }
51
+ public error(...messages: unknown[]): void {
52
+ this.log('error', messages);
53
+ }
54
+ public critical(...messages: unknown[]): void {
55
+ this.log('critical', messages);
56
+ }
57
+ public alert(...messages: unknown[]): void {
58
+ this.log('alert', messages);
59
+ }
60
+ public emergency(...messages: unknown[]): void {
61
+ this.log('emergency', messages);
62
+ }
63
+
64
+ public setContext(context: Nullable<string>): void {
65
+ this.context = context;
66
+ }
67
+
68
+ protected addTransport(transport: Transport): void {
69
+ this.logger.add(transport);
70
+ }
71
+
72
+ private log(level: LogLevel, messages: unknown[]): void {
73
+ const mappedLevel = loggerToSyslogMap.get(level) ?? level;
74
+
75
+ this.logger.log({
76
+ level: mappedLevel,
77
+ message: format(...messages),
78
+ items: messages,
79
+ context: this.context,
80
+ traceId: this.traceContext.get()?.traceId,
81
+ });
82
+ }
83
+ }
@@ -0,0 +1,18 @@
1
+ const isColorAllowed = !process.env['NO_COLOR'];
2
+
3
+ export enum COLOR {
4
+ ORANGE = '38;5;3',
5
+ GREEN = '32',
6
+ YELLOW = '33',
7
+ RED = '31',
8
+ MAGENTA_BRIGHT = '95',
9
+ CYAN_BRIGHT = '96',
10
+ }
11
+
12
+ export function colorize(color: COLOR, text: string): string {
13
+ if (!isColorAllowed) {
14
+ return text;
15
+ }
16
+
17
+ return `\x1B[${color}m${text}\x1B[39m`;
18
+ }
@@ -0,0 +1,97 @@
1
+ import { MESSAGE } from 'triple-beam';
2
+ import winston from 'winston';
3
+ import { TraceContext } from '../trace.context.js';
4
+ import {
5
+ baseFormatter,
6
+ createFormatter,
7
+ LogMessage,
8
+ } from './base.formatter.js';
9
+ import {
10
+ BaseLogger,
11
+ LogLevel,
12
+ } from './base.logger.js';
13
+ import {
14
+ COLOR,
15
+ colorize,
16
+ } from './colors.js';
17
+
18
+ export const colorsMap = new Map<LogLevel, COLOR>()
19
+ .set('emergency', COLOR.RED)
20
+ .set('alert', COLOR.RED)
21
+ .set('critical', COLOR.RED)
22
+ .set('error', COLOR.RED)
23
+ .set('warning', COLOR.YELLOW)
24
+ .set('notice', COLOR.GREEN)
25
+ .set('info', COLOR.CYAN_BRIGHT)
26
+ .set('debug', COLOR.MAGENTA_BRIGHT);
27
+
28
+ function colorizeLevel(level: LogLevel): string {
29
+ const levelColor = colorsMap.get(level)!;
30
+
31
+ return `<${colorize(levelColor, level)}>`;
32
+ }
33
+
34
+ function colorizeContext(context: Nullable<string>): string {
35
+ if (context) {
36
+ return `[${colorize(COLOR.ORANGE, context)}]`;
37
+ }
38
+
39
+ return '';
40
+ }
41
+
42
+ function colorizeMessage(level: LogLevel, message: string): string {
43
+ const levelColor = colorsMap.get(level)!;
44
+
45
+ return colorize(levelColor, message);
46
+ }
47
+
48
+ function formatProcess(process: LogMessage['process']): string {
49
+ if (process.title) {
50
+ return `[${process.title}: ${process.pid}]`;
51
+ }
52
+ return `[pid: ${process.pid}]`;
53
+ }
54
+
55
+ function formatTraceId(traceId: string): string {
56
+ return `[traceId: ${traceId}]`;
57
+ }
58
+
59
+ function formatTime(time: Date): string {
60
+ return time.toLocaleString('ru-RU');
61
+ }
62
+
63
+ const customFormat = createFormatter(info => {
64
+ const process = formatProcess(info.process);
65
+ const timestamp = formatTime(info.timestamp);
66
+ const context = colorizeContext(info.context);
67
+ const level = colorizeLevel(info.level);
68
+ const message = colorizeMessage(info.level, info.message);
69
+ const traceId = info['traceId'] ? formatTraceId(info['traceId'] as string) : null;
70
+
71
+ const metaInfoItems: string[] = [timestamp, context, level, process];
72
+
73
+ if (traceId) {
74
+ metaInfoItems.push(traceId);
75
+ }
76
+
77
+ const metaInfo = metaInfoItems.join(' ');
78
+
79
+ info[MESSAGE] = `${metaInfo}: ${message}`;
80
+
81
+ return info;
82
+ });
83
+
84
+ const consoleTransport = new winston.transports.Console({
85
+ format: winston.format.combine(
86
+ baseFormatter(),
87
+ customFormat(),
88
+ ),
89
+ });
90
+
91
+ export class ConsoleLogger extends BaseLogger {
92
+ public constructor(traceContext: TraceContext) {
93
+ super('debug', 'test', traceContext);
94
+
95
+ this.addTransport(consoleTransport);
96
+ }
97
+ }
@@ -0,0 +1,4 @@
1
+ export { Logger } from './logger.js';
2
+ export { MockLogger } from './mock.logger.js';
3
+ export { ConsoleLogger } from './console.logger.js';
4
+ export { JSONLogger } from './json.logger.js';
@@ -0,0 +1,19 @@
1
+ import winston from 'winston';
2
+ import { TraceContext } from '../trace.context.js';
3
+ import { baseFormatter } from './base.formatter.js';
4
+ import { BaseLogger } from './base.logger.js';
5
+
6
+ const jsonTransport = new winston.transports.Console({
7
+ format: winston.format.combine(
8
+ baseFormatter(),
9
+ winston.format.json(),
10
+ ),
11
+ });
12
+
13
+ export class JSONLogger extends BaseLogger {
14
+ public constructor(traceContext: TraceContext) {
15
+ super('debug', 'test', traceContext);
16
+
17
+ this.addTransport(jsonTransport);
18
+ }
19
+ }
@@ -0,0 +1,12 @@
1
+ export abstract class Logger {
2
+ public abstract debug(...messages: unknown[]): void;
3
+ public abstract info(...messages: unknown[]): void;
4
+ public abstract notice(...messages: unknown[]): void;
5
+ public abstract warning(...messages: unknown[]): void;
6
+ public abstract error(...messages: unknown[]): void;
7
+ public abstract critical(...messages: unknown[]): void;
8
+ public abstract alert(...messages: unknown[]): void;
9
+ public abstract emergency(...messages: unknown[]): void;
10
+
11
+ public abstract setContext(context: Nullable<string>): void;
12
+ }
@@ -0,0 +1,14 @@
1
+ import { Logger } from './logger.js';
2
+
3
+ export class MockLogger extends Logger {
4
+ public debug(): void {}
5
+ public info(): void {}
6
+ public notice(): void {}
7
+ public warning(): void {}
8
+ public error(): void {}
9
+ public critical(): void {}
10
+ public alert(): void {}
11
+ public emergency(): void {}
12
+
13
+ public setContext(): void {}
14
+ }
@@ -0,0 +1,54 @@
1
+ import {
2
+ ExecutionMode,
3
+ ExecutionModeModule,
4
+ ExecutionModeVariants,
5
+ } from '@hg-ts/execution-mode';
6
+
7
+ import {
8
+ Global,
9
+ Module,
10
+ Provider,
11
+ Scope,
12
+ } from '@nestjs/common';
13
+ import { INQUIRER } from '@nestjs/core';
14
+
15
+ import {
16
+ ConsoleLogger,
17
+ JSONLogger,
18
+ Logger,
19
+ MockLogger,
20
+ } from './logger/index.js';
21
+ import { NestWrappedLogger } from './nest-wrapped.logger.js';
22
+ import { TraceContext } from './trace.context.js';
23
+
24
+ @Global()
25
+ @Module({
26
+ imports: [ExecutionModeModule],
27
+ providers: [
28
+ TraceContext,
29
+ {
30
+ provide: Logger,
31
+ useFactory(parent: any, executionMode: ExecutionMode, traceContext: TraceContext): Logger {
32
+ const context = (parent && Object.getPrototypeOf(parent)?.constructor.name) ?? null;
33
+ const shouldUseConsoleLogger = executionMode.isValueIn([
34
+ ExecutionModeVariants.DEBUG,
35
+ ExecutionModeVariants.DEV,
36
+ ]);
37
+ const shouldUseMockLogger = executionMode.is(ExecutionModeVariants.TEST);
38
+
39
+ if (shouldUseMockLogger) {
40
+ return new MockLogger();
41
+ }
42
+ const logger = shouldUseConsoleLogger ? new ConsoleLogger(traceContext) : new JSONLogger(traceContext);
43
+
44
+ logger.setContext(context);
45
+ return logger;
46
+ },
47
+ inject: [INQUIRER, ExecutionMode, TraceContext],
48
+ scope: Scope.TRANSIENT,
49
+ } satisfies Provider<Logger>,
50
+ NestWrappedLogger,
51
+ ],
52
+ exports: [Logger, TraceContext],
53
+ })
54
+ export class LoggerModule {}
@@ -0,0 +1,51 @@
1
+ import {
2
+ Inject,
3
+ Logger as NestLogger,
4
+ LoggerService,
5
+ OnModuleInit,
6
+ } from '@nestjs/common';
7
+ import { Logger } from './logger/index.js';
8
+
9
+ /* eslint-disable @typescript/explicit-module-boundary-types */
10
+ export class NestWrappedLogger implements LoggerService, OnModuleInit {
11
+ @Inject()
12
+ protected readonly logger: Logger;
13
+
14
+ public debug(message: any, context?: string): any {
15
+ this.setOptionalContext(context);
16
+ this.logger.debug(message);
17
+ }
18
+
19
+ public error(message: any, trace?: string, context?: string): any {
20
+ this.setOptionalContext(context);
21
+ this.logger.error(message, trace);
22
+ }
23
+
24
+ public log(message: any, context?: string): any {
25
+ this.setOptionalContext(context);
26
+ this.logger.info(message);
27
+ }
28
+
29
+ public verbose(message: any, context?: string): any {
30
+ this.setOptionalContext(context);
31
+ this.logger.notice(message);
32
+ }
33
+
34
+ public warn(message: any, context?: string): any {
35
+ this.setOptionalContext(context);
36
+ this.logger.warning(message);
37
+ }
38
+ /* eslint-enable @typescript/explicit-module-boundary-types */
39
+
40
+ public async onModuleInit(): Promise<void> {
41
+ this.logger.setContext(null);
42
+ NestLogger.overrideLogger(true);
43
+ NestLogger.overrideLogger(['debug', 'log', 'verbose', 'warn', 'error']);
44
+ NestLogger.overrideLogger(this);
45
+ NestLogger.flush();
46
+ }
47
+
48
+ private setOptionalContext(context: Nullable<string> = null): void {
49
+ this.logger.setContext(context);
50
+ }
51
+ }
@@ -0,0 +1,7 @@
1
+ import { AsyncContextProvider } from '@hg-ts/async-context';
2
+
3
+ export type TraceData = {
4
+ traceId: string;
5
+ };
6
+
7
+ export class TraceContext extends AsyncContextProvider<TraceData> {}