@epztickets/common 1.65.0 → 1.67.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/build/index.d.ts CHANGED
@@ -11,6 +11,9 @@ export * from "./middlewares/require-auth";
11
11
  export * from "./middlewares/validate-request";
12
12
  export * from "./middlewares/require-owner";
13
13
  export * from "./middlewares/require-admin";
14
+ export * from "./middlewares/request-id";
15
+ export * from "./logging/logger";
16
+ export * from "./logging/http-logger";
14
17
  export * from "./events/subjects";
15
18
  export * from "./events/types/order-status";
16
19
  export * from "./events/order-ticket-cancelled-event";
package/build/index.js CHANGED
@@ -29,6 +29,10 @@ __exportStar(require("./middlewares/require-auth"), exports);
29
29
  __exportStar(require("./middlewares/validate-request"), exports);
30
30
  __exportStar(require("./middlewares/require-owner"), exports);
31
31
  __exportStar(require("./middlewares/require-admin"), exports);
32
+ __exportStar(require("./middlewares/request-id"), exports);
33
+ // logging
34
+ __exportStar(require("./logging/logger"), exports);
35
+ __exportStar(require("./logging/http-logger"), exports);
32
36
  // events
33
37
  __exportStar(require("./events/subjects"), exports);
34
38
  __exportStar(require("./events/types/order-status"), exports);
