@mhmdhammoud/meritt-utils 1.2.0 → 1.4.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/ReleaseNotes.md CHANGED
@@ -1,8 +1,22 @@
1
1
  # Changes
2
2
 
3
- ## Version 1.0.5
3
+ ## Version 1.4.0
4
+
5
+ - Changed winston logger to Pino logger to improve performance and reduce memory usage
4
6
 
5
- ### Added
7
+ ## Version 1.3.0
8
+
9
+ - Added toUpperTitle method that accepts a string and removes all non alphanumeric characters and capitalizes each letter of every first word
10
+
11
+ ```typescript
12
+ import {Logger} from '@mhmdhammoud/meritt-utils'
13
+
14
+ // Usage Example
15
+ Logger.info('className', ' functionName', 'logMessage', 'userIp')
16
+ // Available levels info warn error
17
+ ```
18
+
19
+ ## Version 1.0.5
6
20
 
7
21
  - Added toUpperTitle method that accepts a string and removes all non alphanumeric characters and capitalizes each letter of every first word
8
22
 
@@ -19,8 +33,6 @@ console : Hello World 99
19
33
 
20
34
  ## Version 1.0.4
21
35
 
22
- ### Added
23
-
24
36
  - Added generateKeys method that returns public and private keys
25
37
 
26
38
  ```typescript
@@ -38,14 +50,10 @@ console.log(response)
38
50
 
39
51
  ```
40
52
 
41
- ### Fixes and Improvements
42
-
43
53
  - Checking for prime numbers discarding even and odd numbers and skips 6 iterations at a time until radical I
44
54
 
45
55
  ## Version 1.0.3
46
56
 
47
- ### Added
48
-
49
57
  - Added Formatter class for manipulating strings
50
58
 
51
59
  ```typescript
@@ -55,8 +63,6 @@ import {Formatter} from '@mhmdhammoud/meritt-utils'
55
63
  const slug = Formatter.slugify('My Product Name') // my-product-name
56
64
  ```
57
65
 
58
- ### Fixes and Improvements
59
-
60
66
  - Fixed bug in Crypto class where encrypting a string with a key that is not a number would throw an error
61
67
  - Improved Crypto class to allow encrypting and decrypting numbers and objects
62
68
  - Documented Crypto class
