sasai-common-utils 1.0.44 → 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 -135
- 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,142 +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
|
-
severityNumber: getSeverityNumber(level),
|
|
112
|
-
severityText: level.toUpperCase(),
|
|
113
|
-
body: typeof data === "string" ? data : JSON.stringify(data),
|
|
114
|
-
attributes: {
|
|
115
|
-
"log.level": level,
|
|
116
|
-
...(data.trace && { "trace.id": data.trace }),
|
|
117
|
-
...(data.span && { "span.id": data.span }),
|
|
118
|
-
...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 ,
|
|
119
58
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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)
|
|
130
70
|
}
|
|
71
|
+
pinoLogger = pino(pinoOptions, destination);
|
|
131
72
|
}
|
|
132
73
|
}
|
|
133
74
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
}
|
|
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
|
+
});
|
|
145
84
|
}
|
|
146
85
|
|
|
147
|
-
process.on("beforeExit", () => {
|
|
148
|
-
shutdown().catch(console.error);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
process.on("SIGTERM", () => {
|
|
152
|
-
shutdown().then(() => process.exit(0));
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
process.on("SIGINT", () => {
|
|
156
|
-
shutdown().then(() => process.exit(0));
|
|
157
|
-
});
|
|
158
|
-
|
|
159
86
|
function createLogger(config) {
|
|
160
87
|
if (loggerInstance) return loggerInstance;
|
|
161
|
-
|
|
162
88
|
setGlobalConfig(config);
|
|
163
|
-
|
|
164
|
-
initOTEL();
|
|
89
|
+
initPinoLogger();
|
|
165
90
|
|
|
166
91
|
loggerInstance = {
|
|
167
92
|
logInfo: (message, data = {}) => {
|
|
@@ -199,8 +124,9 @@ function createLogger(config) {
|
|
|
199
124
|
const redactedData = redactInformation(logData, {
|
|
200
125
|
sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
|
|
201
126
|
});
|
|
202
|
-
|
|
127
|
+
emitLog("info", redactedData);
|
|
203
128
|
},
|
|
129
|
+
|
|
204
130
|
logError: (data = {}, req = {}) => {
|
|
205
131
|
const traceContext = contextProvider();
|
|
206
132
|
const errorResponse = data?.error?.response;
|
|
@@ -218,30 +144,30 @@ function createLogger(config) {
|
|
|
218
144
|
message: JSON.stringify({
|
|
219
145
|
step: data?.step || "",
|
|
220
146
|
message: data?.error?.message,
|
|
221
|
-
method:
|
|
147
|
+
method: errorResponse?.config?.method?.toUpperCase(),
|
|
222
148
|
parameters: data?.parameters || "",
|
|
223
149
|
path: errorResponse?.config?.url || req?.originalUrl,
|
|
224
150
|
responseStatus: errorResponse?.status || HTTP_STATUS_CODES.BAD_REQUEST,
|
|
225
151
|
headers: errorResponse?.config?.headers || {},
|
|
226
152
|
requestBody: errorResponse?.config?.body || req?.body,
|
|
227
|
-
responseBody: errorResponse?.data || {},
|
|
153
|
+
responseBody: errorResponse?.data || {},
|
|
228
154
|
stack: data?.error?.stack,
|
|
229
155
|
tokenDetails: {
|
|
230
156
|
customerId: tokenData?.customerId || "",
|
|
231
157
|
mid: tokenData?.mid || "",
|
|
232
158
|
tenantId: tokenData?.tenantId || "",
|
|
233
|
-
},
|
|
159
|
+
},
|
|
234
160
|
}),
|
|
235
161
|
};
|
|
236
162
|
const redactedData = redactInformation(logData, {
|
|
237
163
|
sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
|
|
238
164
|
});
|
|
239
|
-
|
|
165
|
+
emitLog("error", redactedData);
|
|
240
166
|
},
|
|
167
|
+
|
|
241
168
|
logDebug: (message, data = {}) => {
|
|
242
|
-
|
|
169
|
+
emitLog("debug", { message, ...data });
|
|
243
170
|
},
|
|
244
|
-
shutdown, // Expose shutdown method
|
|
245
171
|
};
|
|
246
172
|
|
|
247
173
|
return loggerInstance;
|
|
@@ -251,5 +177,4 @@ module.exports = {
|
|
|
251
177
|
createLogger,
|
|
252
178
|
setContextProvider,
|
|
253
179
|
logger: loggerInstance,
|
|
254
|
-
shutdown, // Export shutdown for manual cleanup if needed
|
|
255
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
|
|