@xrystal/core 3.5.5 → 3.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Yusuf Yasir KAYGUSUZ",
3
3
  "name": "@xrystal/core",
4
- "version": "3.5.5",
4
+ "version": "3.5.7",
5
5
  "description": "Project core for xrystal",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -12,6 +12,7 @@ export default class LoggerService {
12
12
  private kafkaProducer;
13
13
  private kafkaTopic;
14
14
  private isKafkaReady;
15
+ private safeReplacer;
15
16
  private getTracingFormat;
16
17
  private getConsoleFormat;
17
18
  winston: CustomLogger;
@@ -1,9 +1,26 @@
1
1
  import winston, { format } from "winston";
2
+ import Transport from "winston-transport";
2
3
  import "winston-daily-rotate-file";
3
4
  import path from "node:path";
4
5
  import { AsyncLocalStorage } from "node:async_hooks";
5
6
  import { Kafka, Partitioners, logLevel } from "kafkajs";
6
7
  import { LoggerLayerEnum } from "../../utils";
8
+ class KafkaTransport extends Transport {
9
+ service;
10
+ constructor(opts, service) {
11
+ super(opts);
12
+ this.service = service;
13
+ }
14
+ log(info, callback) {
15
+ setImmediate(() => {
16
+ this.emit("logged", info);
17
+ });
18
+ if (this.service["isKafkaReady"]) {
19
+ this.service["logToKafka"](info);
20
+ }
21
+ callback();
22
+ }
23
+ }
7
24
  const customLevels = {
8
25
  critical: LoggerLayerEnum.CRITICAL,
9
26
  error: LoggerLayerEnum.ERROR,
@@ -26,6 +43,13 @@ export default class LoggerService {
26
43
  kafkaProducer = null;
27
44
  kafkaTopic = "";
28
45
  isKafkaReady = false;
46
+ safeReplacer = (key, value) => {
47
+ if (value instanceof Headers)
48
+ return Object.fromEntries(value.entries());
49
+ if (value instanceof Error)
50
+ return { message: value.message, stack: value.stack };
51
+ return value;
52
+ };
29
53
  getTracingFormat = format((info) => {
30
54
  const store = LoggerService.storage.getStore();
31
55
  if (store) {
@@ -35,7 +59,9 @@ export default class LoggerService {
35
59
  });
36
60
  getConsoleFormat() {
37
61
  return format.combine(this.getTracingFormat(), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.colorize({ all: true }), format.printf((info) => {
38
- const msg = typeof info.message === "object" ? JSON.stringify(info.message) : info.message;
62
+ const msg = typeof info.message === "object"
63
+ ? JSON.stringify(info.message, this.safeReplacer)
64
+ : info.message;
39
65
  const idPart = info.id ? ` : id: ${info.id}` : "";
40
66
  return `${info.timestamp} [${this.serviceName}] ${info.level}: ${msg}${idPart}`;
41
67
  }));
@@ -55,16 +81,12 @@ export default class LoggerService {
55
81
  runWithId = (id, callback) => {
56
82
  const store = new Map();
57
83
  store.set("correlationId", id);
58
- LoggerService.storage.run(store, callback);
84
+ return LoggerService.storage.run(store, callback);
59
85
  };
60
86
  load = async (config) => {
61
87
  this.serviceName = config?.serviceName || "service";
62
88
  this.environment = config?.env || "dev";
63
89
  this.kafkaTopic = config?.kafkaTopic || "logs";
64
- this.winstonLoader({
65
- loadPath: config?.loadPath || "./logs",
66
- loggerLevel: config?.loggerLevel || "debug"
67
- });
68
90
  const rawBrokers = config?.kafkaBrokers;
69
91
  const brokers = rawBrokers ? String(rawBrokers).split(",").map((b) => b.trim()) : [];
70
92
  if (brokers.length > 0) {
@@ -72,57 +94,56 @@ export default class LoggerService {
72
94
  clientId: this.serviceName,
73
95
  brokers: brokers,
74
96
  logLevel: logLevel.ERROR,
75
- retry: {
76
- initialRetryTime: 1000,
77
- retries: 10,
78
- maxRetryTime: 30000,
79
- factor: 2
80
- },
81
97
  });
82
- this.kafkaProducer = kafka.producer({ createPartitioner: Partitioners.LegacyPartitioner });
83
- const connectWithRetry = async () => {
98
+ this.kafkaProducer = kafka.producer({
99
+ createPartitioner: Partitioners.DefaultPartitioner
100
+ });
101
+ const connect = async () => {
84
102
  try {
85
103
  await this.kafkaProducer?.connect();
86
104
  this.isKafkaReady = true;
87
105
  }
88
106
  catch (err) {
89
107
  this.isKafkaReady = false;
90
- setTimeout(connectWithRetry, 5000);
108
+ setTimeout(connect, 5000);
91
109
  }
92
110
  };
93
- connectWithRetry();
111
+ await connect();
94
112
  }
113
+ this.winstonLoader({
114
+ loadPath: config?.loadPath || "./logs",
115
+ loggerLevel: config?.loggerLevel || "debug"
116
+ });
95
117
  };
96
118
  winstonLoader = ({ loadPath, loggerLevel }) => {
97
119
  const { combine, timestamp, json, errors } = format;
98
- const jsonFileFormat = combine(this.getTracingFormat(), timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), errors({ stack: true }), json());
120
+ const jsonFileFormat = combine(this.getTracingFormat(), timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), errors({ stack: true }), json({ replacer: this.safeReplacer }));
121
+ const transports = [
122
+ new winston.transports.Console({
123
+ format: this.getConsoleFormat()
124
+ }),
125
+ new winston.transports.DailyRotateFile({
126
+ filename: path.resolve(loadPath, "error", "%DATE%_error.log"),
127
+ level: "error",
128
+ format: jsonFileFormat,
129
+ maxSize: "2mb",
130
+ maxFiles: "7d"
131
+ }),
132
+ new winston.transports.DailyRotateFile({
133
+ filename: path.resolve(loadPath, "critical", "%DATE%_critical.log"),
134
+ level: "critical",
135
+ format: jsonFileFormat,
136
+ maxSize: "2mb",
137
+ maxFiles: "14d"
138
+ })
139
+ ];
140
+ if (this.kafkaProducer) {
141
+ transports.push(new KafkaTransport({ level: loggerLevel }, this));
142
+ }
99
143
  const winstonLogger = winston.createLogger({
100
144
  level: loggerLevel,
101
145
  levels: customLevels,
102
- transports: [
103
- new winston.transports.Console({
104
- format: this.getConsoleFormat()
105
- }),
106
- new winston.transports.DailyRotateFile({
107
- filename: path.resolve(loadPath, "error", "%DATE%_error.log"),
108
- level: "error",
109
- format: jsonFileFormat,
110
- maxSize: "2mb",
111
- maxFiles: "7d"
112
- }),
113
- new winston.transports.DailyRotateFile({
114
- filename: path.resolve(loadPath, "critical", "%DATE%_critical.log"),
115
- level: "critical",
116
- format: jsonFileFormat,
117
- maxSize: "2mb",
118
- maxFiles: "14d"
119
- })
120
- ]
121
- });
122
- winstonLogger.on("data", (info) => {
123
- if (this.isKafkaReady) {
124
- this.logToKafka(info);
125
- }
146
+ transports
126
147
  });
127
148
  this.winston = winstonLogger;
128
149
  return winstonLogger;
@@ -131,16 +152,19 @@ export default class LoggerService {
131
152
  if (!this.kafkaProducer || !this.isKafkaReady)
132
153
  return;
133
154
  try {
155
+ const { id, level, message, ...rest } = info;
134
156
  await this.kafkaProducer.send({
135
157
  topic: this.kafkaTopic,
136
158
  messages: [{
137
159
  value: JSON.stringify({
160
+ id: id || null,
138
161
  service: this.serviceName,
139
- id: info.id || null,
140
162
  env: this.environment,
141
- ...info,
163
+ level,
164
+ message,
165
+ ...rest,
142
166
  timestamp: new Date().toISOString()
143
- })
167
+ }, this.safeReplacer)
144
168
  }]
145
169
  });
146
170
  }
@@ -12,6 +12,7 @@ export default class LoggerService {
12
12
  private kafkaProducer;
13
13
  private kafkaTopic;
14
14
  private isKafkaReady;
15
+ private safeReplacer;
15
16
  private getTracingFormat;
16
17
  private getConsoleFormat;
17
18
  winston: CustomLogger;
@@ -1,9 +1,26 @@
1
1
  import winston, { format } from "winston";
2
+ import Transport from "winston-transport";
2
3
  import "winston-daily-rotate-file";
3
4
  import path from "node:path";
4
5
  import { AsyncLocalStorage } from "node:async_hooks";
5
6
  import { Kafka, Partitioners, logLevel } from "kafkajs";
6
7
  import { LoggerLayerEnum } from "../../utils";
8
+ class KafkaTransport extends Transport {
9
+ service;
10
+ constructor(opts, service) {
11
+ super(opts);
12
+ this.service = service;
13
+ }
14
+ log(info, callback) {
15
+ setImmediate(() => {
16
+ this.emit("logged", info);
17
+ });
18
+ if (this.service["isKafkaReady"]) {
19
+ this.service["logToKafka"](info);
20
+ }
21
+ callback();
22
+ }
23
+ }
7
24
  const customLevels = {
8
25
  critical: LoggerLayerEnum.CRITICAL,
9
26
  error: LoggerLayerEnum.ERROR,
@@ -26,6 +43,13 @@ export default class LoggerService {
26
43
  kafkaProducer = null;
27
44
  kafkaTopic = "";
28
45
  isKafkaReady = false;
46
+ safeReplacer = (key, value) => {
47
+ if (value instanceof Headers)
48
+ return Object.fromEntries(value.entries());
49
+ if (value instanceof Error)
50
+ return { message: value.message, stack: value.stack };
51
+ return value;
52
+ };
29
53
  getTracingFormat = format((info) => {
30
54
  const store = LoggerService.storage.getStore();
31
55
  if (store) {
@@ -35,7 +59,9 @@ export default class LoggerService {
35
59
  });
36
60
  getConsoleFormat() {
37
61
  return format.combine(this.getTracingFormat(), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.colorize({ all: true }), format.printf((info) => {
38
- const msg = typeof info.message === "object" ? JSON.stringify(info.message) : info.message;
62
+ const msg = typeof info.message === "object"
63
+ ? JSON.stringify(info.message, this.safeReplacer)
64
+ : info.message;
39
65
  const idPart = info.id ? ` : id: ${info.id}` : "";
40
66
  return `${info.timestamp} [${this.serviceName}] ${info.level}: ${msg}${idPart}`;
41
67
  }));
@@ -55,16 +81,12 @@ export default class LoggerService {
55
81
  runWithId = (id, callback) => {
56
82
  const store = new Map();
57
83
  store.set("correlationId", id);
58
- LoggerService.storage.run(store, callback);
84
+ return LoggerService.storage.run(store, callback);
59
85
  };
60
86
  load = async (config) => {
61
87
  this.serviceName = config?.serviceName || "service";
62
88
  this.environment = config?.env || "dev";
63
89
  this.kafkaTopic = config?.kafkaTopic || "logs";
64
- this.winstonLoader({
65
- loadPath: config?.loadPath || "./logs",
66
- loggerLevel: config?.loggerLevel || "debug"
67
- });
68
90
  const rawBrokers = config?.kafkaBrokers;
69
91
  const brokers = rawBrokers ? String(rawBrokers).split(",").map((b) => b.trim()) : [];
70
92
  if (brokers.length > 0) {
@@ -72,57 +94,56 @@ export default class LoggerService {
72
94
  clientId: this.serviceName,
73
95
  brokers: brokers,
74
96
  logLevel: logLevel.ERROR,
75
- retry: {
76
- initialRetryTime: 1000,
77
- retries: 10,
78
- maxRetryTime: 30000,
79
- factor: 2
80
- },
81
97
  });
82
- this.kafkaProducer = kafka.producer({ createPartitioner: Partitioners.LegacyPartitioner });
83
- const connectWithRetry = async () => {
98
+ this.kafkaProducer = kafka.producer({
99
+ createPartitioner: Partitioners.DefaultPartitioner
100
+ });
101
+ const connect = async () => {
84
102
  try {
85
103
  await this.kafkaProducer?.connect();
86
104
  this.isKafkaReady = true;
87
105
  }
88
106
  catch (err) {
89
107
  this.isKafkaReady = false;
90
- setTimeout(connectWithRetry, 5000);
108
+ setTimeout(connect, 5000);
91
109
  }
92
110
  };
93
- connectWithRetry();
111
+ await connect();
94
112
  }
113
+ this.winstonLoader({
114
+ loadPath: config?.loadPath || "./logs",
115
+ loggerLevel: config?.loggerLevel || "debug"
116
+ });
95
117
  };
96
118
  winstonLoader = ({ loadPath, loggerLevel }) => {
97
119
  const { combine, timestamp, json, errors } = format;
98
- const jsonFileFormat = combine(this.getTracingFormat(), timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), errors({ stack: true }), json());
120
+ const jsonFileFormat = combine(this.getTracingFormat(), timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), errors({ stack: true }), json({ replacer: this.safeReplacer }));
121
+ const transports = [
122
+ new winston.transports.Console({
123
+ format: this.getConsoleFormat()
124
+ }),
125
+ new winston.transports.DailyRotateFile({
126
+ filename: path.resolve(loadPath, "error", "%DATE%_error.log"),
127
+ level: "error",
128
+ format: jsonFileFormat,
129
+ maxSize: "2mb",
130
+ maxFiles: "7d"
131
+ }),
132
+ new winston.transports.DailyRotateFile({
133
+ filename: path.resolve(loadPath, "critical", "%DATE%_critical.log"),
134
+ level: "critical",
135
+ format: jsonFileFormat,
136
+ maxSize: "2mb",
137
+ maxFiles: "14d"
138
+ })
139
+ ];
140
+ if (this.kafkaProducer) {
141
+ transports.push(new KafkaTransport({ level: loggerLevel }, this));
142
+ }
99
143
  const winstonLogger = winston.createLogger({
100
144
  level: loggerLevel,
101
145
  levels: customLevels,
102
- transports: [
103
- new winston.transports.Console({
104
- format: this.getConsoleFormat()
105
- }),
106
- new winston.transports.DailyRotateFile({
107
- filename: path.resolve(loadPath, "error", "%DATE%_error.log"),
108
- level: "error",
109
- format: jsonFileFormat,
110
- maxSize: "2mb",
111
- maxFiles: "7d"
112
- }),
113
- new winston.transports.DailyRotateFile({
114
- filename: path.resolve(loadPath, "critical", "%DATE%_critical.log"),
115
- level: "critical",
116
- format: jsonFileFormat,
117
- maxSize: "2mb",
118
- maxFiles: "14d"
119
- })
120
- ]
121
- });
122
- winstonLogger.on("data", (info) => {
123
- if (this.isKafkaReady) {
124
- this.logToKafka(info);
125
- }
146
+ transports
126
147
  });
127
148
  this.winston = winstonLogger;
128
149
  return winstonLogger;
@@ -131,16 +152,19 @@ export default class LoggerService {
131
152
  if (!this.kafkaProducer || !this.isKafkaReady)
132
153
  return;
133
154
  try {
155
+ const { id, level, message, ...rest } = info;
134
156
  await this.kafkaProducer.send({
135
157
  topic: this.kafkaTopic,
136
158
  messages: [{
137
159
  value: JSON.stringify({
160
+ id: id || null,
138
161
  service: this.serviceName,
139
- id: info.id || null,
140
162
  env: this.environment,
141
- ...info,
163
+ level,
164
+ message,
165
+ ...rest,
142
166
  timestamp: new Date().toISOString()
143
- })
167
+ }, this.safeReplacer)
144
168
  }]
145
169
  });
146
170
  }