@@ -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
+ });
@@ -0,0 +1 @@
1
+ export { default as WinstonConfiguaration } from './logger-config';
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.WinstonConfiguaration = void 0;
7
+ var logger_config_1 = require("./logger-config");
8
+ Object.defineProperty(exports, "WinstonConfiguaration", { enumerable: true, get: function () { return __importDefault(logger_config_1).default; } });
@@ -0,0 +1,5 @@
1
+ import { ElasticsearchTransport } from 'winston-elasticsearch';
2
+ declare const loggerConfiguration: {
3
+ transports: ElasticsearchTransport[];
4
+ };
5
+ export default loggerConfiguration;
@@ -0,0 +1,86 @@
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 winston_elasticsearch_1 = require("winston-elasticsearch");
27
+ const dotenv = __importStar(require("dotenv"));
28
+ dotenv.config();
29
+ const { ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD, ELASTICSEARCH_NODE, SERVER_NICKNAME, LOG_LEVEL, } = process.env;
30
+ const transports = process.env.NODE_ENV !== 'local'
31
+ ? [
32
+ // Elasticsearch Transport for 'info' logs
33
+ new winston_elasticsearch_1.ElasticsearchTransport({
34
+ level: 'info',
35
+ indexPrefix: process.env.SERVER_NICKNAME,
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
+ // Elasticsearch Transport for 'warn' logs
49
+ new winston_elasticsearch_1.ElasticsearchTransport({
50
+ level: LOG_LEVEL,
51
+ indexPrefix: SERVER_NICKNAME,
52
+ clientOpts: {
53
+ node: ELASTICSEARCH_NODE,
54
+ tls: {
55
+ rejectUnauthorized: false, // Only for development, not recommended in production
56
+ },
57
+ auth: {
58
+ username: ELASTICSEARCH_USERNAME,
59
+ password: ELASTICSEARCH_PASSWORD,
60
+ },
61
+ },
62
+ // format: winston.format.combine(winston.format.json()), // Adjust as needed
63
+ }),
64
+ // Elasticsearch Transport for 'error' logs
65
+ new winston_elasticsearch_1.ElasticsearchTransport({
66
+ level: 'error',
67
+ indexPrefix: process.env.SERVER_NICKNAME,
68
+ clientOpts: {
69
+ node: ELASTICSEARCH_NODE,
70
+ tls: {
71
+ rejectUnauthorized: false, // Only for development, not recommended in production
72
+ },
73
+ auth: {
74
+ username: ELASTICSEARCH_USERNAME,
75
+ password: ELASTICSEARCH_PASSWORD,
76
+ },
77
+ },
78
+ // format: winston.format.combine(winston.format.json()), // Adjust as needed
79
+ }),
80
+ // eslint-disable-next-line no-mixed-spaces-and-tabs
81
+ ]
82
+ : [];
83
+ const loggerConfiguration = {
84
+ transports,
85
+ };
86
+ exports.default = loggerConfiguration;
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { Crypto, Formatter, Colorful } from './lib';
1
+ export * from './lib';
package/dist/index.js CHANGED
@@ -1,7 +1,17 @@
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 __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
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Colorful = exports.Formatter = exports.Crypto = void 0;
4
- var lib_1 = require("./lib");
5
- Object.defineProperty(exports, "Crypto", { enumerable: true, get: function () { return lib_1.Crypto; } });
6
- Object.defineProperty(exports, "Formatter", { enumerable: true, get: function () { return lib_1.Formatter; } });
7
- Object.defineProperty(exports, "Colorful", { enumerable: true, get: function () { return lib_1.Colorful; } });
17
+ __exportStar(require("./lib"), exports);
@@ -3,3 +3,4 @@ export { default as Formatter } from './formatter';
3
3
  export { default as Pdf } from './formatter';
4
4
  export { default as Colorful } from './colorful';
5
5
  export { default as ImageFull } from './imagefull';
6
+ export { default as Logger } from './logger';
package/dist/lib/index.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ImageFull = exports.Colorful = exports.Pdf = exports.Formatter = exports.Crypto = void 0;
6
+ exports.Logger = exports.ImageFull = exports.Colorful = exports.Pdf = exports.Formatter = exports.Crypto = void 0;
7
7
  var cypto_1 = require("./cypto");
8
8
  Object.defineProperty(exports, "Crypto", { enumerable: true, get: function () { return __importDefault(cypto_1).default; } });
9
9
  var formatter_1 = require("./formatter");
@@ -14,3 +14,5 @@ var colorful_1 = require("./colorful");
14
14
  Object.defineProperty(exports, "Colorful", { enumerable: true, get: function () { return __importDefault(colorful_1).default; } });
15
15
  var imagefull_1 = require("./imagefull");
16
16
  Object.defineProperty(exports, "ImageFull", { enumerable: true, get: function () { return __importDefault(imagefull_1).default; } });
17
+ var logger_1 = require("./logger");
18
+ Object.defineProperty(exports, "Logger", { enumerable: true, get: function () { return __importDefault(logger_1).default; } });
@@ -0,0 +1,58 @@
1
+ import { ElasticConfig, LOG_LEVEL, LogEvent } from '../types';
2
+ /**
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.
6
+ */
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;
15
+ /**
16
+ * Creates a new Logger instance with default configuration.
17
+ * @param name - The name of the logger.
18
+ */
19
+ constructor(name: string);
20
+ /**
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.
31
+ */
32
+ error(logEvent: LogEvent, ...args: unknown[]): void;
33
+ /**
34
+ * Logs a warning message.
35
+ * @param logEvent - The event to log.
36
+ * @param args - Additional arguments to include in the log.
37
+ */
38
+ warn(logEvent: LogEvent, ...args: unknown[]): void;
39
+ /**
40
+ * Logs an informational message.
41
+ * @param logEvent - The event to log.
42
+ * @param args - Additional arguments to include in the log.
43
+ */
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;
57
+ }
58
+ export default Logger;
@@ -0,0 +1,146 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
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();
34
+ /**
35
+ * Pino logger backend - singleton
36
+ */
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);
73
+ }
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
+ });
104
+ }
105
+ /**
106
+ * Logs an error message.
107
+ * @param logEvent - The event to log.
108
+ * @param args - Additional arguments to include in the log.
109
+ */
110
+ error(logEvent, ...args) {
111
+ this.log('error', logEvent, ...args);
112
+ }
113
+ /**
114
+ * Logs a warning message.
115
+ * @param logEvent - The event to log.
116
+ * @param args - Additional arguments to include in the log.
117
+ */
118
+ warn(logEvent, ...args) {
119
+ this.log('warn', logEvent, ...args);
120
+ }
121
+ /**
122
+ * Logs an informational message.
123
+ * @param logEvent - The event to log.
124
+ * @param args - Additional arguments to include in the log.
125
+ */
126
+ info(logEvent, ...args) {
127
+ this.log('info', logEvent, ...args);
128
+ }
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);
136
+ }
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);
144
+ }
145
+ }
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/example.env ADDED
@@ -0,0 +1,6 @@
1
+ ELASTICSEARCH_USERNAME=
2
+ ELASTICSEARCH_PASSWORD=
3
+ ELASTICSEARCH_NODE=
4
+ SERVER_NICKNAME=
5
+ NODE_ENV=
6
+ LOG_LEVEL=info
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhmdhammoud/meritt-utils",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
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"
@@ -40,6 +41,11 @@
40
41
  "license": "ISC",
