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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sasai-common-utils",
3
- "version": "1.0.45",
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 { trace, context } = require("@opentelemetry/api");
6
- const { LoggerProvider, BatchLogRecordProcessor } = require("@opentelemetry/sdk-logs");
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 initPino() {
35
- const pinoConfig = {
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 initOTEL() {
46
- if (!globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
47
- console.log("[Logger] OTLP endpoint not configured, skipping OTLP logs");
48
- return;
49
- }
50
-
51
- try {
52
- // Create resource with proper attributes
53
- const resource = resourceFromAttributes({
54
- [ATTR_SERVICE_NAME]: globalConfig.SERVICE_NAME,
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
- function sendLog(level, data) {
99
- // Send to Pino (console/file)
100
- if (pinoLogger) {
101
- pinoLogger[level](data);
102
- }
103
-
104
- // Send to OTLP
105
- if (otelLogger) {
106
- try {
107
- const activeSpan = trace.getSpan(context.active());
108
- const spanContext = activeSpan?.spanContext();
109
-
110
- // Use trace/span from data if available, otherwise try to get from active span
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
- // Add trace context to log record if available
128
- if (traceId) {
129
- logRecord.traceId = traceId;
130
- }
131
- if (spanId) {
132
- logRecord.spanId = spanId;
133
- }
134
- if (spanContext?.traceFlags !== undefined) {
135
- logRecord.traceFlags = spanContext.traceFlags;
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
- // Graceful shutdown
148
- async function shutdown() {
149
- if (otelLoggerProvider) {
150
- console.log("[Logger] Shutting down OTLP logger...");
151
- try {
152
- await otelLoggerProvider.shutdown();
153
- console.log("[Logger] OTLP logger shutdown complete");
154
- } catch (error) {
155
- console.error("[Logger] Error during OTLP shutdown:", error);
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
- initPino();
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
- sendLog("info", redactedData);
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: errorResponse?.config?.method?.toUpperCase(),
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
- sendLog("error", redactedData);
162
+ emitLog("error", redactedData);
253
163
  },
164
+
254
165
  logDebug: (message, data = {}) => {
255
- sendLog("debug", { message, ...data });
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 { resourceFromAttributes } = require("@opentelemetry/resources");
11
+ const { Resource } = require("@opentelemetry/resources");
12
12
 
13
- // Create OTLP Log Exporter with collector options
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 batch processor for efficient log export
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: resourceFromAttributes({
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 { 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
- };
83
+ const otelLogger = otelLoggerProvider.getLogger(globalConfig.SERVICE_NAME);
135
84
 
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,
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, error.stack);
100
+ console.error('Failed to send log to OpenTelemetry:', error.message);
160
101
  }
161
102
  }
162
103