@mhmdhammoud/meritt-utils 1.3.0 → 1.4.1

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/ReleaseNotes.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changes
2
2
 
3
+ ## Version 1.4.0
4
+
5
+ - Changed winston logger to Pino logger to improve performance and reduce memory usage
6
+
3
7
  ## Version 1.3.0
4
8
 
5
9
  - Added toUpperTitle method that accepts a string and removes all non alphanumeric characters and capitalizes each letter of every first word
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ const logger_1 = __importStar(require("../lib/logger"));
27
+ const pino_1 = require("pino");
28
+ jest.mock('pino');
29
+ const LOGGER_NAME = 'logger-name';
30
+ const PINO_DESTINATION = {};
31
+ const PINO = {
32
+ info: jest.fn(),
33
+ };
34
+ describe('create logger wrapper', () => {
35
+ test('initially create & configure logger', () => {
36
+ //@ts-ignore
37
+ const mock_pino_destination = jest
38
+ .spyOn(pino_1.pino, 'destination')
39
+ //@ts-ignore
40
+ .mockReturnValue(PINO_DESTINATION);
41
+ //@ts-ignore
42
+ const mock_pino = pino_1.pino.mockReturnValue(PINO);
43
+ /** create logger */
44
+ const logger = new logger_1.default(LOGGER_NAME);
45
+ expect(logger).toBeDefined();
46
+ expect(logger['_name']).toBe(LOGGER_NAME);
47
+ expect(mock_pino).toHaveBeenCalledTimes(1);
48
+ expect(mock_pino).toHaveBeenCalledWith({
49
+ level: 'info',
50
+ timestamp: expect.any(Function),
51
+ }, PINO_DESTINATION);
52
+ expect(mock_pino_destination).toHaveBeenCalledWith({
53
+ minLength: 1024,
54
+ sync: true,
55
+ });
56
+ });
57
+ test('create repeatedly - stick to pino singleton', () => {
58
+ //@ts-ignore
59
+ jest.spyOn(pino_1.pino, 'destination').mockReturnValue(PINO_DESTINATION);
60
+ //@ts-ignore
61
+ const mock_pino = pino_1.pino.mockReturnValue(PINO);
62
+ new logger_1.default(LOGGER_NAME);
63
+ expect(mock_pino).toHaveBeenCalledTimes(1);
64
+ /** create additional logger */
65
+ new logger_1.default(LOGGER_NAME);
66
+ /** no new pino created */
67
+ expect(mock_pino).toHaveBeenCalledTimes(1);
68
+ });
69
+ });
70
+ describe('validate log level', () => {
71
+ test('throw on invalid log level', () => {
72
+ const INVALID_LOG_LEVEL = 'invalid-log-level';
73
+ expect(() => {
74
+ (0, logger_1.isValidLogLevel)(INVALID_LOG_LEVEL);
75
+ }).toThrowError(`Invalid log level "${INVALID_LOG_LEVEL}": only error, warn, info, debug, trace are valid.`);
76
+ });
77
+ });
78
+ describe('route and format logs', () => {
79
+ const LOG_EVENT = {
80
+ code: 'code',
81
+ msg: 'message',
82
+ };
83
+ const DETAILS = {
84
+ key0: 'val0',
85
+ key1: 'val1',
86
+ };
87
+ test('log info with details', () => {
88
+ //@ts-ignore
89
+ jest.spyOn(pino_1.pino, 'destination').mockReturnValue(PINO_DESTINATION);
90
+ //@ts-ignore
91
+ pino_1.pino.mockReturnValue(PINO);
92
+ const logger = new logger_1.default(LOGGER_NAME);
93
+ logger.info(LOG_EVENT, DETAILS);
94
+ expect(PINO.info).toHaveBeenCalledWith({
95
+ component: LOGGER_NAME,
96
+ ...LOG_EVENT,
97
+ detail: [DETAILS],
98
+ });
99
+ });
100
+ });
@@ -1,43 +1,58 @@
1
- import { LoggerOptions } from 'winston';
1
+ import { ElasticConfig, LOG_LEVEL, LogEvent } from '../types';
2
2
  /**
3
- * Service for logging information, warnings, and errors using Winston.
3
+ * Checks if a given log level is valid.
4
+ * @param level - The log level to check.
5
+ * @returns Whether the log level is valid.
4
6
  */
