@munlib/common 1.0.248 → 1.1.2

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 CHANGED
@@ -1,8 +1,30 @@
1
+ # @munlib/common
1
2
 
3
+ ## HTTP logs in MongoDB
4
+
5
+ `createHttpLogger` captures completed Express requests and responses. The consuming service owns the MongoDB connection and collection; a logging failure is swallowed and never changes the HTTP response.
6
+
7
+ ```ts
8
+ import mongoose from "mongoose";
9
+ import { createHttpLogger } from "@munlib/common";
10
+
11
+ app.use(createHttpLogger({
12
+ serviceName: "payment",
13
+ environment: process.env.ENVIRONMENT,
14
+ write: (document) => mongoose.connection
15
+ .collection(process.env.HTTP_LOG_COLLECTION || "http_logs")
16
+ .insertOne(document)
17
+ }));
18
+ ```
19
+
20
+ Register the middleware after the JSON parser and before authentication or routes. It automatically redacts credentials and payment-sensitive fields, limits request and response payloads to 16 KB, adds `x-request-id`, and only persists complete JSON response bodies. Other response types are represented by safe metadata.
21
+
22
+ Use `redactFields`, `maxPayloadBytes`, `shouldLog`, and `onWriteError` to customize the behavior.
23
+
24
+ ## Publishing
25
+
26
+ ```sh
2
27
  npm run build
3
28
  npm publish --dry-run
4
- npm run pub -- --otp=token de npm
5
- npm config set "//registry.npmjs.org/:_authToken=TU_TOKEN_NUEVO" --location=user
6
- luego
7
- npm whoami
8
- npm publish
29
+ npm run pub -- --otp=token-de-npm
30
+ ```
package/build/index.d.ts CHANGED
@@ -11,4 +11,5 @@ export * from './middlewares/validate-request';
11
11
  export * from './events/subjects';
12
12
  export * from './events/types/order-status';
13
13
  export * from './events/types/instituciones-pagos';
14
+ export * from './logs/logger';
14
15
  export * from "./errors/erroresStatus";
package/build/index.js CHANGED
@@ -29,6 +29,6 @@ __exportStar(require("./middlewares/validate-request"), exports);
29
29
  __exportStar(require("./events/subjects"), exports);
30
30
  __exportStar(require("./events/types/order-status"), exports);
31
31
  __exportStar(require("./events/types/instituciones-pagos"), exports);
32
- //export * from './logs/logger';
32
+ __exportStar(require("./logs/logger"), exports);
33
33
  __exportStar(require("./errors/erroresStatus"), exports);
34
34
  //export * from './middlewares/apm';
@@ -0,0 +1,37 @@
1
+ import { Request, RequestHandler } from "express";
2
+ export interface HttpLogDocument {
3
+ timestamp: Date;
4
+ serviceName: string;
5
+ environment?: string;
6
+ requestId: string;
7
+ method: string;
8
+ path: string;
9
+ clientIp?: string;
10
+ userAgent?: string;
11
+ request: {
12
+ headers: {
13
+ [key: string]: unknown;
14
+ };
15
+ query: unknown;
16
+ params: unknown;
17
+ body: unknown;
18
+ };
19
+ response: {
20
+ statusCode: number;
21
+ headers: {
22
+ [key: string]: unknown;
23
+ };
24
+ body: unknown;
25
+ };
26
+ durationMs: number;
27
+ }
28
+ export interface HttpLoggerOptions {
29
+ serviceName: string;
30
+ write: (document: HttpLogDocument) => Promise<unknown> | unknown;
31
+ environment?: string;
32
+ redactFields?: string[];
33
+ maxPayloadBytes?: number;
34
+ shouldLog?: (request: Request) => boolean;
35
+ onWriteError?: (error: unknown) => void;
36
+ }
37
+ export declare function createHttpLogger(options: HttpLoggerOptions): RequestHandler;
@@ -1,10 +1,229 @@
1
1
  "use strict";
