logxpert 2.1.0 → 2.1.2

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/CHANGELOG.md CHANGED
@@ -1,20 +1,20 @@
1
- # Changelog
2
-
3
- ## Unreleased
4
- - Fix TS types for dual CJS/ESM: `export =` + namespace in `index.d.ts`, dedicated `index.d.mts` for the ESM entry (`attw` clean)
5
- - Rename `files` options with backward-compatible aliases: `prefix` (`appName`), `filePattern` (`filename`/`pattern`), `datePattern` (`filesName`/`dateFormat`); `%datePattern%` placeholder; `prefix` + `filePattern` now throws instead of silent precedence
6
- - Default file `datePattern` is now `YYYY_MM_DD`
7
- - License changed to `Apache-2.0`
8
-
9
- ## 2.0.0
10
- - Replace `moment` with `dayjs`
11
- - Add ESM entry (`main.mjs`) + `index.d.ts` types
12
- - Add `log.setLevel/getLevel`, `LOG_LEVEL` env support
13
- - Add `log.createLogger()` isolated instances and `log.child()`
14
- - Add structured logging: objects, meta, `Error` stacks, `splat`
15
- - `files.filename` option, input validation in `settings()`
16
- - Fix license to `GPL-3.0-only`, add `exports`, `files`, `engines`
17
- - Add tests (`node --test`) and CI
18
-
19
- ## 1.0.5
20
- - Previous CJS-only release with `moment` + `winston`.
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+ - Fix TS types for dual CJS/ESM: `export =` + namespace in `index.d.ts`, dedicated `index.d.mts` for the ESM entry (`attw` clean)
5
+ - Rename `files` options with backward-compatible aliases: `prefix` (`appName`), `filePattern` (`filename`/`pattern`), `datePattern` (`filesName`/`dateFormat`); `%datePattern%` placeholder; `prefix` + `filePattern` now throws instead of silent precedence
6
+ - Default file `datePattern` is now `YYYY_MM_DD`
7
+ - License changed to `Apache-2.0`
8
+
9
+ ## 2.0.0
10
+ - Replace `moment` with `dayjs`
11
+ - Add ESM entry (`main.mjs`) + `index.d.ts` types
12
+ - Add `log.setLevel/getLevel`, `LOG_LEVEL` env support
13
+ - Add `log.createLogger()` isolated instances and `log.child()`
14
+ - Add structured logging: objects, meta, `Error` stacks, `splat`
15
+ - `files.filename` option, input validation in `settings()`
16
+ - Fix license to `GPL-3.0-only`, add `exports`, `files`, `engines`
17
+ - Add tests (`node --test`) and CI
18
+
19
+ ## 1.0.5
20
+ - Previous CJS-only release with `moment` + `winston`.
package/README.md CHANGED
@@ -1,118 +1,119 @@
1
- # LogXpert
2
-
3
- Colorful console logging for Node.js with optional daily file rotation.
4
-
5
- - Zero heavy dependencies — built-in engine.
6
- - Daily file rotation with retention, size limits and gzip.
7
- - Per-level colors, lazy messages, child loggers, isolated instances.
8
- - Works from JS and TS, CommonJS and ESM.
9
-
10
- ## Installation
11
-
12
- ```sh
13
- npm install logxpert
14
- ```
15
-
16
- Requires Node.js `>=18`. Recommended minimum: Node.js 22 LTS. See [NODE_SUPPORT.md](NODE_SUPPORT.md) for the full support policy.
17
-
18
- ## Quick start
19
-
20
- ```js
21
- const log = require('logxpert');
22
- // or: import log from 'logxpert';
23
-
24
- log('general message'); // shortcut for info
25
- log.info('hello', { user: 1 }); // with metadata
26
- log.error(new Error('boom')); // Errors print message + stack
27
- log.debug(() => JSON.stringify(hugeObject)); // lazy: skipped if level disabled
28
- ```
29
-
30
- Levels: `error → warn → info → http → verbose → debug → silly` (default `debug`).
31
-
32
- ```js
33
- log.setLevel('info'); // or via LOG_LEVEL env variable
34
- ```
35
-
36
- ## File logging
37
-
38
- ```js
39
- log.settings({ files: { folder: 'logs' } });
40
- // → logs/logxpert-2026_10_08.log (prefix defaults to your package.json name)
41
-
42
- log.settings({ files: { folder: 'logs', prefix: 'api' } });
43
- // → logs/api-2026_10_08.log
44
-
45
- // Full control (omit prefix when used):
46
- log.settings({ files: { folder: 'logs', filePattern: 'app-%datePattern%.log' } });
47
- ```
48
-
49
- `%datePattern%` is replaced by the date (`datePattern` defaults to `YYYY_MM_DD`). Combining `prefix` with `filePattern` throws — pick one.
50
-
51
- ```js
52
- log.settings({
53
- files: {
54
- folder: 'logs',
55
- runNumber: true, // off by default → app-2026_10_08-1.log, -2.log, ...
56
- maxFile: '14d', // retention
57
- maxSize: '20m', // rotate above this size
58
- zippedArchive: true,
59
- },
60
- });
61
- log.close(); // call on shutdown
62
- ```
63
-
64
- ## Console & instances
65
-
66
- ```js
67
- log.settings({
68
- console: {
69
- enableTimestamp: true,
70
- timestampFormat: 'YYYY-MM-DD HH:mm:ss',
71
- colorize: true,
72
- colors: { info: 'blue', error: false }, // names, ANSI 0-255, or false per level
73
- },
74
- });
75
-
76
- // Default colors: error red, warn yellow, info green, http magenta,
77
- // verbose cyan, debug blue, silly gray. Only the [level] tag is colored.
78
-
79
- const { createLogger } = require('logxpert');
80
- const apiLog = createLogger({ level: 'info' }); // independent instance
81
-
82
- const child = log.child({ service: 'api' }); // bound context
83
- ```
84
-
85
- ## Full configuration (copy-paste)
86
-
87
- ```js
88
- log.settings({
89
- level: 'debug', // or process.env.LOG_LEVEL
90
- console: {
91
- enableTimestamp: true,
92
- timestampFormat: 'YYYY-MM-DD HH:mm:ss',
93
- timestampPrefix: '',
94
- timestampSuffix: '',
95
- colorize: true,
96
- colors: { info: 'blue', warn: 208, error: false }
97
- // enabled: false, // file-only logging: silence the console
98
- },
99
- files: {
100
- folder: 'logs',
101
- prefix: undefined, // defaults to your package.json name
102
- // filePattern: 'app-%datePattern%.log', // full control (omit prefix if used)
103
- datePattern: 'YYYY_MM_DD',
104
- runNumber: false, // or true, or { separator: '-', padding: 0, startAt: 1 }
105
- maxFile: '14d',
106
- maxSize: '20m',
107
- zippedArchive: false,
108
- }
109
- });
110
- ```
111
-
112
- Legacy aliases: `appName` (= `prefix`), `filename`/`pattern` (= `filePattern`), `filesName`/`dateFormat` (= `datePattern`).
113
-
114
- Security: messages are stripped of ANSI escapes and control characters (except `\n`, `\t`); file patterns escaping the log folder (`..`, absolute paths) are rejected.
115
-
116
- ## License
117
-
118
- Apache-2.0. See [LICENSE](LICENSE).
1
+ # LogXpert
2
+
3
+ Colorful console logging for Node.js with optional daily file rotation.
4
+
5
+ - Zero heavy dependencies — built-in engine.
6
+ - Daily file rotation with retention, size limits and gzip.
7
+ - Per-level colors, lazy messages, child loggers, isolated instances.
8
+ - Works from JS and TS, CommonJS and ESM.
9
+
10
+ ## Installation
11
+
12
+ ```sh
13
+ npm install logxpert
14
+ ```
15
+
16
+ Requires Node.js `>=18`. Recommended minimum: Node.js 22 LTS. See [NODE_SUPPORT.md](NODE_SUPPORT.md) for the full support policy.
17
+
18
+ ## Quick start
19
+
20
+ ```js
21
+ const log = require('logxpert');
22
+ // or: import log from 'logxpert';
23
+
24
+ log('general message'); // shortcut for info
25
+ log.info('hello', { user: 1 }); // with metadata
26
+ log.error(new Error('boom')); // Errors print message + stack
27
+ log.debug(() => JSON.stringify(hugeObject)); // lazy: skipped if level disabled
28
+ ```
29
+
30
+ Levels: `error → warn → info → http → verbose → debug → silly` (default `debug`).
31
+
32
+ ```js
33
+ log.setLevel('info'); // or via LOG_LEVEL env variable
34
+ ```
35
+
36
+ ## File logging
37
+
38
+ ```js
39
+ log.settings({ files: { folder: 'logs' } });
40
+ // → logs/logxpert-2026_10_08.log (prefix defaults to your package.json name)
41
+
42
+ log.settings({ files: { folder: 'logs', prefix: 'api' } });
43
+ // → logs/api-2026_10_08.log
44
+
45
+ // Full control (omit prefix when used):
46
+ log.settings({ files: { folder: 'logs', filePattern: 'app-%datePattern%.log' } });
47
+ ```
48
+
49
+ `%datePattern%` is replaced by the date (`datePattern` defaults to `YYYY_MM_DD`). Combining `prefix` with `filePattern` throws — pick one.
50
+
51
+ ```js
52
+ log.settings({
53
+ files: {
54
+ folder: 'logs',
55
+ runNumber: true, // off by default → app-2026_10_08-1.log, -2.log, ...
56
+ maxFile: '14d', // retention
57
+ maxSize: '20m', // rotate above this size
58
+ zippedArchive: true,
59
+ },
60
+ });
61
+ log.close(); // call on shutdown
62
+ ```
63
+
64
+ ## Console & instances
65
+
66
+ ```js
67
+ log.settings({
68
+ console: {
69
+ enableTimestamp: true,
70
+ timestampFormat: 'YYYY-MM-DD HH:mm:ss',
71
+ colorize: true,
72
+ colors: { info: 'blue', error: false }, // names, ANSI 0-255, or false per level
73
+ },
74
+ });
75
+
76
+ // Default colors: error red, warn yellow, info green, http magenta,
77
+ // verbose cyan, debug blue, silly gray. Only the [level] tag is colored.
78
+
79
+ const { createLogger } = require('logxpert');
80
+ const apiLog = createLogger({ level: 'info' }); // independent instance
81
+
82
+ const child = log.child({ service: 'api' }); // bound context
83
+ ```
84
+
85
+ ## Full configuration (copy-paste)
86
+
87
+ ```js
88
+ log.settings({
89
+ level: 'debug', // or process.env.LOG_LEVEL
90
+ sanitize: true, // strip ANSI escapes and control chars; false = pass through untouched
91
+ console: {
92
+ enableTimestamp: true,
93
+ timestampFormat: 'YYYY-MM-DD HH:mm:ss',
94
+ timestampPrefix: '',
95
+ timestampSuffix: '',
96
+ colorize: true,
97
+ colors: { info: 'blue', warn: 208, error: false }
98
+ // enabled: false, // file-only logging: silence the console
99
+ },
100
+ files: {
101
+ folder: 'logs',
102
+ prefix: undefined, // defaults to your package.json name
103
+ // filePattern: 'app-%datePattern%.log', // full control (omit prefix if used)
104
+ datePattern: 'YYYY_MM_DD',
105
+ runNumber: false, // or true, or { separator: '-', padding: 0, startAt: 1 }
106
+ maxFile: '14d',
107
+ maxSize: '20m',
108
+ zippedArchive: false,
109
+ }
110
+ });
111
+ ```
112
+
113
+ Legacy aliases: `appName` (= `prefix`), `filename`/`pattern` (= `filePattern`), `filesName`/`dateFormat` (= `datePattern`).
114
+
115
+ Security: messages are stripped of ANSI escapes and control characters (except `\n`, `\t`); file patterns escaping the log folder (`..`, absolute paths) are rejected.
116
+
117
+ ## License
118
+
119
+ Apache-2.0. See [LICENSE](LICENSE).
package/index.d.ts CHANGED
@@ -1,83 +1,84 @@
1
- declare namespace log {
2
- export type LogLevel = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly';
3
-
4
- export type LevelColor = string | number | false | null;
5
-
6
- export interface ConsoleOptions {
7
- enabled?: boolean;
8
- enableTimestamp?: boolean;
9
- timestampFormat?: string;
10
- timestampPrefix?: string;
11
- timestampSuffix?: string;
12
- colorize?: boolean;
13
- colors?: Partial<Record<LogLevel, LevelColor>>;
14
- level?: LogLevel;
15
- }
16
-
17
- export interface RunNumberOptions {
18
- enabled?: boolean;
19
- separator?: string;
20
- padding?: number;
21
- startAt?: number;
22
- }
23
-
24
- export interface FilesOptions {
25
- folder?: string;
26
- /** Preferred: date format for %DATE%. */
27
- datePattern?: string;
28
- /** @deprecated Use datePattern. */
29
- filesName?: string;
30
- dateFormat?: string;
31
- /** Preferred: filename template containing %DATE%. */
32
- filePattern?: string;
33
- pattern?: string;
34
- /** @deprecated Use filePattern. */
35
- filename?: string;
36
- /** Preferred: filename prefix used when no pattern is given. */
37
- prefix?: string;
38
- /** @deprecated Use prefix. */
39
- appName?: string;
40
- runNumber?: boolean | RunNumberOptions;
41
- maxFile?: string;
42
- maxFiles?: string;
43
- maxSize?: string;
44
- zippedArchive?: boolean;
45
- level?: LogLevel;
46
- }
47
-
48
- export interface LogSettingsOptions {
49
- console?: ConsoleOptions | false;
50
- files?: FilesOptions;
51
- level?: LogLevel;
52
- }
53
-
54
- export interface ChildLogger {
55
- error(message: unknown, ...meta: unknown[]): void;
56
- warn(message: unknown, ...meta: unknown[]): void;
57
- info(message: unknown, ...meta: unknown[]): void;
58
- debug(message: unknown, ...meta: unknown[]): void;
59
- log(level: LogLevel, message: unknown, ...meta: unknown[]): void;
60
- }
61
-
62
- export interface LogXpert {
63
- (message: unknown, ...meta: unknown[]): void;
64
- error(message: unknown, ...meta: unknown[]): void;
65
- warn(message: unknown, ...meta: unknown[]): void;
66
- info(message: unknown, ...meta: unknown[]): void;
67
- debug(message: unknown, ...meta: unknown[]): void;
68
- http(message: unknown, ...meta: unknown[]): void;
69
- verbose(message: unknown, ...meta: unknown[]): void;
70
- silly(message: unknown, ...meta: unknown[]): void;
71
- log(level: LogLevel, message: unknown, ...meta: unknown[]): void;
72
- settings(options?: LogSettingsOptions): void;
73
- setLevel(level: LogLevel): void;
74
- getLevel(): string;
75
- child(meta?: Record<string, unknown>): ChildLogger;
76
- close(): void;
77
- createLogger(options?: LogSettingsOptions): LogXpert;
78
- }
79
- }
80
-
81
- declare const log: log.LogXpert;
82
-
83
- export = log;
1
+ declare namespace log {
2
+ export type LogLevel = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly';
3
+
4
+ export type LevelColor = string | number | false | null;
5
+
6
+ export interface ConsoleOptions {
7
+ enabled?: boolean;
8
+ enableTimestamp?: boolean;
9
+ timestampFormat?: string;
10
+ timestampPrefix?: string;
11
+ timestampSuffix?: string;
12
+ colorize?: boolean;
13
+ colors?: Partial<Record<LogLevel, LevelColor>>;
14
+ level?: LogLevel;
15
+ }
16
+
17
+ export interface RunNumberOptions {
18
+ enabled?: boolean;
19
+ separator?: string;
20
+ padding?: number;
21
+ startAt?: number;
22
+ }
23
+
24
+ export interface FilesOptions {
25
+ folder?: string;
26
+ /** Preferred: date format for %DATE%. */
27
+ datePattern?: string;
28
+ /** @deprecated Use datePattern. */
29
+ filesName?: string;
30
+ dateFormat?: string;
31
+ /** Preferred: filename template containing %DATE%. */
32
+ filePattern?: string;
33
+ pattern?: string;
34
+ /** @deprecated Use filePattern. */
35
+ filename?: string;
36
+ /** Preferred: filename prefix used when no pattern is given. */
37
+ prefix?: string;
38
+ /** @deprecated Use prefix. */
39
+ appName?: string;
40
+ runNumber?: boolean | RunNumberOptions;
41
+ maxFile?: string;
42
+ maxFiles?: string;
43
+ maxSize?: string;
44
+ zippedArchive?: boolean;
45
+ level?: LogLevel;
46
+ }
47
+
48
+ export interface LogSettingsOptions {
49
+ console?: ConsoleOptions | false;
50
+ files?: FilesOptions;
51
+ level?: LogLevel;
52
+ sanitize?: boolean;
53
+ }
54
+
55
+ export interface ChildLogger {
56
+ error(message: unknown, ...meta: unknown[]): void;
57
+ warn(message: unknown, ...meta: unknown[]): void;
58
+ info(message: unknown, ...meta: unknown[]): void;
59
+ debug(message: unknown, ...meta: unknown[]): void;
60
+ log(level: LogLevel, message: unknown, ...meta: unknown[]): void;
61
+ }
62
+
63
+ export interface LogXpert {
64
+ (message: unknown, ...meta: unknown[]): void;
65
+ error(message: unknown, ...meta: unknown[]): void;
66
+ warn(message: unknown, ...meta: unknown[]): void;
67
+ info(message: unknown, ...meta: unknown[]): void;
68
+ debug(message: unknown, ...meta: unknown[]): void;
69
+ http(message: unknown, ...meta: unknown[]): void;
70
+ verbose(message: unknown, ...meta: unknown[]): void;
71
+ silly(message: unknown, ...meta: unknown[]): void;
72
+ log(level: LogLevel, message: unknown, ...meta: unknown[]): void;
73
+ settings(options?: LogSettingsOptions): void;
74
+ setLevel(level: LogLevel): void;
75
+ getLevel(): string;
76
+ child(meta?: Record<string, unknown>): ChildLogger;
77
+ close(): void;
78
+ createLogger(options?: LogSettingsOptions): LogXpert;
79
+ }
80
+ }
81
+
82
+ declare const log: log.LogXpert;
83
+
84
+ export = log;
package/lib/mini.js CHANGED
@@ -1,134 +1,134 @@
1
- const LEVELS = {
2
- error: 0,
3
- warn: 1,
4
- info: 2,
5
- http: 3,
6
- verbose: 4,
7
- debug: 5,
8
- silly: 6,
9
- };
10
-
11
- const COLORS = {
12
- error: 31,
13
- warn: 33,
14
- info: 32,
15
- http: 35,
16
- verbose: 36,
17
- debug: 34,
18
- silly: 90,
19
- };
20
-
21
- const NAMED_COLORS = {
22
- black: 30,
23
- red: 31,
24
- green: 32,
25
- yellow: 33,
26
- blue: 34,
27
- magenta: 35,
28
- cyan: 36,
29
- white: 37,
30
- gray: 90,
31
- grey: 90,
32
- };
33
-
34
- function resolveColorCode(value, level) {
35
- if (value === undefined || value === null || value === false) return null;
36
- if (typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 255) return value;
37
- if (typeof value === 'string' && NAMED_COLORS[value.toLowerCase()] !== undefined) return NAMED_COLORS[value.toLowerCase()];
38
- throw new Error(`Unknown color "${value}" for level "${level}". Use one of: ${Object.keys(NAMED_COLORS).join(', ')}, an ANSI code 0-255, or false to disable`);
39
- }
40
-
41
- function resolveLevelColors(custom = {}) {
42
- if (custom === null || typeof custom !== 'object' || Array.isArray(custom)) {
43
- throw new Error('console.colors must be an object mapping level names to colors');
44
- }
45
- const map = {};
46
- for (const level of Object.keys(COLORS)) {
47
- if (custom[level] !== undefined) {
48
- const code = resolveColorCode(custom[level], level);
49
- if (code !== null) map[level] = code;
50
- } else {
51
- map[level] = COLORS[level];
52
- }
53
- }
54
- return map;
55
- }
56
-
57
- function colorizeLevel(level, text, colors = COLORS) {
58
- const code = colors[level] ?? 0;
59
- if (!code) return text;
60
- return `\x1b[${code}m${text}\x1b[0m`;
61
- }
62
-
63
- class NullTransport {
64
- constructor() {
65
- this.level = 'off';
66
- }
67
-
68
- write() {}
69
-
70
- close() {}
71
- }
72
-
73
- class ConsoleTransport {
74
- constructor({ level, render } = {}) {
75
- this.level = level;
76
- this.render = render;
77
- }
78
-
79
- write(entry) {
80
- const line = this.render(entry);
81
- const stream = entry.level === 'error' || entry.level === 'warn' ? process.stderr : process.stdout;
82
- stream.write(line + '\n');
83
- }
84
-
85
- close() {}
86
- }
87
-
88
- class MiniLogger {
89
- constructor({ level = 'debug' } = {}) {
90
- this.level = level;
91
- this.levels = LEVELS;
92
- this.transports = [];
93
- }
94
-
95
- add(transport) {
96
- this.transports.push(transport);
97
- return this;
98
- }
99
-
100
- remove(transport) {
101
- const i = this.transports.indexOf(transport);
102
- if (i >= 0) this.transports.splice(i, 1);
103
- try {
104
- transport.close?.();
105
- } catch {}
106
- return this;
107
- }
108
-
109
- enabled(level, transportLevel) {
110
- const want = LEVELS[level];
111
- const current = LEVELS[transportLevel ?? this.level];
112
- return want !== undefined && current !== undefined && want <= current;
113
- }
114
-
115
- log(level, message, ...meta) {
116
- for (const t of [...this.transports]) {
117
- if (this.enabled(level, t.level)) t.write({ level, message, meta });
118
- }
119
- }
120
-
121
- child(meta = {}) {
122
- const self = this;
123
- const make = (level) => (message, ...args) => self.log(level, message, meta, ...args);
124
- return {
125
- error: make('error'),
126
- warn: make('warn'),
127
- info: make('info'),
128
- debug: make('debug'),
129
- log: (level, message, ...args) => self.log(level, message, meta, ...args),
130
- };
131
- }
132
- }
133
-
134
- module.exports = { LEVELS, ConsoleTransport, NullTransport, MiniLogger, colorizeLevel, resolveLevelColors, NAMED_COLORS };
1
+ const LEVELS = {
2
+ error: 0,
3
+ warn: 1,
4
+ info: 2,
5
+ http: 3,
6
+ verbose: 4,
7
+ debug: 5,
8
+ silly: 6,
9
+ };
10
+
11
+ const COLORS = {
12
+ error: 31,
13
+ warn: 33,
14
+ info: 32,
15
+ http: 35,
16
+ verbose: 36,
17
+ debug: 34,
18
+ silly: 90,
19
+ };
20
+
21
+ const NAMED_COLORS = {
22
+ black: 30,
23
+ red: 31,
24
+ green: 32,
25
+ yellow: 33,
26
+ blue: 34,
27
+ magenta: 35,
28
+ cyan: 36,
29
+ white: 37,
30
+ gray: 90,
31
+ grey: 90,
32
+ };
33
+
34
+ function resolveColorCode(value, level) {
35
+ if (value === undefined || value === null || value === false) return null;
36
+ if (typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 255) return value;
37
+ if (typeof value === 'string' && NAMED_COLORS[value.toLowerCase()] !== undefined) return NAMED_COLORS[value.toLowerCase()];
38
+ throw new Error(`Unknown color "${value}" for level "${level}". Use one of: ${Object.keys(NAMED_COLORS).join(', ')}, an ANSI code 0-255, or false to disable`);
39
+ }
40
+
41
+ function resolveLevelColors(custom = {}) {
42
+ if (custom === null || typeof custom !== 'object' || Array.isArray(custom)) {
43
+ throw new Error('console.colors must be an object mapping level names to colors');
44
+ }
45
+ const map = {};
46
+ for (const level of Object.keys(COLORS)) {
47
+ if (custom[level] !== undefined) {
48
+ const code = resolveColorCode(custom[level], level);
49
+ if (code !== null) map[level] = code;
50
+ } else {
51
+ map[level] = COLORS[level];
52
+ }
53
+ }
54
+ return map;
55
+ }
56
+
57
+ function colorizeLevel(level, text, colors = COLORS) {
58
+ const code = colors[level] ?? 0;
59
+ if (!code) return text;
60
+ return `\x1b[${code}m${text}\x1b[0m`;
61
+ }
62
+
63
+ class NullTransport {
64
+ constructor() {
65
+ this.level = 'off';
66
+ }
67
+
68
+ write() {}
69
+
70
+ close() {}
71
+ }
72
+
73
+ class ConsoleTransport {
74
+ constructor({ level, render } = {}) {
75
+ this.level = level;
76
+ this.render = render;
77
+ }
78
+
79
+ write(entry) {
80
+ const line = this.render(entry);
81
+ const stream = entry.level === 'error' || entry.level === 'warn' ? process.stderr : process.stdout;
82
+ stream.write(line + '\n');
83
+ }
84
+
85
+ close() {}
86
+ }
87
+
88
+ class MiniLogger {
89
+ constructor({ level = 'debug' } = {}) {
90
+ this.level = level;
91
+ this.levels = LEVELS;
92
+ this.transports = [];
93
+ }
94
+
95
+ add(transport) {
96
+ this.transports.push(transport);
97
+ return this;
98
+ }
99
+
100
+ remove(transport) {
101
+ const i = this.transports.indexOf(transport);
102
+ if (i >= 0) this.transports.splice(i, 1);
103
+ try {
104
+ transport.close?.();
105
+ } catch {}
106
+ return this;
107
+ }
108
+
109
+ enabled(level, transportLevel) {
110
+ const want = LEVELS[level];
111
+ const current = LEVELS[transportLevel ?? this.level];
112
+ return want !== undefined && current !== undefined && want <= current;
113
+ }
114
+
115
+ log(level, message, ...meta) {
116
+ for (const t of [...this.transports]) {
117
+ if (this.enabled(level, t.level)) t.write({ level, message, meta });
118
+ }
119
+ }
120
+
121
+ child(meta = {}) {
122
+ const self = this;
123
+ const make = (level) => (message, ...args) => self.log(level, message, meta, ...args);
124
+ return {
125
+ error: make('error'),
126
+ warn: make('warn'),
127
+ info: make('info'),
128
+ debug: make('debug'),
129
+ log: (level, message, ...args) => self.log(level, message, meta, ...args),
130
+ };
131
+ }
132
+ }
133
+
134
+ module.exports = { LEVELS, ConsoleTransport, NullTransport, MiniLogger, colorizeLevel, resolveLevelColors, NAMED_COLORS };
package/lib/sanitize.js CHANGED
@@ -10,29 +10,28 @@ function stripAnsi(str) {
10
10
  let out = '';
11
11
  let i = 0;
12
12
  while (i < str.length) {
13
- if (str[i] !== '\x1b') {
13
+ if (str[i] !== '\x1b' && str[i] !== '\x9b' && str[i] !== '\x9d') {
14
14
  out += str[i];
15
15
  i++;
16
16
  continue;
17
17
  }
18
- const next = str[i + 1];
19
- if (next === ']') {
18
+ if (str[i] === '\x9d' || str[i + 1] === ']') {
20
19
  let j = i + 2;
21
20
  while (j < str.length) {
22
- if (str[j] === '\x07') { j++; break; }
21
+ if (str[j] === '\x07' || str[j] === '\x9c') { j++; break; }
23
22
  if (str[j] === '\x1b' && str[j + 1] === '\\') { j += 2; break; }
24
23
  j++;
25
24
  }
26
25
  i = j;
27
- } else if (next === '[') {
26
+ } else if (str[i] === '\x9b' || str[i + 1] === '[') {
28
27
  let j = i + 2;
29
28
  while (j < str.length && str[j] >= ' ' && str[j] <= '?') j++;
30
29
  if (j < str.length) j++;
31
30
  i = j;
32
- } else if (next === '(' || next === ')') {
31
+ } else if (str[i + 1] === '(' || str[i + 1] === ')') {
33
32
  i += 3;
34
33
  } else {
35
- i += next === undefined ? 1 : 2;
34
+ i += str[i + 1] === undefined ? 1 : 2;
36
35
  }
37
36
  }
38
37
  return out;
package/main.js CHANGED
@@ -1,136 +1,144 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { MiniLogger, ConsoleTransport, NullTransport, colorizeLevel, resolveLevelColors } = require('./lib/mini');
4
- const { FileTransport } = require('./lib/rotate');
5
- const { DEFAULT_CONSOLE_FORMAT, compileTimestampFormatter } = require('./lib/time');
6
- const { buildLine } = require('./lib/format');
7
- const { assertLevel, sanitizeMessage } = require('./lib/sanitize');
8
- const { normalizeFilesOptions } = require('./lib/files');
9
-
10
- function normalizeConsoleOpts(input) {
11
- if (input === false) return { enabled: false };
12
- return input || {};
13
- }
14
-
15
- function buildConsoleTransport({ enabled = true, enableTimestamp = true, timestampFormat = DEFAULT_CONSOLE_FORMAT, timestampPrefix = '', timestampSuffix = '', colorize = true, colors, level } = {}) {
16
- if (!enabled) return new NullTransport();
17
- const stamp = enableTimestamp ? compileTimestampFormatter(timestampFormat) : null;
18
- const palette = resolveLevelColors(colors);
19
- const render = ({ level, message, meta }) => {
20
- const timestamp = stamp ? `${timestampPrefix}${stamp()}${timestampSuffix}` : '';
21
- const tag = colorize ? colorizeLevel(level, `[${level}]`, palette) : `[${level}]`;
22
- return buildLine(timestamp, level, message, meta, tag);
23
- };
24
- return new ConsoleTransport({ level, render });
25
- }
26
-
27
- function buildFileTransport({ folder, datePattern, filename, maxFiles, maxSize, zippedArchive, level }) {
28
- if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
29
- return new FileTransport({
30
- folder: path.normalize(folder),
31
- filename,
32
- datePattern,
33
- zippedArchive,
34
- maxSize,
35
- maxFiles,
36
- level,
37
- render: ({ level, message, meta }) => buildLine(new Date().toISOString(), level, message, meta),
38
- });
39
- }
40
-
41
- function createLoggerInstance(initial = {}) {
42
- const globalLevel = process.env.LOG_LEVEL || initial.level || 'debug';
43
- assertLevel(globalLevel);
44
-
45
- const logger = new MiniLogger({ level: globalLevel });
46
-
47
- let consoleTransport = buildConsoleTransport(normalizeConsoleOpts(initial.console));
48
- consoleTransport.level = normalizeConsoleOpts(initial.console).level;
49
- logger.add(consoleTransport);
50
-
51
- let fileTransport = null;
52
- let consoleOpts = { ...normalizeConsoleOpts(initial.console) };
53
- let filesOpts = null;
54
-
55
- function applySettings(options = {}) {
56
- if (options.level !== undefined) {
57
- assertLevel(options.level);
58
- logger.level = options.level;
59
- }
60
- if (options.console !== undefined) {
61
- const next = normalizeConsoleOpts(options.console);
62
- if (next.level !== undefined && next.level !== null) assertLevel(next.level);
63
- consoleOpts = { ...consoleOpts, ...next };
64
- logger.remove(consoleTransport);
65
- consoleTransport = buildConsoleTransport(consoleOpts);
66
- consoleTransport.level = consoleOpts.level;
67
- logger.add(consoleTransport);
68
- }
69
- if (options.files) {
70
- const normalized = normalizeFilesOptions(options.files);
71
- if (normalized.level !== undefined && normalized.level !== null) assertLevel(normalized.level);
72
- filesOpts = normalized;
73
- if (fileTransport) logger.remove(fileTransport);
74
- fileTransport = buildFileTransport(normalized);
75
- logger.add(fileTransport);
76
- }
77
- }
78
-
79
- function isEnabled(level) {
80
- const want = logger.levels[level];
81
- const current = logger.levels[logger.level];
82
- return want !== undefined && current !== undefined && want <= current;
83
- }
84
-
85
- function write(level, message, ...meta) {
86
- if (typeof message === 'function') {
87
- if (!isEnabled(level)) return;
88
- message = message();
89
- }
90
- if (message instanceof Error) {
91
- logger.log(level, sanitizeMessage(message.message), { stack: message.stack, ...meta[0] });
92
- return;
93
- }
94
- logger.log(level, sanitizeMessage(message), ...meta);
95
- }
96
-
97
- function callable(message, ...meta) {
98
- write('info', message, ...meta);
99
- }
100
-
101
- callable.error = (m, ...a) => write('error', m, ...a);
102
- callable.warn = (m, ...a) => write('warn', m, ...a);
103
- callable.info = (m, ...a) => write('info', m, ...a);
104
- callable.debug = (m, ...a) => write('debug', m, ...a);
105
- callable.http = (m, ...a) => write('http', m, ...a);
106
- callable.verbose = (m, ...a) => write('verbose', m, ...a);
107
- callable.silly = (m, ...a) => write('silly', m, ...a);
108
- callable.log = (level, m, ...a) => { assertLevel(level); write(level, m, ...a); };
109
-
110
- callable.settings = applySettings;
111
- callable.setLevel = (level) => { assertLevel(level); logger.level = level; };
112
- callable.getLevel = () => logger.level;
113
- callable.child = (meta = {}) => {
114
- const child = logger.child(meta);
115
- return {
116
- error: (m, ...a) => child.error(m, ...a),
117
- warn: (m, ...a) => child.warn(m, ...a),
118
- info: (m, ...a) => child.info(m, ...a),
119
- debug: (m, ...a) => child.debug(m, ...a),
120
- log: child.log.bind(child),
121
- };
122
- };
123
- callable.close = () => {
124
- if (fileTransport) { logger.remove(fileTransport); try { fileTransport.close?.(); } catch {} fileTransport = null; }
125
- };
126
- callable.createLogger = (opts = {}) => createLoggerInstance(opts);
127
- callable._winston = logger;
128
-
129
- if (initial.files) applySettings({ files: initial.files });
130
- if (initial.console || initial.level) applySettings({ console: initial.console, level: initial.level });
131
-
132
- return callable;
133
- }
134
-
135
- module.exports = createLoggerInstance();
136
- module.exports.createLogger = createLoggerInstance;
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { MiniLogger, ConsoleTransport, NullTransport, colorizeLevel, resolveLevelColors } = require('./lib/mini');
4
+ const { FileTransport } = require('./lib/rotate');
5
+ const { DEFAULT_CONSOLE_FORMAT, compileTimestampFormatter } = require('./lib/time');
6
+ const { buildLine } = require('./lib/format');
7
+ const { assertLevel, sanitizeMessage } = require('./lib/sanitize');
8
+ const { normalizeFilesOptions } = require('./lib/files');
9
+
10
+ function normalizeConsoleOpts(input) {
11
+ if (input === false) return { enabled: false };
12
+ return input || {};
13
+ }
14
+
15
+ function buildConsoleTransport({ enabled = true, enableTimestamp = true, timestampFormat = DEFAULT_CONSOLE_FORMAT, timestampPrefix = '', timestampSuffix = '', colorize = true, colors, level } = {}) {
16
+ if (!enabled) return new NullTransport();
17
+ const stamp = enableTimestamp ? compileTimestampFormatter(timestampFormat) : null;
18
+ const palette = resolveLevelColors(colors);
19
+ const render = ({ level, message, meta }) => {
20
+ const timestamp = stamp ? `${timestampPrefix}${stamp()}${timestampSuffix}` : '';
21
+ const tag = colorize ? colorizeLevel(level, `[${level}]`, palette) : `[${level}]`;
22
+ return buildLine(timestamp, level, message, meta, tag);
23
+ };
24
+ return new ConsoleTransport({ level, render });
25
+ }
26
+
27
+ function buildFileTransport({ folder, datePattern, filename, maxFiles, maxSize, zippedArchive, level }) {
28
+ if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
29
+ return new FileTransport({
30
+ folder: path.normalize(folder),
31
+ filename,
32
+ datePattern,
33
+ zippedArchive,
34
+ maxSize,
35
+ maxFiles,
36
+ level,
37
+ render: ({ level, message, meta }) => buildLine(new Date().toISOString(), level, message, meta),
38
+ });
39
+ }
40
+
41
+ function createLoggerInstance(initial = {}) {
42
+ const globalLevel = process.env.LOG_LEVEL || initial.level || 'debug';
43
+ assertLevel(globalLevel);
44
+
45
+ const logger = new MiniLogger({ level: globalLevel });
46
+
47
+ let consoleTransport = buildConsoleTransport(normalizeConsoleOpts(initial.console));
48
+ consoleTransport.level = normalizeConsoleOpts(initial.console).level;
49
+ logger.add(consoleTransport);
50
+
51
+ let fileTransport = null;
52
+ let consoleOpts = { ...normalizeConsoleOpts(initial.console) };
53
+ let filesOpts = null;
54
+ let sanitizeEnabled = initial.sanitize ?? true;
55
+
56
+ function applySettings(options = {}) {
57
+ if (options.level !== undefined) {
58
+ assertLevel(options.level);
59
+ logger.level = options.level;
60
+ }
61
+ if (options.sanitize !== undefined) {
62
+ if (typeof options.sanitize !== 'boolean') throw new Error('log.settings sanitize must be a boolean');
63
+ sanitizeEnabled = options.sanitize;
64
+ }
65
+ if (options.console !== undefined) {
66
+ const next = normalizeConsoleOpts(options.console);
67
+ if (next.level !== undefined && next.level !== null) assertLevel(next.level);
68
+ consoleOpts = { ...consoleOpts, ...next };
69
+ logger.remove(consoleTransport);
70
+ consoleTransport = buildConsoleTransport(consoleOpts);
71
+ consoleTransport.level = consoleOpts.level;
72
+ logger.add(consoleTransport);
73
+ }
74
+ if (options.files) {
75
+ const normalized = normalizeFilesOptions(options.files);
76
+ if (normalized.level !== undefined && normalized.level !== null) assertLevel(normalized.level);
77
+ filesOpts = normalized;
78
+ if (fileTransport) logger.remove(fileTransport);
79
+ fileTransport = buildFileTransport(normalized);
80
+ logger.add(fileTransport);
81
+ }
82
+ }
83
+
84
+ function isEnabled(level) {
85
+ const want = logger.levels[level];
86
+ const current = logger.levels[logger.level];
87
+ return want !== undefined && current !== undefined && want <= current;
88
+ }
89
+
90
+ function write(level, message, ...meta) {
91
+ if (typeof message === 'function') {
92
+ if (!isEnabled(level)) return;
93
+ message = message();
94
+ }
95
+ if (message instanceof Error) {
96
+ const text = sanitizeEnabled ? sanitizeMessage(message.message) : message.message;
97
+ logger.log(level, text, { stack: message.stack, ...meta[0] });
98
+ return;
99
+ }
100
+ logger.log(level, sanitizeEnabled ? sanitizeMessage(message) : message, ...meta);
101
+ }
102
+
103
+ function callable(message, ...meta) {
104
+ write('info', message, ...meta);
105
+ }
106
+
107
+ callable.error = (m, ...a) => write('error', m, ...a);
108
+ callable.warn = (m, ...a) => write('warn', m, ...a);
109
+ callable.info = (m, ...a) => write('info', m, ...a);
110
+ callable.debug = (m, ...a) => write('debug', m, ...a);
111
+ callable.http = (m, ...a) => write('http', m, ...a);
112
+ callable.verbose = (m, ...a) => write('verbose', m, ...a);
113
+ callable.silly = (m, ...a) => write('silly', m, ...a);
114
+ callable.log = (level, m, ...a) => { assertLevel(level); write(level, m, ...a); };
115
+
116
+ callable.settings = applySettings;
117
+ callable.setLevel = (level) => { assertLevel(level); logger.level = level; };
118
+ callable.getLevel = () => logger.level;
119
+ callable.child = (meta = {}) => {
120
+ const child = logger.child(meta);
121
+ return {
122
+ error: (m, ...a) => child.error(m, ...a),
123
+ warn: (m, ...a) => child.warn(m, ...a),
124
+ info: (m, ...a) => child.info(m, ...a),
125
+ debug: (m, ...a) => child.debug(m, ...a),
126
+ log: child.log.bind(child),
127
+ };
128
+ };
129
+ callable.close = () => {
130
+ if (fileTransport) { logger.remove(fileTransport); try { fileTransport.close?.(); } catch {} fileTransport = null; }
131
+ };
132
+ callable.createLogger = (opts = {}) => createLoggerInstance(opts);
133
+ callable._winston = logger;
134
+
135
+ if (initial.files) applySettings({ files: initial.files });
136
+ if (initial.console || initial.level || initial.sanitize !== undefined) {
137
+ applySettings({ console: initial.console, level: initial.level, sanitize: initial.sanitize });
138
+ }
139
+
140
+ return callable;
141
+ }
142
+
143
+ module.exports = createLoggerInstance();
144
+ module.exports.createLogger = createLoggerInstance;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "logxpert",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "A powerful logging library for Node.js with colorful output and daily file rotation",
5
5
  "main": "main.js",
6
6
  "types": "index.d.ts",