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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sasai-common-utils",
3
- "version": "1.0.45",
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 { 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;
@@ -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 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);
29
+ function cleanString(str) {
30
+ return typeof str === "string" ? str.replace(/\\"/g, '"') : str;
43
31
  }
44
32
 
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,
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
- 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,
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
- // 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);
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
- // 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
- }
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
- initPino();
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
- sendLog("info", redactedData);
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: errorResponse?.config?.method?.toUpperCase(),
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
- sendLog("error", redactedData);
165
+ emitLog("error", redactedData);
253
166
  },
167
+
254
168
  logDebug: (message, data = {}) => {
255
- sendLog("debug", { message, ...data });
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 { 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