@@ -0,0 +1,2 @@
1
+ import { Logger } from "pino";
2
+ export declare function createHttpLogger(logger: Logger): import("pino-http").HttpLogger<import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, never>;
@@ -0,0 +1,80 @@
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.createHttpLogger = createHttpLogger;
7
+ const pino_http_1 = __importDefault(require("pino-http"));
8
+ // Per-request access-log middleware. Builds on pino-http with the
9
+ // conventions we want across services:
10
+ //
11
+ // - Uses req.id (set by the requestId middleware) as the
12
+ // per-request log correlation id. Echoes it as `req.id` on
13
+ // every line.
14
+ // - Includes the route, method, status, response time (ms), and
15
+ // authenticated user id when present.
16
+ // - Demotes 4xx-but-expected statuses (401/403/404) from "warn"
17
+ // to "info" — they're routine and shouldn't fire log-volume
18
+ // alerts. Genuine 5xx stays "error".
19
+ // - Silences health-check noise. The k8s liveness/readiness
20
+ // probes hit /healthz + /readyz once per second per pod, which
21
+ // is a 7000x amplifier on log volume if we don't filter them.
22
+ //
23
+ // Mount this AFTER the requestId middleware and BEFORE the routes
24
+ // so every route handler can call req.log.info(...) with the per-
25
+ // request child logger already attached.
26
+ function createHttpLogger(logger) {
27
+ return (0, pino_http_1.default)({
28
+ logger,
29
+ // Reuse the id set by the requestId middleware. pino-http would
30
+ // otherwise generate its own (different) id, which defeats the
31
+ // whole correlation-with-the-X-Request-Id-header idea.
32
+ genReqId: (req) => { var _a; return (_a = req.id) !== null && _a !== void 0 ? _a : "no-id"; },
33
+ // Per-line log level. 5xx → error, 4xx → warn EXCEPT for the
34
+ // expected auth-related statuses which we keep at info to avoid
35
+ // alert fatigue.
36
+ customLogLevel: (_req, res, err) => {
37
+ if (err || res.statusCode >= 500)
38
+ return "error";
39
+ if (res.statusCode === 401 ||
40
+ res.statusCode === 403 ||
41
+ res.statusCode === 404) {
42
+ return "info";
43
+ }
44
+ if (res.statusCode >= 400)
45
+ return "warn";
46
+ return "info";
47
+ },
48
+ // Trim the access-log shape — pino-http's default dumps the
49
+ // entire req object which is verbose and noisy in dashboards.
50
+ serializers: {
51
+ req: (req) => {
52
+ var _a, _b;
53
+ return ({
54
+ id: req.id,
55
+ method: req.method,
56
+ url: req.url,
57
+ // userId comes from common's currentUser middleware. Helpful
58
+ // for "show me all failures by user X" queries. Optional —
59
+ // anonymous requests don't have one.
60
+ userId: (_b = (_a = req.raw) === null || _a === void 0 ? void 0 : _a.currentUser) === null || _b === void 0 ? void 0 : _b.id,
61
+ });
62
+ },
63
+ res: (res) => ({ statusCode: res.statusCode }),
64
+ },
65
+ // Mute k8s probe traffic. These hit ~1/sec per pod and would
66
+ // otherwise dominate the log volume.
67
+ autoLogging: {
68
+ ignore: (req) => {
69
+ var _a;
70
+ const url = (_a = req.url) !== null && _a !== void 0 ? _a : "";
71
+ return url === "/healthz" || url === "/readyz";
72
+ },
73
+ },
74
+ // Per-request log message. Keep it short and consistent across
75
+ // services so a dashboard filtering on `msg: "request"` catches
76
+ // everything.
77
+ customSuccessMessage: () => "request",
78
+ customErrorMessage: () => "request",
79
+ });
80
+ }
@@ -0,0 +1,8 @@
1
+ import pino from "pino";
2
+ export interface LoggerOptions {
3
+ /** Service name — emitted on every line as `service` for filtering. */
4
+ service: string;
5
+ /** Override the global level (defaults to LOG_LEVEL env or "info"). */
6
+ level?: pino.Level;
7
+ }
8
+ export declare function createLogger(serviceOrOpts: string | LoggerOptions): pino.Logger;
@@ -0,0 +1,62 @@
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.createLogger = createLogger;
7
+ const pino_1 = __importDefault(require("pino"));
8
+ const isTest = process.env.NODE_ENV === "test";
9
+ // Pretty-printing is OPT-IN. The previous behavior ("pretty unless
10
+ // production or test") caused crashes in dockerized dev/staging
11
+ // environments where NODE_ENV is unset — pino's transport mechanism
12
+ // would try to spawn the pino-pretty worker and fail. Inverting the
13
+ // check so prod-defaults (no NODE_ENV) get raw JSON matches what
14
+ // log aggregators want anyway.
15
+ //
16
+ // To enable pretty output on a local laptop: `NODE_ENV=development`
17
+ // or `LOG_PRETTY=1`.
18
+ const wantsPretty = process.env.NODE_ENV === "development" || process.env.LOG_PRETTY === "1";
19
+ function createLogger(serviceOrOpts) {
20
+ var _a, _b;
21
+ const opts = typeof serviceOrOpts === "string"
22
+ ? { service: serviceOrOpts }
23
+ : serviceOrOpts;
24
+ const level = (_a = opts.level) !== null && _a !== void 0 ? _a : (isTest ? "silent" : (_b = process.env.LOG_LEVEL) !== null && _b !== void 0 ? _b : "info");
25
+ return (0, pino_1.default)({
26
+ name: opts.service,
27
+ level,
28
+ // Pretty-printed colored lines for local dev only. Defaults to
29
+ // raw JSON everywhere else — exactly what every log aggregator
30
+ // (Loki, Datadog, CloudWatch, etc.) expects.
31
+ transport: wantsPretty
32
+ ? {
33
+ target: "pino-pretty",
34
+ options: {
35
+ colorize: true,
36
+ translateTime: "SYS:HH:MM:ss.l",
37
+ ignore: "pid,hostname",
38
+ },
39
+ }
40
+ : undefined,
41
+ // Standardize the field names across services. Without these,
42
+ // some pino versions emit "time" while others emit "timestamp",
43
+ // making cross-service correlation harder.
44
+ timestamp: pino_1.default.stdTimeFunctions.isoTime,
45
+ formatters: {
46
+ level: (label) => ({ level: label }),
47
+ },
48
+ // Strip these standard fields from PII when they ride along on
49
+ // logged error objects. Add more as we discover them.
50
+ redact: {
51
+ paths: [
52
+ "req.headers.authorization",
53
+ "req.headers.cookie",
54
+ "*.password",
55
+ "*.token",
56
+ "*.refreshToken",
57
+ "*.jwt",
58
+ ],
59
+ remove: true,
60
+ },
61
+ });
62
+ }
@@ -0,0 +1,10 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ declare global {
3
+ namespace Express {
4
+ interface Request {
5
+ /** Stable id for this request, set by requestId middleware. */
6
+ id?: string;
7
+ }
8
+ }
9
+ }
10
+ export declare const requestId: (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requestId = void 0;
4
+ const crypto_1 = require("crypto");
5
+ // Request-ID middleware.
6
+ //
7
+ // Accepts an `X-Request-Id` header from the caller (e.g. the
8
+ // reservation-frontend's axiosClient, an upstream proxy, or another
9
+ // service forwarding context). When absent, generates a fresh UUID.
10
+ // Always echoes the chosen id back in the response header so the
11
+ // client can record it for later support requests.
12
+ //
13
+ // Why this matters: without per-request ids, log lines from
14
+ // concurrent requests interleave with no way to tell which "auth
15
+ // failed" log goes with which "checkout 500'd" log. With ids, every
16
+ // pino log line emitted during the request lifetime carries the
17
+ // same id (via pino-http's auto-child-logger), and the response
18
+ // header lets you grep one id across every service's logs to
19
+ // reconstruct the full causal chain.
20
+ //
21
+ // Mount BEFORE pino-http so the request id is available when
22
+ // pino-http creates the per-request child logger.
23
+ const HEADER = "x-request-id";
24
+ // Permissive — UUIDs, ULIDs, kubectl-style request IDs all fit. We
25
+ // just want to reject obviously-malicious inputs that would mess
26
+ // with our log files (newlines, control chars, absurd lengths).
27
+ const ID_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
28
+ const requestId = (req, res, next) => {
29
+ const incoming = req.get(HEADER);
30
+ const id = incoming && ID_REGEX.test(incoming) ? incoming : (0, crypto_1.randomUUID)();
31
+ req.id = id;
32
+ res.setHeader("X-Request-Id", id);
33
+ next();
34
+ };
35
+ exports.requestId = requestId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epztickets/common",
3
- "version": "1.65.0",
3
+ "version": "1.67.0",
4
4
  "main": "./build/index.js",
5
5
  "types": "./build/index.d.ts",
6
6
  "files": [
@@ -33,6 +33,9 @@
33
33
  "express": "^4.21.2",
34
34
  "express-validator": "^7.2.1",
35
35
  "jsonwebtoken": "^9.0.2",
36
- "nats": "^2.29.3"
36
+ "nats": "^2.29.3",
37
+ "pino": "^9.5.0",
38
+ "pino-http": "^10.3.0",
39
+ "pino-pretty": "^11.3.0"
37
40
  }
38
41
  }