2
- //@ts-ignore
3
- /* import { LogService } from "logstash-winston-3";
4
-
5
- export const logger = LogService.getInstance({
6
- serviceName: process.env.SERVICE_NAME,
7
- logstashHost: process.env.LOGS_SERVER,
8
- logstashPort: process.env.LOGS_PORT,
9
- });
10
- */
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createHttpLogger = createHttpLogger;
4
+ var DEFAULT_REDACTED_FIELDS = [
5
+ "authorization",
6
+ "cookie",
7
+ "set-cookie",
8
+ "x-api-key",
9
+ "api-key",
10
+ "password",
11
+ "token",
12
+ "access-token",
13
+ "refresh-token",
14
+ "secret",
15
+ "cvv",
16
+ "card-number"
17
+ ];
18
+ function normalizeFieldName(value) {
19
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
20
+ }
21
+ function isSensitiveField(fieldName, redactedFields) {
22
+ var normalized = normalizeFieldName(fieldName);
23
+ return redactedFields.indexOf(normalized) !== -1
24
+ || normalized.indexOf("password") !== -1
25
+ || normalized.indexOf("token") !== -1
26
+ || normalized.indexOf("secret") !== -1
27
+ || normalized.indexOf("authorization") !== -1
28
+ || normalized.indexOf("cookie") !== -1
29
+ || normalized.indexOf("apikey") !== -1
30
+ || normalized.indexOf("cardnumber") !== -1
31
+ || normalized === "cvv";
32
+ }
33
+ function sanitize(value, redactedFields, ancestors, depth) {
34
+ if (ancestors === void 0) { ancestors = []; }
35
+ if (depth === void 0) { depth = 0; }
36
+ if (value === null || value === undefined || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
37
+ return value;
38
+ }
39
+ if (depth >= 8) {
40
+ return "[Truncated: maximum depth]";
41
+ }
42
+ if (typeof value !== "object") {
43
+ return String(value);
44
+ }
45
+ if (ancestors.indexOf(value) !== -1) {
46
+ return "[Circular]";
47
+ }
48
+ var nextAncestors = ancestors.concat([value]);
49
+ if (Array.isArray(value)) {
50
+ return value.map(function (item) { return sanitize(item, redactedFields, nextAncestors, depth + 1); });
51
+ }
52
+ var result = {};
53
+ Object.keys(value).forEach(function (key) {
54
+ var record = value;
55
+ result[key] = isSensitiveField(key, redactedFields)
56
+ ? "[REDACTED]"
57
+ : sanitize(record[key], redactedFields, nextAncestors, depth + 1);
58
+ });
59
+ return result;
60
+ }
61
+ function byteLength(value) {
62
+ return Buffer.byteLength(value, "utf8");
63
+ }
64
+ function truncateText(value, maxBytes) {
65
+ if (byteLength(value) <= maxBytes) {
66
+ return value;
67
+ }
68
+ var result = "";
69
+ for (var index = 0; index < value.length; index += 1) {
70
+ var next = result + value[index];
71
+ if (byteLength(next) > maxBytes) {
72
+ break;
73
+ }
74
+ result = next;
75
+ }
76
+ return result;
77
+ }
78
+ function limitPayload(value, maxBytes) {
79
+ var serialized;
80
+ try {
81
+ serialized = JSON.stringify(value);
82
+ }
83
+ catch (_error) {
84
+ serialized = String(value);
85
+ }
86
+ if (byteLength(serialized) <= maxBytes) {
87
+ return value;
88
+ }
89
+ return {
90
+ truncated: true,
91
+ sizeBytes: byteLength(serialized),
92
+ preview: truncateText(serialized, maxBytes)
93
+ };
94
+ }
95
+ function responseBody(content, contentType, wasTruncated, maxBytes, redactedFields) {
96
+ var normalizedContentType = contentType ? contentType.toLowerCase() : "";
97
+ var jsonContent = normalizedContentType.indexOf("application/json") !== -1;
98
+ if (!jsonContent) {
99
+ return { omitted: true, reason: "non-json-response" };
100
+ }
101
+ if (wasTruncated) {
102
+ return { truncated: true, capturedBytes: byteLength(content) };
103
+ }
104
+ try {
105
+ return limitPayload(sanitize(JSON.parse(content), redactedFields), maxBytes);
106
+ }
107
+ catch (_error) {
108
+ return { omitted: true, reason: "invalid-json-response" };
109
+ }
110
+ }
111
+ function createRequestId() {
112
+ return Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
113
+ }
114
+ function reportWriteError(error, onWriteError) {
115
+ if (!onWriteError) {
116
+ return;
117
+ }
118
+ try {
119
+ onWriteError(error);
120
+ }
121
+ catch (_callbackError) {
122
+ // Logging failures must never affect the request lifecycle.
123
+ }
124
+ }
125
+ function createHttpLogger(options) {
126
+ var maxPayloadBytes = options.maxPayloadBytes || 16 * 1024;
127
+ var redactedFields = DEFAULT_REDACTED_FIELDS
128
+ .concat(options.redactFields || [])
129
+ .map(normalizeFieldName);
130
+ return function (req, res, next) {
131
+ try {
132
+ if (options.shouldLog && !options.shouldLog(req)) {
133
+ next();
134
+ return;
135
+ }
136
+ var startedAt_1 = Date.now();
137
+ var suppliedRequestId = req.header("x-request-id");
138
+ var requestId_1 = suppliedRequestId && suppliedRequestId.length <= 128
139
+ ? suppliedRequestId
140
+ : createRequestId();
141
+ var responseContent_1 = "";
142
+ var responseTruncated_1 = false;
143
+ var originalWrite_1 = res.write.bind(res);
144
+ var originalEnd_1 = res.end.bind(res);
145
+ res.setHeader("x-request-id", requestId_1);
146
+ var captureResponseChunk_1 = function (chunk) {
147
+ try {
148
+ if (chunk === undefined || chunk === null || responseTruncated_1) {
149
+ return;
150
+ }
151
+ var text = typeof chunk === "string"
152
+ ? chunk
153
+ : typeof chunk.toString === "function"
154
+ ? chunk.toString("utf8")
155
+ : undefined;
156
+ if (text === undefined) {
157
+ return;
158
+ }
159
+ var availableBytes = maxPayloadBytes - byteLength(responseContent_1);
160
+ if (availableBytes <= 0) {
161
+ responseTruncated_1 = true;
162
+ return;
163
+ }
164
+ if (byteLength(text) > availableBytes) {
165
+ responseContent_1 += truncateText(text, availableBytes);
166
+ responseTruncated_1 = true;
167
+ return;
168
+ }
169
+ responseContent_1 += text;
170
+ }
171
+ catch (error) {
172
+ reportWriteError(error, options.onWriteError);
173
+ }
174
+ };
175
+ res.write = function () {
176
+ var args = [];
177
+ for (var _i = 0; _i < arguments.length; _i++) {
178
+ args[_i] = arguments[_i];
179
+ }
180
+ captureResponseChunk_1(args[0]);
181
+ return originalWrite_1.apply(void 0, args);
182
+ };
183
+ res.end = function () {
184
+ var args = [];
185
+ for (var _i = 0; _i < arguments.length; _i++) {
186
+ args[_i] = arguments[_i];
187
+ }
188
+ captureResponseChunk_1(args[0]);
189
+ return originalEnd_1.apply(void 0, args);
190
+ };
191
+ res.once("finish", function () {
192
+ try {
193
+ var document = {
194
+ timestamp: new Date(),
195
+ serviceName: options.serviceName,
196
+ environment: options.environment,
197
+ requestId: requestId_1,
198
+ method: req.method,
199
+ path: req.path,
200
+ clientIp: req.ip,
201
+ userAgent: req.get("user-agent"),
202
+ request: {
203
+ headers: sanitize(req.headers, redactedFields),
204
+ query: limitPayload(sanitize(req.query, redactedFields), maxPayloadBytes),
205
+ params: limitPayload(sanitize(req.params, redactedFields), maxPayloadBytes),
206
+ body: limitPayload(sanitize(req.body, redactedFields), maxPayloadBytes)
207
+ },
208
+ response: {
209
+ statusCode: res.statusCode,
210
+ headers: sanitize(res.getHeaders(), redactedFields),
211
+ body: responseBody(responseContent_1, res.getHeader("content-type"), responseTruncated_1, maxPayloadBytes, redactedFields)
212
+ },
213
+ durationMs: Date.now() - startedAt_1
214
+ };
215
+ Promise.resolve(options.write(document)).catch(function (error) {
216
+ reportWriteError(error, options.onWriteError);
217
+ });
218
+ }
219
+ catch (error) {
220
+ reportWriteError(error, options.onWriteError);
221
+ }
222
+ });
223
+ }
224
+ catch (error) {
225
+ reportWriteError(error, options.onWriteError);
226
+ }
227
+ next();
228
+ };
229
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@munlib/common",
3
- "version": "1.0.248",
3
+ "version": "1.1.2",
4
4
  "description": "Common errors, middlewares and shared types for Munlib services",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",