@swirepay-developer/common-logging-nodejs 1.0.0
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/README.md +37 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +18 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +17 -0
- package/dist/logger.d.ts +64 -0
- package/dist/logger.js +305 -0
- package/dist/loki-transport.d.ts +18 -0
- package/dist/loki-transport.js +118 -0
- package/dist/otel-config.d.ts +25 -0
- package/dist/otel-config.js +49 -0
- package/dist/otel-init.d.ts +14 -0
- package/dist/otel-init.js +89 -0
- package/dist/otel-transport.d.ts +18 -0
- package/dist/otel-transport.js +125 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Swirepay Common Logging - Node.js SDK
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @swirepay/common-logging-nodejs
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const { initializeLogger, getLogger } = require('@swirepay/common-logging-nodejs');
|
|
13
|
+
|
|
14
|
+
// OpenTelemetry is automatically initialized
|
|
15
|
+
initializeLogger({
|
|
16
|
+
environment: 'staging',
|
|
17
|
+
serviceName: 'my-service',
|
|
18
|
+
serviceVersion: '1.0.0',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const logger = getLogger();
|
|
22
|
+
logger.info('Service started');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Complete Documentation
|
|
26
|
+
|
|
27
|
+
See [../USAGE_GUIDE.md](../USAGE_GUIDE.md) for complete examples including:
|
|
28
|
+
- Basic logging
|
|
29
|
+
- Express.js integration
|
|
30
|
+
- API call logging
|
|
31
|
+
- Request logging
|
|
32
|
+
- Webhook logging
|
|
33
|
+
- TypeScript examples
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
Proprietary - Swirepay
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface LoggingConfig {
|
|
2
|
+
environment: 'staging' | 'production';
|
|
3
|
+
serviceName: string;
|
|
4
|
+
serviceVersion: string;
|
|
5
|
+
lokiUrl?: string;
|
|
6
|
+
enableConsole?: boolean;
|
|
7
|
+
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
8
|
+
}
|
|
9
|
+
export declare function getLokiUrl(environment: 'staging' | 'production'): string;
|
|
10
|
+
export declare function getDefaultConfig(): Partial<LoggingConfig>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLokiUrl = getLokiUrl;
|
|
4
|
+
exports.getDefaultConfig = getDefaultConfig;
|
|
5
|
+
const STAG_LOKI_URL = 'https://stag-loki.swirepay.com:443/loki/api/v1/push';
|
|
6
|
+
const PROD_LOKI_URL = 'https://loki.swirepay.com:443/loki/api/v1/push';
|
|
7
|
+
function getLokiUrl(environment) {
|
|
8
|
+
return environment === 'production' ? PROD_LOKI_URL : STAG_LOKI_URL;
|
|
9
|
+
}
|
|
10
|
+
function getDefaultConfig() {
|
|
11
|
+
return {
|
|
12
|
+
environment: process.env.ENVIRONMENT || 'staging',
|
|
13
|
+
serviceName: process.env.SERVICE_NAME || 'swirepay-service',
|
|
14
|
+
serviceVersion: process.env.SERVICE_VERSION || '1.0.0',
|
|
15
|
+
enableConsole: process.env.ENABLE_CONSOLE_LOG === 'true',
|
|
16
|
+
logLevel: process.env.LOG_LEVEL || 'info',
|
|
17
|
+
};
|
|
18
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SwirepayLogger, initializeLogger, getLogger, TraceContext } from './logger';
|
|
2
|
+
export { LoggingConfig, getLokiUrl, getDefaultConfig } from './config';
|
|
3
|
+
export { LokiTransport } from './loki-transport';
|
|
4
|
+
export { OTEL_ENDPOINTS, getOtelEndpoint } from './otel-config';
|
|
5
|
+
export { initOpenTelemetry, OpenTelemetryConfig } from './otel-init';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initOpenTelemetry = exports.getOtelEndpoint = exports.OTEL_ENDPOINTS = exports.LokiTransport = exports.getDefaultConfig = exports.getLokiUrl = exports.getLogger = exports.initializeLogger = exports.SwirepayLogger = void 0;
|
|
4
|
+
var logger_1 = require("./logger");
|
|
5
|
+
Object.defineProperty(exports, "SwirepayLogger", { enumerable: true, get: function () { return logger_1.SwirepayLogger; } });
|
|
6
|
+
Object.defineProperty(exports, "initializeLogger", { enumerable: true, get: function () { return logger_1.initializeLogger; } });
|
|
7
|
+
Object.defineProperty(exports, "getLogger", { enumerable: true, get: function () { return logger_1.getLogger; } });
|
|
8
|
+
var config_1 = require("./config");
|
|
9
|
+
Object.defineProperty(exports, "getLokiUrl", { enumerable: true, get: function () { return config_1.getLokiUrl; } });
|
|
10
|
+
Object.defineProperty(exports, "getDefaultConfig", { enumerable: true, get: function () { return config_1.getDefaultConfig; } });
|
|
11
|
+
var loki_transport_1 = require("./loki-transport");
|
|
12
|
+
Object.defineProperty(exports, "LokiTransport", { enumerable: true, get: function () { return loki_transport_1.LokiTransport; } });
|
|
13
|
+
var otel_config_1 = require("./otel-config");
|
|
14
|
+
Object.defineProperty(exports, "OTEL_ENDPOINTS", { enumerable: true, get: function () { return otel_config_1.OTEL_ENDPOINTS; } });
|
|
15
|
+
Object.defineProperty(exports, "getOtelEndpoint", { enumerable: true, get: function () { return otel_config_1.getOtelEndpoint; } });
|
|
16
|
+
var otel_init_1 = require("./otel-init");
|
|
17
|
+
Object.defineProperty(exports, "initOpenTelemetry", { enumerable: true, get: function () { return otel_init_1.initOpenTelemetry; } });
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { LoggingConfig } from './config';
|
|
2
|
+
export interface TraceContext {
|
|
3
|
+
traceId: string;
|
|
4
|
+
spanId: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class SwirepayLogger {
|
|
7
|
+
private logger;
|
|
8
|
+
private config;
|
|
9
|
+
constructor(config?: Partial<LoggingConfig>);
|
|
10
|
+
/**
|
|
11
|
+
* Generate a new trace context with unique trace-id and span-id
|
|
12
|
+
* @returns New trace context with 32-char hex trace-id and 16-char hex span-id
|
|
13
|
+
*/
|
|
14
|
+
generateTraceContext(): TraceContext;
|
|
15
|
+
/**
|
|
16
|
+
* Get trace context from OpenTelemetry active span or create a new span
|
|
17
|
+
* @param customTraceContext Optional custom trace context to use instead of OpenTelemetry
|
|
18
|
+
* @returns Trace context object
|
|
19
|
+
*/
|
|
20
|
+
private getTraceContext;
|
|
21
|
+
private log;
|
|
22
|
+
debug(message: string, meta?: any, traceContext?: TraceContext): void;
|
|
23
|
+
info(message: string, meta?: any, traceContext?: TraceContext): void;
|
|
24
|
+
warn(message: string, meta?: any, traceContext?: TraceContext): void;
|
|
25
|
+
error(message: string, error?: Error | any, meta?: any, traceContext?: TraceContext): void;
|
|
26
|
+
logApiCall(options: {
|
|
27
|
+
method: string;
|
|
28
|
+
url: string;
|
|
29
|
+
statusCode?: number;
|
|
30
|
+
duration?: number;
|
|
31
|
+
requestBody?: any;
|
|
32
|
+
responseBody?: any;
|
|
33
|
+
headers?: Record<string, string>;
|
|
34
|
+
error?: Error;
|
|
35
|
+
traceContext?: TraceContext;
|
|
36
|
+
}): void;
|
|
37
|
+
logRequest(options: {
|
|
38
|
+
method: string;
|
|
39
|
+
path: string;
|
|
40
|
+
statusCode?: number;
|
|
41
|
+
duration?: number;
|
|
42
|
+
userId?: string;
|
|
43
|
+
ip?: string;
|
|
44
|
+
userAgent?: string;
|
|
45
|
+
requestBody?: any;
|
|
46
|
+
responseBody?: any;
|
|
47
|
+
headers?: Record<string, string>;
|
|
48
|
+
error?: Error;
|
|
49
|
+
traceContext?: TraceContext;
|
|
50
|
+
}): void;
|
|
51
|
+
logWebhook(options: {
|
|
52
|
+
source: string;
|
|
53
|
+
event: string;
|
|
54
|
+
webhookId?: string;
|
|
55
|
+
status: 'received' | 'processed' | 'failed';
|
|
56
|
+
payload?: any;
|
|
57
|
+
error?: Error;
|
|
58
|
+
processingTime?: number;
|
|
59
|
+
traceContext?: TraceContext;
|
|
60
|
+
}): void;
|
|
61
|
+
private sanitizeBody;
|
|
62
|
+
}
|
|
63
|
+
export declare function initializeLogger(config?: Partial<LoggingConfig>): SwirepayLogger;
|
|
64
|
+
export declare function getLogger(): SwirepayLogger;
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SwirepayLogger = void 0;
|
|
7
|
+
exports.initializeLogger = initializeLogger;
|
|
8
|
+
exports.getLogger = getLogger;
|
|
9
|
+
const winston_1 = __importDefault(require("winston"));
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const loki_transport_1 = require("./loki-transport");
|
|
12
|
+
const otel_transport_1 = require("./otel-transport");
|
|
13
|
+
const api_1 = require("@opentelemetry/api");
|
|
14
|
+
const otel_init_1 = require("./otel-init");
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
class SwirepayLogger {
|
|
17
|
+
constructor(config = {}) {
|
|
18
|
+
const defaultConfig = (0, config_1.getDefaultConfig)();
|
|
19
|
+
this.config = { ...defaultConfig, ...config };
|
|
20
|
+
const lokiUrl = this.config.lokiUrl || (0, config_1.getLokiUrl)(this.config.environment);
|
|
21
|
+
const transports = [
|
|
22
|
+
// Always send to Loki
|
|
23
|
+
new loki_transport_1.LokiTransport({
|
|
24
|
+
lokiUrl,
|
|
25
|
+
environment: this.config.environment,
|
|
26
|
+
serviceName: this.config.serviceName,
|
|
27
|
+
serviceVersion: this.config.serviceVersion,
|
|
28
|
+
}),
|
|
29
|
+
// Automatically send to OpenTelemetry if initialized
|
|
30
|
+
new otel_transport_1.OtelTransport({
|
|
31
|
+
environment: this.config.environment,
|
|
32
|
+
serviceName: this.config.serviceName,
|
|
33
|
+
serviceVersion: this.config.serviceVersion,
|
|
34
|
+
}),
|
|
35
|
+
];
|
|
36
|
+
if (this.config.enableConsole) {
|
|
37
|
+
transports.push(new winston_1.default.transports.Console({
|
|
38
|
+
format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.json()),
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
this.logger = winston_1.default.createLogger({
|
|
42
|
+
level: this.config.logLevel || 'info',
|
|
43
|
+
format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.json()),
|
|
44
|
+
transports,
|
|
45
|
+
defaultMeta: {
|
|
46
|
+
service: this.config.serviceName,
|
|
47
|
+
version: this.config.serviceVersion,
|
|
48
|
+
environment: this.config.environment,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate a new trace context with unique trace-id and span-id
|
|
54
|
+
* @returns New trace context with 32-char hex trace-id and 16-char hex span-id
|
|
55
|
+
*/
|
|
56
|
+
generateTraceContext() {
|
|
57
|
+
return {
|
|
58
|
+
traceId: (0, crypto_1.randomBytes)(16).toString('hex'), // 32 character hex string
|
|
59
|
+
spanId: (0, crypto_1.randomBytes)(8).toString('hex'), // 16 character hex string
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get trace context from OpenTelemetry active span or create a new span
|
|
64
|
+
* @param customTraceContext Optional custom trace context to use instead of OpenTelemetry
|
|
65
|
+
* @returns Trace context object
|
|
66
|
+
*/
|
|
67
|
+
getTraceContext(customTraceContext) {
|
|
68
|
+
// If custom trace context is provided, use it
|
|
69
|
+
if (customTraceContext) {
|
|
70
|
+
return customTraceContext;
|
|
71
|
+
}
|
|
72
|
+
// Try to get from OpenTelemetry active span
|
|
73
|
+
const activeSpan = api_1.trace.getActiveSpan();
|
|
74
|
+
if (activeSpan) {
|
|
75
|
+
const spanContext = activeSpan.spanContext();
|
|
76
|
+
// Check if trace ID is valid (not all zeros)
|
|
77
|
+
if (spanContext.traceId && spanContext.traceId !== '00000000000000000000000000000000') {
|
|
78
|
+
return {
|
|
79
|
+
traceId: spanContext.traceId,
|
|
80
|
+
spanId: spanContext.spanId,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// If no active span, create a new one for this log
|
|
85
|
+
// This ensures we always have trace context even if called outside a request context
|
|
86
|
+
try {
|
|
87
|
+
const tracer = api_1.trace.getTracer(this.config.serviceName, this.config.serviceVersion);
|
|
88
|
+
const span = tracer.startSpan('swirepay-log');
|
|
89
|
+
const spanContext = span.spanContext();
|
|
90
|
+
span.end();
|
|
91
|
+
if (spanContext.traceId && spanContext.traceId !== '00000000000000000000000000000000') {
|
|
92
|
+
return {
|
|
93
|
+
traceId: spanContext.traceId,
|
|
94
|
+
spanId: spanContext.spanId,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// If OpenTelemetry isn't ready yet, fall back to generated trace context
|
|
100
|
+
}
|
|
101
|
+
// Fallback: generate new trace context
|
|
102
|
+
return this.generateTraceContext();
|
|
103
|
+
}
|
|
104
|
+
log(level, message, meta = {}, traceContext) {
|
|
105
|
+
const trace = this.getTraceContext(traceContext);
|
|
106
|
+
this.logger.log(level, message, {
|
|
107
|
+
...meta,
|
|
108
|
+
...trace,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
debug(message, meta, traceContext) {
|
|
112
|
+
this.log('debug', message, meta, traceContext);
|
|
113
|
+
}
|
|
114
|
+
info(message, meta, traceContext) {
|
|
115
|
+
this.log('info', message, meta, traceContext);
|
|
116
|
+
}
|
|
117
|
+
warn(message, meta, traceContext) {
|
|
118
|
+
this.log('warn', message, meta, traceContext);
|
|
119
|
+
}
|
|
120
|
+
error(message, error, meta, traceContext) {
|
|
121
|
+
const errorMeta = {
|
|
122
|
+
...meta,
|
|
123
|
+
error: {
|
|
124
|
+
message: error?.message || String(error),
|
|
125
|
+
stack: error?.stack,
|
|
126
|
+
name: error?.name,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
this.log('error', message || error?.message || 'Error occurred', errorMeta, traceContext);
|
|
130
|
+
}
|
|
131
|
+
logApiCall(options) {
|
|
132
|
+
const { method, url, statusCode, duration, requestBody, responseBody, headers, error, traceContext } = options;
|
|
133
|
+
const trace = this.getTraceContext(traceContext);
|
|
134
|
+
const logData = {
|
|
135
|
+
method: method.toUpperCase(),
|
|
136
|
+
url,
|
|
137
|
+
eventType: 'api_call',
|
|
138
|
+
apiCall: {
|
|
139
|
+
method: method.toUpperCase(),
|
|
140
|
+
url,
|
|
141
|
+
statusCode,
|
|
142
|
+
duration,
|
|
143
|
+
timestamp: new Date().toISOString(),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
if (statusCode)
|
|
147
|
+
logData.statusCode = statusCode;
|
|
148
|
+
if (duration !== undefined)
|
|
149
|
+
logData.apiCall.duration_ms = duration;
|
|
150
|
+
if (headers)
|
|
151
|
+
logData.apiCall.headers = headers;
|
|
152
|
+
if (requestBody)
|
|
153
|
+
logData.apiCall.request_body = this.sanitizeBody(requestBody);
|
|
154
|
+
if (responseBody)
|
|
155
|
+
logData.apiCall.response_body = this.sanitizeBody(responseBody);
|
|
156
|
+
if (error) {
|
|
157
|
+
logData.error = {
|
|
158
|
+
message: error.message,
|
|
159
|
+
stack: error.stack,
|
|
160
|
+
name: error.name,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const level = error || (statusCode && statusCode >= 400) ? 'error' : 'info';
|
|
164
|
+
const message = `API Call: ${method.toUpperCase()} ${url}${statusCode ? ` - ${statusCode}` : ''}${duration ? ` (${duration}ms)` : ''}`;
|
|
165
|
+
this.logger.log(level, message, {
|
|
166
|
+
...logData,
|
|
167
|
+
...trace,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
logRequest(options) {
|
|
171
|
+
const { method, path, statusCode, duration, userId, ip, userAgent, requestBody, responseBody, headers, error, traceContext } = options;
|
|
172
|
+
const trace = this.getTraceContext(traceContext);
|
|
173
|
+
const logData = {
|
|
174
|
+
method: method.toUpperCase(),
|
|
175
|
+
url: path,
|
|
176
|
+
eventType: 'request',
|
|
177
|
+
request: {
|
|
178
|
+
method: method.toUpperCase(),
|
|
179
|
+
path,
|
|
180
|
+
statusCode,
|
|
181
|
+
duration,
|
|
182
|
+
timestamp: new Date().toISOString(),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
if (statusCode)
|
|
186
|
+
logData.statusCode = statusCode;
|
|
187
|
+
if (duration !== undefined)
|
|
188
|
+
logData.request.duration_ms = duration;
|
|
189
|
+
if (userId)
|
|
190
|
+
logData.request.user_id = userId;
|
|
191
|
+
if (ip)
|
|
192
|
+
logData.request.ip = ip;
|
|
193
|
+
if (userAgent)
|
|
194
|
+
logData.request.user_agent = userAgent;
|
|
195
|
+
if (headers)
|
|
196
|
+
logData.request.headers = headers;
|
|
197
|
+
if (requestBody)
|
|
198
|
+
logData.request.request_body = this.sanitizeBody(requestBody);
|
|
199
|
+
if (responseBody)
|
|
200
|
+
logData.request.response_body = this.sanitizeBody(responseBody);
|
|
201
|
+
if (error) {
|
|
202
|
+
logData.error = {
|
|
203
|
+
message: error.message,
|
|
204
|
+
stack: error.stack,
|
|
205
|
+
name: error.name,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const level = error || (statusCode && statusCode >= 400) ? 'error' : 'info';
|
|
209
|
+
const message = `Request: ${method.toUpperCase()} ${path}${statusCode ? ` - ${statusCode}` : ''}${duration ? ` (${duration}ms)` : ''}`;
|
|
210
|
+
this.logger.log(level, message, {
|
|
211
|
+
...logData,
|
|
212
|
+
...trace,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
logWebhook(options) {
|
|
216
|
+
const { source, event, webhookId, status, payload, error, processingTime, traceContext } = options;
|
|
217
|
+
const trace = this.getTraceContext(traceContext);
|
|
218
|
+
const logData = {
|
|
219
|
+
eventType: 'webhook',
|
|
220
|
+
webhook: {
|
|
221
|
+
source,
|
|
222
|
+
event,
|
|
223
|
+
status,
|
|
224
|
+
timestamp: new Date().toISOString(),
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
if (webhookId)
|
|
228
|
+
logData.webhook.webhook_id = webhookId;
|
|
229
|
+
if (processingTime !== undefined)
|
|
230
|
+
logData.webhook.processing_time_ms = processingTime;
|
|
231
|
+
if (payload)
|
|
232
|
+
logData.webhook.payload = this.sanitizeBody(payload);
|
|
233
|
+
if (error) {
|
|
234
|
+
logData.error = {
|
|
235
|
+
message: error.message,
|
|
236
|
+
stack: error.stack,
|
|
237
|
+
name: error.name,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const level = error || status === 'failed' ? 'error' : 'info';
|
|
241
|
+
const message = `Webhook: ${source} - ${event} (${status})`;
|
|
242
|
+
this.logger.log(level, message, {
|
|
243
|
+
...logData,
|
|
244
|
+
...trace,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
sanitizeBody(body) {
|
|
248
|
+
if (!body)
|
|
249
|
+
return body;
|
|
250
|
+
if (typeof body !== 'object')
|
|
251
|
+
return body;
|
|
252
|
+
const sensitiveFields = ['password', 'token', 'secret', 'apiKey', 'api_key', 'authorization', 'creditCard', 'credit_card', 'cvv', 'ssn'];
|
|
253
|
+
const sanitized = { ...body };
|
|
254
|
+
for (const key in sanitized) {
|
|
255
|
+
if (sensitiveFields.some(field => key.toLowerCase().includes(field.toLowerCase()))) {
|
|
256
|
+
sanitized[key] = '***REDACTED***';
|
|
257
|
+
}
|
|
258
|
+
else if (typeof sanitized[key] === 'object' && sanitized[key] !== null) {
|
|
259
|
+
sanitized[key] = this.sanitizeBody(sanitized[key]);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return sanitized;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
exports.SwirepayLogger = SwirepayLogger;
|
|
266
|
+
let defaultLogger = null;
|
|
267
|
+
function initializeLogger(config) {
|
|
268
|
+
// Get config with defaults
|
|
269
|
+
const defaultConfig = (0, config_1.getDefaultConfig)();
|
|
270
|
+
const finalConfig = { ...defaultConfig, ...config };
|
|
271
|
+
// Automatically initialize OpenTelemetry (SDK handles this internally)
|
|
272
|
+
try {
|
|
273
|
+
(0, otel_init_1.initOpenTelemetry)({
|
|
274
|
+
serviceName: finalConfig.serviceName,
|
|
275
|
+
serviceVersion: finalConfig.serviceVersion,
|
|
276
|
+
environment: finalConfig.environment,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
throw new Error(`Failed to initialize OpenTelemetry: ${error instanceof Error ? error.message : String(error)}. ` +
|
|
281
|
+
'OpenTelemetry is required for SwirepayLogger.');
|
|
282
|
+
}
|
|
283
|
+
defaultLogger = new SwirepayLogger(config);
|
|
284
|
+
return defaultLogger;
|
|
285
|
+
}
|
|
286
|
+
function getLogger() {
|
|
287
|
+
if (!defaultLogger) {
|
|
288
|
+
// Automatically initialize with defaults if not already initialized
|
|
289
|
+
// This ensures OpenTelemetry is initialized automatically
|
|
290
|
+
const defaultConfig = (0, config_1.getDefaultConfig)();
|
|
291
|
+
try {
|
|
292
|
+
(0, otel_init_1.initOpenTelemetry)({
|
|
293
|
+
serviceName: defaultConfig.serviceName || 'swirepay-service',
|
|
294
|
+
serviceVersion: defaultConfig.serviceVersion || '1.0.0',
|
|
295
|
+
environment: defaultConfig.environment || 'staging',
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
throw new Error(`Failed to initialize OpenTelemetry: ${error instanceof Error ? error.message : String(error)}. ` +
|
|
300
|
+
'OpenTelemetry is required for SwirepayLogger.');
|
|
301
|
+
}
|
|
302
|
+
defaultLogger = new SwirepayLogger();
|
|
303
|
+
}
|
|
304
|
+
return defaultLogger;
|
|
305
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Transport from 'winston-transport';
|
|
2
|
+
import { LogEntry } from 'winston';
|
|
3
|
+
export declare class LokiTransport extends Transport {
|
|
4
|
+
private lokiUrl;
|
|
5
|
+
private batch;
|
|
6
|
+
private batchTimeout;
|
|
7
|
+
private readonly batchSize;
|
|
8
|
+
private readonly batchInterval;
|
|
9
|
+
constructor(opts: {
|
|
10
|
+
lokiUrl: string;
|
|
11
|
+
environment: string;
|
|
12
|
+
serviceName: string;
|
|
13
|
+
serviceVersion: string;
|
|
14
|
+
});
|
|
15
|
+
log(info: LogEntry, callback: () => void): void;
|
|
16
|
+
private flush;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LokiTransport = void 0;
|
|
7
|
+
const winston_transport_1 = __importDefault(require("winston-transport"));
|
|
8
|
+
class LokiTransport extends winston_transport_1.default {
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
super();
|
|
11
|
+
this.batch = [];
|
|
12
|
+
this.batchTimeout = null;
|
|
13
|
+
this.batchSize = 100;
|
|
14
|
+
this.batchInterval = 5000; // 5 seconds
|
|
15
|
+
this.lokiUrl = opts.lokiUrl;
|
|
16
|
+
}
|
|
17
|
+
log(info, callback) {
|
|
18
|
+
setImmediate(() => {
|
|
19
|
+
this.emit('logged', info);
|
|
20
|
+
});
|
|
21
|
+
const timestamp = new Date().toISOString();
|
|
22
|
+
const nanoTimestamp = `${Date.now()}000000`; // Convert to nanoseconds
|
|
23
|
+
// Extract log level and message
|
|
24
|
+
const level = info.level || 'info';
|
|
25
|
+
const message = info.message || JSON.stringify(info);
|
|
26
|
+
// Build labels for Loki stream
|
|
27
|
+
const stream = {
|
|
28
|
+
level: level.toUpperCase(),
|
|
29
|
+
service_name: info.service || 'unknown',
|
|
30
|
+
environment: info.environment || 'unknown',
|
|
31
|
+
version: info.version || 'unknown',
|
|
32
|
+
};
|
|
33
|
+
// Add custom labels from info object
|
|
34
|
+
if (info.traceId)
|
|
35
|
+
stream.trace_id = info.traceId;
|
|
36
|
+
if (info.spanId)
|
|
37
|
+
stream.span_id = info.spanId;
|
|
38
|
+
if (info.method)
|
|
39
|
+
stream.method = info.method;
|
|
40
|
+
if (info.url)
|
|
41
|
+
stream.url = info.url;
|
|
42
|
+
if (info.statusCode)
|
|
43
|
+
stream.status_code = String(info.statusCode);
|
|
44
|
+
if (info.eventType)
|
|
45
|
+
stream.event_type = info.eventType;
|
|
46
|
+
// Build log line with structured data
|
|
47
|
+
const logLine = {
|
|
48
|
+
timestamp,
|
|
49
|
+
level: level.toUpperCase(),
|
|
50
|
+
message,
|
|
51
|
+
service: {
|
|
52
|
+
name: info.service || 'unknown',
|
|
53
|
+
version: info.version || 'unknown',
|
|
54
|
+
},
|
|
55
|
+
environment: info.environment || 'unknown',
|
|
56
|
+
};
|
|
57
|
+
// Add trace context
|
|
58
|
+
if (info.traceId)
|
|
59
|
+
logLine.trace_id = info.traceId;
|
|
60
|
+
if (info.spanId)
|
|
61
|
+
logLine.span_id = info.spanId;
|
|
62
|
+
// Add attributes
|
|
63
|
+
if (info.attributes) {
|
|
64
|
+
logLine.attributes = info.attributes;
|
|
65
|
+
}
|
|
66
|
+
// Add specific fields based on log type
|
|
67
|
+
if (info.apiCall) {
|
|
68
|
+
logLine.api_call = info.apiCall;
|
|
69
|
+
}
|
|
70
|
+
if (info.request) {
|
|
71
|
+
logLine.request = info.request;
|
|
72
|
+
}
|
|
73
|
+
if (info.webhook) {
|
|
74
|
+
logLine.webhook = info.webhook;
|
|
75
|
+
}
|
|
76
|
+
const logEntry = {
|
|
77
|
+
stream,
|
|
78
|
+
values: [[nanoTimestamp, JSON.stringify(logLine)]],
|
|
79
|
+
};
|
|
80
|
+
this.batch.push(logEntry);
|
|
81
|
+
if (this.batch.length >= this.batchSize) {
|
|
82
|
+
this.flush();
|
|
83
|
+
}
|
|
84
|
+
else if (!this.batchTimeout) {
|
|
85
|
+
this.batchTimeout = setTimeout(() => this.flush(), this.batchInterval);
|
|
86
|
+
}
|
|
87
|
+
callback();
|
|
88
|
+
}
|
|
89
|
+
async flush() {
|
|
90
|
+
if (this.batch.length === 0)
|
|
91
|
+
return;
|
|
92
|
+
const batchToSend = [...this.batch];
|
|
93
|
+
this.batch = [];
|
|
94
|
+
if (this.batchTimeout) {
|
|
95
|
+
clearTimeout(this.batchTimeout);
|
|
96
|
+
this.batchTimeout = null;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetch(this.lokiUrl, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: {
|
|
102
|
+
'Content-Type': 'application/json',
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({ streams: batchToSend }),
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
console.error(`Failed to send logs to Loki: ${response.status} ${response.statusText}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error('Error sending logs to Loki:', error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async close() {
|
|
115
|
+
await this.flush();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.LokiTransport = LokiTransport;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry Configuration
|
|
3
|
+
* Centralized configuration for OpenTelemetry Collector endpoints
|
|
4
|
+
*
|
|
5
|
+
* Endpoints can be overridden via environment variables:
|
|
6
|
+
* - OTEL_EXPORTER_OTLP_ENDPOINT (base URL, e.g., "http://localhost:4318")
|
|
7
|
+
* - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (full traces URL)
|
|
8
|
+
* - OTEL_EXPORTER_OTLP_LOGS_ENDPOINT (full logs URL)
|
|
9
|
+
* - OTEL_EXPORTER_OTLP_METRICS_ENDPOINT (full metrics URL)
|
|
10
|
+
*/
|
|
11
|
+
export declare const OTEL_ENDPOINTS: {
|
|
12
|
+
readonly STAGING: {
|
|
13
|
+
readonly base: "https://stag-otel.swirepay.com";
|
|
14
|
+
readonly traces: "https://stag-otel.swirepay.com/v1/traces";
|
|
15
|
+
readonly logs: "https://stag-otel.swirepay.com/v1/logs";
|
|
16
|
+
readonly metrics: "https://stag-otel.swirepay.com/v1/metrics";
|
|
17
|
+
};
|
|
18
|
+
readonly PRODUCTION: {
|
|
19
|
+
readonly base: "https://otel.swirepay.com";
|
|
20
|
+
readonly traces: "https://otel.swirepay.com/v1/traces";
|
|
21
|
+
readonly logs: "https://otel.swirepay.com/v1/logs";
|
|
22
|
+
readonly metrics: "https://otel.swirepay.com/v1/metrics";
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export declare function getOtelEndpoint(environment: "staging" | "production", type?: "base" | "traces" | "logs" | "metrics"): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenTelemetry Configuration
|
|
4
|
+
* Centralized configuration for OpenTelemetry Collector endpoints
|
|
5
|
+
*
|
|
6
|
+
* Endpoints can be overridden via environment variables:
|
|
7
|
+
* - OTEL_EXPORTER_OTLP_ENDPOINT (base URL, e.g., "http://localhost:4318")
|
|
8
|
+
* - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (full traces URL)
|
|
9
|
+
* - OTEL_EXPORTER_OTLP_LOGS_ENDPOINT (full logs URL)
|
|
10
|
+
* - OTEL_EXPORTER_OTLP_METRICS_ENDPOINT (full metrics URL)
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.OTEL_ENDPOINTS = void 0;
|
|
14
|
+
exports.getOtelEndpoint = getOtelEndpoint;
|
|
15
|
+
exports.OTEL_ENDPOINTS = {
|
|
16
|
+
STAGING: {
|
|
17
|
+
base: "https://stag-otel.swirepay.com",
|
|
18
|
+
traces: "https://stag-otel.swirepay.com/v1/traces",
|
|
19
|
+
logs: "https://stag-otel.swirepay.com/v1/logs",
|
|
20
|
+
metrics: "https://stag-otel.swirepay.com/v1/metrics",
|
|
21
|
+
},
|
|
22
|
+
PRODUCTION: {
|
|
23
|
+
base: "https://otel.swirepay.com",
|
|
24
|
+
traces: "https://otel.swirepay.com/v1/traces",
|
|
25
|
+
logs: "https://otel.swirepay.com/v1/logs",
|
|
26
|
+
metrics: "https://otel.swirepay.com/v1/metrics",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
function getOtelEndpoint(environment, type = "base") {
|
|
30
|
+
// Check for environment variable overrides first
|
|
31
|
+
const envVarMap = {
|
|
32
|
+
base: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
33
|
+
traces: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
|
|
34
|
+
logs: process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
|
|
35
|
+
metrics: process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
|
|
36
|
+
};
|
|
37
|
+
// If specific endpoint type is set, use it
|
|
38
|
+
if (envVarMap[type]) {
|
|
39
|
+
return envVarMap[type];
|
|
40
|
+
}
|
|
41
|
+
// If base endpoint is set, construct the full URL
|
|
42
|
+
if (type !== "base" && envVarMap.base) {
|
|
43
|
+
const base = envVarMap.base.replace(/\/$/, ""); // Remove trailing slash
|
|
44
|
+
return `${base}/v1/${type}`;
|
|
45
|
+
}
|
|
46
|
+
// Fallback to default endpoints
|
|
47
|
+
const endpoints = environment === "production" ? exports.OTEL_ENDPOINTS.PRODUCTION : exports.OTEL_ENDPOINTS.STAGING;
|
|
48
|
+
return endpoints[type];
|
|
49
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
2
|
+
export interface OpenTelemetryConfig {
|
|
3
|
+
serviceName?: string;
|
|
4
|
+
serviceVersion?: string;
|
|
5
|
+
environment?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Initialize OpenTelemetry with automatic instrumentation and log export
|
|
9
|
+
* This is called automatically by the SDK - users don't need to call this
|
|
10
|
+
* @param config Configuration options for OpenTelemetry
|
|
11
|
+
* @returns NodeSDK instance
|
|
12
|
+
* @throws Error if initialization fails
|
|
13
|
+
*/
|
|
14
|
+
export declare function initOpenTelemetry({ serviceName, serviceVersion, environment, }?: OpenTelemetryConfig): NodeSDK;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initOpenTelemetry = initOpenTelemetry;
|
|
4
|
+
const sdk_node_1 = require("@opentelemetry/sdk-node");
|
|
5
|
+
const auto_instrumentations_node_1 = require("@opentelemetry/auto-instrumentations-node");
|
|
6
|
+
const resources_1 = require("@opentelemetry/resources");
|
|
7
|
+
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
8
|
+
const exporter_otlp_proto_1 = require("@opentelemetry/exporter-otlp-proto");
|
|
9
|
+
const exporter_logs_otlp_proto_1 = require("@opentelemetry/exporter-logs-otlp-proto");
|
|
10
|
+
const api_logs_1 = require("@opentelemetry/api-logs");
|
|
11
|
+
const sdk_logs_1 = require("@opentelemetry/sdk-logs");
|
|
12
|
+
const otel_config_1 = require("./otel-config");
|
|
13
|
+
let isInitialized = false;
|
|
14
|
+
/**
|
|
15
|
+
* Initialize OpenTelemetry with automatic instrumentation and log export
|
|
16
|
+
* This is called automatically by the SDK - users don't need to call this
|
|
17
|
+
* @param config Configuration options for OpenTelemetry
|
|
18
|
+
* @returns NodeSDK instance
|
|
19
|
+
* @throws Error if initialization fails
|
|
20
|
+
*/
|
|
21
|
+
function initOpenTelemetry({ serviceName = "unknown-service", serviceVersion = "0.0.0", environment = process.env.ENVIRONMENT || "staging", } = {}) {
|
|
22
|
+
// Only initialize once
|
|
23
|
+
if (isInitialized) {
|
|
24
|
+
return global.__swirepayOtelSdk;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
// Ensure environment is valid type
|
|
28
|
+
const env = environment === "production" ? "production" : "staging";
|
|
29
|
+
// Get OpenTelemetry endpoints from centralized config
|
|
30
|
+
const otelTracesEndpoint = (0, otel_config_1.getOtelEndpoint)(env, "traces");
|
|
31
|
+
const otelLogsEndpoint = (0, otel_config_1.getOtelEndpoint)(env, "logs");
|
|
32
|
+
// Configure OpenTelemetry Logger Provider for logs
|
|
33
|
+
const logResource = new resources_1.Resource({
|
|
34
|
+
[semantic_conventions_1.SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
35
|
+
[semantic_conventions_1.SemanticResourceAttributes.SERVICE_VERSION]: serviceVersion,
|
|
36
|
+
[semantic_conventions_1.SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: env,
|
|
37
|
+
});
|
|
38
|
+
const loggerProvider = new sdk_logs_1.LoggerProvider({
|
|
39
|
+
resource: logResource,
|
|
40
|
+
});
|
|
41
|
+
const logExporter = new exporter_logs_otlp_proto_1.OTLPLogExporter({
|
|
42
|
+
url: otelLogsEndpoint,
|
|
43
|
+
});
|
|
44
|
+
// Use BatchLogRecordProcessor for better performance
|
|
45
|
+
const { BatchLogRecordProcessor } = require("@opentelemetry/sdk-logs");
|
|
46
|
+
loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
|
|
47
|
+
api_logs_1.logs.setGlobalLoggerProvider(loggerProvider);
|
|
48
|
+
// Configure OpenTelemetry SDK
|
|
49
|
+
const sdk = new sdk_node_1.NodeSDK({
|
|
50
|
+
resource: new resources_1.Resource({
|
|
51
|
+
[semantic_conventions_1.SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
52
|
+
[semantic_conventions_1.SemanticResourceAttributes.SERVICE_VERSION]: serviceVersion,
|
|
53
|
+
[semantic_conventions_1.SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: env,
|
|
54
|
+
}),
|
|
55
|
+
traceExporter: new exporter_otlp_proto_1.OTLPTraceExporter({
|
|
56
|
+
url: otelTracesEndpoint,
|
|
57
|
+
}), // Type compatibility workaround for version mismatch
|
|
58
|
+
instrumentations: [
|
|
59
|
+
(0, auto_instrumentations_node_1.getNodeAutoInstrumentations)({
|
|
60
|
+
// Disable fs instrumentation in production for performance
|
|
61
|
+
"@opentelemetry/instrumentation-fs": {
|
|
62
|
+
enabled: environment !== "production",
|
|
63
|
+
},
|
|
64
|
+
// Enable HTTP instrumentation (automatic)
|
|
65
|
+
"@opentelemetry/instrumentation-http": {
|
|
66
|
+
enabled: true,
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
sdk.start();
|
|
72
|
+
isInitialized = true;
|
|
73
|
+
global.__swirepayOtelSdk = sdk;
|
|
74
|
+
// Create an initial span so there's always an active span available
|
|
75
|
+
const { trace } = require("@opentelemetry/api");
|
|
76
|
+
const tracer = trace.getTracer(serviceName, serviceVersion);
|
|
77
|
+
const span = tracer.startSpan("swirepay-logger-init");
|
|
78
|
+
span.setAttribute("init", true);
|
|
79
|
+
span.end();
|
|
80
|
+
// Graceful shutdown
|
|
81
|
+
process.on("SIGTERM", () => {
|
|
82
|
+
sdk.shutdown().finally(() => process.exit(0));
|
|
83
|
+
});
|
|
84
|
+
return sdk;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw new Error(`Failed to initialize OpenTelemetry: ${error instanceof Error ? error.message : String(error)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Transport from 'winston-transport';
|
|
2
|
+
import { LogEntry } from 'winston';
|
|
3
|
+
/**
|
|
4
|
+
* OpenTelemetry Transport for Winston
|
|
5
|
+
* Automatically sends logs to OpenTelemetry when initialized
|
|
6
|
+
*/
|
|
7
|
+
export declare class OtelTransport extends Transport {
|
|
8
|
+
private logger;
|
|
9
|
+
private environment;
|
|
10
|
+
constructor(opts: {
|
|
11
|
+
environment: string;
|
|
12
|
+
serviceName: string;
|
|
13
|
+
serviceVersion: string;
|
|
14
|
+
});
|
|
15
|
+
log(info: LogEntry, callback: () => void): void;
|
|
16
|
+
private mapLogLevel;
|
|
17
|
+
private getSeverityNumber;
|
|
18
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OtelTransport = void 0;
|
|
7
|
+
const winston_transport_1 = __importDefault(require("winston-transport"));
|
|
8
|
+
const api_logs_1 = require("@opentelemetry/api-logs");
|
|
9
|
+
/**
|
|
10
|
+
* OpenTelemetry Transport for Winston
|
|
11
|
+
* Automatically sends logs to OpenTelemetry when initialized
|
|
12
|
+
*/
|
|
13
|
+
class OtelTransport extends winston_transport_1.default {
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
super();
|
|
16
|
+
this.environment = opts.environment;
|
|
17
|
+
// OpenTelemetry is REQUIRED - get logger from OpenTelemetry API
|
|
18
|
+
try {
|
|
19
|
+
const loggerProvider = api_logs_1.logs.getLoggerProvider();
|
|
20
|
+
if (!loggerProvider) {
|
|
21
|
+
throw new Error('OpenTelemetry LoggerProvider is not available. Please initialize OpenTelemetry first.');
|
|
22
|
+
}
|
|
23
|
+
this.logger = loggerProvider.getLogger(opts.serviceName, opts.serviceVersion);
|
|
24
|
+
if (!this.logger) {
|
|
25
|
+
throw new Error('Failed to get OpenTelemetry logger. Please ensure OpenTelemetry is properly initialized.');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// OpenTelemetry is required - throw error instead of silently failing
|
|
30
|
+
throw new Error(`OpenTelemetry is required for SwirepayLogger. ${error instanceof Error ? error.message : 'Please initialize OpenTelemetry before using the logger.'}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
log(info, callback) {
|
|
34
|
+
setImmediate(() => {
|
|
35
|
+
this.emit('logged', info);
|
|
36
|
+
});
|
|
37
|
+
// OpenTelemetry logger is required (checked in constructor)
|
|
38
|
+
if (!this.logger) {
|
|
39
|
+
callback();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const level = this.mapLogLevel(info.level || 'info');
|
|
44
|
+
const message = info.message || JSON.stringify(info);
|
|
45
|
+
// Build log record body
|
|
46
|
+
const body = {
|
|
47
|
+
stringValue: message,
|
|
48
|
+
};
|
|
49
|
+
// Build attributes
|
|
50
|
+
const attributes = {
|
|
51
|
+
'log.level': level,
|
|
52
|
+
'service.name': info.service || 'unknown',
|
|
53
|
+
'service.version': info.version || 'unknown',
|
|
54
|
+
'deployment.environment': info.environment || 'unknown',
|
|
55
|
+
};
|
|
56
|
+
// Add trace context if available
|
|
57
|
+
if (info.traceId) {
|
|
58
|
+
attributes['trace_id'] = info.traceId;
|
|
59
|
+
}
|
|
60
|
+
if (info.spanId) {
|
|
61
|
+
attributes['span_id'] = info.spanId;
|
|
62
|
+
}
|
|
63
|
+
// Add custom attributes
|
|
64
|
+
if (info.attributes) {
|
|
65
|
+
Object.assign(attributes, info.attributes);
|
|
66
|
+
}
|
|
67
|
+
// Add specific fields based on log type
|
|
68
|
+
if (info.apiCall) {
|
|
69
|
+
attributes['api_call.method'] = info.apiCall.method;
|
|
70
|
+
attributes['api_call.url'] = info.apiCall.url;
|
|
71
|
+
attributes['api_call.status_code'] = info.apiCall.statusCode;
|
|
72
|
+
if (info.apiCall.duration) {
|
|
73
|
+
attributes['api_call.duration_ms'] = info.apiCall.duration;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (info.request) {
|
|
77
|
+
attributes['http.method'] = info.request.method;
|
|
78
|
+
attributes['http.route'] = info.request.path;
|
|
79
|
+
attributes['http.status_code'] = info.request.statusCode;
|
|
80
|
+
if (info.request.duration) {
|
|
81
|
+
attributes['http.duration_ms'] = info.request.duration;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (info.webhook) {
|
|
85
|
+
attributes['webhook.source'] = info.webhook.source;
|
|
86
|
+
attributes['webhook.event'] = info.webhook.event;
|
|
87
|
+
attributes['webhook.status'] = info.webhook.status;
|
|
88
|
+
}
|
|
89
|
+
// Emit log record
|
|
90
|
+
this.logger.emit({
|
|
91
|
+
body,
|
|
92
|
+
severityNumber: this.getSeverityNumber(level),
|
|
93
|
+
severityText: level.toUpperCase(),
|
|
94
|
+
attributes,
|
|
95
|
+
timestamp: Date.now() * 1000000, // Convert to nanoseconds
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// Silently fail - don't break logging if OpenTelemetry has issues
|
|
100
|
+
console.error('Error sending log to OpenTelemetry:', error);
|
|
101
|
+
}
|
|
102
|
+
callback();
|
|
103
|
+
}
|
|
104
|
+
mapLogLevel(level) {
|
|
105
|
+
const levelMap = {
|
|
106
|
+
error: 'error',
|
|
107
|
+
warn: 'warn',
|
|
108
|
+
info: 'info',
|
|
109
|
+
debug: 'debug',
|
|
110
|
+
};
|
|
111
|
+
return levelMap[level.toLowerCase()] || 'info';
|
|
112
|
+
}
|
|
113
|
+
getSeverityNumber(level) {
|
|
114
|
+
const severityMap = {
|
|
115
|
+
trace: 1,
|
|
116
|
+
debug: 5,
|
|
117
|
+
info: 9,
|
|
118
|
+
warn: 13,
|
|
119
|
+
error: 17,
|
|
120
|
+
fatal: 21,
|
|
121
|
+
};
|
|
122
|
+
return severityMap[level.toLowerCase()] || 9; // Default to INFO
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.OtelTransport = OtelTransport;
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@swirepay-developer/common-logging-nodejs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Swirepay Common Logging SDK for Node.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://bitbucket.org/zetametrics/open_telementry.git",
|
|
14
|
+
"directory": "nodejs"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=14.0.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"example": "npm run build && cd examples && node express-with-otel.js",
|
|
23
|
+
"start:example": "npm run build && cd examples && node express-with-otel.js",
|
|
24
|
+
"example:ts": "npm run build && node -r ts-node/register examples/with-opentelemetry.ts"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@opentelemetry/api": "^1.8.0",
|
|
28
|
+
"@opentelemetry/api-logs": "^0.49.1",
|
|
29
|
+
"@opentelemetry/auto-instrumentations-node": "^0.67.3",
|
|
30
|
+
"@opentelemetry/exporter-logs-otlp-proto": "^0.208.0",
|
|
31
|
+
"@opentelemetry/exporter-otlp-proto": "^0.26.0",
|
|
32
|
+
"@opentelemetry/instrumentation": "^0.49.1",
|
|
33
|
+
"@opentelemetry/instrumentation-http": "^0.49.1",
|
|
34
|
+
"@opentelemetry/resources": "^1.30.1",
|
|
35
|
+
"@opentelemetry/sdk-logs": "^0.49.1",
|
|
36
|
+
"@opentelemetry/sdk-node": "^0.49.1",
|
|
37
|
+
"@opentelemetry/semantic-conventions": "^1.38.0",
|
|
38
|
+
"winston": "^3.11.0",
|
|
39
|
+
"winston-transport": "^4.7.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.19.27",
|
|
43
|
+
"typescript": "^5.3.3"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"logging",
|
|
47
|
+
"opentelemetry",
|
|
48
|
+
"loki",
|
|
49
|
+
"swirepay",
|
|
50
|
+
"nodejs"
|
|
51
|
+
],
|
|
52
|
+
"author": "Swirepay",
|
|
53
|
+
"license": "UNLICENSED"
|
|
54
|
+
}
|