sasai-common-utils 1.0.45 → 1.0.46
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 +60 -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.46",
|
|
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;
|
|
@@ -26,155 +18,75 @@ function setGlobalConfig(config) {
|
|
|
26
18
|
LOG_LEVEL: config?.LOG_LEVEL || "info",
|
|
27
19
|
DEBUG_MODE: config?.DEBUG_MODE,
|
|
28
20
|
SENSITIVE_KEYS: config?.SENSITIVE_KEYS,
|
|
21
|
+
<<<<<<< HEAD
|
|
29
22
|
NODE_ENV: config?.NODE_ENV,
|
|
23
|
+
=======
|
|
24
|
+
>>>>>>> d764c31 (Log and Metric Exporter Configured)
|
|
30
25
|
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: config?.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
|
|
31
26
|
};
|
|
32
27
|
}
|
|
33
28
|
|
|
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);
|
|
29
|
+
function cleanString(str) {
|
|
30
|
+
return typeof str === "string" ? str.replace(/\\"/g, '"') : str;
|
|
43
31
|
}
|
|
44
32
|
|
|
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,
|
|
33
|
+
function initPinoLogger() {
|
|
34
|
+
const pinoOptions = {
|
|
35
|
+
timestamp: false,
|
|
36
|
+
level: globalConfig.DEBUG_MODE === DEBUG_MODES.DISABLE ? "silent" : globalConfig.LOG_LEVEL,
|
|
37
|
+
base: { service_name: globalConfig.SERVICE_NAME },
|
|
38
|
+
formatters: {
|
|
39
|
+
level() {
|
|
40
|
+
return { level_name: globalConfig.LOG_LEVEL.toUpperCase() };
|
|
41
|
+
},
|
|
42
|
+
},
|
|
94
43
|
};
|
|
95
|
-
return severityMap[level] || SeverityNumber.INFO;
|
|
96
|
-
}
|
|
97
44
|
|
|
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,
|
|
45
|
+
if (globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
|
|
46
|
+
// When OTLP logs endpoint is configured, fan-out to both stdout and OTLP.
|
|
47
|
+
// Use require.resolve so pino's worker thread can find the transport
|
|
48
|
+
// regardless of the caller's working directory.
|
|
49
|
+
const stdoutTarget = {
|
|
50
|
+
target: require.resolve("pino/file"),
|
|
51
|
+
options: { destination: 1 }, // stdout
|
|
52
|
+
};
|
|
53
|
+
const otelTarget = {
|
|
54
|
+
target: require.resolve("pino-opentelemetry-transport"),
|
|
55
|
+
options: {
|
|
56
|
+
resourceAttributes: {
|
|
57
|
+
"service.name": globalConfig.SERVICE_NAME ,
|
|
124
58
|
},
|
|
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);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const transport = pino.transport({ targets: [stdoutTarget, otelTarget] });
|
|
62
|
+
pinoLogger = pino(pinoOptions, transport);
|
|
63
|
+
} else {
|
|
64
|
+
// No OTLP endpoint — write to stdout or file based on DEBUG_MODE
|
|
65
|
+
let destination;
|
|
66
|
+
if (globalConfig.DEBUG_MODE === DEBUG_MODES.FILE) {
|
|
67
|
+
destination = pino.destination("app.log");
|
|
68
|
+
} else {
|
|
69
|
+
destination = pino.destination(1); // stdout (CONSOLE or default)
|
|
143
70
|
}
|
|
71
|
+
pinoLogger = pino(pinoOptions, destination);
|
|
144
72
|
}
|
|
145
73
|
}
|
|
146
74
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
}
|
|
75
|
+
function emitLog(level, data) {
|
|
76
|
+
if (!pinoLogger) return;
|
|
77
|
+
const { trace, span, message, ...rest } = data;
|
|
78
|
+
pinoLogger[level]({
|
|
79
|
+
trace_id: trace || undefined,
|
|
80
|
+
span_id: span || undefined,
|
|
81
|
+
message: cleanString(message),
|
|
82
|
+
...rest,
|
|
83
|
+
});
|
|
158
84
|
}
|
|
159
85
|
|
|
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
86
|
function createLogger(config) {
|
|
173
87
|
if (loggerInstance) return loggerInstance;
|
|
174
|
-
|
|
175
88
|
setGlobalConfig(config);
|
|
176
|
-
|
|
177
|
-
initOTEL();
|
|
89
|
+
initPinoLogger();
|
|
178
90
|
|
|
179
91
|
loggerInstance = {
|
|
180
92
|
logInfo: (message, data = {}) => {
|
|
@@ -212,8 +124,9 @@ function createLogger(config) {
|
|
|
212
124
|
const redactedData = redactInformation(logData, {
|
|
213
125
|
sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
|
|
214
126
|
});
|
|
215
|
-
|
|
127
|
+
emitLog("info", redactedData);
|
|
216
128
|
},
|
|
129
|
+
|
|
217
130
|
logError: (data = {}, req = {}) => {
|
|
218
131
|
const traceContext = contextProvider();
|
|
219
132
|
const errorResponse = data?.error?.response;
|
|
@@ -231,30 +144,30 @@ function createLogger(config) {
|
|
|
231
144
|
message: JSON.stringify({
|
|
232
145
|
step: data?.step || "",
|
|
233
146
|
message: data?.error?.message,
|
|
234
|
-
method:
|
|
147
|
+
method: errorResponse?.config?.method?.toUpperCase(),
|
|
235
148
|
parameters: data?.parameters || "",
|
|
236
149
|
path: errorResponse?.config?.url || req?.originalUrl,
|
|
237
150
|
responseStatus: errorResponse?.status || HTTP_STATUS_CODES.BAD_REQUEST,
|
|
238
151
|
headers: errorResponse?.config?.headers || {},
|
|
239
152
|
requestBody: errorResponse?.config?.body || req?.body,
|
|
240
|
-
responseBody: errorResponse?.data || {},
|
|
153
|
+
responseBody: errorResponse?.data || {},
|
|
241
154
|
stack: data?.error?.stack,
|
|
242
155
|
tokenDetails: {
|
|
243
156
|
customerId: tokenData?.customerId || "",
|
|
244
157
|
mid: tokenData?.mid || "",
|
|
245
158
|
tenantId: tokenData?.tenantId || "",
|
|
246
|
-
},
|
|
159
|
+
},
|
|
247
160
|
}),
|
|
248
161
|
};
|
|
249
162
|
const redactedData = redactInformation(logData, {
|
|
250
163
|
sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
|
|
251
164
|
});
|
|
252
|
-
|
|
165
|
+
emitLog("error", redactedData);
|
|
253
166
|
},
|
|
167
|
+
|
|
254
168
|
logDebug: (message, data = {}) => {
|
|
255
|
-
|
|
169
|
+
emitLog("debug", { message, ...data });
|
|
256
170
|
},
|
|
257
|
-
shutdown, // Expose shutdown method
|
|
258
171
|
};
|
|
259
172
|
|
|
260
173
|
return loggerInstance;
|
|
@@ -264,5 +177,4 @@ module.exports = {
|
|
|
264
177
|
createLogger,
|
|
265
178
|
setContextProvider,
|
|
266
179
|
logger: loggerInstance,
|
|
267
|
-
shutdown, // Export shutdown for manual cleanup if needed
|
|
268
180
|
};
|
|
@@ -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
|
|