sasai-common-utils 1.0.41 → 1.0.43
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 +2 -1
- package/src/features/logger/index.js +142 -17
- package/src/features/logger/logWorker.js +64 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sasai-common-utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.43",
|
|
4
4
|
"description": "Reusable utility library for common logging and other shared features.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@elastic/ecs-winston-format": "^1.5.3",
|
|
14
|
+
"@opentelemetry/api": "^1.9.0",
|
|
14
15
|
"@opentelemetry/api-logs": "^0.211.0",
|
|
15
16
|
"@opentelemetry/exporter-logs-otlp-http": "^0.211.0",
|
|
16
17
|
"@opentelemetry/resources": "^2.5.0",
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
const jwt = require("jsonwebtoken");
|
|
2
2
|
const { HTTP_STATUS_CODES } = require("./constants");
|
|
3
3
|
const { redactInformation } = require("./redact");
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const pino = require("pino");
|
|
5
|
+
const { trace, context } = require("@opentelemetry/api");
|
|
6
|
+
const { LoggerProvider, BatchLogRecordProcessor } = require("@opentelemetry/sdk-logs");
|
|
7
|
+
const { OTLPLogExporter } = require("@opentelemetry/exporter-logs-otlp-http");
|
|
8
|
+
const { Resource } = require("@opentelemetry/resources");
|
|
9
|
+
const { SeverityNumber } = require("@opentelemetry/api-logs");
|
|
10
|
+
|
|
6
11
|
let contextProvider = () => ({});
|
|
7
12
|
let globalConfig = {};
|
|
8
13
|
let loggerInstance;
|
|
14
|
+
let pinoLogger;
|
|
15
|
+
let otelLoggerProvider;
|
|
16
|
+
let otelLogger;
|
|
9
17
|
|
|
10
18
|
function setContextProvider(provider) {
|
|
11
19
|
contextProvider = provider;
|
|
12
20
|
}
|
|
13
|
-
let loggerWorker;
|
|
14
|
-
|
|
15
21
|
|
|
16
|
-
function initLoggerWorker() {
|
|
17
|
-
loggerWorker = new Worker(path.join(__dirname, "logWorker.js"), {
|
|
18
|
-
workerData: { globalConfig }
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
22
|
function setGlobalConfig(config) {
|
|
22
23
|
globalConfig = {
|
|
23
24
|
SERVICE_NAME: config?.SERVICE_NAME,
|
|
@@ -29,18 +30,138 @@ function setGlobalConfig(config) {
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
function initPino() {
|
|
34
|
+
const pinoConfig = {
|
|
35
|
+
level: globalConfig.LOG_LEVEL || "info",
|
|
36
|
+
transport:
|
|
37
|
+
globalConfig.DEBUG_MODE === "true"
|
|
38
|
+
? { target: "pino-pretty", options: { colorize: true } }
|
|
39
|
+
: undefined,
|
|
40
|
+
};
|
|
41
|
+
pinoLogger = pino(pinoConfig);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function initOTEL() {
|
|
45
|
+
if (!globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
|
|
46
|
+
console.log("[Logger] OTLP endpoint not configured, skipping OTLP logs");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const resource = new Resource({
|
|
52
|
+
"service.name": globalConfig.SERVICE_NAME || "unknown-service",
|
|
53
|
+
"deployment.environment": globalConfig.NODE_ENV || "unknown",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const logExporter = new OTLPLogExporter({
|
|
57
|
+
url: globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
|
|
58
|
+
headers: {},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const logRecordProcessor = new BatchLogRecordProcessor(logExporter, {
|
|
62
|
+
maxQueueSize: 2048,
|
|
63
|
+
maxExportBatchSize: 512,
|
|
64
|
+
scheduledDelayMillis: 5000,
|
|
65
|
+
exportTimeoutMillis: 30000,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
otelLoggerProvider = new LoggerProvider({
|
|
69
|
+
resource,
|
|
70
|
+
logRecordProcessors: [logRecordProcessor],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
otelLogger = otelLoggerProvider.getLogger(
|
|
74
|
+
globalConfig.SERVICE_NAME || "default-logger",
|
|
75
|
+
"1.0.0"
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
console.log("[Logger] OTLP logs initialized successfully");
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error("[Logger] Failed to initialize OTLP:", error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getSeverityNumber(level) {
|
|
85
|
+
const severityMap = {
|
|
86
|
+
trace: SeverityNumber.TRACE,
|
|
87
|
+
debug: SeverityNumber.DEBUG,
|
|
88
|
+
info: SeverityNumber.INFO,
|
|
89
|
+
warn: SeverityNumber.WARN,
|
|
90
|
+
error: SeverityNumber.ERROR,
|
|
91
|
+
fatal: SeverityNumber.FATAL,
|
|
92
|
+
};
|
|
93
|
+
return severityMap[level] || SeverityNumber.INFO;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function sendLog(level, data) {
|
|
97
|
+
// Send to Pino (console/file)
|
|
98
|
+
if (pinoLogger) {
|
|
99
|
+
pinoLogger[level](data);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Send to OTLP
|
|
103
|
+
if (otelLogger) {
|
|
104
|
+
try {
|
|
105
|
+
const activeSpan = trace.getSpan(context.active());
|
|
106
|
+
const spanContext = activeSpan?.spanContext();
|
|
107
|
+
|
|
108
|
+
const logRecord = {
|
|
109
|
+
severityNumber: getSeverityNumber(level),
|
|
110
|
+
severityText: level.toUpperCase(),
|
|
111
|
+
body: typeof data === "string" ? data : JSON.stringify(data),
|
|
112
|
+
attributes: {
|
|
113
|
+
"log.level": level,
|
|
114
|
+
...(data.trace && { "trace.id": data.trace }),
|
|
115
|
+
...(data.span && { "span.id": data.span }),
|
|
116
|
+
...data,
|
|
117
|
+
},
|
|
118
|
+
...(spanContext?.traceId && { traceId: spanContext.traceId }),
|
|
119
|
+
...(spanContext?.spanId && { spanId: spanContext.spanId }),
|
|
120
|
+
...(spanContext?.traceFlags !== undefined && {
|
|
121
|
+
traceFlags: spanContext.traceFlags,
|
|
122
|
+
}),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
otelLogger.emit(logRecord);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error("[Logger] Failed to send OTLP log:", error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Graceful shutdown
|
|
133
|
+
async function shutdown() {
|
|
134
|
+
if (otelLoggerProvider) {
|
|
135
|
+
console.log("[Logger] Shutting down OTLP logger...");
|
|
136
|
+
try {
|
|
137
|
+
await otelLoggerProvider.shutdown();
|
|
138
|
+
console.log("[Logger] OTLP logger shutdown complete");
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error("[Logger] Error during OTLP shutdown:", error);
|
|
141
|
+
}
|
|
35
142
|
}
|
|
36
143
|
}
|
|
37
144
|
|
|
145
|
+
process.on("beforeExit", () => {
|
|
146
|
+
shutdown().catch(console.error);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
process.on("SIGTERM", () => {
|
|
150
|
+
shutdown().then(() => process.exit(0));
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
process.on("SIGINT", () => {
|
|
154
|
+
shutdown().then(() => process.exit(0));
|
|
155
|
+
});
|
|
156
|
+
|
|
38
157
|
function createLogger(config) {
|
|
39
158
|
if (loggerInstance) return loggerInstance;
|
|
159
|
+
|
|
40
160
|
setGlobalConfig(config);
|
|
41
|
-
|
|
161
|
+
initPino();
|
|
162
|
+
initOTEL();
|
|
42
163
|
|
|
43
|
-
|
|
164
|
+
loggerInstance = {
|
|
44
165
|
logInfo: (message, data = {}) => {
|
|
45
166
|
const traceContext = contextProvider();
|
|
46
167
|
const authHeader =
|
|
@@ -76,7 +197,7 @@ function createLogger(config) {
|
|
|
76
197
|
const redactedData = redactInformation(logData, {
|
|
77
198
|
sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
|
|
78
199
|
});
|
|
79
|
-
|
|
200
|
+
sendLog("info", redactedData);
|
|
80
201
|
},
|
|
81
202
|
logError: (data = {}, req = {}) => {
|
|
82
203
|
const traceContext = contextProvider();
|
|
@@ -113,16 +234,20 @@ function createLogger(config) {
|
|
|
113
234
|
const redactedData = redactInformation(logData, {
|
|
114
235
|
sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
|
|
115
236
|
});
|
|
116
|
-
|
|
237
|
+
sendLog("error", redactedData);
|
|
117
238
|
},
|
|
118
239
|
logDebug: (message, data = {}) => {
|
|
119
|
-
|
|
240
|
+
sendLog("debug", { message, ...data });
|
|
120
241
|
},
|
|
242
|
+
shutdown, // Expose shutdown method
|
|
121
243
|
};
|
|
244
|
+
|
|
245
|
+
return loggerInstance;
|
|
122
246
|
}
|
|
123
247
|
|
|
124
248
|
module.exports = {
|
|
125
249
|
createLogger,
|
|
126
250
|
setContextProvider,
|
|
127
251
|
logger: loggerInstance,
|
|
252
|
+
shutdown, // Export shutdown for manual cleanup if needed
|
|
128
253
|
};
|
|
@@ -36,6 +36,41 @@ try {
|
|
|
36
36
|
console.warn('⚠️ OpenTelemetry Logs SDK not available, logs will only write locally:', error.message);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Graceful shutdown handlers for OpenTelemetry Logger Provider
|
|
40
|
+
// CRITICAL: Without this, batched logs will be lost when worker exits
|
|
41
|
+
process.on('beforeExit', async () => {
|
|
42
|
+
if (otelLoggerProvider) {
|
|
43
|
+
try {
|
|
44
|
+
await otelLoggerProvider.shutdown();
|
|
45
|
+
console.log('✅ OpenTelemetry Logs SDK gracefully shutdown');
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('❌ Error shutting down OpenTelemetry Logs SDK:', error.message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
process.on('SIGTERM', async () => {
|
|
53
|
+
if (otelLoggerProvider) {
|
|
54
|
+
try {
|
|
55
|
+
await otelLoggerProvider.shutdown();
|
|
56
|
+
console.log('✅ OpenTelemetry Logs SDK shutdown on SIGTERM');
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('❌ Error shutting down OpenTelemetry Logs SDK on SIGTERM:', error.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
process.on('SIGINT', async () => {
|
|
64
|
+
if (otelLoggerProvider) {
|
|
65
|
+
try {
|
|
66
|
+
await otelLoggerProvider.shutdown();
|
|
67
|
+
console.log('✅ OpenTelemetry Logs SDK shutdown on SIGINT');
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('❌ Error shutting down OpenTelemetry Logs SDK on SIGINT:', error.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
39
74
|
// Initialize Pino logger inside the worker
|
|
40
75
|
function cleanString(str) {
|
|
41
76
|
return typeof str === "string"
|
|
@@ -85,24 +120,43 @@ function sendToOtel(level, logData) {
|
|
|
85
120
|
if (!otelLoggerProvider) return;
|
|
86
121
|
|
|
87
122
|
try {
|
|
88
|
-
const
|
|
123
|
+
const { SeverityNumber } = require('@opentelemetry/api-logs');
|
|
124
|
+
const otelLogger = otelLoggerProvider.getLogger(globalConfig.SERVICE_NAME, '1.0.0');
|
|
89
125
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
126
|
+
// Get severity from level
|
|
127
|
+
const severityMap = {
|
|
128
|
+
'trace': SeverityNumber.TRACE,
|
|
129
|
+
'debug': SeverityNumber.DEBUG,
|
|
130
|
+
'info': SeverityNumber.INFO,
|
|
131
|
+
'warn': SeverityNumber.WARN,
|
|
132
|
+
'error': SeverityNumber.ERROR,
|
|
133
|
+
'fatal': SeverityNumber.FATAL
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Prepare the log record according to OpenTelemetry Logs spec
|
|
137
|
+
const logRecord = {
|
|
138
|
+
timestamp: Date.now() * 1000000, // Convert to nanoseconds
|
|
139
|
+
severityNumber: severityMap[level] || SeverityNumber.INFO,
|
|
93
140
|
severityText: level.toUpperCase(),
|
|
94
141
|
body: logData.message || JSON.stringify(logData),
|
|
95
142
|
attributes: {
|
|
96
|
-
'trace.id': logData.trace_id,
|
|
97
|
-
'span.id': logData.span_id,
|
|
98
143
|
'service.name': globalConfig.SERVICE_NAME,
|
|
99
144
|
...logData
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Add trace context if available
|
|
149
|
+
if (logData.trace_id && logData.trace_id !== '') {
|
|
150
|
+
logRecord.traceId = logData.trace_id;
|
|
151
|
+
logRecord.spanId = logData.span_id || '';
|
|
152
|
+
logRecord.traceFlags = 1; // SAMPLED
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Emit log record to OpenTelemetry
|
|
156
|
+
otelLogger.emit(logRecord);
|
|
103
157
|
} catch (error) {
|
|
104
158
|
// Silently fail to avoid breaking the logging flow
|
|
105
|
-
console.error('Failed to send log to OpenTelemetry:', error.message);
|
|
159
|
+
console.error('Failed to send log to OpenTelemetry:', error.message, error.stack);
|
|
106
160
|
}
|
|
107
161
|
}
|
|
108
162
|
|