@nicomatic/logger 0.0.0-stage → 0.1.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 CHANGED
@@ -1,3 +1,56 @@
1
- # Temporary Holding Version
1
+ # `@nicomatic/logger`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Package npm de logging JSON unifié pour les services Nicomatic (JS/TS).
4
+
5
+ Prérequis consommateur : **Node.js ≥ 20**.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @nicomatic/logger
11
+ # ou : npm install @nicomatic/logger
12
+ # ou : yarn add @nicomatic/logger
13
+ ```
14
+
15
+ Package **public** sur [npmjs](https://www.npmjs.com/) (scope `@nicomatic`).
16
+
17
+ ## Publication (maintainers)
18
+
19
+ Prérequis : compte npm membre de l’org `@nicomatic`, puis `npm login`.
20
+
21
+ ```bash
22
+ just check
23
+ npm publish
24
+ ```
25
+
26
+ ## Développement
27
+
28
+ Prérequis : [Nix](https://nixos.org/) (flakes). Optionnel : [direnv](https://direnv.net/) + [nix-direnv](https://github.com/nix-community/nix-direnv) pour entrer automatiquement dans le shell.
29
+
30
+ ```bash
31
+ direnv allow # une fois, si direnv est installé
32
+ # sinon : nix develop
33
+ just setup
34
+ just check # test + typecheck + build
35
+ ```
36
+
37
+ ## API minimale
38
+
39
+ ```ts
40
+ import { createLogger } from "@nicomatic/logger";
41
+
42
+ const log = createLogger({
43
+ service: "app-web",
44
+ env: "dev",
45
+ version: "0.1.0",
46
+ });
47
+
48
+ log.info({ message: "hello", traceId: "abc" });
49
+ ```
50
+
51
+ ## Spec (M1)
52
+
53
+ - Schéma JSON : [`schemas/log-event.schema.json`](schemas/log-event.schema.json)
54
+ - Exemples : [`schemas/examples/`](schemas/examples/)
55
+ - ADR format : [`docs/adr/0001-format-de-log-unifie.md`](docs/adr/0001-format-de-log-unifie.md)
56
+ - ADR RGPD / sécurité (NMTC-106) : [`docs/adr/0002-rgpd-securite-capture-logs.md`](docs/adr/0002-rgpd-securite-capture-logs.md)
package/dist/index.cjs ADDED
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createLogger: () => createLogger
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var SCHEMA_VERSION = "1.0";
27
+ function normalizeFields(input) {
28
+ if (typeof input === "string") {
29
+ return { message: input };
30
+ }
31
+ return input;
32
+ }
33
+ function createLogger(options, bindings = {}) {
34
+ const logAt = (level) => (input) => {
35
+ const fields = normalizeFields(input);
36
+ const { message, level: _ignoredLevel, ...rest } = fields;
37
+ const event = {
38
+ schemaVersion: SCHEMA_VERSION,
39
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
40
+ level,
41
+ service: options.service,
42
+ env: options.env,
43
+ version: options.version,
44
+ message,
45
+ ...bindings,
46
+ ...rest
47
+ };
48
+ process.stdout.write(`${JSON.stringify(event)}
49
+ `);
50
+ };
51
+ return {
52
+ trace: logAt("trace"),
53
+ debug: logAt("debug"),
54
+ info: logAt("info"),
55
+ warn: logAt("warn"),
56
+ error: logAt("error"),
57
+ fatal: logAt("fatal"),
58
+ child(childBindings) {
59
+ return createLogger(options, { ...bindings, ...childBindings });
60
+ }
61
+ };
62
+ }
63
+ // Annotate the CommonJS export names for ESM import in node:
64
+ 0 && (module.exports = {
65
+ createLogger
66
+ });
67
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n CreateLoggerOptions,\n LogFields,\n LogLevel,\n Logger,\n} from \"./types.js\";\n\nexport type {\n CreateLoggerOptions,\n LogError,\n LogFields,\n LogLevel,\n LogRequest,\n LogResponse,\n Logger,\n} from \"./types.js\";\n\nconst SCHEMA_VERSION = \"1.0\";\n\nfunction normalizeFields(input: LogFields | string): LogFields {\n if (typeof input === \"string\") {\n return { message: input };\n }\n return input;\n}\n\nexport function createLogger(\n options: CreateLoggerOptions,\n bindings: Record<string, unknown> = {},\n): Logger {\n const logAt =\n (level: LogLevel) =>\n (input: LogFields | string): void => {\n const fields = normalizeFields(input);\n const { message, level: _ignoredLevel, ...rest } = fields;\n\n const event = {\n schemaVersion: SCHEMA_VERSION,\n timestamp: new Date().toISOString(),\n level,\n service: options.service,\n env: options.env,\n version: options.version,\n message,\n ...bindings,\n ...rest,\n };\n\n process.stdout.write(`${JSON.stringify(event)}\\n`);\n };\n\n return {\n trace: logAt(\"trace\"),\n debug: logAt(\"debug\"),\n info: logAt(\"info\"),\n warn: logAt(\"warn\"),\n error: logAt(\"error\"),\n fatal: logAt(\"fatal\"),\n child(childBindings: Record<string, unknown>): Logger {\n return createLogger(options, { ...bindings, ...childBindings });\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBA,IAAM,iBAAiB;AAEvB,SAAS,gBAAgB,OAAsC;AAC7D,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B;AACA,SAAO;AACT;AAEO,SAAS,aACd,SACA,WAAoC,CAAC,GAC7B;AACR,QAAM,QACJ,CAAC,UACD,CAAC,UAAoC;AACnC,UAAM,SAAS,gBAAgB,KAAK;AACpC,UAAM,EAAE,SAAS,OAAO,eAAe,GAAG,KAAK,IAAI;AAEnD,UAAM,QAAQ;AAAA,MACZ,eAAe;AAAA,MACf,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,MACA,SAAS,QAAQ;AAAA,MACjB,KAAK,QAAQ;AAAA,MACb,SAAS,QAAQ;AAAA,MACjB;AAAA,MACA,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAEA,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI;AAAA,EACnD;AAEF,SAAO;AAAA,IACL,OAAO,MAAM,OAAO;AAAA,IACpB,OAAO,MAAM,OAAO;AAAA,IACpB,MAAM,MAAM,MAAM;AAAA,IAClB,MAAM,MAAM,MAAM;AAAA,IAClB,OAAO,MAAM,OAAO;AAAA,IACpB,OAAO,MAAM,OAAO;AAAA,IACpB,MAAM,eAAgD;AACpD,aAAO,aAAa,SAAS,EAAE,GAAG,UAAU,GAAG,cAAc,CAAC;AAAA,IAChE;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,49 @@
1
+ type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
2
+ type CreateLoggerOptions = {
3
+ service: string;
4
+ env: string;
5
+ version: string;
6
+ };
7
+ type LogRequest = {
8
+ method?: string;
9
+ url?: string;
10
+ status?: number;
11
+ durationMs?: number;
12
+ userId?: string;
13
+ headers?: Record<string, string>;
14
+ body?: unknown;
15
+ bodyTruncated?: boolean;
16
+ };
17
+ type LogResponse = {
18
+ body?: unknown;
19
+ };
20
+ type LogError = {
21
+ type?: string;
22
+ message?: string;
23
+ stack?: string;
24
+ code?: string | number;
25
+ cause?: unknown;
26
+ };
27
+ type LogFields = {
28
+ message: string;
29
+ level?: LogLevel;
30
+ traceId?: string;
31
+ correlationId?: string;
32
+ request?: LogRequest;
33
+ response?: LogResponse;
34
+ error?: LogError | null;
35
+ [key: string]: unknown;
36
+ };
37
+ type Logger = {
38
+ trace: (fields: LogFields | string) => void;
39
+ debug: (fields: LogFields | string) => void;
40
+ info: (fields: LogFields | string) => void;
41
+ warn: (fields: LogFields | string) => void;
42
+ error: (fields: LogFields | string) => void;
43
+ fatal: (fields: LogFields | string) => void;
44
+ child: (bindings: Record<string, unknown>) => Logger;
45
+ };
46
+
47
+ declare function createLogger(options: CreateLoggerOptions, bindings?: Record<string, unknown>): Logger;
48
+
49
+ export { type CreateLoggerOptions, type LogError, type LogFields, type LogLevel, type LogRequest, type LogResponse, type Logger, createLogger };
@@ -0,0 +1,49 @@
1
+ type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
2
+ type CreateLoggerOptions = {
3
+ service: string;
4
+ env: string;
5
+ version: string;
6
+ };
7
+ type LogRequest = {
8
+ method?: string;
9
+ url?: string;
10
+ status?: number;
11
+ durationMs?: number;
12
+ userId?: string;
13
+ headers?: Record<string, string>;
14
+ body?: unknown;
15
+ bodyTruncated?: boolean;
16
+ };
17
+ type LogResponse = {
18
+ body?: unknown;
19
+ };
20
+ type LogError = {
21
+ type?: string;
22
+ message?: string;
23
+ stack?: string;
24
+ code?: string | number;
25
+ cause?: unknown;
26
+ };
27
+ type LogFields = {
28
+ message: string;
29
+ level?: LogLevel;
30
+ traceId?: string;
31
+ correlationId?: string;
32
+ request?: LogRequest;
33
+ response?: LogResponse;
34
+ error?: LogError | null;
35
+ [key: string]: unknown;
36
+ };
37
+ type Logger = {
38
+ trace: (fields: LogFields | string) => void;
39
+ debug: (fields: LogFields | string) => void;
40
+ info: (fields: LogFields | string) => void;
41
+ warn: (fields: LogFields | string) => void;
42
+ error: (fields: LogFields | string) => void;
43
+ fatal: (fields: LogFields | string) => void;
44
+ child: (bindings: Record<string, unknown>) => Logger;
45
+ };
46
+
47
+ declare function createLogger(options: CreateLoggerOptions, bindings?: Record<string, unknown>): Logger;
48
+
49
+ export { type CreateLoggerOptions, type LogError, type LogFields, type LogLevel, type LogRequest, type LogResponse, type Logger, createLogger };
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ // src/index.ts
2
+ var SCHEMA_VERSION = "1.0";
3
+ function normalizeFields(input) {
4
+ if (typeof input === "string") {
5
+ return { message: input };
6
+ }
7
+ return input;
8
+ }
9
+ function createLogger(options, bindings = {}) {
10
+ const logAt = (level) => (input) => {
11
+ const fields = normalizeFields(input);
12
+ const { message, level: _ignoredLevel, ...rest } = fields;
13
+ const event = {
14
+ schemaVersion: SCHEMA_VERSION,
15
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
16
+ level,
17
+ service: options.service,
18
+ env: options.env,
19
+ version: options.version,
20
+ message,
21
+ ...bindings,
22
+ ...rest
23
+ };
24
+ process.stdout.write(`${JSON.stringify(event)}
25
+ `);
26
+ };
27
+ return {
28
+ trace: logAt("trace"),
29
+ debug: logAt("debug"),
30
+ info: logAt("info"),
31
+ warn: logAt("warn"),
32
+ error: logAt("error"),
33
+ fatal: logAt("fatal"),
34
+ child(childBindings) {
35
+ return createLogger(options, { ...bindings, ...childBindings });
36
+ }
37
+ };
38
+ }
39
+ export {
40
+ createLogger
41
+ };
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n CreateLoggerOptions,\n LogFields,\n LogLevel,\n Logger,\n} from \"./types.js\";\n\nexport type {\n CreateLoggerOptions,\n LogError,\n LogFields,\n LogLevel,\n LogRequest,\n LogResponse,\n Logger,\n} from \"./types.js\";\n\nconst SCHEMA_VERSION = \"1.0\";\n\nfunction normalizeFields(input: LogFields | string): LogFields {\n if (typeof input === \"string\") {\n return { message: input };\n }\n return input;\n}\n\nexport function createLogger(\n options: CreateLoggerOptions,\n bindings: Record<string, unknown> = {},\n): Logger {\n const logAt =\n (level: LogLevel) =>\n (input: LogFields | string): void => {\n const fields = normalizeFields(input);\n const { message, level: _ignoredLevel, ...rest } = fields;\n\n const event = {\n schemaVersion: SCHEMA_VERSION,\n timestamp: new Date().toISOString(),\n level,\n service: options.service,\n env: options.env,\n version: options.version,\n message,\n ...bindings,\n ...rest,\n };\n\n process.stdout.write(`${JSON.stringify(event)}\\n`);\n };\n\n return {\n trace: logAt(\"trace\"),\n debug: logAt(\"debug\"),\n info: logAt(\"info\"),\n warn: logAt(\"warn\"),\n error: logAt(\"error\"),\n fatal: logAt(\"fatal\"),\n child(childBindings: Record<string, unknown>): Logger {\n return createLogger(options, { ...bindings, ...childBindings });\n },\n };\n}\n"],"mappings":";AAiBA,IAAM,iBAAiB;AAEvB,SAAS,gBAAgB,OAAsC;AAC7D,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B;AACA,SAAO;AACT;AAEO,SAAS,aACd,SACA,WAAoC,CAAC,GAC7B;AACR,QAAM,QACJ,CAAC,UACD,CAAC,UAAoC;AACnC,UAAM,SAAS,gBAAgB,KAAK;AACpC,UAAM,EAAE,SAAS,OAAO,eAAe,GAAG,KAAK,IAAI;AAEnD,UAAM,QAAQ;AAAA,MACZ,eAAe;AAAA,MACf,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,MACA,SAAS,QAAQ;AAAA,MACjB,KAAK,QAAQ;AAAA,MACb,SAAS,QAAQ;AAAA,MACjB;AAAA,MACA,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAEA,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI;AAAA,EACnD;AAEF,SAAO;AAAA,IACL,OAAO,MAAM,OAAO;AAAA,IACpB,OAAO,MAAM,OAAO;AAAA,IACpB,MAAM,MAAM,MAAM;AAAA,IAClB,MAAM,MAAM,MAAM;AAAA,IAClB,OAAO,MAAM,OAAO;AAAA,IACpB,OAAO,MAAM,OAAO;AAAA,IACpB,MAAM,eAAgD;AACpD,aAAO,aAAa,SAAS,EAAE,GAAG,UAAU,GAAG,cAAc,CAAC;AAAA,IAChE;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "@nicomatic/logger",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Logger JSON unifié pour les services Nicomatic (JS/TS)",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "schemas"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "license": "UNLICENSED",
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "registry": "https://registry.npmjs.org/"
33
+ },
34
+ "pnpm": {
35
+ "onlyBuiltDependencies": ["esbuild"]
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.13.10",
39
+ "ajv": "^8.17.1",
40
+ "ajv-formats": "^3.0.1",
41
+ "tsup": "^8.4.0",
42
+ "typescript": "^5.8.2",
43
+ "vitest": "^3.0.8"
44
+ }
45
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "timestamp": "30/10/2026 22:15",
4
+ "level": "info",
5
+ "service": "app-web",
6
+ "env": "prod",
7
+ "version": "2.4.1",
8
+ "message": "request completed"
9
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "timestamp": "2026-10-30T22:15:03.512Z",
4
+ "level": "info",
5
+ "service": "app-web",
6
+ "env": "prod",
7
+ "version": "2.4.1",
8
+ "message": "request completed",
9
+ "request": {
10
+ "method": "POST",
11
+ "url": "/api/orders",
12
+ "status": 201,
13
+ "headers": {
14
+ "authorization": "Bearer secret-token"
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "timestamp": "2026-10-30T22:15:03.512Z",
4
+ "level": "info",
5
+ "message": "incomplete event"
6
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "timestamp": "2026-10-30T22:15:03.512Z",
3
+ "level": "info",
4
+ "service": "app-web",
5
+ "env": "prod",
6
+ "version": "2.4.1",
7
+ "message": "request completed"
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "timestamp": "2026-10-30T22:15:03.512Z",
4
+ "level": "verbose",
5
+ "service": "app-web",
6
+ "env": "prod",
7
+ "version": "2.4.1",
8
+ "message": "request completed"
9
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "timestamp": "2026-11-07T18:05:00.000Z",
4
+ "level": "error",
5
+ "service": "app-web",
6
+ "env": "prod",
7
+ "version": "2.4.1",
8
+ "message": "request failed",
9
+ "traceId": "6d014f5799d56fc8c5e0141f2a2a6958",
10
+ "request": {
11
+ "method": "POST",
12
+ "url": "/api/profile",
13
+ "status": 500,
14
+ "durationMs": 120,
15
+ "userId": "u_5678",
16
+ "headers": {
17
+ "content-type": "application/json",
18
+ "user-agent": "Mozilla/5.0"
19
+ },
20
+ "body": {
21
+ "displayName": "Ada",
22
+ "email": "[REDACTED]",
23
+ "password": "[REDACTED]",
24
+ "token": "[REDACTED]"
25
+ },
26
+ "bodyTruncated": false
27
+ },
28
+ "error": {
29
+ "type": "InternalServerError",
30
+ "message": "unexpected failure",
31
+ "code": 500
32
+ }
33
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "timestamp": "2026-10-30T22:16:11.004Z",
4
+ "level": "error",
5
+ "service": "app-web",
6
+ "env": "prod",
7
+ "version": "2.4.1",
8
+ "message": "unhandled exception",
9
+ "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
10
+ "correlationId": "c0a8012e-7f3d-4a8b-9b1e-2f6d5c3e9a10",
11
+ "request": {
12
+ "method": "GET",
13
+ "url": "/api/orders/O-5521",
14
+ "status": 500,
15
+ "durationMs": 18
16
+ },
17
+ "error": {
18
+ "type": "Error",
19
+ "message": "database connection refused",
20
+ "stack": "Error: database connection refused\n at Query.run (db.js:10:5)",
21
+ "code": "ECONNREFUSED",
22
+ "cause": {
23
+ "type": "Error",
24
+ "message": "connect ECONNREFUSED 127.0.0.1:5432"
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "timestamp": "2026-11-07T18:00:00.000Z",
4
+ "level": "info",
5
+ "service": "auth-api",
6
+ "env": "prod",
7
+ "version": "1.2.0",
8
+ "message": "login attempt completed",
9
+ "traceId": "5cf03e4688c45eb7b4df030e1f1f5847",
10
+ "correlationId": "login-req-9f2a",
11
+ "request": {
12
+ "method": "POST",
13
+ "url": "/auth/login",
14
+ "status": 401,
15
+ "durationMs": 18,
16
+ "headers": {
17
+ "content-type": "application/json",
18
+ "user-agent": "Mozilla/5.0"
19
+ }
20
+ },
21
+ "error": null
22
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "timestamp": "2026-10-30T22:17:00.000Z",
4
+ "level": "debug",
5
+ "service": "worker-billing",
6
+ "env": "staging",
7
+ "version": "1.0.0",
8
+ "message": "job started"
9
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "timestamp": "2026-10-30T22:15:03.512Z",
4
+ "level": "info",
5
+ "service": "app-web",
6
+ "env": "prod",
7
+ "version": "2.4.1",
8
+ "message": "request completed",
9
+ "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
10
+ "correlationId": "c0a8012e-7f3d-4a8b-9b1e-2f6d5c3e9a10",
11
+ "request": {
12
+ "method": "POST",
13
+ "url": "/api/orders",
14
+ "status": 201,
15
+ "durationMs": 42,
16
+ "userId": "u_1234",
17
+ "headers": {
18
+ "user-agent": "Mozilla/5.0",
19
+ "content-type": "application/json"
20
+ },
21
+ "body": {
22
+ "productId": "P-88",
23
+ "quantity": 3,
24
+ "customer": {
25
+ "email": "[REDACTED]"
26
+ }
27
+ },
28
+ "bodyTruncated": false
29
+ },
30
+ "response": {
31
+ "body": {
32
+ "orderId": "O-5521"
33
+ }
34
+ },
35
+ "error": null
36
+ }
@@ -0,0 +1,157 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://nicomatic.local/schemas/log-event.schema.json",
4
+ "title": "Nicomatic unified log event",
5
+ "description": "Format JSON unifié des logs Nicomatic (schemaVersion 1.0). Voir docs/adr/0001-format-de-log-unifie.md.",
6
+ "type": "object",
7
+ "additionalProperties": true,
8
+ "required": [
9
+ "schemaVersion",
10
+ "timestamp",
11
+ "level",
12
+ "service",
13
+ "env",
14
+ "version",
15
+ "message"
16
+ ],
17
+ "properties": {
18
+ "schemaVersion": {
19
+ "type": "string",
20
+ "const": "1.0",
21
+ "description": "Version du schéma de log (majeur.mineur)."
22
+ },
23
+ "timestamp": {
24
+ "type": "string",
25
+ "format": "date-time",
26
+ "description": "Horodatage ISO 8601 en UTC (ex. 2026-10-30T22:15:03.512Z)."
27
+ },
28
+ "level": {
29
+ "type": "string",
30
+ "enum": ["trace", "debug", "info", "warn", "error", "fatal"]
31
+ },
32
+ "service": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "description": "Identifiant du service émetteur (label Loki)."
36
+ },
37
+ "env": {
38
+ "type": "string",
39
+ "minLength": 1,
40
+ "description": "Environnement (ex. dev, staging, prod) — label Loki."
41
+ },
42
+ "version": {
43
+ "type": "string",
44
+ "minLength": 1,
45
+ "description": "Version déployée du service."
46
+ },
47
+ "message": {
48
+ "type": "string",
49
+ "description": "Message humain court décrivant l'événement."
50
+ },
51
+ "traceId": {
52
+ "type": "string",
53
+ "minLength": 1,
54
+ "description": "Identifiant de trace (W3C trace-id ou équivalent)."
55
+ },
56
+ "correlationId": {
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "description": "Identifiant de corrélation métier / requête."
60
+ },
61
+ "request": {
62
+ "$ref": "#/$defs/request"
63
+ },
64
+ "response": {
65
+ "$ref": "#/$defs/response"
66
+ },
67
+ "error": {
68
+ "oneOf": [
69
+ { "type": "null" },
70
+ { "$ref": "#/$defs/error" }
71
+ ]
72
+ }
73
+ },
74
+ "$defs": {
75
+ "request": {
76
+ "type": "object",
77
+ "additionalProperties": false,
78
+ "properties": {
79
+ "method": {
80
+ "type": "string",
81
+ "minLength": 1
82
+ },
83
+ "url": {
84
+ "type": "string",
85
+ "minLength": 1
86
+ },
87
+ "status": {
88
+ "type": "integer",
89
+ "minimum": 100,
90
+ "maximum": 599
91
+ },
92
+ "durationMs": {
93
+ "type": "number",
94
+ "minimum": 0
95
+ },
96
+ "userId": {
97
+ "type": "string"
98
+ },
99
+ "headers": {
100
+ "type": "object",
101
+ "description": "Headers en liste blanche uniquement (jamais Authorization, Cookie, Set-Cookie).",
102
+ "additionalProperties": {
103
+ "type": "string"
104
+ },
105
+ "propertyNames": {
106
+ "enum": [
107
+ "accept",
108
+ "accept-language",
109
+ "content-type",
110
+ "content-length",
111
+ "user-agent",
112
+ "x-request-id",
113
+ "x-forwarded-for",
114
+ "x-forwarded-proto",
115
+ "x-correlation-id",
116
+ "traceparent",
117
+ "tracestate"
118
+ ]
119
+ }
120
+ },
121
+ "body": true,
122
+ "bodyTruncated": {
123
+ "type": "boolean"
124
+ }
125
+ }
126
+ },
127
+ "response": {
128
+ "type": "object",
129
+ "additionalProperties": false,
130
+ "properties": {
131
+ "body": true
132
+ }
133
+ },
134
+ "error": {
135
+ "type": "object",
136
+ "additionalProperties": true,
137
+ "properties": {
138
+ "type": {
139
+ "type": "string"
140
+ },
141
+ "message": {
142
+ "type": "string"
143
+ },
144
+ "stack": {
145
+ "type": "string"
146
+ },
147
+ "code": {
148
+ "oneOf": [
149
+ { "type": "string" },
150
+ { "type": "number" }
151
+ ]
152
+ },
153
+ "cause": true
154
+ }
155
+ }
156
+ }
157
+ }