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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sasai-common-utils",
3
- "version": "1.0.44",
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,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 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
- const logRecord = {
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
- ...(spanContext?.traceId && { traceId: spanContext.traceId }),
121
- ...(spanContext?.spanId && { spanId: spanContext.spanId }),
122
- ...(spanContext?.traceFlags !== undefined && {
123
- traceFlags: spanContext.traceFlags,
124
- }),
125
- };
126
-
127
- otelLogger.emit(logRecord);
128
- } catch (error) {
129
- 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)
130
70
  }
71
+ pinoLogger = pino(pinoOptions, destination);
131
72
  }
132
73
  }
133
74
 
134
- // Graceful shutdown
135
- async function shutdown() {
136
- if (otelLoggerProvider) {
137
- console.log("[Logger] Shutting down OTLP logger...");
138
- try {
139
- await otelLoggerProvider.shutdown();
140
- console.log("[Logger] OTLP logger shutdown complete");
141
- } catch (error) {
142
- console.error("[Logger] Error during OTLP shutdown:", error);
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
- initPino();
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
- sendLog("info", redactedData);
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: errorResponse?.config?.method?.toUpperCase(),
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
- sendLog("error", redactedData);
165
+ emitLog("error", redactedData);
240
166
  },
167
+
241
168
  logDebug: (message, data = {}) => {
242
- sendLog("debug", { message, ...data });
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 { 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