5
- declare class LoggerService {
6
- private logger;
7
+ export declare function isValidLogLevel(level: string): level is LOG_LEVEL;
8
+ /**
9
+ * Logger Wrapper.
10
+ * Wraps a Pino logger instance and provides logging methods.
11
+ */
12
+ declare class Logger {
13
+ private readonly _name;
14
+ private readonly _logger;
7
15
  /**
8
- * Creates an instance of LoggerService.
9
- * @param config - The configuration options for Winston logger.
16
+ * Creates a new Logger instance with default configuration.
17
+ * @param name - The name of the logger.
10
18
  */
11
- constructor(config: LoggerOptions);
19
+ constructor(name: string);
12
20
  /**
13
- * Logs an informational message.
14
- * @param className - The name of the class that generated the log.
15
- * @param functionName - The name of the function that generated the log.
16
- * @param logMessage - The log message text.
17
- * @param userIp - The user's IP address associated with the log.
18
- * @param data - Additional data associated with the log.
21
+ * Creates a new Logger instance with custom Elasticsearch configuration.
22
+ * @param name - The name of the logger.
23
+ * @param elasticConfig - Optional Elasticsearch configuration.
24
+ */
25
+ constructor(name: string, elasticConfig?: ElasticConfig);
26
+ private log;
27
+ /**
28
+ * Logs an error message.
29
+ * @param logEvent - The event to log.
30
+ * @param args - Additional arguments to include in the log.
19
31
  */
20
- info(className: string, functionName: string, logMessage: string, userIp: string): void;
32
+ error(logEvent: LogEvent, ...args: unknown[]): void;
21
33
  /**
22
34
  * Logs a warning message.
23
- * @param className - The name of the class that generated the log.
24
- * @param functionName - The name of the function that generated the log.
25
- * @param logMessage - The log message text.
26
- * @param userIp - The user's IP address associated with the log.
27
- * @param data - Additional data associated with the log.
35
+ * @param logEvent - The event to log.
36
+ * @param args - Additional arguments to include in the log.
28
37
  */
29
- warn(className: string, functionName: string, logMessage: string, userIp: string): void;
38
+ warn(logEvent: LogEvent, ...args: unknown[]): void;
30
39
  /**
31
- * Logs an error message.
32
- * @param className - The name of the class that generated the log.
33
- * @param functionName - The name of the function that generated the log.
34
- * @param logMessage - The log message text.
35
- * @param userIp - The user's IP address associated with the log.
36
- * @param data - Additional data associated with the log.
40
+ * Logs an informational message.
41
+ * @param logEvent - The event to log.
42
+ * @param args - Additional arguments to include in the log.
37
43
  */
38
- error(className: string, functionName: string, logMessage: string, userIp: string, data?: Object): void;
39
- private createLogMessage;
40
- private log;
44
+ info(logEvent: LogEvent, ...args: unknown[]): void;
45
+ /**
46
+ * Logs a debug message.
47
+ * @param logEvent - The event to log.
48
+ * @param args - Additional arguments to include in the log.
49
+ */
50
+ debug(logEvent: LogEvent, ...args: unknown[]): void;
51
+ /**
52
+ * Logs a trace message.
53
+ * @param logEvent - The event to log.
54
+ * @param args - Additional arguments to include in the log.
55
+ */
56
+ trace(logEvent: LogEvent, ...args: unknown[]): void;
41
57
  }
42
- declare const Logger: LoggerService;
43
58
  export default Logger;
@@ -1,92 +1,146 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const winston_1 = __importDefault(require("winston"));
7
- const config_1 = require("../config");
29
+ exports.isValidLogLevel = void 0;
30
+ const pino_1 = require("pino");
31
+ const dotenv = __importStar(require("dotenv"));
32
+ const pino_elasticsearch_1 = __importDefault(require("pino-elasticsearch"));
33
+ dotenv.config();
8
34
  /**
9
- * Service for logging information, warnings, and errors using Winston.
35
+ * Pino logger backend - singleton
10
36
  */
11
- class LoggerService {
12
- /**
13
- * Creates an instance of LoggerService.
14
- * @param config - The configuration options for Winston logger.
15
- */
16
- constructor(config) {
17
- this.logger = winston_1.default.createLogger({
18
- ...config,
19
- level: process.env.LOG_LEVEL,
20
- });
21
- if (process.env.NODE_ENV === 'local') {
22
- this.logger.add(new winston_1.default.transports.Console({
23
- level: 'info',
24
- format: winston_1.default.format.combine(winston_1.default.format.simple(), winston_1.default.format.colorize(), winston_1.default.format.printf(({ level, message }) => {
25
- return `[${level}]: ${JSON.stringify(message)}`;
26
- // Parse the message as JSON and include in the log
27
- })),
28
- }));
37
+ let pinoLogger;
38
+ /**
39
+ * Creates a Pino logger instance with specified Elasticsearch configuration.
40
+ * @param elasticConfig - Optional Elasticsearch configuration.
41
+ * @returns The Pino logger instance.
42
+ */
43
+ function getLogger(elasticConfig) {
44
+ if (!pinoLogger) {
45
+ if (isValidLogLevel(process.env.LOG_LEVEL)) {
46
+ const transports = [];
47
+ if (process.env.NODE_ENV !== 'local' && process.env.NODE_ENV !== 'test') {
48
+ const esConfig = {
49
+ index: process.env.SERVER_NICKNAME,
50
+ node: process.env.ELASTICSEARCH_NODE,
51
+ auth: {
52
+ username: process.env.ELASTICSEARCH_USERNAME,
53
+ password: process.env.ELASTICSEARCH_PASSWORD,
54
+ },
55
+ flushInterval: 10000,
56
+ };
57
+ if (elasticConfig) {
58
+ Object.assign(esConfig, elasticConfig);
59
+ }
60
+ const esTransport = (0, pino_elasticsearch_1.default)(esConfig);
61
+ transports.push(esTransport);
62
+ }
63
+ else {
64
+ transports.push(pino_1.pino.destination({
65
+ minLength: 1024,
66
+ sync: true,
67
+ }));
68
+ }
69
+ pinoLogger = (0, pino_1.pino)({
70
+ level: process.env.LOG_LEVEL,
71
+ timestamp: pino_1.stdTimeFunctions.isoTime.bind(pino_1.stdTimeFunctions),
72
+ }, ...transports);
29
73
  }
30
- this.logger.level = process.env.LOG_LEVEL;
74
+ }
75
+ return pinoLogger;
76
+ }
77
+ /**
78
+ * Checks if a given log level is valid.
79
+ * @param level - The log level to check.
80
+ * @returns Whether the log level is valid.
81
+ */
82
+ function isValidLogLevel(level) {
83
+ if (!['error', 'warn', 'info', 'debug', 'trace'].includes(level)) {
84
+ throw new Error(`Invalid log level "${level}": only error, warn, info, debug, trace are valid.`);
85
+ }
86
+ return true;
87
+ }
88
+ exports.isValidLogLevel = isValidLogLevel;
89
+ /**
90
+ * Logger Wrapper.
91
+ * Wraps a Pino logger instance and provides logging methods.
92
+ */
93
+ class Logger {
94
+ constructor(name, elasticConfig) {
95
+ this._name = name;
96
+ this._logger = getLogger(elasticConfig);
97
+ }
98
+ log(logLevel, logEvent, ...args) {
99
+ this._logger[logLevel]({
100
+ component: this._name,
101
+ ...logEvent,
102
+ detail: args,
103
+ });
31
104
  }
32
105
  /**
33
- * Logs an informational message.
34
- * @param className - The name of the class that generated the log.
35
- * @param functionName - The name of the function that generated the log.
36
- * @param logMessage - The log message text.
37
- * @param userIp - The user's IP address associated with the log.
38
- * @param data - Additional data associated with the log.
106
+ * Logs an error message.
107
+ * @param logEvent - The event to log.
108
+ * @param args - Additional arguments to include in the log.
39
109
  */
40
- info(className, functionName, logMessage, userIp) {
41
- const message = this.createLogMessage(className, functionName, logMessage, userIp);
42
- this.log('info', message);
110
+ error(logEvent, ...args) {
111
+ this.log('error', logEvent, ...args);
43
112
  }
44
113
  /**
45
114
  * Logs a warning message.
46
- * @param className - The name of the class that generated the log.
47
- * @param functionName - The name of the function that generated the log.
48
- * @param logMessage - The log message text.
49
- * @param userIp - The user's IP address associated with the log.
50
- * @param data - Additional data associated with the log.
115
+ * @param logEvent - The event to log.
116
+ * @param args - Additional arguments to include in the log.
51
117
  */
52
- warn(className, functionName, logMessage, userIp) {
53
- const message = this.createLogMessage(className, functionName, logMessage, userIp);
54
- this.log('warn', message);
118
+ warn(logEvent, ...args) {
119
+ this.log('warn', logEvent, ...args);
55
120
  }
56
121
  /**
57
- * Logs an error message.
58
- * @param className - The name of the class that generated the log.
59
- * @param functionName - The name of the function that generated the log.
60
- * @param logMessage - The log message text.
61
- * @param userIp - The user's IP address associated with the log.
62
- * @param data - Additional data associated with the log.
122
+ * Logs an informational message.
123
+ * @param logEvent - The event to log.
124
+ * @param args - Additional arguments to include in the log.
63
125
  */
64
- error(className, functionName, logMessage, userIp, data = {}) {
65
- const message = this.createLogMessage(className, functionName, logMessage, userIp, data);
66
- this.log('error', message);
126
+ info(logEvent, ...args) {
127
+ this.log('info', logEvent, ...args);
67
128
  }
68
- createLogMessage(className, functionName, logMessage, userIp, data = {}) {
69
- return {
70
- className,
71
- functionName,
72
- logMessage,
73
- userIp,
74
- data,
75
- };
129
+ /**
130
+ * Logs a debug message.
131
+ * @param logEvent - The event to log.
132
+ * @param args - Additional arguments to include in the log.
133
+ */
134
+ debug(logEvent, ...args) {
135
+ this.log('debug', logEvent, ...args);
76
136
  }
77
- log(level, logMessage) {
78
- this.logger.log({
79
- level,
80
- //@ts-ignore
81
- message: logMessage,
82
- });
83
- process.on('unhandledRejection', (reason, promise) => {
84
- this.logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
85
- });
86
- process.on('uncaughtException', (err) => {
87
- this.logger.error('Uncaught Exception:', err);
88
- });
137
+ /**
138
+ * Logs a trace message.
139
+ * @param logEvent - The event to log.
140
+ * @param args - Additional arguments to include in the log.
141
+ */
142
+ trace(logEvent, ...args) {
143
+ this.log('trace', logEvent, ...args);
89
144
  }
90
145
  }
91
- const Logger = new LoggerService(config_1.WinstonConfiguaration);
92
146
  exports.default = Logger;
@@ -0,0 +1 @@
1
+ export * from './logger';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./logger"), exports);
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Log levels
3
+ */
4
+ export type LOG_LEVEL = 'error' | 'warn' | 'info' | 'debug' | 'trace';
5
+ /**
6
+ * Represents a log event.
7
+ */
8
+ export interface LogEvent {
9
+ /**
10
+ * The code associated with the log event.
11
+ */
12
+ code: string;
13
+ /**
14
+ * The message describing the log event.
15
+ */
16
+ msg: string;
17
+ }
18
+ /**
19
+ * Configuration options for Elasticsearch.
20
+ */
21
+ export interface ElasticConfig {
22
+ /**
23
+ * The Elasticsearch index to write logs to.
24
+ */
25
+ index?: string;
26
+ /**
27
+ * The URL of the Elasticsearch node.
28
+ */
29
+ node?: string;
30
+ /**
31
+ * Authentication details for accessing Elasticsearch.
32
+ */
33
+ auth?: {
34
+ /**
35
+ * The username for authentication.
36
+ */
37
+ username: string;
38
+ /**
39
+ * The password for authentication.
40
+ */
41
+ password: string;
42
+ };
43
+ /**
44
+ * The interval (in milliseconds) at which logs are flushed to Elasticsearch.
45
+ */
46
+ flushInterval?: number;
47
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhmdhammoud/meritt-utils",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
@@ -13,7 +13,7 @@
13
13
  "access": "public"
14
14
  },
15
15
  "scripts": {
16
- "dev-ts": "ts-node-dev --respawn --transpile-only src/index.ts",
16
+ "dev-ts": "ts-node-dev --respawn --transpile-only src/index.ts | pino-pretty",
17
17
  "dev": "nodemon ./dist/index.js",
18
18
  "watch": "tsc --watch",
19
19
  "build": "tsc",
@@ -32,6 +32,7 @@
32
32
  "eslint-plugin-tsdoc": "^0.2.14",
33
33
  "husky": "^8.0.0",
34
34
  "jest": "^29.7.0",
35
+ "pino-pretty": "^10.3.1",
35
36
  "ts-jest": "^29.1.1",
36
37
  "ts-node-dev": "^1.1.8",
37
38
  "typescript": "^4.6.3"
@@ -42,7 +43,7 @@
42
43
  "axios": "^1.4.0",
43
44
  "dotenv": "^16.4.1",
44
45
  "imagesloaded": "^5.0.0",
45
- "winston": "^3.11.0",
46
- "winston-elasticsearch": "^0.17.4"
46
+ "pino": "^8.19.0",
47
+ "pino-elasticsearch": "^8.0.0"
47
48
  }
48
49
  }
@@ -0,0 +1,88 @@
1
+ import Logger, {isValidLogLevel} from '../lib/logger'
2
+ import {pino} from 'pino'
3
+
4
+ jest.mock('pino')
5
+
6
+ const LOGGER_NAME = 'logger-name'
7
+ const PINO_DESTINATION = {}
8
+ const PINO = {
9
+ info: jest.fn(),
10
+ }
11
+
12
+ describe('create logger wrapper', () => {
13
+ test('initially create & configure logger', () => {
14
+ //@ts-ignore
15
+ const mock_pino_destination = jest
16
+ .spyOn(pino, 'destination')
17
+ //@ts-ignore
18
+ .mockReturnValue(PINO_DESTINATION)
19
+ //@ts-ignore
20
+ const mock_pino = pino.mockReturnValue(PINO)
21
+
22
+ /** create logger */
23
+ const logger = new Logger(LOGGER_NAME)
24
+
25
+ expect(logger).toBeDefined()
26
+ expect(logger['_name']).toBe(LOGGER_NAME)
27
+ expect(mock_pino).toHaveBeenCalledTimes(1)
28
+ expect(mock_pino).toHaveBeenCalledWith(
29
+ {
30
+ level: 'info',
31
+ timestamp: expect.any(Function),
32
+ },
33
+ PINO_DESTINATION
34
+ )
35
+ expect(mock_pino_destination).toHaveBeenCalledWith({
36
+ minLength: 1024,
37
+ sync: true,
38
+ })
39
+ })
40
+ test('create repeatedly - stick to pino singleton', () => {
41
+ //@ts-ignore
42
+ jest.spyOn(pino, 'destination').mockReturnValue(PINO_DESTINATION)
43
+ //@ts-ignore
44
+ const mock_pino = pino.mockReturnValue(PINO)
45
+ new Logger(LOGGER_NAME)
46
+ expect(mock_pino).toHaveBeenCalledTimes(1)
47
+
48
+ /** create additional logger */
49
+ new Logger(LOGGER_NAME)
50
+ /** no new pino created */
51
+ expect(mock_pino).toHaveBeenCalledTimes(1)
52
+ })
53
+ })
54
+ describe('validate log level', () => {
55
+ test('throw on invalid log level', () => {
56
+ const INVALID_LOG_LEVEL = 'invalid-log-level'
57
+ expect(() => {
58
+ isValidLogLevel(INVALID_LOG_LEVEL)
59
+ }).toThrowError(
60
+ `Invalid log level "${INVALID_LOG_LEVEL}": only error, warn, info, debug, trace are valid.`
61
+ )
62
+ })
63
+ })
64
+ describe('route and format logs', () => {
65
+ const LOG_EVENT = {
66
+ code: 'code',
67
+ msg: 'message',
68
+ }
69
+ const DETAILS = {
70
+ key0: 'val0',
71
+ key1: 'val1',
72
+ }
73
+ test('log info with details', () => {
74
+ //@ts-ignore
75
+ jest.spyOn(pino, 'destination').mockReturnValue(PINO_DESTINATION)
76
+ //@ts-ignore
77
+ pino.mockReturnValue(PINO)
78
+ const logger = new Logger(LOGGER_NAME)
79
+
80
+ logger.info(LOG_EVENT, DETAILS)
81
+
82
+ expect(PINO.info).toHaveBeenCalledWith({
83
+ component: LOGGER_NAME,
84
+ ...LOG_EVENT,
85
+ detail: [DETAILS],
86
+ })
87
+ })
88
+ })
package/src/lib/logger.ts CHANGED
@@ -1,172 +1,151 @@
1
- import Winston, {Logger as WinstonLogger, LoggerOptions} from 'winston'
2
- import {WinstonConfiguaration} from '../config'
1
+ import {Logger as PinoLogger, pino, stdTimeFunctions} from 'pino'
2
+ import * as dotenv from 'dotenv'
3
+ import pinoElastic from 'pino-elasticsearch'
4
+ import {ElasticConfig, LOG_LEVEL, LogEvent} from '../types'
5
+
6
+ dotenv.config()
3
7
 
4
8
  /**
5
- * Represents a log message with structured information.
9
+ * Pino logger backend - singleton
6
10
  */
7
- interface LogMessage {
8
- /**
9
- * The name of the class that generated the log.
10
- */
11
- className: string
11
+ let pinoLogger: PinoLogger
12
12
 
13
- /**
14
- * The name of the function that generated the log.
15
- */
16
- functionName: string
13
+ /**
14
+ * Creates a Pino logger instance with specified Elasticsearch configuration.
15
+ * @param elasticConfig - Optional Elasticsearch configuration.
16
+ * @returns The Pino logger instance.
17
+ */
18
+ function getLogger(elasticConfig?: ElasticConfig): PinoLogger {
19
+ if (!pinoLogger) {
20
+ if (isValidLogLevel(process.env.LOG_LEVEL)) {
21
+ const transports = []
22
+ if (process.env.NODE_ENV !== 'local' && process.env.NODE_ENV !== 'test') {
23
+ const esConfig: ElasticConfig = {
24
+ index: process.env.SERVER_NICKNAME,
25
+ node: process.env.ELASTICSEARCH_NODE,
26
+ auth: {
27
+ username: process.env.ELASTICSEARCH_USERNAME,
28
+ password: process.env.ELASTICSEARCH_PASSWORD,
29
+ },
30
+ flushInterval: 10000,
31
+ }
32
+ if (elasticConfig) {
33
+ Object.assign(esConfig, elasticConfig)
34
+ }
35
+ const esTransport = pinoElastic(esConfig)
36
+ transports.push(esTransport)
37
+ } else {
38
+ transports.push(
39
+ pino.destination({
40
+ minLength: 1024,
41
+ sync: true,
42
+ })
43
+ )
44
+ }
45
+ pinoLogger = pino(
46
+ {
47
+ level: process.env.LOG_LEVEL,
48
+ timestamp: stdTimeFunctions.isoTime.bind(stdTimeFunctions),
49
+ },
50
+ ...transports
51
+ )
52
+ }
53
+ }
54
+ return pinoLogger
55
+ }
17
56
 
18
- /**
19
- * The log message text.
20
- */
21
- logMessage: string
57
+ /**
58
+ * Checks if a given log level is valid.
59
+ * @param level - The log level to check.
60
+ * @returns Whether the log level is valid.
61
+ */
62
+ export function isValidLogLevel(level: string): level is LOG_LEVEL {
63
+ if (!['error', 'warn', 'info', 'debug', 'trace'].includes(level)) {
64
+ throw new Error(
65
+ `Invalid log level "${level}": only error, warn, info, debug, trace are valid.`
66
+ )
67
+ }
68
+ return true
69
+ }
70
+
71
+ /**
72
+ * Logger Wrapper.
73
+ * Wraps a Pino logger instance and provides logging methods.
74
+ */
75
+ class Logger {
76
+ private readonly _name: string
77
+ private readonly _logger: PinoLogger
22
78
 
23
79
  /**
24
- * The user's IP address associated with the log.
80
+ * Creates a new Logger instance with default configuration.
81
+ * @param name - The name of the logger.
25
82
  */
26
- userIp: string
83
+ constructor(name: string)
27
84
 
28
85
  /**
29
- * Additional data associated with the log.
86
+ * Creates a new Logger instance with custom Elasticsearch configuration.
87
+ * @param name - The name of the logger.
88
+ * @param elasticConfig - Optional Elasticsearch configuration.
30
89
  */
31
- data?: Record<string, any>
32
- }
90
+ constructor(name: string, elasticConfig?: ElasticConfig)
33
91
 
34
- /**
35
- * Service for logging information, warnings, and errors using Winston.
36
- */
37
- class LoggerService {
38
- private logger: WinstonLogger
92
+ constructor(name: string, elasticConfig?: ElasticConfig) {
93
+ this._name = name
94
+ this._logger = getLogger(elasticConfig)
95
+ }
39
96
 
40
- /**
41
- * Creates an instance of LoggerService.
42
- * @param config - The configuration options for Winston logger.
43
- */
44
- constructor(config: LoggerOptions) {
45
- this.logger = Winston.createLogger({
46
- ...config,
47
- level: process.env.LOG_LEVEL,
97
+ private log(logLevel: LOG_LEVEL, logEvent: LogEvent, ...args: unknown[]) {
98
+ this._logger[logLevel]({
99
+ component: this._name,
100
+ ...logEvent,
101
+ detail: args,
48
102
  })
49
- if (process.env.NODE_ENV === 'local') {
50
- this.logger.add(
51
- new Winston.transports.Console({
52
- level: 'info',
53
- format: Winston.format.combine(
54
- Winston.format.simple(),
55
- Winston.format.colorize(),
56
- Winston.format.printf(({level, message}) => {
57
- return `[${level}]: ${JSON.stringify(message)}`
58
- // Parse the message as JSON and include in the log
59
- })
60
- ),
61
- })
62
- )
63
- }
64
-
65
- this.logger.level = process.env.LOG_LEVEL
66
103
  }
67
104
 
68
105
  /**
69
- * Logs an informational message.
70
- * @param className - The name of the class that generated the log.
71
- * @param functionName - The name of the function that generated the log.
72
- * @param logMessage - The log message text.
73
- * @param userIp - The user's IP address associated with the log.
74
- * @param data - Additional data associated with the log.
106
+ * Logs an error message.
107
+ * @param logEvent - The event to log.
108
+ * @param args - Additional arguments to include in the log.
75
109
  */
76
- info(
77
- className: string,
78
- functionName: string,
79
- logMessage: string,
80
- userIp: string
81
- ): void {
82
- const message = this.createLogMessage(
83
- className,
84
- functionName,
85
- logMessage,
86
- userIp
87
- )
88
- this.log('info', message)
110
+ error(logEvent: LogEvent, ...args: unknown[]) {
111
+ this.log('error', logEvent, ...args)
89
112
  }
90
113
 
91
114
  /**
92
115
  * Logs a warning message.
93
- * @param className - The name of the class that generated the log.
94
- * @param functionName - The name of the function that generated the log.
95
- * @param logMessage - The log message text.
96
- * @param userIp - The user's IP address associated with the log.
97
- * @param data - Additional data associated with the log.
116
+ * @param logEvent - The event to log.
117
+ * @param args - Additional arguments to include in the log.
98
118
  */
99
- warn(
100
- className: string,
101
- functionName: string,
102
- logMessage: string,
103
- userIp: string
104
- ): void {
105
- const message = this.createLogMessage(
106
- className,
107
- functionName,
108
- logMessage,
109
- userIp
110
- )
111
- this.log('warn', message)
119
+ warn(logEvent: LogEvent, ...args: unknown[]) {
120
+ this.log('warn', logEvent, ...args)
112
121
  }
113
122
 
114
123
  /**
115
- * Logs an error message.
116
- * @param className - The name of the class that generated the log.
117
- * @param functionName - The name of the function that generated the log.
118
- * @param logMessage - The log message text.
119
- * @param userIp - The user's IP address associated with the log.
120
- * @param data - Additional data associated with the log.
124
+ * Logs an informational message.
125
+ * @param logEvent - The event to log.
126
+ * @param args - Additional arguments to include in the log.
121
127
  */
122
- error(
123
- className: string,
124
- functionName: string,
125
- logMessage: string,
126
- userIp: string,
127
- data: Object = {}
128
- ): void {
129
- const message = this.createLogMessage(
130
- className,
131
- functionName,
132
- logMessage,
133
- userIp,
134
- data
135
- )
136
- this.log('error', message)
128
+ info(logEvent: LogEvent, ...args: unknown[]) {
129
+ this.log('info', logEvent, ...args)
137
130
  }
138
131
 
139
- private createLogMessage(
140
- className: string,
141
- functionName: string,
142
- logMessage: string,
143
- userIp: string,
144
- data: Record<string, any> = {}
145
- ): LogMessage {
146
- return {
147
- className,
148
- functionName,
149
- logMessage,
150
- userIp,
151
- data,
152
- }
132
+ /**
133
+ * Logs a debug message.
134
+ * @param logEvent - The event to log.
135
+ * @param args - Additional arguments to include in the log.
136
+ */
137
+ debug(logEvent: LogEvent, ...args: unknown[]) {
138
+ this.log('debug', logEvent, ...args)
153
139
  }
154
140
 
155
- private log(level: string, logMessage: LogMessage): void {
156
- this.logger.log({
157
- level,
158
- //@ts-ignore
159
- message: logMessage,
160
- })
161
- process.on('unhandledRejection', (reason, promise) => {
162
- this.logger.error('Unhandled Rejection at:', promise, 'reason:', reason)
163
- })
164
-
165
- process.on('uncaughtException', (err) => {
166
- this.logger.error('Uncaught Exception:', err)
167
- })
141
+ /**
142
+ * Logs a trace message.
143
+ * @param logEvent - The event to log.
144
+ * @param args - Additional arguments to include in the log.
145
+ */
146
+ trace(logEvent: LogEvent, ...args: unknown[]) {
147
+ this.log('trace', logEvent, ...args)
168
148
  }
169
149
  }
170
150
 
171
- const Logger = new LoggerService(WinstonConfiguaration)
172
151
  export default Logger
@@ -0,0 +1 @@
1
+ export * from './logger'
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Log levels
3
+ */
4
+ export type LOG_LEVEL = 'error' | 'warn' | 'info' | 'debug' | 'trace'
5
+
6
+ /**
7
+ * Represents a log event.
8
+ */
9
+ export interface LogEvent {
10
+ /**
11
+ * The code associated with the log event.
12
+ */
13
+ code: string
14
+ /**
15
+ * The message describing the log event.
16
+ */
17
+ msg: string
18
+ }
19
+
20
+ /**
21
+ * Configuration options for Elasticsearch.
22
+ */
23
+ export interface ElasticConfig {
24
+ /**
25
+ * The Elasticsearch index to write logs to.
26
+ */
27
+ index?: string
28
+ /**
29
+ * The URL of the Elasticsearch node.
30
+ */
31
+ node?: string
32
+ /**
33
+ * Authentication details for accessing Elasticsearch.
34
+ */
35
+ auth?: {
36
+ /**
37
+ * The username for authentication.
38
+ */
39
+ username: string
40
+ /**
41
+ * The password for authentication.
42
+ */
43
+ password: string
44
+ }
45
+ /**
46
+ * The interval (in milliseconds) at which logs are flushed to Elasticsearch.
47
+ */
48
+ flushInterval?: number
49
+ }
@@ -1 +0,0 @@
1
- export {default as WinstonConfiguaration} from './logger-config'
@@ -1,73 +0,0 @@
1
- import {ElasticsearchTransport} from 'winston-elasticsearch'
2
- import * as dotenv from 'dotenv'
3
- dotenv.config()
4
- const {
5
- ELASTICSEARCH_USERNAME,
6
- ELASTICSEARCH_PASSWORD,
7
- ELASTICSEARCH_NODE,
8
- SERVER_NICKNAME,
9
- LOG_LEVEL,
10
- } = process.env
11
-
12
- const transports =
13
- process.env.NODE_ENV !== 'local'
14
- ? [
15
- // Elasticsearch Transport for 'info' logs
16
- new ElasticsearchTransport({
17
- level: 'info',
18
- indexPrefix: process.env.SERVER_NICKNAME, // Index prefix for Elasticsearch
19
- clientOpts: {
20
- node: ELASTICSEARCH_NODE,
21
- tls: {
22
- rejectUnauthorized: false, // Only for development, not recommended in production
23
- },
24
- auth: {
25
- username: ELASTICSEARCH_USERNAME,
26
- password: ELASTICSEARCH_PASSWORD,
27
- },
28
- },
29
- // format: winston.format.combine(winston.format.json()), // Adjust as needed
30
- }),
31
-
32
- // Elasticsearch Transport for 'warn' logs
33
- new ElasticsearchTransport({
34
- level: LOG_LEVEL,
35
- indexPrefix: SERVER_NICKNAME, // Index prefix for Elasticsearch
36
- clientOpts: {
37
- node: ELASTICSEARCH_NODE,
38
- tls: {
39
- rejectUnauthorized: false, // Only for development, not recommended in production
40
- },
41
- auth: {
42
- username: ELASTICSEARCH_USERNAME,
43
- password: ELASTICSEARCH_PASSWORD,
44
- },
45
- },
46
- // format: winston.format.combine(winston.format.json()), // Adjust as needed
47
- }),
48
-
49
- // Elasticsearch Transport for 'error' logs
50
- new ElasticsearchTransport({
51
- level: 'error',
52
- indexPrefix: process.env.SERVER_NICKNAME, // Index prefix for Elasticsearch
53
- clientOpts: {
54
- node: ELASTICSEARCH_NODE,
55
- tls: {
56
- rejectUnauthorized: false, // Only for development, not recommended in production
57
- },
58
- auth: {
59
- username: ELASTICSEARCH_USERNAME,
60
- password: ELASTICSEARCH_PASSWORD,
61
- },
62
- },
63
- // format: winston.format.combine(winston.format.json()), // Adjust as needed
64
- }),
65
- // eslint-disable-next-line no-mixed-spaces-and-tabs
66
- ]
67
- : []
68
-
69
- const loggerConfiguration = {
70
- transports,
71
- }
72
-
73
- export default loggerConfiguration