sasai-common-utils 1.0.41 → 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.41",
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,24 +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');
89
125
 
90
- // Emit log record to OpenTelemetry
91
- otelLogger.emit({
92
- severityNumber: pinoLevelToOtelSeverity(level),
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
+ };
135
+
136
+ // Prepare the log record according to OpenTelemetry Logs spec
137
+ const logRecord = {
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
- 'trace.id': logData.trace_id,
97
- 'span.id': logData.span_id,
98
143
  'service.name': globalConfig.SERVICE_NAME,
99
144
  ...logData
100
- },
101
- timestamp: Date.now() * 1000000, // Convert to nanoseconds
102
- });
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);
103
157
  } catch (error) {
104
158
  // Silently fail to avoid breaking the logging flow
105
- console.error('Failed to send log to OpenTelemetry:', error.message);
159
+ console.error('Failed to send log to OpenTelemetry:', error.message, error.stack);
106
160
  }
107
161
  }
108
162