@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,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"
|
|
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({
|
|
83
|
-
|
|
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(
|
|
108
|
+
setTimeout(connect, 5000);
|
|
91
109
|
}
|
|
92
110
|
};
|
|
93
|
-
|
|
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
|
-
|
|
163
|
+
level,
|
|
164
|
+
message,
|
|
165
|
+
...rest,
|
|
142
166
|
timestamp: new Date().toISOString()
|
|
143
|
-
})
|
|
167
|
+
}, this.safeReplacer)
|
|
144
168
|
}]
|
|
145
169
|
});
|
|
146
170
|
}
|
|
@@ -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"
|
|
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({
|
|
83
|
-
|
|
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(
|
|
108
|
+
setTimeout(connect, 5000);
|
|
91
109
|
}
|
|
92
110
|
};
|
|
93
|
-
|
|
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
|
-
|
|
163
|
+
level,
|
|
164
|
+
message,
|
|
165
|
+
...rest,
|
|
142
166
|
timestamp: new Date().toISOString()
|
|
143
|
-
})
|
|
167
|
+
}, this.safeReplacer)
|
|
144
168
|
}]
|
|
145
169
|
});
|
|
146
170
|
}
|