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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sasai-common-utils",
3
- "version": "1.0.42",
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 { Worker } = require("worker_threads");
5
- const path = require("path");
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 sendToWorker(level, data) {
33
- if (loggerWorker) {
34
- loggerWorker.postMessage({ level, data });
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
- initLoggerWorker();
161
+ initPino();
162
+ initOTEL();
42
163
 
43
- return {
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
- sendToWorker("info", redactedData);
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
- sendToWorker("error", redactedData);
237
+ sendLog("error", redactedData);
117
238
  },
118
239
  logDebug: (message, data = {}) => {
119
- sendToWorker("debug", { message, ...data });
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 otelLogger = otelLoggerProvider.getLogger(globalConfig.SERVICE_NAME);
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 with proper trace context
136
+ // Prepare the log record according to OpenTelemetry Logs spec
91
137
  const logRecord = {
92
- severityNumber: pinoLevelToOtelSeverity(level),
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 at root level if available
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
- if (logData.span_id) {
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