logixia 1.3.1 → 1.5.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 +848 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/{index-iDTW2-eY.d.mts → index-Ium497V3.d.mts} +180 -2
- package/dist/index-Ium497V3.d.mts.map +1 -0
- package/dist/{index-CHIsdA9n.d.ts → index-t-ActikQ.d.ts} +180 -2
- package/dist/index-t-ActikQ.d.ts.map +1 -0
- package/dist/index.d.mts +734 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +734 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +643 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +618 -3
- package/dist/index.mjs.map +1 -1
- package/dist/{logitron-logger.module-BqNKp0Fs.js → logitron-logger.module-B7CsnCYA.js} +418 -52
- package/dist/logitron-logger.module-B7CsnCYA.js.map +1 -0
- package/dist/{logitron-logger.module-Bq3_KckP.d.ts → logitron-logger.module-CMRBDGwm.d.ts} +181 -34
- package/dist/logitron-logger.module-CMRBDGwm.d.ts.map +1 -0
- package/dist/{logitron-logger.module-2AzkadqZ.mjs → logitron-logger.module-DdVy45xE.mjs} +366 -54
- package/dist/logitron-logger.module-DdVy45xE.mjs.map +1 -0
- package/dist/{logitron-logger.module-DW_mmXbj.d.mts → logitron-logger.module-Eaw6bH9M.d.mts} +181 -34
- package/dist/logitron-logger.module-Eaw6bH9M.d.mts.map +1 -0
- package/dist/middleware.d.mts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/nest.d.mts +2 -2
- package/dist/nest.d.ts +2 -2
- package/dist/nest.js +2 -2
- package/dist/nest.mjs +2 -2
- package/dist/{promise-DaiZ2BaH.js → promise-BI-3eI4n.js} +285 -128
- package/dist/promise-BI-3eI4n.js.map +1 -0
- package/dist/{promise-C4pQPcK4.mjs → promise-BrZcjavs.mjs} +285 -128
- package/dist/promise-BrZcjavs.mjs.map +1 -0
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +7 -1
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +7 -1
- package/dist/testing.mjs.map +1 -1
- package/dist/{transport.manager-5VVdqS3o.mjs → transport.manager-DR7TLXQT.mjs} +82 -4
- package/dist/transport.manager-DR7TLXQT.mjs.map +1 -0
- package/dist/{transport.manager-DCOm4uIQ.js → transport.manager-DVTM978M.js} +82 -4
- package/dist/transport.manager-DVTM978M.js.map +1 -0
- package/dist/transports.d.mts +61 -168
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +61 -168
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +1 -1
- package/dist/transports.mjs +1 -1
- package/package.json +62 -1
- package/dist/index-CHIsdA9n.d.ts.map +0 -1
- package/dist/index-iDTW2-eY.d.mts.map +0 -1
- package/dist/logitron-logger.module-2AzkadqZ.mjs.map +0 -1
- package/dist/logitron-logger.module-Bq3_KckP.d.ts.map +0 -1
- package/dist/logitron-logger.module-BqNKp0Fs.js.map +0 -1
- package/dist/logitron-logger.module-DW_mmXbj.d.mts.map +0 -1
- package/dist/promise-C4pQPcK4.mjs.map +0 -1
- package/dist/promise-DaiZ2BaH.js.map +0 -1
- package/dist/transport.manager-5VVdqS3o.mjs.map +0 -1
- package/dist/transport.manager-DCOm4uIQ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,322 @@
|
|
|
1
|
-
const
|
|
2
|
-
require('./
|
|
1
|
+
const require_chunk = require('./chunk-BTgCAUrQ.js');
|
|
2
|
+
const require_logitron_logger_module = require('./logitron-logger.module-B7CsnCYA.js');
|
|
3
|
+
const require_transport_manager = require('./transport.manager-DVTM978M.js');
|
|
3
4
|
require('./search-DeZHhWxB.js');
|
|
5
|
+
let __nestjs_common = require("@nestjs/common");
|
|
6
|
+
__nestjs_common = require_chunk.__toESM(__nestjs_common);
|
|
7
|
+
let node_crypto = require("node:crypto");
|
|
8
|
+
node_crypto = require_chunk.__toESM(node_crypto);
|
|
4
9
|
|
|
10
|
+
//#region src/exceptions/exception.ts
|
|
11
|
+
var LogixiaException = class extends Error {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super(options.message, { cause: options.cause });
|
|
14
|
+
this.name = "LogixiaException";
|
|
15
|
+
this.errorCode = options.code;
|
|
16
|
+
this.errorType = options.type;
|
|
17
|
+
this.httpStatus = options.httpStatus;
|
|
18
|
+
this.param = options.param;
|
|
19
|
+
this.details = options.details;
|
|
20
|
+
this.docUrl = options.docUrl;
|
|
21
|
+
this.metadata = options.metadata;
|
|
22
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Returns `true` when `value` is a `LogixiaException` instance.
|
|
27
|
+
*
|
|
28
|
+
* Useful in exception filters or middleware that need to distinguish a
|
|
29
|
+
* `LogixiaException` from a plain `Error` or a framework `HttpException`.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* if (isLogixiaException(err)) {
|
|
34
|
+
* console.log(err.errorCode, err.httpStatus);
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
function isLogixiaException(value) {
|
|
39
|
+
return value instanceof LogixiaException;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/exceptions/builder.ts
|
|
44
|
+
/**
|
|
45
|
+
* Generates a `req_` prefixed request ID using Node's built-in `crypto.randomUUID`.
|
|
46
|
+
* No extra dependencies required.
|
|
47
|
+
*
|
|
48
|
+
* @example `'req_550e8400e29b41d4a716446655440000'`
|
|
49
|
+
*/
|
|
50
|
+
function generateRequestId() {
|
|
51
|
+
return `req_${(0, node_crypto.randomUUID)().replaceAll("-", "")}`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Maps an HTTP status code to a human-friendly error type string.
|
|
55
|
+
* Used as a fallback when the exception is a generic `HttpException`.
|
|
56
|
+
*/
|
|
57
|
+
function httpStatusToType(status) {
|
|
58
|
+
if (status === 400) return "api_error";
|
|
59
|
+
if (status === 401) return "authentication_error";
|
|
60
|
+
if (status === 402) return "payment_error";
|
|
61
|
+
if (status === 403) return "authorization_error";
|
|
62
|
+
if (status === 404) return "not_found_error";
|
|
63
|
+
if (status === 408) return "timeout_error";
|
|
64
|
+
if (status === 409) return "conflict_error";
|
|
65
|
+
if (status === 422) return "validation_error";
|
|
66
|
+
if (status === 429) return "rate_limit_error";
|
|
67
|
+
if (status >= 500) return "server_error";
|
|
68
|
+
if (status >= 400) return "api_error";
|
|
69
|
+
return "server_error";
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Duck-type check for NestJS `HttpException`.
|
|
73
|
+
* Avoids a hard import on `@nestjs/common` so the builder is usable outside NestJS.
|
|
74
|
+
*/
|
|
75
|
+
function isHttpException(value) {
|
|
76
|
+
return typeof value === "object" && value !== null && typeof value["getStatus"] === "function" && typeof value["getResponse"] === "function";
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Builds the optional `debug` block from an error.
|
|
80
|
+
* Returns `undefined` when there is nothing useful to include.
|
|
81
|
+
*/
|
|
82
|
+
function buildDebug(error, service, durationMs) {
|
|
83
|
+
const stack = error instanceof Error ? error.stack : void 0;
|
|
84
|
+
const causeRaw = error instanceof Error ? error.cause : void 0;
|
|
85
|
+
let cause;
|
|
86
|
+
if (causeRaw instanceof Error) cause = causeRaw.message;
|
|
87
|
+
else if (causeRaw !== void 0 && causeRaw !== null) cause = String(causeRaw);
|
|
88
|
+
if (!stack && !cause && !service && durationMs === void 0) return void 0;
|
|
89
|
+
return {
|
|
90
|
+
...stack !== void 0 ? { stack } : {},
|
|
91
|
+
...cause !== void 0 ? { cause } : {},
|
|
92
|
+
...service !== void 0 ? { service } : {},
|
|
93
|
+
...durationMs !== void 0 ? { duration_ms: durationMs } : {}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
var ErrorResponseBuilder = class {
|
|
97
|
+
/**
|
|
98
|
+
* Build a `LogixiaErrorResponse` from any thrown value.
|
|
99
|
+
*
|
|
100
|
+
* @param params - See `BuildParams`.
|
|
101
|
+
* @returns The unified error response and the HTTP status code to send.
|
|
102
|
+
*
|
|
103
|
+
* @example In a NestJS exception filter
|
|
104
|
+
* ```ts
|
|
105
|
+
* const { response, httpStatus } = ErrorResponseBuilder.build<AppCode, AppType>({
|
|
106
|
+
* exception,
|
|
107
|
+
* requestId: request.headers['x-trace-id'] as string | undefined,
|
|
108
|
+
* path: request.url,
|
|
109
|
+
* service: process.env.SERVICE_NAME,
|
|
110
|
+
* startTime: request.startTime,
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* if (process.env.NODE_ENV === 'production') delete response.debug;
|
|
114
|
+
* response.setHeader('X-Trace-ID', response.meta.request_id);
|
|
115
|
+
* response.status(httpStatus).json(response);
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
static build(params) {
|
|
119
|
+
const { exception, path, service, startTime, requestId: rawRequestId } = params;
|
|
120
|
+
const requestId = rawRequestId ?? generateRequestId();
|
|
121
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
122
|
+
const durationMs = startTime !== void 0 ? Date.now() - startTime : void 0;
|
|
123
|
+
if (isLogixiaException(exception)) {
|
|
124
|
+
const debug$1 = buildDebug(exception, service, durationMs);
|
|
125
|
+
const errorBlock = {
|
|
126
|
+
type: exception.errorType,
|
|
127
|
+
code: exception.errorCode,
|
|
128
|
+
message: exception.message
|
|
129
|
+
};
|
|
130
|
+
if (exception.param !== void 0) errorBlock.param = exception.param;
|
|
131
|
+
if (exception.details !== void 0 && exception.details.length > 0) errorBlock.details = exception.details;
|
|
132
|
+
if (exception.docUrl !== void 0) errorBlock.doc_url = exception.docUrl;
|
|
133
|
+
return {
|
|
134
|
+
response: {
|
|
135
|
+
success: false,
|
|
136
|
+
error: errorBlock,
|
|
137
|
+
meta: {
|
|
138
|
+
request_id: requestId,
|
|
139
|
+
timestamp,
|
|
140
|
+
path,
|
|
141
|
+
status: exception.httpStatus
|
|
142
|
+
},
|
|
143
|
+
...debug$1 !== void 0 ? { debug: debug$1 } : {}
|
|
144
|
+
},
|
|
145
|
+
httpStatus: exception.httpStatus
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (isHttpException(exception)) {
|
|
149
|
+
const status = exception.getStatus();
|
|
150
|
+
const exResponse = exception.getResponse();
|
|
151
|
+
let message;
|
|
152
|
+
if (typeof exResponse === "string") message = exResponse;
|
|
153
|
+
else if (typeof exResponse === "object" && exResponse !== null && "message" in exResponse) {
|
|
154
|
+
const raw = exResponse.message;
|
|
155
|
+
message = Array.isArray(raw) ? raw.join(", ") : String(raw);
|
|
156
|
+
} else message = exception.message;
|
|
157
|
+
const type = httpStatusToType(status);
|
|
158
|
+
const code = `HTTP_${status}`;
|
|
159
|
+
const debug$1 = buildDebug(exception, service, durationMs);
|
|
160
|
+
return {
|
|
161
|
+
response: {
|
|
162
|
+
success: false,
|
|
163
|
+
error: {
|
|
164
|
+
type,
|
|
165
|
+
code,
|
|
166
|
+
message
|
|
167
|
+
},
|
|
168
|
+
meta: {
|
|
169
|
+
request_id: requestId,
|
|
170
|
+
timestamp,
|
|
171
|
+
path,
|
|
172
|
+
status
|
|
173
|
+
},
|
|
174
|
+
...debug$1 !== void 0 ? { debug: debug$1 } : {}
|
|
175
|
+
},
|
|
176
|
+
httpStatus: status
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const debug = buildDebug(exception instanceof Error ? exception : new Error(String(exception)), service, durationMs);
|
|
180
|
+
return {
|
|
181
|
+
response: {
|
|
182
|
+
success: false,
|
|
183
|
+
error: {
|
|
184
|
+
type: "server_error",
|
|
185
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
186
|
+
message: "An unexpected error occurred."
|
|
187
|
+
},
|
|
188
|
+
meta: {
|
|
189
|
+
request_id: requestId,
|
|
190
|
+
timestamp,
|
|
191
|
+
path,
|
|
192
|
+
status: 500
|
|
193
|
+
},
|
|
194
|
+
...debug !== void 0 ? { debug } : {}
|
|
195
|
+
},
|
|
196
|
+
httpStatus: 500
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region \0@oxc-project+runtime@0.95.0/helpers/decorateParam.js
|
|
203
|
+
function __decorateParam(paramIndex, decorator) {
|
|
204
|
+
return function(target, key) {
|
|
205
|
+
decorator(target, key, paramIndex);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/core/nestjs-extras.ts
|
|
211
|
+
/**
|
|
212
|
+
* Inject the Logixia logger registered in the current NestJS DI container.
|
|
213
|
+
*
|
|
214
|
+
* Equivalent to `@Inject(LOGIXIA_LOGGER_TOKEN)` but without needing to import
|
|
215
|
+
* the internal token constant yourself.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* constructor(@InjectLogger() private readonly logger: LogixiaLoggerService) {}
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
const InjectLogger = () => (0, __nestjs_common.Inject)(`${require_logitron_logger_module.LOGIXIA_LOGGER_PREFIX}SERVICE`);
|
|
223
|
+
/**
|
|
224
|
+
* Method decorator that auto-logs entry, exit, duration, and errors.
|
|
225
|
+
*
|
|
226
|
+
* Works on both async and sync methods. Attaches to the logger found on the
|
|
227
|
+
* class instance via a `logger` property (the conventional NestJS name).
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* @LogMethod({ level: 'info', logArgs: true })
|
|
232
|
+
* async processPayment(orderId: string): Promise<void> { … }
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
function LogMethod(options = {}) {
|
|
236
|
+
const { level = "debug", logArgs = true, logResult = false, logErrors = true } = options;
|
|
237
|
+
return function(target, propertyKey, descriptor) {
|
|
238
|
+
var _constructor;
|
|
239
|
+
const originalMethod = descriptor.value;
|
|
240
|
+
if (typeof originalMethod !== "function") return descriptor;
|
|
241
|
+
const methodName = String(propertyKey);
|
|
242
|
+
const className = ((_constructor = target.constructor) === null || _constructor === void 0 ? void 0 : _constructor.name) ?? "Unknown";
|
|
243
|
+
const label = options.label ?? `${className}.${methodName}`;
|
|
244
|
+
descriptor.value = async function(...args) {
|
|
245
|
+
const logger$1 = this.logger;
|
|
246
|
+
const start = Date.now();
|
|
247
|
+
const entry = { method: label };
|
|
248
|
+
if (logArgs && args.length > 0) entry["args"] = args;
|
|
249
|
+
if (logger$1) {
|
|
250
|
+
const logFn = logger$1[level] ?? logger$1.debug.bind(logger$1);
|
|
251
|
+
if (typeof logFn === "function") await logFn(`→ ${label}`, entry).catch(() => void 0);
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const result = await originalMethod.apply(this, args);
|
|
255
|
+
const exit = {
|
|
256
|
+
method: label,
|
|
257
|
+
durationMs: Date.now() - start
|
|
258
|
+
};
|
|
259
|
+
if (logResult) exit["result"] = result;
|
|
260
|
+
if (logger$1) {
|
|
261
|
+
const logFn = logger$1[level] ?? logger$1.debug.bind(logger$1);
|
|
262
|
+
if (typeof logFn === "function") await logFn(`← ${label}`, exit).catch(() => void 0);
|
|
263
|
+
}
|
|
264
|
+
return result;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (logger$1 && logErrors) {
|
|
267
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
268
|
+
logger$1.error(err, `${label} durationMs=${Date.now() - start}`);
|
|
269
|
+
}
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
return descriptor;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
let LogixiaExceptionFilter = class LogixiaExceptionFilter$1 {
|
|
277
|
+
constructor(logger$1) {
|
|
278
|
+
this.logger = logger$1;
|
|
279
|
+
}
|
|
280
|
+
catch(exception, host) {
|
|
281
|
+
var _request$headers;
|
|
282
|
+
const ctx = host.switchToHttp();
|
|
283
|
+
const request = ctx.getRequest();
|
|
284
|
+
const response = ctx.getResponse();
|
|
285
|
+
const requestId = ((_request$headers = request.headers) === null || _request$headers === void 0 ? void 0 : _request$headers["x-trace-id"]) ?? request.id ?? void 0;
|
|
286
|
+
const { response: errorResponse, httpStatus } = ErrorResponseBuilder.build({
|
|
287
|
+
exception,
|
|
288
|
+
requestId,
|
|
289
|
+
path: request.url ?? "/",
|
|
290
|
+
startTime: request.startTime
|
|
291
|
+
});
|
|
292
|
+
if (this.logger) {
|
|
293
|
+
const contextStr = [
|
|
294
|
+
`method=${String(request.method ?? "")}`,
|
|
295
|
+
`url=${String(request.url ?? "")}`,
|
|
296
|
+
`status=${httpStatus}`,
|
|
297
|
+
`request_id=${errorResponse.meta.request_id}`
|
|
298
|
+
].join(" ");
|
|
299
|
+
if (httpStatus >= 500) {
|
|
300
|
+
const err = exception instanceof Error ? exception : new Error(String(exception));
|
|
301
|
+
this.logger.error(err, void 0, contextStr);
|
|
302
|
+
} else if (isLogixiaException(exception)) this.logger.warn(`[${errorResponse.error.code}] ${errorResponse.error.message}`, contextStr);
|
|
303
|
+
else this.logger.warn(`[${httpStatus}] ${errorResponse.error.message}`, contextStr);
|
|
304
|
+
}
|
|
305
|
+
if (process.env["NODE_ENV"] === "production") delete errorResponse.debug;
|
|
306
|
+
response.setHeader("X-Trace-ID", errorResponse.meta.request_id);
|
|
307
|
+
response.setHeader("X-Request-ID", errorResponse.meta.request_id);
|
|
308
|
+
if (httpStatus === 429) response.setHeader("Retry-After", "60");
|
|
309
|
+
response.status(httpStatus).json(errorResponse);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
LogixiaExceptionFilter = require_logitron_logger_module.__decorate([
|
|
313
|
+
(0, __nestjs_common.Catch)(),
|
|
314
|
+
__decorateParam(0, (0, __nestjs_common.Optional)()),
|
|
315
|
+
__decorateParam(0, (0, __nestjs_common.Inject)(`${require_logitron_logger_module.LOGIXIA_LOGGER_PREFIX}SERVICE`)),
|
|
316
|
+
require_logitron_logger_module.__decorateMetadata("design:paramtypes", [Object])
|
|
317
|
+
], LogixiaExceptionFilter);
|
|
318
|
+
|
|
319
|
+
//#endregion
|
|
5
320
|
//#region src/formatters/json.formatter.ts
|
|
6
321
|
var JsonFormatter = class {
|
|
7
322
|
constructor(options = {}) {
|
|
@@ -165,6 +480,309 @@ var TextFormatter = class TextFormatter {
|
|
|
165
480
|
}
|
|
166
481
|
};
|
|
167
482
|
|
|
483
|
+
//#endregion
|
|
484
|
+
//#region src/utils/typed-logger.ts
|
|
485
|
+
/**
|
|
486
|
+
* Define a typed schema for a category of log entries.
|
|
487
|
+
*
|
|
488
|
+
* Call this once at module initialisation and pass it to `createTypedLogger`.
|
|
489
|
+
* In development, every log call is validated against the schema.
|
|
490
|
+
*/
|
|
491
|
+
function defineLogSchema(fields) {
|
|
492
|
+
return {
|
|
493
|
+
fields,
|
|
494
|
+
validate(payload) {
|
|
495
|
+
if (process.env["NODE_ENV"] === "production") return [];
|
|
496
|
+
const warnings = [];
|
|
497
|
+
for (const [key, def] of Object.entries(fields)) {
|
|
498
|
+
const value = payload[key];
|
|
499
|
+
if (def.required && (value === void 0 || value === null)) {
|
|
500
|
+
warnings.push(`Required field "${key}" is missing`);
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (value !== void 0 && value !== null) {
|
|
504
|
+
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
505
|
+
if (actualType !== def.type) warnings.push(`Field "${key}" expected type "${def.type}" but got "${actualType}"`);
|
|
506
|
+
if (def.validate) {
|
|
507
|
+
const msg = def.validate(value);
|
|
508
|
+
if (msg) warnings.push(`Field "${key}": ${msg}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return warnings;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Wrap any logixia logger with a type-safe field interface.
|
|
518
|
+
*
|
|
519
|
+
* @param logger Any object that implements `IBaseLogger` (e.g. the result of `createLogger()`)
|
|
520
|
+
* @param schema Optional schema for dev-time validation. Created with `defineLogSchema()`.
|
|
521
|
+
*/
|
|
522
|
+
function createTypedLogger(logger$1, schema) {
|
|
523
|
+
function withValidation(level, fn, message, data) {
|
|
524
|
+
if (schema && data) {
|
|
525
|
+
const warnings = schema.validate(data);
|
|
526
|
+
for (const w of warnings) require_transport_manager.internalWarn(`[logixia/schema] ${w} — level=${level} message="${message}"`);
|
|
527
|
+
}
|
|
528
|
+
return fn(message, data);
|
|
529
|
+
}
|
|
530
|
+
return {
|
|
531
|
+
raw: logger$1,
|
|
532
|
+
error(messageOrError, data) {
|
|
533
|
+
return logger$1.error(messageOrError, data);
|
|
534
|
+
},
|
|
535
|
+
warn: (m, d) => withValidation("warn", logger$1.warn.bind(logger$1), m, d),
|
|
536
|
+
info: (m, d) => withValidation("info", logger$1.info.bind(logger$1), m, d),
|
|
537
|
+
debug: (m, d) => withValidation("debug", logger$1.debug.bind(logger$1), m, d),
|
|
538
|
+
verbose: (m, d) => withValidation("verbose", (logger$1.verbose ?? logger$1.debug).bind(logger$1), m, d),
|
|
539
|
+
trace: (m, d) => withValidation("trace", (logger$1.trace ?? logger$1.debug).bind(logger$1), m, d)
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
//#endregion
|
|
544
|
+
//#region src/metrics.ts
|
|
545
|
+
const DEFAULT_BUCKETS = [
|
|
546
|
+
1,
|
|
547
|
+
5,
|
|
548
|
+
10,
|
|
549
|
+
25,
|
|
550
|
+
50,
|
|
551
|
+
100,
|
|
552
|
+
250,
|
|
553
|
+
500,
|
|
554
|
+
1e3,
|
|
555
|
+
2500,
|
|
556
|
+
5e3,
|
|
557
|
+
1e4
|
|
558
|
+
];
|
|
559
|
+
function buildLabelKey(config, entry) {
|
|
560
|
+
const labelNames = config.labels ?? [];
|
|
561
|
+
if (labelNames.length === 0) return "{}";
|
|
562
|
+
const pairs = {};
|
|
563
|
+
const payload = entry.payload ?? {};
|
|
564
|
+
for (const name of labelNames) {
|
|
565
|
+
const raw = name === "level" ? entry.level : payload[name];
|
|
566
|
+
pairs[name] = raw !== void 0 && raw !== null ? String(raw) : "";
|
|
567
|
+
}
|
|
568
|
+
return JSON.stringify(pairs);
|
|
569
|
+
}
|
|
570
|
+
function renderLabels(labelKey) {
|
|
571
|
+
if (labelKey === "{}") return "";
|
|
572
|
+
const obj = JSON.parse(labelKey);
|
|
573
|
+
return `{${Object.entries(obj).map(([k, v]) => `${k}="${escapeLabel(v)}"`).join(",")}}`;
|
|
574
|
+
}
|
|
575
|
+
function escapeLabel(value) {
|
|
576
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* A logixia plugin that extracts Prometheus-compatible metrics from log entries.
|
|
580
|
+
*
|
|
581
|
+
* Implements `LogixiaPlugin` — pass directly to `logger.use()`:
|
|
582
|
+
* ```ts
|
|
583
|
+
* const metrics = new MetricsPlugin({ ... });
|
|
584
|
+
* logger.use(metrics);
|
|
585
|
+
* ```
|
|
586
|
+
*
|
|
587
|
+
* Or use the `createMetricsPlugin()` factory (preferred):
|
|
588
|
+
* ```ts
|
|
589
|
+
* const metrics = createMetricsPlugin({ ... });
|
|
590
|
+
* logger.use(metrics);
|
|
591
|
+
* ```
|
|
592
|
+
*/
|
|
593
|
+
var MetricsPlugin = class {
|
|
594
|
+
constructor(map) {
|
|
595
|
+
this.name = "logixia-metrics";
|
|
596
|
+
this.metricState = /* @__PURE__ */ new Map();
|
|
597
|
+
this.map = map;
|
|
598
|
+
this.initAllState();
|
|
599
|
+
}
|
|
600
|
+
onInit() {}
|
|
601
|
+
onLog(entry) {
|
|
602
|
+
const payload = entry.payload ?? {};
|
|
603
|
+
for (const [rawName, config] of Object.entries(this.map)) {
|
|
604
|
+
const state = this.metricState.get(rawName);
|
|
605
|
+
if (!state) continue;
|
|
606
|
+
const labelKey = buildLabelKey(config, entry);
|
|
607
|
+
if (config.type === "counter" && state.type === "counter") {
|
|
608
|
+
if (config.levelFilter && entry.level !== config.levelFilter) continue;
|
|
609
|
+
if (config.field !== void 0 && payload[config.field] !== config.value) continue;
|
|
610
|
+
state.values.set(labelKey, (state.values.get(labelKey) ?? 0) + 1);
|
|
611
|
+
} else if (config.type === "histogram" && state.type === "histogram") {
|
|
612
|
+
const raw = payload[config.field];
|
|
613
|
+
if (raw === void 0 || raw === null) continue;
|
|
614
|
+
const val = Number(raw);
|
|
615
|
+
if (Number.isNaN(val)) continue;
|
|
616
|
+
if (!state.counts.has(labelKey)) {
|
|
617
|
+
state.counts.set(labelKey, Array.from({ length: state.buckets.length + 1 }).fill(0));
|
|
618
|
+
state.sums.set(labelKey, 0);
|
|
619
|
+
state.observations.set(labelKey, 0);
|
|
620
|
+
}
|
|
621
|
+
const bucketArr = state.counts.get(labelKey);
|
|
622
|
+
for (let i = 0; i < state.buckets.length; i++) if (val <= state.buckets[i]) bucketArr[i]++;
|
|
623
|
+
bucketArr[state.buckets.length]++;
|
|
624
|
+
state.sums.set(labelKey, (state.sums.get(labelKey) ?? 0) + val);
|
|
625
|
+
state.observations.set(labelKey, (state.observations.get(labelKey) ?? 0) + 1);
|
|
626
|
+
} else if (config.type === "gauge" && state.type === "gauge") {
|
|
627
|
+
const raw = payload[config.field];
|
|
628
|
+
if (raw === void 0 || raw === null) continue;
|
|
629
|
+
const val = Number(raw);
|
|
630
|
+
if (Number.isNaN(val)) continue;
|
|
631
|
+
state.values.set(labelKey, val);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return entry;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Render all registered metrics in the Prometheus text exposition format
|
|
638
|
+
* (version 0.0.4).
|
|
639
|
+
*
|
|
640
|
+
* All metric names are prefixed with `logixia_`.
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```
|
|
644
|
+
* # HELP logixia_error_count Total error log entries
|
|
645
|
+
* # TYPE logixia_error_count counter
|
|
646
|
+
* logixia_error_count{context="OrderService"} 7
|
|
647
|
+
* ```
|
|
648
|
+
*/
|
|
649
|
+
render() {
|
|
650
|
+
const lines = [];
|
|
651
|
+
for (const [rawName, config] of Object.entries(this.map)) {
|
|
652
|
+
const metricName = `logixia_${rawName}`;
|
|
653
|
+
const state = this.metricState.get(rawName);
|
|
654
|
+
if (!state) continue;
|
|
655
|
+
const helpText = config.help ?? rawName.replace(/_/g, " ");
|
|
656
|
+
lines.push(`# HELP ${metricName} ${helpText}`);
|
|
657
|
+
lines.push(`# TYPE ${metricName} ${config.type}`);
|
|
658
|
+
if (state.type === "counter") if (state.values.size === 0) lines.push(`${metricName} 0`);
|
|
659
|
+
else for (const [labelKey, count] of state.values) lines.push(`${metricName}${renderLabels(labelKey)} ${count}`);
|
|
660
|
+
else if (state.type === "histogram") for (const [labelKey, bucketCounts] of state.counts) {
|
|
661
|
+
const labelSuffix = renderLabels(labelKey);
|
|
662
|
+
const baseObj = labelKey === "{}" ? {} : JSON.parse(labelKey);
|
|
663
|
+
for (let i = 0; i < state.buckets.length; i++) {
|
|
664
|
+
const leKey = JSON.stringify({
|
|
665
|
+
...baseObj,
|
|
666
|
+
le: String(state.buckets[i])
|
|
667
|
+
});
|
|
668
|
+
lines.push(`${metricName}_bucket${renderLabels(leKey)} ${bucketCounts[i] ?? 0}`);
|
|
669
|
+
}
|
|
670
|
+
const infKey = JSON.stringify({
|
|
671
|
+
...baseObj,
|
|
672
|
+
le: "+Inf"
|
|
673
|
+
});
|
|
674
|
+
lines.push(`${metricName}_bucket${renderLabels(infKey)} ${bucketCounts[state.buckets.length] ?? 0}`);
|
|
675
|
+
lines.push(`${metricName}_sum${labelSuffix} ${state.sums.get(labelKey) ?? 0}`);
|
|
676
|
+
lines.push(`${metricName}_count${labelSuffix} ${state.observations.get(labelKey) ?? 0}`);
|
|
677
|
+
}
|
|
678
|
+
else if (state.type === "gauge") if (state.values.size === 0) lines.push(`${metricName} 0`);
|
|
679
|
+
else for (const [labelKey, val] of state.values) lines.push(`${metricName}${renderLabels(labelKey)} ${val}`);
|
|
680
|
+
}
|
|
681
|
+
return `${lines.join("\n")}\n`;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Reset all metric counters and observations back to zero.
|
|
685
|
+
* Useful between test runs or on a scheduled reset interval.
|
|
686
|
+
*/
|
|
687
|
+
reset() {
|
|
688
|
+
this.metricState.clear();
|
|
689
|
+
this.initAllState();
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Express route handler — call `app.get('/metrics', metrics.expressHandler())`.
|
|
693
|
+
*
|
|
694
|
+
* @example
|
|
695
|
+
* ```ts
|
|
696
|
+
* import express from 'express';
|
|
697
|
+
* const app = express();
|
|
698
|
+
* app.get('/metrics', metrics.expressHandler());
|
|
699
|
+
* ```
|
|
700
|
+
*/
|
|
701
|
+
expressHandler() {
|
|
702
|
+
return (_req, res) => {
|
|
703
|
+
res.set("Content-Type", "text/plain; version=0.0.4; charset=utf-8");
|
|
704
|
+
res.end(this.render());
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Raw Node.js `http` server handler.
|
|
709
|
+
* Responds to `GET /metrics` with the Prometheus text payload.
|
|
710
|
+
*
|
|
711
|
+
* @example Start a dedicated metrics server on the standard port:
|
|
712
|
+
* ```ts
|
|
713
|
+
* import http from 'node:http';
|
|
714
|
+
* const server = http.createServer(metrics.httpHandler());
|
|
715
|
+
* server.listen(9464); // Prometheus default port for custom exporters
|
|
716
|
+
* ```
|
|
717
|
+
*/
|
|
718
|
+
httpHandler() {
|
|
719
|
+
return (req, res) => {
|
|
720
|
+
if (!(req.url === "/metrics" || req.url === "/metrics/")) {
|
|
721
|
+
res.writeHead(404).end("Not found");
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const body = this.render();
|
|
725
|
+
res.writeHead(200, {
|
|
726
|
+
"Content-Type": "text/plain; version=0.0.4; charset=utf-8",
|
|
727
|
+
"Content-Length": Buffer.byteLength(body)
|
|
728
|
+
});
|
|
729
|
+
res.end(body);
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
initAllState() {
|
|
733
|
+
for (const [rawName, config] of Object.entries(this.map)) this.initSingleState(rawName, config);
|
|
734
|
+
}
|
|
735
|
+
initSingleState(rawName, config) {
|
|
736
|
+
if (config.type === "counter") this.metricState.set(rawName, {
|
|
737
|
+
type: "counter",
|
|
738
|
+
values: /* @__PURE__ */ new Map()
|
|
739
|
+
});
|
|
740
|
+
else if (config.type === "histogram") {
|
|
741
|
+
const buckets = [...config.buckets ?? DEFAULT_BUCKETS].sort((a, b) => a - b);
|
|
742
|
+
this.metricState.set(rawName, {
|
|
743
|
+
type: "histogram",
|
|
744
|
+
buckets,
|
|
745
|
+
counts: /* @__PURE__ */ new Map(),
|
|
746
|
+
sums: /* @__PURE__ */ new Map(),
|
|
747
|
+
observations: /* @__PURE__ */ new Map()
|
|
748
|
+
});
|
|
749
|
+
} else this.metricState.set(rawName, {
|
|
750
|
+
type: "gauge",
|
|
751
|
+
values: /* @__PURE__ */ new Map()
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
/**
|
|
756
|
+
* Create a `MetricsPlugin` from a metrics map and return it ready for
|
|
757
|
+
* `logger.use()`.
|
|
758
|
+
*
|
|
759
|
+
* @example
|
|
760
|
+
* ```ts
|
|
761
|
+
* import { createMetricsPlugin } from 'logixia';
|
|
762
|
+
*
|
|
763
|
+
* const metrics = createMetricsPlugin({
|
|
764
|
+
* http_request_duration: {
|
|
765
|
+
* type: 'histogram',
|
|
766
|
+
* field: 'duration',
|
|
767
|
+
* labels: ['method', 'statusCode'],
|
|
768
|
+
* help: 'HTTP request duration in milliseconds',
|
|
769
|
+
* },
|
|
770
|
+
* error_count: {
|
|
771
|
+
* type: 'counter',
|
|
772
|
+
* levelFilter: 'error',
|
|
773
|
+
* labels: ['context'],
|
|
774
|
+
* help: 'Total error log entries',
|
|
775
|
+
* },
|
|
776
|
+
* });
|
|
777
|
+
*
|
|
778
|
+
* logger.use(metrics);
|
|
779
|
+
* app.get('/metrics', metrics.expressHandler());
|
|
780
|
+
* ```
|
|
781
|
+
*/
|
|
782
|
+
function createMetricsPlugin(map) {
|
|
783
|
+
return new MetricsPlugin(map);
|
|
784
|
+
}
|
|
785
|
+
|
|
168
786
|
//#endregion
|
|
169
787
|
//#region src/index.ts
|
|
170
788
|
/**
|
|
@@ -240,6 +858,8 @@ exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
|
240
858
|
exports.DEFAULT_LOG_COLORS = require_logitron_logger_module.DEFAULT_LOG_COLORS;
|
|
241
859
|
exports.DEFAULT_LOG_LEVELS = require_logitron_logger_module.DEFAULT_LOG_LEVELS;
|
|
242
860
|
exports.DEFAULT_TRACE_HEADERS = require_logitron_logger_module.DEFAULT_TRACE_HEADERS;
|
|
861
|
+
exports.ErrorResponseBuilder = ErrorResponseBuilder;
|
|
862
|
+
exports.InjectLogger = InjectLogger;
|
|
243
863
|
exports.JsonFormatter = JsonFormatter;
|
|
244
864
|
Object.defineProperty(exports, 'KafkaTraceInterceptor', {
|
|
245
865
|
enumerable: true,
|
|
@@ -250,7 +870,15 @@ Object.defineProperty(exports, 'KafkaTraceInterceptor', {
|
|
|
250
870
|
exports.LOGIXIA_LOGGER_CONFIG = require_logitron_logger_module.LOGIXIA_LOGGER_CONFIG;
|
|
251
871
|
exports.LOGIXIA_LOGGER_PREFIX = require_logitron_logger_module.LOGIXIA_LOGGER_PREFIX;
|
|
252
872
|
exports.LogLevel = require_logitron_logger_module.LogLevel;
|
|
873
|
+
exports.LogMethod = LogMethod;
|
|
253
874
|
exports.LogixiaContext = require_logitron_logger_module.LogixiaContext;
|
|
875
|
+
exports.LogixiaException = LogixiaException;
|
|
876
|
+
Object.defineProperty(exports, 'LogixiaExceptionFilter', {
|
|
877
|
+
enumerable: true,
|
|
878
|
+
get: function () {
|
|
879
|
+
return LogixiaExceptionFilter;
|
|
880
|
+
}
|
|
881
|
+
});
|
|
254
882
|
exports.LogixiaLogger = require_logitron_logger_module.LogixiaLogger;
|
|
255
883
|
Object.defineProperty(exports, 'LogixiaLoggerModule', {
|
|
256
884
|
enumerable: true,
|
|
@@ -264,6 +892,8 @@ Object.defineProperty(exports, 'LogixiaLoggerService', {
|
|
|
264
892
|
return require_logitron_logger_module.LogixiaLoggerService;
|
|
265
893
|
}
|
|
266
894
|
});
|
|
895
|
+
exports.MetricsPlugin = MetricsPlugin;
|
|
896
|
+
exports.PluginRegistry = require_logitron_logger_module.PluginRegistry;
|
|
267
897
|
exports.TextFormatter = TextFormatter;
|
|
268
898
|
Object.defineProperty(exports, 'WebSocketTraceInterceptor', {
|
|
269
899
|
enumerable: true,
|
|
@@ -276,13 +906,23 @@ exports.createExpressContextMiddleware = require_logitron_logger_module.createEx
|
|
|
276
906
|
exports.createFastifyContextHook = require_logitron_logger_module.createFastifyContextHook;
|
|
277
907
|
exports.createLogger = createLogger;
|
|
278
908
|
exports.createLoggerService = createLoggerService;
|
|
909
|
+
exports.createMetricsPlugin = createMetricsPlugin;
|
|
279
910
|
exports.createTraceMiddleware = require_logitron_logger_module.createTraceMiddleware;
|
|
911
|
+
exports.createTypedLogger = createTypedLogger;
|
|
912
|
+
exports.defineLogSchema = defineLogSchema;
|
|
280
913
|
exports.deregisterFromShutdown = require_logitron_logger_module.deregisterFromShutdown;
|
|
914
|
+
exports.disableOtelBridge = require_logitron_logger_module.disableOtelBridge;
|
|
281
915
|
exports.extractTraceId = require_logitron_logger_module.extractTraceId;
|
|
282
916
|
exports.flushOnExit = require_logitron_logger_module.flushOnExit;
|
|
917
|
+
exports.generateRequestId = generateRequestId;
|
|
283
918
|
exports.generateTraceId = require_logitron_logger_module.generateTraceId;
|
|
919
|
+
exports.getActiveOtelContext = require_logitron_logger_module.getActiveOtelContext;
|
|
284
920
|
exports.getCurrentTraceId = require_logitron_logger_module.getCurrentTraceId;
|
|
921
|
+
exports.getOtelMetaFields = require_logitron_logger_module.getOtelMetaFields;
|
|
922
|
+
exports.globalPluginRegistry = require_logitron_logger_module.globalPluginRegistry;
|
|
923
|
+
exports.initOtelBridge = require_logitron_logger_module.initOtelBridge;
|
|
285
924
|
exports.isError = require_logitron_logger_module.isError;
|
|
925
|
+
exports.isLogixiaException = isLogixiaException;
|
|
286
926
|
exports.logger = logger;
|
|
287
927
|
exports.normalizeError = require_logitron_logger_module.normalizeError;
|
|
288
928
|
exports.redactObject = require_logitron_logger_module.redactObject;
|
|
@@ -292,4 +932,5 @@ exports.runWithTraceId = require_logitron_logger_module.runWithTraceId;
|
|
|
292
932
|
exports.serializeError = require_logitron_logger_module.serializeError;
|
|
293
933
|
exports.setTraceId = require_logitron_logger_module.setTraceId;
|
|
294
934
|
exports.traceStorage = require_logitron_logger_module.traceStorage;
|
|
935
|
+
exports.usePlugin = require_logitron_logger_module.usePlugin;
|
|
295
936
|
//# sourceMappingURL=index.js.map
|