sasai-common-utils 1.0.42 → 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 +56 -11
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,33 +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');
|
|
125
|
+
|
|
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
|
+
};
|
|
89
135
|
|
|
90
|
-
// Prepare the log record
|
|
136
|
+
// Prepare the log record according to OpenTelemetry Logs spec
|
|
91
137
|
const logRecord = {
|
|
92
|
-
|
|
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
143
|
'service.name': globalConfig.SERVICE_NAME,
|
|
97
144
|
...logData
|
|
98
|
-
}
|
|
99
|
-
timestamp: Date.now() * 1000000, // Convert to nanoseconds
|
|
145
|
+
}
|
|
100
146
|
};
|
|
101
147
|
|
|
102
|
-
// Add trace context
|
|
103
|
-
if (logData.trace_id) {
|
|
148
|
+
// Add trace context if available
|
|
149
|
+
if (logData.trace_id && logData.trace_id !== '') {
|
|
104
150
|
logRecord.traceId = logData.trace_id;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
logRecord.spanId = logData.span_id;
|
|
151
|
+
logRecord.spanId = logData.span_id || '';
|
|
152
|
+
logRecord.traceFlags = 1; // SAMPLED
|
|
108
153
|
}
|
|
109
154
|
|
|
110
155
|
// Emit log record to OpenTelemetry
|
|
111
156
|
otelLogger.emit(logRecord);
|
|
112
157
|
} catch (error) {
|
|
113
158
|
// Silently fail to avoid breaking the logging flow
|
|
114
|
-
console.error('Failed to send log to OpenTelemetry:', error.message);
|
|
159
|
+
console.error('Failed to send log to OpenTelemetry:', error.message, error.stack);
|
|
115
160
|
}
|
|
116
161
|
}
|
|
117
162
|
|