@mimik/mim-logger 1.0.0

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/README.md ADDED
@@ -0,0 +1,168 @@
1
+ <a name="MimLogger"></a>
2
+
3
+ ## MimLogger
4
+ Logger instance
5
+
6
+ **Kind**: global class
7
+
8
+ * [MimLogger](#MimLogger)
9
+ * [.init(contextOrConfig)](#MimLogger+init) ⇒ [<code>MimLogger</code>](#MimLogger)
10
+ * [.getConfig()](#MimLogger+getConfig) ⇒ <code>Object</code>
11
+ * [._log(level, message, ...args)](#MimLogger+_log)
12
+ * [.log(level, message, ...args)](#MimLogger+log)
13
+ * [.error(message, ...args)](#MimLogger+error)
14
+ * [.warn(message, ...args)](#MimLogger+warn)
15
+ * [.info(message, ...args)](#MimLogger+info)
16
+ * [.verbose(message, ...args)](#MimLogger+verbose)
17
+ * [.debug(message, ...args)](#MimLogger+debug)
18
+ * [.silly(message, ...args)](#MimLogger+silly)
19
+ * [.flush()](#MimLogger+flush) ⇒ <code>Promise.&lt;void&gt;</code>
20
+ * [.flushAndExit(exitCode)](#MimLogger+flushAndExit) ⇒ <code>Promise.&lt;void&gt;</code>
21
+ * [.serializeError(err)](#MimLogger+serializeError) ⇒ <code>Object</code>
22
+
23
+ <a name="MimLogger+init"></a>
24
+
25
+ ### mimLogger.init(contextOrConfig) ⇒ [<code>MimLogger</code>](#MimLogger)
26
+ Initializes the logger with context or configuration
27
+
28
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
29
+ **Returns**: [<code>MimLogger</code>](#MimLogger) - Logger instance for chaining
30
+
31
+ | Param | Type | Description |
32
+ | --- | --- | --- |
33
+ | contextOrConfig | <code>Object</code> | mimik context object or config with env |
34
+
35
+ <a name="MimLogger+getConfig"></a>
36
+
37
+ ### mimLogger.getConfig() ⇒ <code>Object</code>
38
+ Gets current configuration, refreshing from context if available
39
+
40
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
41
+ **Returns**: <code>Object</code> - Current configuration
42
+ <a name="MimLogger+_log"></a>
43
+
44
+ ### mimLogger.\_log(level, message, ...args)
45
+ Internal log method
46
+
47
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
48
+
49
+ | Param | Type | Description |
50
+ | --- | --- | --- |
51
+ | level | <code>string</code> | Log level |
52
+ | message | <code>string</code> \| <code>Object</code> | Log message or object with message property |
53
+ | ...args | <code>\*</code> | Additional arguments (metadata, correlationId) |
54
+
55
+ <a name="MimLogger+log"></a>
56
+
57
+ ### mimLogger.log(level, message, ...args)
58
+ Generic log method with explicit level
59
+
60
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
61
+
62
+ | Param | Type | Description |
63
+ | --- | --- | --- |
64
+ | level | <code>string</code> | Log level |
65
+ | message | <code>string</code> \| <code>Object</code> | Log message |
66
+ | ...args | <code>\*</code> | Additional arguments |
67
+
68
+ <a name="MimLogger+error"></a>
69
+
70
+ ### mimLogger.error(message, ...args)
71
+ Log error message
72
+
73
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
74
+
75
+ | Param | Type | Description |
76
+ | --- | --- | --- |
77
+ | message | <code>string</code> \| <code>Object</code> | Log message |
78
+ | ...args | <code>\*</code> | Additional arguments |
79
+
80
+ <a name="MimLogger+warn"></a>
81
+
82
+ ### mimLogger.warn(message, ...args)
83
+ Log warning message
84
+
85
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
86
+
87
+ | Param | Type | Description |
88
+ | --- | --- | --- |
89
+ | message | <code>string</code> \| <code>Object</code> | Log message |
90
+ | ...args | <code>\*</code> | Additional arguments |
91
+
92
+ <a name="MimLogger+info"></a>
93
+
94
+ ### mimLogger.info(message, ...args)
95
+ Log info message
96
+
97
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
98
+
99
+ | Param | Type | Description |
100
+ | --- | --- | --- |
101
+ | message | <code>string</code> \| <code>Object</code> | Log message |
102
+ | ...args | <code>\*</code> | Additional arguments |
103
+
104
+ <a name="MimLogger+verbose"></a>
105
+
106
+ ### mimLogger.verbose(message, ...args)
107
+ Log verbose message
108
+
109
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
110
+
111
+ | Param | Type | Description |
112
+ | --- | --- | --- |
113
+ | message | <code>string</code> \| <code>Object</code> | Log message |
114
+ | ...args | <code>\*</code> | Additional arguments |
115
+
116
+ <a name="MimLogger+debug"></a>
117
+
118
+ ### mimLogger.debug(message, ...args)
119
+ Log debug message
120
+
121
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
122
+
123
+ | Param | Type | Description |
124
+ | --- | --- | --- |
125
+ | message | <code>string</code> \| <code>Object</code> | Log message |
126
+ | ...args | <code>\*</code> | Additional arguments |
127
+
128
+ <a name="MimLogger+silly"></a>
129
+
130
+ ### mimLogger.silly(message, ...args)
131
+ Log silly message
132
+
133
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
134
+
135
+ | Param | Type | Description |
136
+ | --- | --- | --- |
137
+ | message | <code>string</code> \| <code>Object</code> | Log message |
138
+ | ...args | <code>\*</code> | Additional arguments |
139
+
140
+ <a name="MimLogger+flush"></a>
141
+
142
+ ### mimLogger.flush() ⇒ <code>Promise.&lt;void&gt;</code>
143
+ Flush all transports (for graceful shutdown)
144
+
145
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
146
+ <a name="MimLogger+flushAndExit"></a>
147
+
148
+ ### mimLogger.flushAndExit(exitCode) ⇒ <code>Promise.&lt;void&gt;</code>
149
+ Flush all transports and exit process
150
+
151
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
152
+
153
+ | Param | Type | Default | Description |
154
+ | --- | --- | --- | --- |
155
+ | exitCode | <code>number</code> | <code>0</code> | Process exit code |
156
+
157
+ <a name="MimLogger+serializeError"></a>
158
+
159
+ ### mimLogger.serializeError(err) ⇒ <code>Object</code>
160
+ Utility to serialize errors
161
+
162
+ **Kind**: instance method of [<code>MimLogger</code>](#MimLogger)
163
+ **Returns**: <code>Object</code> - Serialized error
164
+
165
+ | Param | Type | Description |
166
+ | --- | --- | --- |
167
+ | err | <code>Error</code> | Error to serialize |
168
+
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Configuration management for mim-logger
3
+ * Supports both direct env object and context-based env access
4
+ */
5
+
6
+ const { LEVELS, LEVEL_PRIORITY } = require('../lib/common');
7
+
8
+ const DEFAULT_CONFIG = {
9
+ level: 'debug',
10
+ enabled: true,
11
+ format: 'text',
12
+ useColors: true,
13
+ includeStack: false,
14
+ serverType: null,
15
+ serverId: null,
16
+ };
17
+
18
+ /**
19
+ * Parses a boolean environment variable
20
+ * @param {string} value - Environment variable value
21
+ * @param {boolean} defaultValue - Default if not set
22
+ * @returns {boolean}
23
+ */
24
+ const parseBoolean = (value, defaultValue) => {
25
+ if (value === undefined || value === null || value === '') {
26
+ return defaultValue;
27
+ }
28
+ const lower = String(value).toLowerCase();
29
+ return lower === 'true' || lower === '1' || lower === 'yes';
30
+ };
31
+
32
+ /**
33
+ * Creates configuration from environment object
34
+ * @param {Object} env - Environment variables object
35
+ * @returns {Object} Logger configuration
36
+ */
37
+ const createConfig = (env = {}) => {
38
+ const level = env.LOG_LEVEL || DEFAULT_CONFIG.level;
39
+
40
+ // Validate level
41
+ if (!LEVELS.includes(level)) {
42
+ console.warn(`[mim-logger] Invalid LOG_LEVEL "${level}", using default "${DEFAULT_CONFIG.level}"`);
43
+ }
44
+
45
+ return {
46
+ level: LEVELS.includes(level) ? level : DEFAULT_CONFIG.level,
47
+ enabled: parseBoolean(env.LOG_ENABLED, DEFAULT_CONFIG.enabled),
48
+ format: env.LOG_FORMAT === 'json' ? 'json' : DEFAULT_CONFIG.format,
49
+ useColors: parseBoolean(env.LOG_COLORS, DEFAULT_CONFIG.useColors),
50
+ includeStack: parseBoolean(env.LOG_STACK, DEFAULT_CONFIG.includeStack),
51
+ serverType: env.SERVER_TYPE || globalThis.serverType || DEFAULT_CONFIG.serverType,
52
+ serverId: env.SERVER_ID || globalThis.serverId || DEFAULT_CONFIG.serverId,
53
+ };
54
+ };
55
+
56
+ /**
57
+ * Checks if a log level should be logged based on configured minimum level
58
+ * @param {string} level - Level to check
59
+ * @param {string} minLevel - Minimum configured level
60
+ * @returns {boolean}
61
+ */
62
+ const shouldLog = (level, minLevel) => {
63
+ const levelPriority = LEVEL_PRIORITY[level];
64
+ const minPriority = LEVEL_PRIORITY[minLevel];
65
+
66
+ if (levelPriority === undefined || minPriority === undefined) {
67
+ return true;
68
+ }
69
+
70
+ return levelPriority <= minPriority;
71
+ };
72
+
73
+ module.exports = {
74
+ DEFAULT_CONFIG,
75
+ createConfig,
76
+ shouldLog,
77
+ parseBoolean,
78
+ };
@@ -0,0 +1,79 @@
1
+ const loadPlugin = async (name) => {
2
+ const mod = await import(name);
3
+ return mod.default ?? mod;
4
+ };
5
+
6
+ module.exports = (async () => {
7
+ const js = await loadPlugin('@eslint/js');
8
+ const importPlugin = await loadPlugin('eslint-plugin-import');
9
+ const stylistic = await loadPlugin('@stylistic/eslint-plugin');
10
+ const processDoc = await loadPlugin('@mimik/eslint-plugin-document-env');
11
+
12
+ const MAX_LENGTH_LINE = 180;
13
+ const MAX_FUNCTION_PARAMETERS = 6;
14
+ const MAX_LINES_IN_FILES = 600;
15
+ const MAX_LINES_IN_FUNCTION = 150;
16
+ const MAX_STATEMENTS_IN_FUNCTION = 45;
17
+ const MIN_KEYS_IN_OBJECT = 10;
18
+ const MAX_COMPLEXITY = 30;
19
+
20
+ return [
21
+ {
22
+ ignores: ['mochawesome-report/**', '**/node_modules', 'node_modules/**', 'dist/**', '**/eslint.config.js', '**/webpack.config.js'],
23
+ },
24
+
25
+ // Baseline best-practices
26
+ js.configs.recommended,
27
+ importPlugin.flatConfigs.recommended,
28
+ stylistic.configs.recommended,
29
+
30
+ {
31
+ plugins: { processDoc },
32
+ languageOptions: {
33
+ ecmaVersion: 'latest',
34
+ sourceType: 'commonjs',
35
+ globals: {
36
+ mimikModule: true,
37
+ console: 'readonly',
38
+ describe: 'readonly',
39
+ it: 'readonly',
40
+ require: 'readonly',
41
+ module: 'readonly',
42
+ __dirname: 'readonly',
43
+ process: 'readonly',
44
+ },
45
+ },
46
+ linterOptions: {
47
+ reportUnusedDisableDirectives: true,
48
+ },
49
+ rules: {
50
+ '@stylistic/brace-style': ['warn', 'stroustrup', { allowSingleLine: true }],
51
+ '@stylistic/line-comment-position': 'off',
52
+ '@stylistic/semi': ['error', 'always'],
53
+
54
+ 'capitalized-comments': 'off',
55
+ 'complexity': ['error', MAX_COMPLEXITY],
56
+ 'curly': 'off',
57
+ 'id-length': ['error', { exceptions: ['x', 'y', 'z', 'i', 'j', 'k'] }],
58
+ 'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
59
+ 'import/no-unresolved': ['error', { amd: true, caseSensitiveStrict: true, commonjs: true }],
60
+ 'init-declarations': 'off',
61
+ 'linebreak-style': 'off',
62
+ 'max-len': ['warn', MAX_LENGTH_LINE, { ignoreComments: true }],
63
+ 'max-lines': ['warn', { max: MAX_LINES_IN_FILES, skipComments: true }],
64
+ 'max-lines-per-function': ['warn', { max: MAX_LINES_IN_FUNCTION, skipComments: true }],
65
+ 'max-params': ['error', MAX_FUNCTION_PARAMETERS],
66
+ 'max-statements': ['warn', MAX_STATEMENTS_IN_FUNCTION],
67
+ 'no-confusing-arrow': 'off',
68
+ 'no-inline-comments': 'off',
69
+ 'no-process-env': 'error',
70
+ 'no-ternary': 'off',
71
+ 'no-undefined': 'off',
72
+ 'one-var': ['error', 'never'],
73
+ 'processDoc/validate-document-env': 'error',
74
+ 'quotes': ['warn', 'single'],
75
+ 'sort-keys': ['error', 'asc', { caseSensitive: true, minKeys: MIN_KEYS_IN_OBJECT, natural: false }],
76
+ },
77
+ },
78
+ ];
79
+ })();
package/index.js ADDED
@@ -0,0 +1,279 @@
1
+ /**
2
+ * mim-logger - Lightweight logging library for mimOE runtime environment
3
+ *
4
+ * Usage:
5
+ * const logger = require('@mimik/mim-logger');
6
+ *
7
+ * // Initialize with context (recommended for mimOE)
8
+ * logger.init(context);
9
+ *
10
+ * // Or initialize with env object directly
11
+ * logger.init({ env: { LOG_LEVEL: 'debug' } });
12
+ *
13
+ * // Log messages
14
+ * logger.info('message');
15
+ * logger.error('message', { meta: 'data' });
16
+ * logger.debug('message', { meta: 'data' }, 'correlationId');
17
+ *
18
+ * Environment Variables:
19
+ * LOG_ENABLED - Enable/disable logging ('true'/'false', default: 'true')
20
+ * LOG_LEVEL - Minimum log level (error/warn/info/verbose/debug/silly, default: 'debug')
21
+ * LOG_FORMAT - Output format ('text'/'json', default: 'text')
22
+ * LOG_COLORS - Use ANSI colors ('true'/'false', default: 'true')
23
+ * LOG_STACK - Include stack traces ('true'/'false', default: 'false')
24
+ * SERVER_TYPE - Server type identifier
25
+ * SERVER_ID - Server instance identifier
26
+ */
27
+
28
+ const { LEVELS, serializeData, serializeError } = require('./lib/common');
29
+ const { parseCorrelationId, getStackInfo } = require('./lib/formatLib');
30
+ const { createConfig, shouldLog, DEFAULT_CONFIG } = require('./configuration/config');
31
+ const ConsoleTransport = require('./lib/consoleTransport');
32
+
33
+ /**
34
+ * Logger instance
35
+ */
36
+ class MimLogger {
37
+ constructor() {
38
+ this.config = { ...DEFAULT_CONFIG };
39
+ this.transport = new ConsoleTransport();
40
+ this.initialized = false;
41
+ this.context = null;
42
+
43
+ // Expose LEVELS constant
44
+ this.LEVELS = LEVELS;
45
+ }
46
+
47
+ /**
48
+ * Initializes the logger with context or configuration
49
+ * @param {Object} contextOrConfig - mimik context object or config with env
50
+ * @returns {MimLogger} Logger instance for chaining
51
+ */
52
+ init(contextOrConfig = {}) {
53
+ this.context = contextOrConfig;
54
+
55
+ // Extract env from context or use directly
56
+ const env = contextOrConfig.env || contextOrConfig || {};
57
+
58
+ this.config = createConfig(env);
59
+ this.transport = new ConsoleTransport({
60
+ format: this.config.format,
61
+ useColors: this.config.useColors,
62
+ });
63
+
64
+ this.initialized = true;
65
+ return this;
66
+ }
67
+
68
+ /**
69
+ * Gets current configuration, refreshing from context if available
70
+ * @returns {Object} Current configuration
71
+ */
72
+ getConfig() {
73
+ // If we have a context with env, refresh config each time
74
+ // This allows dynamic config changes
75
+ if (this.context && this.context.env) {
76
+ this.config = createConfig(this.context.env);
77
+ this.transport = new ConsoleTransport({
78
+ format: this.config.format,
79
+ useColors: this.config.useColors,
80
+ });
81
+ }
82
+ return this.config;
83
+ }
84
+
85
+ /**
86
+ * Internal log method
87
+ * @param {string} level - Log level
88
+ * @param {string|Object} message - Log message or object with message property
89
+ * @param {...*} args - Additional arguments (metadata, correlationId)
90
+ */
91
+ _log(level, message, ...args) {
92
+ const config = this.getConfig();
93
+
94
+ // Check if logging is enabled
95
+ if (!config.enabled) {
96
+ return;
97
+ }
98
+
99
+ // Check if this level should be logged
100
+ if (!shouldLog(level, config.level)) {
101
+ return;
102
+ }
103
+
104
+ // Parse arguments
105
+ let meta = null;
106
+ let correlationId = null;
107
+
108
+ // Last string argument is correlation ID
109
+ if (args.length > 0 && typeof args[args.length - 1] === 'string') {
110
+ correlationId = args.pop();
111
+ }
112
+
113
+ // Remaining arguments are metadata
114
+ if (args.length > 0) {
115
+ // Merge all metadata objects
116
+ meta = args.reduce((acc, arg) => {
117
+ if (arg && typeof arg === 'object') {
118
+ return { ...acc, ...serializeData(arg) };
119
+ }
120
+ return acc;
121
+ }, {});
122
+ }
123
+
124
+ // Handle message as object
125
+ let messageStr = message;
126
+ if (message && typeof message === 'object') {
127
+ if (message.message) {
128
+ messageStr = message.message;
129
+ // Merge remaining properties into meta
130
+ // eslint-disable-next-line id-length, no-unused-vars
131
+ const { message: _, ...rest } = message;
132
+ meta = { ...meta, ...serializeData(rest) };
133
+ }
134
+ else {
135
+ messageStr = JSON.stringify(serializeData(message));
136
+ }
137
+ }
138
+
139
+ // Build log info object
140
+ const info = {
141
+ level,
142
+ message: String(messageStr),
143
+ timestamp: new Date().toISOString(),
144
+ };
145
+
146
+ // Add correlation ID if present
147
+ if (correlationId) {
148
+ info.correlationId = correlationId;
149
+ info.correlationParsed = parseCorrelationId(correlationId);
150
+ }
151
+
152
+ // Add metadata
153
+ if (meta && Object.keys(meta).length > 0) {
154
+ info.meta = meta;
155
+ }
156
+
157
+ // Add stack info for errors or if configured
158
+ if (level === 'error' || config.includeStack) {
159
+ const stackInfo = getStackInfo();
160
+ if (stackInfo) {
161
+ info.stack = stackInfo;
162
+ }
163
+ }
164
+
165
+ // Add server identification
166
+ if (config.serverType) {
167
+ info.serverType = config.serverType;
168
+ }
169
+ if (config.serverId) {
170
+ info.serverId = config.serverId;
171
+ }
172
+
173
+ // Send to transport
174
+ this.transport.log(info);
175
+ }
176
+
177
+ /**
178
+ * Generic log method with explicit level
179
+ * @param {string} level - Log level
180
+ * @param {string|Object} message - Log message
181
+ * @param {...*} args - Additional arguments
182
+ */
183
+ log(level, message, ...args) {
184
+ if (!LEVELS.includes(level)) {
185
+ console.warn(`[mim-logger] Invalid log level "${level}"`);
186
+ return;
187
+ }
188
+ this._log(level, message, ...args);
189
+ }
190
+
191
+ /**
192
+ * Log error message
193
+ * @param {string|Object} message - Log message
194
+ * @param {...*} args - Additional arguments
195
+ */
196
+ error(message, ...args) {
197
+ this._log('error', message, ...args);
198
+ }
199
+
200
+ /**
201
+ * Log warning message
202
+ * @param {string|Object} message - Log message
203
+ * @param {...*} args - Additional arguments
204
+ */
205
+ warn(message, ...args) {
206
+ this._log('warn', message, ...args);
207
+ }
208
+
209
+ /**
210
+ * Log info message
211
+ * @param {string|Object} message - Log message
212
+ * @param {...*} args - Additional arguments
213
+ */
214
+ info(message, ...args) {
215
+ this._log('info', message, ...args);
216
+ }
217
+
218
+ /**
219
+ * Log verbose message
220
+ * @param {string|Object} message - Log message
221
+ * @param {...*} args - Additional arguments
222
+ */
223
+ verbose(message, ...args) {
224
+ this._log('verbose', message, ...args);
225
+ }
226
+
227
+ /**
228
+ * Log debug message
229
+ * @param {string|Object} message - Log message
230
+ * @param {...*} args - Additional arguments
231
+ */
232
+ debug(message, ...args) {
233
+ this._log('debug', message, ...args);
234
+ }
235
+
236
+ /**
237
+ * Log silly message
238
+ * @param {string|Object} message - Log message
239
+ * @param {...*} args - Additional arguments
240
+ */
241
+ silly(message, ...args) {
242
+ this._log('silly', message, ...args);
243
+ }
244
+
245
+ /**
246
+ * Flush all transports (for graceful shutdown)
247
+ * @returns {Promise<void>}
248
+ */
249
+ flush() {
250
+ return this.transport.flush();
251
+ }
252
+
253
+ /**
254
+ * Flush all transports and exit process
255
+ * @param {number} exitCode - Process exit code
256
+ * @returns {Promise<void>}
257
+ */
258
+ flushAndExit(exitCode = 0) {
259
+ return this.flush().then(() => {
260
+ if (typeof process !== 'undefined' && typeof process.exit === 'function') {
261
+ process.exit(exitCode);
262
+ }
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Utility to serialize errors
268
+ * @param {Error} err - Error to serialize
269
+ * @returns {Object} Serialized error
270
+ */
271
+ serializeError(err) {
272
+ return serializeError(err);
273
+ }
274
+ }
275
+
276
+ // Export singleton instance
277
+ const logger = new MimLogger();
278
+
279
+ module.exports = logger;
package/lib/common.js ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Common constants and utilities for mim-logger
3
+ */
4
+
5
+ const LEVELS = ['error', 'warn', 'info', 'verbose', 'debug', 'silly'];
6
+
7
+ const LEVEL_PRIORITY = {
8
+ error: 0,
9
+ warn: 1,
10
+ info: 2,
11
+ verbose: 3,
12
+ debug: 4,
13
+ silly: 5,
14
+ };
15
+
16
+ const LEVEL_COLORS = {
17
+ error: '\x1b[31m', // Red
18
+ warn: '\x1b[33m', // Yellow
19
+ info: '\x1b[36m', // Cyan
20
+ verbose: '\x1b[35m', // Magenta
21
+ debug: '\x1b[32m', // Green
22
+ silly: '\x1b[90m', // Gray
23
+ };
24
+
25
+ const RESET_COLOR = '\x1b[0m';
26
+
27
+ /**
28
+ * Serializes an error object into a plain object
29
+ * @param {Error} err - Error to serialize
30
+ * @returns {Object} Serialized error
31
+ */
32
+ const serializeError = (err) => {
33
+ if (!err) return err;
34
+ if (!(err instanceof Error)) return err;
35
+
36
+ const serialized = {
37
+ message: err.message,
38
+ name: err.name,
39
+ };
40
+
41
+ if (err.stack) {
42
+ serialized.stack = err.stack;
43
+ }
44
+
45
+ if (err.code) {
46
+ serialized.code = err.code;
47
+ }
48
+
49
+ if (err.status) {
50
+ serialized.status = err.status;
51
+ }
52
+
53
+ if (err.statusCode) {
54
+ serialized.statusCode = err.statusCode;
55
+ }
56
+
57
+ // Copy any additional enumerable properties
58
+ Object.keys(err).forEach((key) => {
59
+ if (!(key in serialized)) {
60
+ serialized[key] = err[key];
61
+ }
62
+ });
63
+
64
+ return serialized;
65
+ };
66
+
67
+ /**
68
+ * Deep clones and serializes data, handling errors and circular references
69
+ * Uses array-based tracking for mimOE runtime compatibility (no WeakSet)
70
+ * @param {*} data - Data to serialize
71
+ * @param {Array} seen - Array of seen objects for circular reference detection
72
+ * @returns {*} Serialized data
73
+ */
74
+ const serializeData = (data, seen) => {
75
+ if (data === null || data === undefined) return data;
76
+ if (typeof data !== 'object') return data;
77
+
78
+ if (data instanceof Error) {
79
+ return serializeError(data);
80
+ }
81
+
82
+ // Initialize seen array on first call
83
+ var seenArr = seen || [];
84
+
85
+ // Check for circular reference using indexOf (works in ES5)
86
+ if (seenArr.indexOf(data) !== -1) {
87
+ return '[Circular]';
88
+ }
89
+
90
+ seenArr.push(data);
91
+
92
+ if (Array.isArray(data)) {
93
+ return data.map(function (item) {
94
+ return serializeData(item, seenArr);
95
+ });
96
+ }
97
+
98
+ if (data instanceof Date) {
99
+ return data.toISOString();
100
+ }
101
+
102
+ // Handle Mongoose-like objects with toObject method
103
+ if (typeof data.toObject === 'function') {
104
+ return serializeData(data.toObject(), seenArr);
105
+ }
106
+
107
+ var result = {};
108
+ Object.keys(data).forEach(function (key) {
109
+ result[key] = serializeData(data[key], seenArr);
110
+ });
111
+
112
+ return result;
113
+ };
114
+
115
+ module.exports = {
116
+ LEVELS,
117
+ LEVEL_PRIORITY,
118
+ LEVEL_COLORS,
119
+ RESET_COLOR,
120
+ serializeError,
121
+ serializeData,
122
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Console transport for mim-logger
3
+ */
4
+
5
+ const { formatText, formatJson } = require('./formatLib');
6
+
7
+ /**
8
+ * Console transport class
9
+ */
10
+ class ConsoleTransport {
11
+ /**
12
+ * Creates a new ConsoleTransport
13
+ * @param {Object} options - Transport options
14
+ * @param {string} options.format - Output format ('text' or 'json')
15
+ * @param {boolean} options.useColors - Use ANSI colors (text format only)
16
+ */
17
+ constructor(options = {}) {
18
+ this.format = options.format || 'text';
19
+ this.useColors = options.useColors !== false;
20
+ }
21
+
22
+ /**
23
+ * Logs a message to console
24
+ * @param {Object} info - Log info object
25
+ */
26
+ log(info) {
27
+ const formatted = this.format === 'json'
28
+ ? formatJson(info)
29
+ : formatText(info, this.useColors);
30
+
31
+ const { level } = info;
32
+
33
+ // Use appropriate console method based on level
34
+ switch (level) {
35
+ case 'error':
36
+ console.error(formatted);
37
+ break;
38
+ case 'warn':
39
+ console.warn(formatted);
40
+ break;
41
+ case 'debug':
42
+ case 'verbose':
43
+ case 'silly':
44
+ // console.debug may not be available in all environments
45
+ if (typeof console.debug === 'function') {
46
+ console.debug(formatted);
47
+ }
48
+ else {
49
+ console.log(formatted);
50
+ }
51
+ break;
52
+ default:
53
+ console.log(formatted);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Flush method (no-op for console, included for interface compatibility)
59
+ * @returns {Promise<void>}
60
+ */
61
+ flush() {
62
+ return Promise.resolve();
63
+ }
64
+ }
65
+
66
+ module.exports = ConsoleTransport;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Log formatting utilities for mim-logger
3
+ */
4
+
5
+ const {
6
+ LEVEL_COLORS,
7
+ RESET_COLOR,
8
+ serializeData,
9
+ } = require('./common');
10
+
11
+ /**
12
+ * Pads a string to a target length (ES5 compatible replacement for padEnd)
13
+ * @param {string} str - String to pad
14
+ * @param {number} targetLength - Desired length
15
+ * @param {string} padChar - Character to pad with (default: space)
16
+ * @returns {string} Padded string
17
+ */
18
+ function padEnd(str, targetLength, padChar) {
19
+ var padS = String(str);
20
+ var pad = padChar || ' ';
21
+ if (padS.length >= targetLength) {
22
+ return padS;
23
+ }
24
+ var padding = '';
25
+ for (var i = padS.length; i < targetLength; i++) {
26
+ padding += pad;
27
+ }
28
+ return padS + padding;
29
+ }
30
+
31
+ /**
32
+ * Parses correlation ID string into components
33
+ * Format: correlationId/timestamp or correlationId@step/timestamp
34
+ * @param {string} correlationId - Correlation ID string
35
+ * @returns {Object} Parsed correlation components
36
+ */
37
+ const parseCorrelationId = (correlationId) => {
38
+ if (!correlationId || typeof correlationId !== 'string') {
39
+ return null;
40
+ }
41
+
42
+ const result = {
43
+ correlationId: null,
44
+ step: null,
45
+ timestamp: null,
46
+ };
47
+
48
+ // Check for step separator (@)
49
+ const atIndex = correlationId.indexOf('@');
50
+ const slashIndex = correlationId.lastIndexOf('/');
51
+
52
+ if (slashIndex > 0) {
53
+ result.timestamp = correlationId.substring(slashIndex + 1);
54
+ const beforeSlash = correlationId.substring(0, slashIndex);
55
+
56
+ if (atIndex > 0 && atIndex < slashIndex) {
57
+ result.correlationId = beforeSlash.substring(0, atIndex);
58
+ result.step = beforeSlash.substring(atIndex + 1);
59
+ }
60
+ else {
61
+ result.correlationId = beforeSlash;
62
+ }
63
+ }
64
+ else if (atIndex > 0) {
65
+ result.correlationId = correlationId.substring(0, atIndex);
66
+ result.step = correlationId.substring(atIndex + 1);
67
+ }
68
+ else {
69
+ result.correlationId = correlationId;
70
+ }
71
+
72
+ return result;
73
+ };
74
+
75
+ /**
76
+ * Extracts stack trace info from the call site
77
+ * @param {number} depth - Stack depth to extract from
78
+ * @returns {Object|null} Stack info object
79
+ */
80
+ const getStackInfo = (depth = 4) => {
81
+ const stackLines = new Error().stack.split('\n');
82
+
83
+ if (stackLines.length <= depth) {
84
+ return null;
85
+ }
86
+
87
+ const line = stackLines[depth];
88
+ // Match: at functionName (path:line:col) or at path:line:col
89
+ const match = line.match(/at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?/);
90
+
91
+ if (!match) {
92
+ return null;
93
+ }
94
+
95
+ const [, method, path, lineNum, pos] = match;
96
+ const file = path.split('/').pop();
97
+
98
+ return {
99
+ method: method || '<anonymous>',
100
+ path,
101
+ line: parseInt(lineNum, 10),
102
+ pos: parseInt(pos, 10),
103
+ file,
104
+ };
105
+ };
106
+
107
+ /**
108
+ * Formats a log entry as text
109
+ * @param {Object} info - Log info object
110
+ * @param {boolean} useColors - Whether to use ANSI colors
111
+ * @returns {string} Formatted log string
112
+ */
113
+ const formatText = (info, useColors = true) => {
114
+ const { level, message, timestamp, meta, correlationId, stack } = info;
115
+
116
+ const parts = [];
117
+
118
+ // Timestamp
119
+ parts.push(`[${timestamp}]`);
120
+
121
+ // Level with optional color
122
+ var levelStr = padEnd(level.toUpperCase(), 7);
123
+ if (useColors) {
124
+ var color = LEVEL_COLORS[level] || '';
125
+ parts.push(color + levelStr + RESET_COLOR);
126
+ }
127
+ else {
128
+ parts.push(levelStr);
129
+ }
130
+
131
+ // Correlation ID if present
132
+ if (correlationId) {
133
+ parts.push(`[${correlationId}]`);
134
+ }
135
+
136
+ // Message
137
+ parts.push(message);
138
+
139
+ // Stack info for errors
140
+ if (stack && stack.file) {
141
+ parts.push(`(${stack.file}:${stack.line})`);
142
+ }
143
+
144
+ let result = parts.join(' ');
145
+
146
+ // Metadata on separate line if present
147
+ if (meta && Object.keys(meta).length > 0) {
148
+ result += `\n ${JSON.stringify(meta)}`;
149
+ }
150
+
151
+ return result;
152
+ };
153
+
154
+ /**
155
+ * Formats a log entry as JSON
156
+ * @param {Object} info - Log info object
157
+ * @returns {string} JSON formatted log string
158
+ */
159
+ const formatJson = (info) => {
160
+ const output = {
161
+ timestamp: info.timestamp,
162
+ level: info.level,
163
+ message: info.message,
164
+ };
165
+
166
+ if (info.correlationId) {
167
+ output.correlationId = info.correlationId;
168
+ }
169
+
170
+ if (info.correlationParsed) {
171
+ output.correlation = info.correlationParsed;
172
+ }
173
+
174
+ if (info.stack) {
175
+ output.stack = info.stack;
176
+ }
177
+
178
+ if (info.meta && Object.keys(info.meta).length > 0) {
179
+ output.meta = serializeData(info.meta);
180
+ }
181
+
182
+ if (info.serverType) {
183
+ output.serverType = info.serverType;
184
+ }
185
+
186
+ if (info.serverId) {
187
+ output.serverId = info.serverId;
188
+ }
189
+
190
+ return JSON.stringify(output);
191
+ };
192
+
193
+ module.exports = {
194
+ parseCorrelationId,
195
+ getStackInfo,
196
+ formatText,
197
+ formatJson,
198
+ };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@mimik/mim-logger",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight logging library for mimOE runtime environment",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "docs": "jsdoc2md index.js > README.md",
8
+ "lint": "eslint . --no-error-on-unmatched-pattern",
9
+ "test": "echo \"Error: no test specified\" && exit 1"
10
+ },
11
+ "keywords": [
12
+ "mimik",
13
+ "logger",
14
+ "edge",
15
+ "microservice",
16
+ "mim"
17
+ ],
18
+ "author": "mimik",
19
+ "license": "MIT",
20
+ "engines": {
21
+ "node": ">=14.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "@eslint/js": "9.39.2",
25
+ "@mimik/eslint-plugin-document-env": "2.0.8",
26
+ "@stylistic/eslint-plugin": "5.7.0",
27
+ "eslint": "9.39.2",
28
+ "eslint-plugin-import": "2.32.0",
29
+ "jsdoc-to-markdown": "9.1.3"
30
+ }
31
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Basic tests for mim-logger
3
+ */
4
+
5
+ const logger = require('../index');
6
+
7
+ // Test initialization
8
+ console.log('\n=== Test 1: Default initialization ===');
9
+ logger.init();
10
+ logger.info('Logger initialized with defaults');
11
+
12
+ // Test all log levels
13
+ console.log('\n=== Test 2: All log levels ===');
14
+ logger.error('This is an error message');
15
+ logger.warn('This is a warning message');
16
+ logger.info('This is an info message');
17
+ logger.verbose('This is a verbose message');
18
+ logger.debug('This is a debug message');
19
+ logger.silly('This is a silly message');
20
+
21
+ // Test with metadata
22
+ console.log('\n=== Test 3: With metadata ===');
23
+ logger.info('User logged in', { userId: '123', email: 'test@example.com' });
24
+ logger.error('Request failed', { status: 500, endpoint: '/api/data' });
25
+
26
+ // Test with correlation ID
27
+ console.log('\n=== Test 4: With correlation ID ===');
28
+ logger.info('Processing request', { action: 'create' }, 'req-abc123/1704067200');
29
+ logger.debug('Cache hit', { key: 'user:123' }, 'req-abc123@step2/1704067200');
30
+
31
+ // Test with Error object
32
+ console.log('\n=== Test 5: With Error object ===');
33
+ const testError = new Error('Something went wrong');
34
+ testError.code = 'ERR_TEST';
35
+ logger.error('Operation failed', { error: testError });
36
+
37
+ // Test log level filtering
38
+ console.log('\n=== Test 6: Log level filtering (LOG_LEVEL=warn) ===');
39
+ logger.init({ LOG_LEVEL: 'warn' });
40
+ logger.debug('This should NOT appear (debug < warn)');
41
+ logger.info('This should NOT appear (info < warn)');
42
+ logger.warn('This SHOULD appear');
43
+ logger.error('This SHOULD appear');
44
+
45
+ // Test disabled logging
46
+ console.log('\n=== Test 7: Disabled logging ===');
47
+ logger.init({ LOG_ENABLED: 'false' });
48
+ logger.error('This should NOT appear (logging disabled)');
49
+ logger.init({ LOG_ENABLED: 'true' }); // Re-enable
50
+
51
+ // Test JSON format
52
+ console.log('\n=== Test 8: JSON format ===');
53
+ logger.init({ LOG_FORMAT: 'json', LOG_LEVEL: 'debug' });
54
+ logger.info('JSON formatted message', { data: { nested: true } });
55
+ logger.error('JSON error with stack');
56
+
57
+ // Test with mimik context pattern
58
+ console.log('\n=== Test 9: mimik context pattern ===');
59
+ const mockContext = {
60
+ env: {
61
+ LOG_LEVEL: 'info',
62
+ LOG_FORMAT: 'text',
63
+ SERVER_TYPE: 'mEHR',
64
+ SERVER_ID: 'edge-001',
65
+ },
66
+ http: {}, // Mock http client
67
+ };
68
+ logger.init(mockContext);
69
+ logger.info('Using mimik context', { feature: 'EHR' });
70
+
71
+ // Test generic log method
72
+ console.log('\n=== Test 10: Generic log method ===');
73
+ logger.log('debug', 'Using generic log method', { custom: true });
74
+ logger.log('invalid', 'This should show warning'); // Invalid level
75
+
76
+ // Test LEVELS constant
77
+ console.log('\n=== Test 11: LEVELS constant ===');
78
+ console.log('Available levels:', logger.LEVELS);
79
+
80
+ console.log('\n=== All tests completed ===\n');