sasai-common-utils 1.0.45 → 1.0.47
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 -3
- package/src/features/logger/index.js +57 -148
- package/src/features/logger/logWorker.js +20 -79
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sasai-common-utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.47",
|
|
4
4
|
"description": "Reusable utility library for common logging and other shared features.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,15 +11,14 @@
|
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@elastic/ecs-winston-format": "^1.5.3",
|
|
14
|
-
"@opentelemetry/api": "^1.9.0",
|
|
15
14
|
"@opentelemetry/api-logs": "^0.211.0",
|
|
16
15
|
"@opentelemetry/exporter-logs-otlp-http": "^0.211.0",
|
|
17
16
|
"@opentelemetry/resources": "^2.5.0",
|
|
18
17
|
"@opentelemetry/sdk-logs": "^0.211.0",
|
|
19
|
-
"@opentelemetry/semantic-conventions": "^1.39.0",
|
|
20
18
|
"jsonwebtoken": "^9.0.2",
|
|
21
19
|
"klona": "^2.0.6",
|
|
22
20
|
"pino": "^9.6.0",
|
|
21
|
+
"pino-opentelemetry-transport": "^1.1.0",
|
|
23
22
|
"pino-pretty": "^13.0.0",
|
|
24
23
|
"traverse": "^0.6.11",
|
|
25
24
|
"winston": "^3.17.0"
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
const jwt = require("jsonwebtoken");
|
|
2
|
-
const { HTTP_STATUS_CODES } = require("./constants");
|
|
3
|
-
const { redactInformation } = require("./redact");
|
|
4
2
|
const pino = require("pino");
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
const { OTLPLogExporter } = require("@opentelemetry/exporter-logs-otlp-http");
|
|
8
|
-
const { resourceFromAttributes } = require("@opentelemetry/resources");
|
|
9
|
-
const { ATTR_SERVICE_NAME, ATTR_DEPLOYMENT_ENVIRONMENT } = require("@opentelemetry/semantic-conventions");
|
|
10
|
-
const { SeverityNumber } = require("@opentelemetry/api-logs");
|
|
3
|
+
const { HTTP_STATUS_CODES, DEBUG_MODES } = require("./constants");
|
|
4
|
+
const { redactInformation } = require("./redact");
|
|
11
5
|
|
|
12
6
|
let contextProvider = () => ({});
|
|
13
7
|
let globalConfig = {};
|
|
14
8
|
let loggerInstance;
|
|
15
9
|
let pinoLogger;
|
|
16
|
-
let otelLoggerProvider;
|
|
17
|
-
let otelLogger;
|
|
18
10
|
|
|
19
11
|
function setContextProvider(provider) {
|
|
20
12
|
contextProvider = provider;
|
|
@@ -31,150 +23,67 @@ function setGlobalConfig(config) {
|
|
|
31
23
|
};
|
|
32
24
|
}
|
|
33
25
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
level: globalConfig.LOG_LEVEL || "info",
|
|
37
|
-
transport:
|
|
38
|
-
globalConfig.DEBUG_MODE === "true"
|
|
39
|
-
? { target: "pino-pretty", options: { colorize: true } }
|
|
40
|
-
: undefined,
|
|
41
|
-
};
|
|
42
|
-
pinoLogger = pino(pinoConfig);
|
|
26
|
+
function cleanString(str) {
|
|
27
|
+
return typeof str === "string" ? str.replace(/\\"/g, '"') : str;
|
|
43
28
|
}
|
|
44
29
|
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
[ATTR_DEPLOYMENT_ENVIRONMENT]: globalConfig.NODE_ENV,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const logExporter = new OTLPLogExporter({
|
|
59
|
-
url: globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
|
|
60
|
-
headers: {},
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const logRecordProcessor = new BatchLogRecordProcessor(logExporter, {
|
|
64
|
-
maxQueueSize: 2048,
|
|
65
|
-
maxExportBatchSize: 512,
|
|
66
|
-
scheduledDelayMillis: 5000,
|
|
67
|
-
exportTimeoutMillis: 30000,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
otelLoggerProvider = new LoggerProvider({
|
|
71
|
-
resource,
|
|
72
|
-
logRecordProcessors: [logRecordProcessor],
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
otelLogger = otelLoggerProvider.getLogger(
|
|
76
|
-
globalConfig.SERVICE_NAME || "default-logger",
|
|
77
|
-
"1.0.0"
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
console.log("[Logger] OTLP logs initialized successfully");
|
|
81
|
-
} catch (error) {
|
|
82
|
-
console.error("[Logger] Failed to initialize OTLP:", error);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function getSeverityNumber(level) {
|
|
87
|
-
const severityMap = {
|
|
88
|
-
trace: SeverityNumber.TRACE,
|
|
89
|
-
debug: SeverityNumber.DEBUG,
|
|
90
|
-
info: SeverityNumber.INFO,
|
|
91
|
-
warn: SeverityNumber.WARN,
|
|
92
|
-
error: SeverityNumber.ERROR,
|
|
93
|
-
fatal: SeverityNumber.FATAL,
|
|
30
|
+
function initPinoLogger() {
|
|
31
|
+
const pinoOptions = {
|
|
32
|
+
timestamp: false,
|
|
33
|
+
level: globalConfig.DEBUG_MODE === DEBUG_MODES.DISABLE ? "silent" : globalConfig.LOG_LEVEL,
|
|
34
|
+
base: { service_name: globalConfig.SERVICE_NAME },
|
|
35
|
+
formatters: {
|
|
36
|
+
level() {
|
|
37
|
+
return { level_name: globalConfig.LOG_LEVEL.toUpperCase() };
|
|
38
|
+
},
|
|
39
|
+
},
|
|
94
40
|
};
|
|
95
|
-
return severityMap[level] || SeverityNumber.INFO;
|
|
96
|
-
}
|
|
97
41
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
let traceId = data.trace || spanContext?.traceId || "";
|
|
112
|
-
let spanId = data.span || spanContext?.spanId || "";
|
|
113
|
-
|
|
114
|
-
const logRecord = {
|
|
115
|
-
severityNumber: getSeverityNumber(level),
|
|
116
|
-
severityText: level.toUpperCase(),
|
|
117
|
-
body: typeof data === "string" ? data : JSON.stringify(data),
|
|
118
|
-
attributes: {
|
|
119
|
-
"log.level": level,
|
|
120
|
-
"service.name": globalConfig.SERVICE_NAME,
|
|
121
|
-
...(traceId && { "trace.id": traceId }),
|
|
122
|
-
...(spanId && { "span.id": spanId }),
|
|
123
|
-
...data,
|
|
42
|
+
if (globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
|
|
43
|
+
// When OTLP logs endpoint is configured, fan-out to both stdout and OTLP.
|
|
44
|
+
// Use require.resolve so pino's worker thread can find the transport
|
|
45
|
+
// regardless of the caller's working directory.
|
|
46
|
+
const stdoutTarget = {
|
|
47
|
+
target: require.resolve("pino/file"),
|
|
48
|
+
options: { destination: 1 }, // stdout
|
|
49
|
+
};
|
|
50
|
+
const otelTarget = {
|
|
51
|
+
target: require.resolve("pino-opentelemetry-transport"),
|
|
52
|
+
options: {
|
|
53
|
+
resourceAttributes: {
|
|
54
|
+
"service.name": globalConfig.SERVICE_NAME ,
|
|
124
55
|
},
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
} else if (traceId) {
|
|
137
|
-
logRecord.traceFlags = 1; // Default to SAMPLED if we have a traceId
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
otelLogger.emit(logRecord);
|
|
141
|
-
} catch (error) {
|
|
142
|
-
console.error("[Logger] Failed to send OTLP log:", error);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const transport = pino.transport({ targets: [stdoutTarget, otelTarget] });
|
|
59
|
+
pinoLogger = pino(pinoOptions, transport);
|
|
60
|
+
} else {
|
|
61
|
+
// No OTLP endpoint — write to stdout or file based on DEBUG_MODE
|
|
62
|
+
let destination;
|
|
63
|
+
if (globalConfig.DEBUG_MODE === DEBUG_MODES.FILE) {
|
|
64
|
+
destination = pino.destination("app.log");
|
|
65
|
+
} else {
|
|
66
|
+
destination = pino.destination(1); // stdout (CONSOLE or default)
|
|
143
67
|
}
|
|
68
|
+
pinoLogger = pino(pinoOptions, destination);
|
|
144
69
|
}
|
|
145
70
|
}
|
|
146
71
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
}
|
|
72
|
+
function emitLog(level, data) {
|
|
73
|
+
if (!pinoLogger) return;
|
|
74
|
+
const { trace, span, message, ...rest } = data;
|
|
75
|
+
pinoLogger[level]({
|
|
76
|
+
trace_id: trace || undefined,
|
|
77
|
+
span_id: span || undefined,
|
|
78
|
+
message: cleanString(message),
|
|
79
|
+
...rest,
|
|
80
|
+
});
|
|
158
81
|
}
|
|
159
82
|
|
|
160
|
-
process.on("beforeExit", () => {
|
|
161
|
-
shutdown().catch(console.error);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
process.on("SIGTERM", () => {
|
|
165
|
-
shutdown().then(() => process.exit(0));
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
process.on("SIGINT", () => {
|
|
169
|
-
shutdown().then(() => process.exit(0));
|
|
170
|
-
});
|
|
171
|
-
|
|
172
83
|
function createLogger(config) {
|
|
173
84
|
if (loggerInstance) return loggerInstance;
|
|
174
|
-
|
|
175
85
|
setGlobalConfig(config);
|
|
176
|
-
|
|
177
|
-
initOTEL();
|
|
86
|
+
initPinoLogger();
|
|
178
87
|
|
|
179
88
|
loggerInstance = {
|
|
180
89
|
logInfo: (message, data = {}) => {
|
|
@@ -212,8 +121,9 @@ function createLogger(config) {
|
|
|
212
121
|
const redactedData = redactInformation(logData, {
|
|
213
122
|
sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
|
|
214
123
|
});
|
|
215
|
-
|
|
124
|
+
emitLog("info", redactedData);
|
|
216
125
|
},
|
|
126
|
+
|
|
217
127
|
logError: (data = {}, req = {}) => {
|
|
218
128
|
const traceContext = contextProvider();
|
|
219
129
|
const errorResponse = data?.error?.response;
|
|
@@ -231,30 +141,30 @@ function createLogger(config) {
|
|
|
231
141
|
message: JSON.stringify({
|
|
232
142
|
step: data?.step || "",
|
|
233
143
|
message: data?.error?.message,
|
|
234
|
-
method:
|
|
144
|
+
method: errorResponse?.config?.method?.toUpperCase(),
|
|
235
145
|
parameters: data?.parameters || "",
|
|
236
146
|
path: errorResponse?.config?.url || req?.originalUrl,
|
|
237
147
|
responseStatus: errorResponse?.status || HTTP_STATUS_CODES.BAD_REQUEST,
|
|
238
148
|
headers: errorResponse?.config?.headers || {},
|
|
239
149
|
requestBody: errorResponse?.config?.body || req?.body,
|
|
240
|
-
responseBody: errorResponse?.data || {},
|
|
150
|
+
responseBody: errorResponse?.data || {},
|
|
241
151
|
stack: data?.error?.stack,
|
|
242
152
|
tokenDetails: {
|
|
243
153
|
customerId: tokenData?.customerId || "",
|
|
244
154
|
mid: tokenData?.mid || "",
|
|
245
155
|
tenantId: tokenData?.tenantId || "",
|
|
246
|
-
},
|
|
156
|
+
},
|
|
247
157
|
}),
|
|
248
158
|
};
|
|
249
159
|
const redactedData = redactInformation(logData, {
|
|
250
160
|
sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
|
|
251
161
|
});
|
|
252
|
-
|
|
162
|
+
emitLog("error", redactedData);
|
|
253
163
|
},
|
|
164
|
+
|
|
254
165
|
logDebug: (message, data = {}) => {
|
|
255
|
-
|
|
166
|
+
emitLog("debug", { message, ...data });
|
|
256
167
|
},
|
|
257
|
-
shutdown, // Expose shutdown method
|
|
258
168
|
};
|
|
259
169
|
|
|
260
170
|
return loggerInstance;
|
|
@@ -264,5 +174,4 @@ module.exports = {
|
|
|
264
174
|
createLogger,
|
|
265
175
|
setContextProvider,
|
|
266
176
|
logger: loggerInstance,
|
|
267
|
-
shutdown, // Export shutdown for manual cleanup if needed
|
|
268
177
|
};
|
|
@@ -8,69 +8,29 @@ let otelLoggerProvider = null;
|
|
|
8
8
|
try {
|
|
9
9
|
const { LoggerProvider, BatchLogRecordProcessor } = require("@opentelemetry/sdk-logs");
|
|
10
10
|
const { OTLPLogExporter } = require("@opentelemetry/exporter-logs-otlp-http");
|
|
11
|
-
const {
|
|
11
|
+
const { Resource } = require("@opentelemetry/resources");
|
|
12
12
|
|
|
13
|
-
// Create OTLP Log Exporter
|
|
13
|
+
// Create OTLP Log Exporter
|
|
14
14
|
const logExporter = new OTLPLogExporter({
|
|
15
|
-
url: globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || 'http://localhost:4318/v1/logs'
|
|
16
|
-
concurrencyLimit: 1 // Limit on pending requests
|
|
15
|
+
url: globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || 'http://localhost:4318/v1/logs'
|
|
17
16
|
});
|
|
18
17
|
|
|
19
|
-
// Create
|
|
20
|
-
const logRecordProcessor = new BatchLogRecordProcessor(logExporter);
|
|
21
|
-
|
|
22
|
-
// Create Logger Provider with resource attributes and processors
|
|
18
|
+
// Create Logger Provider with resource attributes
|
|
23
19
|
otelLoggerProvider = new LoggerProvider({
|
|
24
|
-
resource:
|
|
20
|
+
resource: new Resource({
|
|
25
21
|
'service.name': globalConfig.SERVICE_NAME || 'sasai-service',
|
|
26
22
|
'deployment.environment': globalConfig.NODE_ENV || ''
|
|
27
|
-
})
|
|
28
|
-
processors: [logRecordProcessor] // Use 'processors' not 'logRecordProcessors'
|
|
23
|
+
})
|
|
29
24
|
});
|
|
25
|
+
|
|
26
|
+
// Add batch processor for efficient log export
|
|
27
|
+
otelLoggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
|
|
30
28
|
|
|
31
|
-
console.log('✅ OpenTelemetry Logs SDK initialized'
|
|
32
|
-
endpoint: globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || 'http://localhost:4318/v1/logs',
|
|
33
|
-
serviceName: globalConfig.SERVICE_NAME
|
|
34
|
-
});
|
|
29
|
+
console.log('✅ OpenTelemetry Logs SDK initialized');
|
|
35
30
|
} catch (error) {
|
|
36
31
|
console.warn('⚠️ OpenTelemetry Logs SDK not available, logs will only write locally:', error.message);
|
|
37
32
|
}
|
|
38
33
|
|
|
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
|
-
|
|
74
34
|
// Initialize Pino logger inside the worker
|
|
75
35
|
function cleanString(str) {
|
|
76
36
|
return typeof str === "string"
|
|
@@ -120,43 +80,24 @@ function sendToOtel(level, logData) {
|
|
|
120
80
|
if (!otelLoggerProvider) return;
|
|
121
81
|
|
|
122
82
|
try {
|
|
123
|
-
const
|
|
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
|
-
};
|
|
83
|
+
const otelLogger = otelLoggerProvider.getLogger(globalConfig.SERVICE_NAME);
|
|
135
84
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
severityNumber: severityMap[level] || SeverityNumber.INFO,
|
|
85
|
+
// Emit log record to OpenTelemetry
|
|
86
|
+
otelLogger.emit({
|
|
87
|
+
severityNumber: pinoLevelToOtelSeverity(level),
|
|
140
88
|
severityText: level.toUpperCase(),
|
|
141
89
|
body: logData.message || JSON.stringify(logData),
|
|
142
90
|
attributes: {
|
|
91
|
+
'trace.id': logData.trace_id,
|
|
92
|
+
'span.id': logData.span_id,
|
|
143
93
|
'service.name': globalConfig.SERVICE_NAME,
|
|
144
94
|
...logData
|
|
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);
|
|
95
|
+
},
|
|
96
|
+
timestamp: Date.now() * 1000000, // Convert to nanoseconds
|
|
97
|
+
});
|
|
157
98
|
} catch (error) {
|
|
158
99
|
// Silently fail to avoid breaking the logging flow
|
|
159
|
-
console.error('Failed to send log to OpenTelemetry:', error.message
|
|
100
|
+
console.error('Failed to send log to OpenTelemetry:', error.message);
|
|
160
101
|
}
|
|
161
102
|
}
|
|
162
103
|
|