sasai-common-utils 1.0.47 → 1.0.49

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.
@@ -1,177 +1,206 @@
1
- const jwt = require("jsonwebtoken");
2
- const pino = require("pino");
3
- const { HTTP_STATUS_CODES, DEBUG_MODES } = require("./constants");
4
- const { redactInformation } = require("./redact");
5
-
6
- let contextProvider = () => ({});
7
- let globalConfig = {};
8
- let loggerInstance;
9
- let pinoLogger;
10
-
11
- function setContextProvider(provider) {
12
- contextProvider = provider;
13
- }
14
-
15
- function setGlobalConfig(config) {
16
- globalConfig = {
17
- SERVICE_NAME: config?.SERVICE_NAME,
18
- LOG_LEVEL: config?.LOG_LEVEL || "info",
19
- DEBUG_MODE: config?.DEBUG_MODE,
20
- SENSITIVE_KEYS: config?.SENSITIVE_KEYS,
21
- NODE_ENV: config?.NODE_ENV,
22
- OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: config?.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
23
- };
24
- }
25
-
26
- function cleanString(str) {
27
- return typeof str === "string" ? str.replace(/\\"/g, '"') : str;
28
- }
29
-
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
- },
40
- };
41
-
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 ,
55
- },
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)
67
- }
68
- pinoLogger = pino(pinoOptions, destination);
69
- }
70
- }
71
-
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
- });
81
- }
82
-
83
- function createLogger(config) {
84
- if (loggerInstance) return loggerInstance;
85
- setGlobalConfig(config);
86
- initPinoLogger();
87
-
88
- loggerInstance = {
89
- logInfo: (message, data = {}) => {
90
- const traceContext = contextProvider();
91
- const authHeader =
92
- data?.headers?.authorization || data?.headers?.Authorization;
93
- const tokenData = jwt.decode(authHeader?.split(" ")[1], {
94
- complete: true,
95
- })?.payload;
96
-
97
- const logData = {
98
- trace: traceContext?.trace_id || "",
99
- span: traceContext?.span_id || "",
100
- thread: "",
101
- class: "",
102
- message: JSON.stringify({
103
- step: data?.step,
104
- message,
105
- method: data?.method?.toUpperCase() || "",
106
- parameters: data?.parameters || "",
107
- path: data?.url || "",
108
- headers: data?.headers || {},
109
- responseHeaders: data?.responseHeaders || {},
110
- api: data?.api || "",
111
- tokenDetails: {
112
- customerId: tokenData?.customerId || "",
113
- mid: tokenData?.mid || "",
114
- tenantId: tokenData?.tenantId || "",
115
- },
116
- responseStatus: data?.statusCode || "",
117
- requestBody: data?.requestBody || {},
118
- responseBody: data?.responseBody || {},
119
- }),
120
- };
121
- const redactedData = redactInformation(logData, {
122
- sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
123
- });
124
- emitLog("info", redactedData);
125
- },
126
-
127
- logError: (data = {}, req = {}) => {
128
- const traceContext = contextProvider();
129
- const errorResponse = data?.error?.response;
130
- const authHeader =
131
- data?.headers?.authorization || data?.headers?.Authorization;
132
- const tokenData = jwt.decode(authHeader?.split(" ")[1], {
133
- complete: true,
134
- })?.payload;
135
-
136
- const logData = {
137
- trace: traceContext?.trace_id || "",
138
- span: traceContext?.span_id || "",
139
- thread: "",
140
- class: "",
141
- message: JSON.stringify({
142
- step: data?.step || "",
143
- message: data?.error?.message,
144
- method: errorResponse?.config?.method?.toUpperCase(),
145
- parameters: data?.parameters || "",
146
- path: errorResponse?.config?.url || req?.originalUrl,
147
- responseStatus: errorResponse?.status || HTTP_STATUS_CODES.BAD_REQUEST,
148
- headers: errorResponse?.config?.headers || {},
149
- requestBody: errorResponse?.config?.body || req?.body,
150
- responseBody: errorResponse?.data || {},
151
- stack: data?.error?.stack,
152
- tokenDetails: {
153
- customerId: tokenData?.customerId || "",
154
- mid: tokenData?.mid || "",
155
- tenantId: tokenData?.tenantId || "",
156
- },
157
- }),
158
- };
159
- const redactedData = redactInformation(logData, {
160
- sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
161
- });
162
- emitLog("error", redactedData);
163
- },
164
-
165
- logDebug: (message, data = {}) => {
166
- emitLog("debug", { message, ...data });
167
- },
168
- };
169
-
170
- return loggerInstance;
171
- }
172
-
173
- module.exports = {
174
- createLogger,
175
- setContextProvider,
176
- logger: loggerInstance,
177
- };
1
+ const jwt = require("jsonwebtoken");
2
+ const pino = require("pino");
3
+ const { HTTP_STATUS_CODES, DEBUG_MODES } = require("./constants");
4
+ const { redactInformation } = require("./redact");
5
+
6
+ let contextProvider = () => ({});
7
+ let globalConfig = {};
8
+ let loggerInstance;
9
+ let pinoLogger;
10
+
11
+ function setContextProvider(provider) {
12
+ contextProvider = provider;
13
+ }
14
+
15
+ function setGlobalConfig(config) {
16
+ globalConfig = {
17
+ SERVICE_NAME: config?.SERVICE_NAME,
18
+ LOG_LEVEL: config?.LOG_LEVEL || "info",
19
+ DEBUG_MODE: config?.DEBUG_MODE,
20
+ SENSITIVE_KEYS: config?.SENSITIVE_KEYS,
21
+ NODE_ENV: config?.NODE_ENV,
22
+ OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: config?.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
23
+ OTEL_EXPORTER_OTLP_LOGS_PROTOCOL:
24
+ config?.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL || "http/protobuf",
25
+ };
26
+ }
27
+
28
+ function cleanString(str) {
29
+ return typeof str === "string" ? str.replace(/\\"/g, '"') : str;
30
+ }
31
+
32
+ function initPinoLogger() {
33
+ const pinoOptions = {
34
+ timestamp: false,
35
+ level:
36
+ globalConfig.DEBUG_MODE === DEBUG_MODES.DISABLE
37
+ ? "silent"
38
+ : globalConfig.LOG_LEVEL,
39
+ base: {
40
+ service_name: globalConfig.SERVICE_NAME,
41
+ environment: globalConfig.NODE_ENV || "unknown",
42
+ },
43
+ formatters: {
44
+ level(label) {
45
+ return { level_name: label.toUpperCase() };
46
+ },
47
+ },
48
+ };
49
+
50
+ if (globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
51
+ const stdoutTarget = {
52
+ target: require.resolve("pino/file"),
53
+ options: { destination: 1 },
54
+ };
55
+
56
+ const otelTarget = {
57
+ target: require.resolve("pino-opentelemetry-transport"),
58
+ options: {
59
+ url: globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
60
+ protocol: globalConfig.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
61
+ resourceAttributes: {
62
+ "service.name": globalConfig.SERVICE_NAME || "bff-service",
63
+ "deployment.environment": globalConfig.NODE_ENV || "unknown",
64
+ },
65
+ },
66
+ };
67
+
68
+ const transport = pino.transport({
69
+ targets: [stdoutTarget, otelTarget],
70
+ });
71
+
72
+ transport.on("error", (err) => {
73
+ console.error("Pino OTEL transport error:", err);
74
+ });
75
+
76
+ pinoLogger = pino(pinoOptions, transport);
77
+ } else {
78
+ let destination;
79
+ if (globalConfig.DEBUG_MODE === DEBUG_MODES.FILE) {
80
+ destination = pino.destination("app.log");
81
+ } else {
82
+ destination = pino.destination(1);
83
+ }
84
+ pinoLogger = pino(pinoOptions, destination);
85
+ }
86
+ }
87
+
88
+ function emitLog(level, data) {
89
+ if (!pinoLogger) return;
90
+ const { trace, span, message, ...rest } = data;
91
+ pinoLogger[level]({
92
+ trace_id: trace || undefined,
93
+ span_id: span || undefined,
94
+ message: cleanString(message),
95
+ ...rest,
96
+ });
97
+ }
98
+
99
+ function createLogger(config) {
100
+ if (loggerInstance) return loggerInstance;
101
+
102
+ setGlobalConfig(config);
103
+ initPinoLogger();
104
+
105
+ loggerInstance = {
106
+ logInfo: (message, data = {}) => {
107
+ const traceContext = contextProvider();
108
+ const authHeader =
109
+ data?.headers?.authorization || data?.headers?.Authorization;
110
+ const tokenData = jwt.decode(authHeader?.split(" ")[1], {
111
+ complete: true,
112
+ })?.payload;
113
+
114
+ const logData = {
115
+ trace: traceContext?.trace_id || "",
116
+ span: traceContext?.span_id || "",
117
+ thread: "",
118
+ class: "",
119
+ message: JSON.stringify({
120
+ step: data?.step,
121
+ message,
122
+ method: data?.method?.toUpperCase() || "",
123
+ parameters: data?.parameters || "",
124
+ path: data?.url || "",
125
+ headers: data?.headers || {},
126
+ responseHeaders: data?.responseHeaders || {},
127
+ api: data?.api || "",
128
+ tokenDetails: {
129
+ customerId: tokenData?.customerId || "",
130
+ mid: tokenData?.mid || "",
131
+ tenantId: tokenData?.tenantId || "",
132
+ },
133
+ responseStatus: data?.statusCode || "",
134
+ requestBody: data?.requestBody || {},
135
+ responseBody: data?.responseBody || {},
136
+ }),
137
+ };
138
+
139
+ const redactedData = redactInformation(logData, {
140
+ sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
141
+ });
142
+
143
+ emitLog("info", redactedData);
144
+ },
145
+
146
+ logError: (data = {}, req = {}) => {
147
+ const traceContext = contextProvider();
148
+ const errorResponse = data?.error?.response;
149
+ const authHeader =
150
+ data?.headers?.authorization || data?.headers?.Authorization;
151
+ const tokenData = jwt.decode(authHeader?.split(" ")[1], {
152
+ complete: true,
153
+ })?.payload;
154
+
155
+ const logData = {
156
+ trace: traceContext?.trace_id || "",
157
+ span: traceContext?.span_id || "",
158
+ thread: "",
159
+ class: "",
160
+ message: JSON.stringify({
161
+ step: data?.step || "",
162
+ message: data?.error?.message,
163
+ method: errorResponse?.config?.method?.toUpperCase(),
164
+ parameters: data?.parameters || "",
165
+ path: errorResponse?.config?.url || req?.originalUrl,
166
+ responseStatus:
167
+ errorResponse?.status || HTTP_STATUS_CODES.BAD_REQUEST,
168
+ headers: errorResponse?.config?.headers || {},
169
+ requestBody: errorResponse?.config?.body || req?.body,
170
+ responseBody: errorResponse?.data || {},
171
+ stack: data?.error?.stack,
172
+ tokenDetails: {
173
+ customerId: tokenData?.customerId || "",
174
+ mid: tokenData?.mid || "",
175
+ tenantId: tokenData?.tenantId || "",
176
+ },
177
+ }),
178
+ };
179
+
180
+ const redactedData = redactInformation(logData, {
181
+ sensitiveKeys: globalConfig?.SENSITIVE_KEYS,
182
+ });
183
+
184
+ emitLog("error", redactedData);
185
+ },
186
+
187
+ logDebug: (message, data = {}) => {
188
+ const traceContext = contextProvider();
189
+ emitLog("debug", {
190
+ trace: traceContext?.trace_id || "",
191
+ span: traceContext?.span_id || "",
192
+ message:
193
+ typeof message === "string" ? message : JSON.stringify(message),
194
+ ...data,
195
+ });
196
+ },
197
+ };
198
+
199
+ return loggerInstance;
200
+ }
201
+
202
+ module.exports = {
203
+ createLogger,
204
+ setContextProvider,
205
+ logger: loggerInstance,
206
+ };
@@ -1,119 +1,119 @@
1
- const { parentPort, workerData } = require("worker_threads");
2
- const pino = require("pino");
3
- const { DEBUG_MODES } = require("./constants");
4
- const globalConfig = workerData.globalConfig;
5
-
6
- // OpenTelemetry Logs SDK setup
7
- let otelLoggerProvider = null;
8
- try {
9
- const { LoggerProvider, BatchLogRecordProcessor } = require("@opentelemetry/sdk-logs");
10
- const { OTLPLogExporter } = require("@opentelemetry/exporter-logs-otlp-http");
11
- const { Resource } = require("@opentelemetry/resources");
12
-
13
- // Create OTLP Log Exporter
14
- const logExporter = new OTLPLogExporter({
15
- url: globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || 'http://localhost:4318/v1/logs'
16
- });
17
-
18
- // Create Logger Provider with resource attributes
19
- otelLoggerProvider = new LoggerProvider({
20
- resource: new Resource({
21
- 'service.name': globalConfig.SERVICE_NAME || 'sasai-service',
22
- 'deployment.environment': globalConfig.NODE_ENV || ''
23
- })
24
- });
25
-
26
- // Add batch processor for efficient log export
27
- otelLoggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
28
-
29
- console.log('✅ OpenTelemetry Logs SDK initialized');
30
- } catch (error) {
31
- console.warn('⚠️ OpenTelemetry Logs SDK not available, logs will only write locally:', error.message);
32
- }
33
-
34
- // Initialize Pino logger inside the worker
35
- function cleanString(str) {
36
- return typeof str === "string"
37
- ? str.replace(/\\"/g, '"')
38
- : str;
39
- }
40
- const transports = []
41
- if (globalConfig.DEBUG_MODE === DEBUG_MODES.CONSOLE) {
42
- transports.push(pino.destination(1));
43
- } else if (globalConfig.DEBUG_MODE === DEBUG_MODES.FILE) {
44
- transports.push(pino.destination("app.log"));
45
- } else {
46
- pino.destination(1);
47
- pino.destination("app.log");
48
- }
49
- const logger = pino({
50
- timestamp: false,
51
- level:globalConfig.DEBUG_MODE == DEBUG_MODES.DISABLE ? 'silent': globalConfig.LOG_LEVEL,
52
-
53
- base: {
54
- service_name: globalConfig.SERVICE_NAME
55
- },
56
- formatters: {
57
- level() {
58
- return {
59
- level_name: globalConfig.LOG_LEVEL.toUpperCase()
60
- };
61
- }
62
- }
63
- }, transports.length ? transports[0] : undefined)
64
-
65
- // Helper function to convert Pino log level to OpenTelemetry severity
66
- function pinoLevelToOtelSeverity(pinoLevel) {
67
- const levelMap = {
68
- 'trace': 1, // TRACE
69
- 'debug': 5, // DEBUG
70
- 'info': 9, // INFO
71
- 'warn': 13, // WARN
72
- 'error': 17, // ERROR
73
- 'fatal': 21 // FATAL
74
- };
75
- return levelMap[pinoLevel] || 9; // Default to INFO
76
- }
77
-
78
- // Helper function to send logs to OpenTelemetry
79
- function sendToOtel(level, logData) {
80
- if (!otelLoggerProvider) return;
81
-
82
- try {
83
- const otelLogger = otelLoggerProvider.getLogger(globalConfig.SERVICE_NAME);
84
-
85
- // Emit log record to OpenTelemetry
86
- otelLogger.emit({
87
- severityNumber: pinoLevelToOtelSeverity(level),
88
- severityText: level.toUpperCase(),
89
- body: logData.message || JSON.stringify(logData),
90
- attributes: {
91
- 'trace.id': logData.trace_id,
92
- 'span.id': logData.span_id,
93
- 'service.name': globalConfig.SERVICE_NAME,
94
- ...logData
95
- },
96
- timestamp: Date.now() * 1000000, // Convert to nanoseconds
97
- });
98
- } catch (error) {
99
- // Silently fail to avoid breaking the logging flow
100
- console.error('Failed to send log to OpenTelemetry:', error.message);
101
- }
102
- }
103
-
104
- // Listen for log messages from the main thread
105
- parentPort.on("message", ({ level, data }) => {
106
- const { trace, span, message, ...rest } = data;
107
- const logData = {
108
- trace_id: trace || undefined,
109
- span_id: span || undefined,
110
- message: cleanString(message),
111
- ...rest
112
- };
113
-
114
- // Write to Pino (console/file)
115
- logger[level](logData);
116
-
117
- // Also send to OpenTelemetry if available
118
- sendToOtel(level, logData);
119
- });
1
+ const { parentPort, workerData } = require("worker_threads");
2
+ const pino = require("pino");
3
+ const { DEBUG_MODES } = require("./constants");
4
+ const globalConfig = workerData.globalConfig;
5
+
6
+ // OpenTelemetry Logs SDK setup
7
+ let otelLoggerProvider = null;
8
+ try {
9
+ const { LoggerProvider, BatchLogRecordProcessor } = require("@opentelemetry/sdk-logs");
10
+ const { OTLPLogExporter } = require("@opentelemetry/exporter-logs-otlp-http");
11
+ const { Resource } = require("@opentelemetry/resources");
12
+
13
+ // Create OTLP Log Exporter
14
+ const logExporter = new OTLPLogExporter({
15
+ url: globalConfig.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || 'http://localhost:4318/v1/logs'
16
+ });
17
+
18
+ // Create Logger Provider with resource attributes
19
+ otelLoggerProvider = new LoggerProvider({
20
+ resource: new Resource({
21
+ 'service.name': globalConfig.SERVICE_NAME || 'sasai-service',
22
+ 'deployment.environment': globalConfig.NODE_ENV || ''
23
+ })
24
+ });
25
+
26
+ // Add batch processor for efficient log export
27
+ otelLoggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
28
+
29
+ console.log('✅ OpenTelemetry Logs SDK initialized');
30
+ } catch (error) {
31
+ console.warn('⚠️ OpenTelemetry Logs SDK not available, logs will only write locally:', error.message);
32
+ }
33
+
34
+ // Initialize Pino logger inside the worker
35
+ function cleanString(str) {
36
+ return typeof str === "string"
37
+ ? str.replace(/\\"/g, '"')
38
+ : str;
39
+ }
40
+ const transports = []
41
+ if (globalConfig.DEBUG_MODE === DEBUG_MODES.CONSOLE) {
42
+ transports.push(pino.destination(1));
43
+ } else if (globalConfig.DEBUG_MODE === DEBUG_MODES.FILE) {
44
+ transports.push(pino.destination("app.log"));
45
+ } else {
46
+ pino.destination(1);
47
+ pino.destination("app.log");
48
+ }
49
+ const logger = pino({
50
+ timestamp: false,
51
+ level:globalConfig.DEBUG_MODE == DEBUG_MODES.DISABLE ? 'silent': globalConfig.LOG_LEVEL,
52
+
53
+ base: {
54
+ service_name: globalConfig.SERVICE_NAME
55
+ },
56
+ formatters: {
57
+ level() {
58
+ return {
59
+ level_name: globalConfig.LOG_LEVEL.toUpperCase()
60
+ };
61
+ }
62
+ }
63
+ }, transports.length ? transports[0] : undefined)
64
+
65
+ // Helper function to convert Pino log level to OpenTelemetry severity
66
+ function pinoLevelToOtelSeverity(pinoLevel) {
67
+ const levelMap = {
68
+ 'trace': 1, // TRACE
69
+ 'debug': 5, // DEBUG
70
+ 'info': 9, // INFO
71
+ 'warn': 13, // WARN
72
+ 'error': 17, // ERROR
73
+ 'fatal': 21 // FATAL
74
+ };
75
+ return levelMap[pinoLevel] || 9; // Default to INFO
76
+ }
77
+
78
+ // Helper function to send logs to OpenTelemetry
79
+ function sendToOtel(level, logData) {
80
+ if (!otelLoggerProvider) return;
81
+
82
+ try {
83
+ const otelLogger = otelLoggerProvider.getLogger(globalConfig.SERVICE_NAME);
84
+
85
+ // Emit log record to OpenTelemetry
86
+ otelLogger.emit({
87
+ severityNumber: pinoLevelToOtelSeverity(level),
88
+ severityText: level.toUpperCase(),
89
+ body: logData.message || JSON.stringify(logData),
90
+ attributes: {
91
+ 'trace.id': logData.trace_id,
92
+ 'span.id': logData.span_id,
93
+ 'service.name': globalConfig.SERVICE_NAME,
94
+ ...logData
95
+ },
96
+ timestamp: Date.now() * 1000000, // Convert to nanoseconds
97
+ });
98
+ } catch (error) {
99
+ // Silently fail to avoid breaking the logging flow
100
+ console.error('Failed to send log to OpenTelemetry:', error.message);
101
+ }
102
+ }
103
+
104
+ // Listen for log messages from the main thread
105
+ parentPort.on("message", ({ level, data }) => {
106
+ const { trace, span, message, ...rest } = data;
107
+ const logData = {
108
+ trace_id: trace || undefined,
109
+ span_id: span || undefined,
110
+ message: cleanString(message),
111
+ ...rest
112
+ };
113
+
114
+ // Write to Pino (console/file)
115
+ logger[level](logData);
116
+
117
+ // Also send to OpenTelemetry if available
118
+ sendToOtel(level, logData);
119
+ });