41
42
  "dependencies": {
42
43
  "axios": "^1.4.0",
43
- "imagesloaded": "^5.0.0"
44
+ "dotenv": "^16.4.1",
45
+ "imagesloaded": "^5.0.0",
46
+ "pino": "^8.19.0",
47
+ "pino-elasticsearch": "^8.0.0",
48
+ "winston": "^3.11.0",
49
+ "winston-elasticsearch": "^0.17.4"
44
50
  }
45
51
  }
@@ -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/env.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ declare namespace NodeJS {
2
+ export interface ProcessEnv {
3
+ NODE_ENV: 'development' | 'production' | 'test' | 'local'
4
+ ELASTICSEARCH_NODE: string
5
+ ELASTICSEARCH_USERNAME: string
6
+ ELASTICSEARCH_PASSWORD: string
7
+ LOG_LEVEL: string
8
+ }
9
+ }
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export {Crypto, Formatter, Colorful} from './lib'
1
+ export * from './lib'
package/src/lib/index.ts CHANGED
@@ -3,3 +3,4 @@ export {default as Formatter} from './formatter'
3
3
  export {default as Pdf} from './formatter'
4
4
  export {default as Colorful} from './colorful'
5
5
  export {default as ImageFull} from './imagefull'
6
+ export {default as Logger} from './logger'
@@ -0,0 +1,151 @@
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()
7
+
8
+ /**
9
+ * Pino logger backend - singleton
10
+ */
11
+ let pinoLogger: PinoLogger
12
+
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
+ }
56
+
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
78
+
79
+ /**
80
+ * Creates a new Logger instance with default configuration.
81
+ * @param name - The name of the logger.
82
+ */
83
+ constructor(name: string)
84
+
85
+ /**
86
+ * Creates a new Logger instance with custom Elasticsearch configuration.
87
+ * @param name - The name of the logger.
88
+ * @param elasticConfig - Optional Elasticsearch configuration.
89
+ */
90
+ constructor(name: string, elasticConfig?: ElasticConfig)
91
+
92
+ constructor(name: string, elasticConfig?: ElasticConfig) {
93
+ this._name = name
94
+ this._logger = getLogger(elasticConfig)
95
+ }
96
+
97
+ private log(logLevel: LOG_LEVEL, logEvent: LogEvent, ...args: unknown[]) {
98
+ this._logger[logLevel]({
99
+ component: this._name,
100
+ ...logEvent,
101
+ detail: args,
102
+ })
103
+ }
104
+
105
+ /**
106
+ * Logs an error message.
107
+ * @param logEvent - The event to log.
108
+ * @param args - Additional arguments to include in the log.
109
+ */
110
+ error(logEvent: LogEvent, ...args: unknown[]) {
111
+ this.log('error', logEvent, ...args)
112
+ }
113
+
114
+ /**
115
+ * Logs a warning message.
116
+ * @param logEvent - The event to log.
117
+ * @param args - Additional arguments to include in the log.
118
+ */
119
+ warn(logEvent: LogEvent, ...args: unknown[]) {
120
+ this.log('warn', logEvent, ...args)
121
+ }
122
+
123
+ /**
124
+ * Logs an informational message.
125
+ * @param logEvent - The event to log.
126
+ * @param args - Additional arguments to include in the log.
127
+ */
128
+ info(logEvent: LogEvent, ...args: unknown[]) {
129
+ this.log('info', logEvent, ...args)
130
+ }
131
+
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)
139
+ }
140
+
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)
148
+ }
149
+ }
150
+
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
+ }