@volontariapp/logger 0.2.6 → 0.3.0-snap-271697b
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/CHANGELOG.md +18 -0
- package/README.md +23 -23
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +9 -2
- package/dist/logger.js.map +1 -1
- package/dist/masking.d.ts +21 -0
- package/dist/masking.d.ts.map +1 -0
- package/dist/masking.js +86 -0
- package/dist/masking.js.map +1 -0
- package/dist/test/core/logger-masking.unit.spec.d.ts +2 -0
- package/dist/test/core/logger-masking.unit.spec.d.ts.map +1 -0
- package/dist/test/core/logger-masking.unit.spec.js +62 -0
- package/dist/test/core/logger-masking.unit.spec.js.map +1 -0
- package/dist/test/core/masking.unit.spec.d.ts +2 -0
- package/dist/test/core/masking.unit.spec.d.ts.map +1 -0
- package/dist/test/core/masking.unit.spec.js +117 -0
- package/dist/test/core/masking.unit.spec.js.map +1 -0
- package/dist/test/core/trace-context.unit.spec.d.ts +2 -0
- package/dist/test/core/trace-context.unit.spec.d.ts.map +1 -0
- package/dist/test/core/trace-context.unit.spec.js +69 -0
- package/dist/test/core/trace-context.unit.spec.js.map +1 -0
- package/dist/test/factories/span-context.factory.d.ts +5 -0
- package/dist/test/factories/span-context.factory.d.ts.map +1 -0
- package/dist/test/factories/span-context.factory.js +10 -0
- package/dist/test/factories/span-context.factory.js.map +1 -0
- package/dist/trace-context.d.ts +7 -0
- package/dist/trace-context.d.ts.map +1 -0
- package/dist/trace-context.js +15 -0
- package/dist/trace-context.js.map +1 -0
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Mask personal data and add trace correlation (tickets 3.0 and 3.1).
|
|
8
|
+
|
|
9
|
+
- Masking (3.0): metadata, positional params, error details and the message are masked before serialization, in JSON and text. Keys are matched by fragment, case-insensitively and ignoring `-` and `_` (`password`, `token`, `authorization`, `cookie`, `apikey`, `email`, `phone`, `iban`... so `x-internal-token` and `refresh_token` are covered), and strings are scanned for emails, JWTs and `Bearer` credentials. The walk is recursive, handles arrays, class instances, errors and circular references (`[Circular]`), and never mutates the object passed by the caller.
|
|
10
|
+
- Configurable: `new Logger({ masking: { keys, patterns, replacement } })` extends the defaults, `masking: false` disables it. New exports: `Masker`, `MaskingConfig`, `DEFAULT_SENSITIVE_KEYS`, `DEFAULT_SENSITIVE_PATTERNS`, `DEFAULT_REDACTION`, `CIRCULAR_REFERENCE`.
|
|
11
|
+
- Trace correlation (3.1): inside an active OpenTelemetry span, the JSON payload carries `trace_id` (hex), `dd.trace_id` (lower 64 bits, decimal) and `dd.span_id` (decimal). They are written after the caller metadata, which cannot override them. The text format is unchanged. New exports: `getTraceFields`, `TraceFields`. New dependency: `@opentelemetry/api` (`^1.9.0`, no SDK).
|
|
12
|
+
|
|
13
|
+
Behavior change: masking is on by default, so a consumer that logged an `email`, `phone` or `token` field will now see `[REDACTED]` once it bumps.
|
|
14
|
+
|
|
15
|
+
## 0.2.7
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- README bump
|
|
20
|
+
|
|
3
21
|
## 0.2.6
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @volontariapp/logger
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Un utilitaire de log cross-platform qui fonctionne parfaitement sur NodeJS, NestJS et React Native. Il génère des logs JSON structurés ou des logs texte colorés selon l'environnement d'exécution.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,13 +8,13 @@ A cross-platform logging utility that works seamlessly in NodeJS, NestJS, and Re
|
|
|
8
8
|
npm install @volontariapp/logger
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Configuration & Mise en place
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Ce logger est très polyvalent et supporte différentes stratégies d'instanciation en fonction de la plateforme ciblée.
|
|
14
14
|
|
|
15
|
-
### 1. NodeJS
|
|
15
|
+
### 1. Projet NodeJS
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Pour une application ou un script NodeJS classique, vous pouvez instancier le logger directement. Nous recommandons d'utiliser le format `text` pour le développement local et `json` en production.
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
20
|
import { Logger } from '@volontariapp/logger';
|
|
@@ -25,22 +25,22 @@ const logger = new Logger({
|
|
|
25
25
|
minLevel: 'info', // 'debug' | 'info' | 'warn' | 'error' | 'fatal'
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
logger.info('Worker
|
|
29
|
-
logger.error('
|
|
28
|
+
logger.info('Worker initialisé avec succès.');
|
|
29
|
+
logger.error('Échec de synchronisation des données.', new Error('Connection timeout'));
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
### 2. NestJS
|
|
32
|
+
### 2. Projet NestJS
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Le logger respecte l'interface `LoggerService` de NestJS de manière native (`log`, `error`, `warn`, `debug`, `verbose`). Vous pouvez l'utiliser comme logger global de l'application ou via l'injection de dépendances.
|
|
35
35
|
|
|
36
|
-
**Application
|
|
36
|
+
**Initialisation de l'Application** (`main.ts`) :
|
|
37
37
|
```typescript
|
|
38
38
|
import { NestFactory } from '@nestjs/core';
|
|
39
39
|
import { AppModule } from './app.module';
|
|
40
40
|
import { Logger } from '@volontariapp/logger';
|
|
41
41
|
|
|
42
42
|
async function bootstrap() {
|
|
43
|
-
//
|
|
43
|
+
// Utilise le format JSON pour des logs structurés dans les applications NestJS
|
|
44
44
|
const appLogger = new Logger({ format: 'json', context: 'NestApplication' });
|
|
45
45
|
|
|
46
46
|
const app = await NestFactory.create(AppModule, {
|
|
@@ -52,7 +52,7 @@ async function bootstrap() {
|
|
|
52
52
|
bootstrap();
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
**
|
|
55
|
+
**Module d'Injection de Dépendances** (`logger.module.ts`) :
|
|
56
56
|
```typescript
|
|
57
57
|
import { Module, Global } from '@nestjs/common';
|
|
58
58
|
import { Logger } from '@volontariapp/logger';
|
|
@@ -72,34 +72,34 @@ const customLogger = new Logger({ format: 'json', context: 'RequestScope' });
|
|
|
72
72
|
export class LoggerModule {}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
### 3. React Native
|
|
75
|
+
### 3. Projet React Native
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
Sur React Native, il est souvent plus simple de lire des logs texte dans la console Metro Bundler plutôt qu'un objet JSON complet. Vous pouvez l'instancier localement pour un composant spécifique ou en Singleton pour toute l'application.
|
|
78
78
|
|
|
79
79
|
```typescript
|
|
80
80
|
import { Logger } from '@volontariapp/logger';
|
|
81
81
|
|
|
82
82
|
export const logger = new Logger({
|
|
83
83
|
context: 'MobileApp',
|
|
84
|
-
format: 'text', //
|
|
84
|
+
format: 'text', // Recommandé pour la console metro bundler de react native
|
|
85
85
|
minLevel: 'debug',
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
//
|
|
88
|
+
// À l'intérieur d'un composant React
|
|
89
89
|
export const HomeScreen = () => {
|
|
90
|
-
logger.debug('HomeScreen
|
|
90
|
+
logger.debug('HomeScreen monté', { timestamp: Date.now() });
|
|
91
91
|
|
|
92
92
|
const handlePress = () => {
|
|
93
|
-
logger.info('
|
|
93
|
+
logger.info('Bouton cliqué');
|
|
94
94
|
};
|
|
95
95
|
|
|
96
96
|
return <Action handlePress={handlePress} />;
|
|
97
97
|
};
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
##
|
|
100
|
+
## Fonctionnalités
|
|
101
101
|
|
|
102
|
-
- **JSON &
|
|
103
|
-
- **Log
|
|
104
|
-
- **NestJS
|
|
105
|
-
- **Micro-metadata
|
|
102
|
+
- **Sortie JSON & Texte** : Changez de format selon l'environnement (`json` pour Grafana/ELK, `text` pour le dev local).
|
|
103
|
+
- **Niveaux de Log** : Niveaux standards (`debug`, `info`, `warn`, `error`, `fatal`).
|
|
104
|
+
- **Compatible NestJS** : Les méthodes `log` et `verbose` garantissent la compatibilité avec l'interface NestJS.
|
|
105
|
+
- **Micro-metadata** : Passez facilement des objets de métadonnées. `logger.info('User Login', { userId: 1 })`.
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC"}
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { LogLevel } from './colors.js';
|
|
2
|
+
import type { MaskingConfig } from './masking.js';
|
|
2
3
|
export interface LoggerConfig {
|
|
3
4
|
context?: string;
|
|
4
5
|
format?: 'json' | 'text';
|
|
5
6
|
minLevel?: LogLevel;
|
|
7
|
+
masking?: MaskingConfig | false;
|
|
6
8
|
}
|
|
7
9
|
export declare class Logger {
|
|
8
10
|
private readonly context;
|
|
9
11
|
private readonly format;
|
|
10
12
|
private readonly minLevel;
|
|
13
|
+
private readonly masker;
|
|
11
14
|
private readonly levels;
|
|
12
15
|
constructor(config?: LoggerConfig);
|
|
13
16
|
log(message: unknown, ...optionalParams: unknown[]): void;
|
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAIlD,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB,OAAO,CAAC,EAAE,aAAa,GAAG,KAAK,CAAC;CACjC;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAE5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAMrB;gBAEU,MAAM,CAAC,EAAE,YAAY;IAO1B,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,IAAI;IAIzD,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,IAAI;IAI3D,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,IAAI;IAI1D,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,IAAI;IAI1D,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,IAAI;IAI3D,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,IAAI;IAI3D,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,IAAI;IAIpE,OAAO,CAAC,KAAK;IAqDb,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,WAAW;CAoBpB"}
|
package/dist/logger.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Colors, getLevelColor } from './colors.js';
|
|
2
|
+
import { Masker } from './masking.js';
|
|
3
|
+
import { getTraceFields } from './trace-context.js';
|
|
2
4
|
export class Logger {
|
|
3
5
|
context;
|
|
4
6
|
format;
|
|
5
7
|
minLevel;
|
|
8
|
+
masker;
|
|
6
9
|
levels = {
|
|
7
10
|
debug: 0,
|
|
8
11
|
info: 1,
|
|
@@ -14,6 +17,7 @@ export class Logger {
|
|
|
14
17
|
this.context = config?.context ?? 'App';
|
|
15
18
|
this.format = config?.format ?? 'text';
|
|
16
19
|
this.minLevel = config?.minLevel ?? 'debug';
|
|
20
|
+
this.masker = config?.masking === false ? undefined : new Masker(config?.masking);
|
|
17
21
|
}
|
|
18
22
|
log(message, ...optionalParams) {
|
|
19
23
|
this.info(message, ...optionalParams);
|
|
@@ -47,8 +51,10 @@ export class Logger {
|
|
|
47
51
|
contextOverride = params[params.length - 1];
|
|
48
52
|
optionalArgs = params.slice(0, params.length - 1);
|
|
49
53
|
}
|
|
50
|
-
const
|
|
51
|
-
const
|
|
54
|
+
const rawMessage = typeof message === 'string' ? message : this.stringifyUnknown(message);
|
|
55
|
+
const parsedMessage = this.masker?.maskString(rawMessage) ?? rawMessage;
|
|
56
|
+
const rawMeta = this.parseParams(optionalArgs);
|
|
57
|
+
const meta = this.masker?.maskRecord(rawMeta) ?? rawMeta;
|
|
52
58
|
const logMethod = level === 'error' || level === 'fatal' ? 'error' : level === 'warn' ? 'warn' : 'log';
|
|
53
59
|
if (this.format === 'json') {
|
|
54
60
|
const payload = {
|
|
@@ -57,6 +63,7 @@ export class Logger {
|
|
|
57
63
|
context: contextOverride,
|
|
58
64
|
message: parsedMessage,
|
|
59
65
|
...meta,
|
|
66
|
+
...getTraceFields(),
|
|
60
67
|
};
|
|
61
68
|
console[logMethod](JSON.stringify(payload));
|
|
62
69
|
return;
|
package/dist/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const DEFAULT_REDACTION = "[REDACTED]";
|
|
2
|
+
export declare const CIRCULAR_REFERENCE = "[Circular]";
|
|
3
|
+
export declare const DEFAULT_SENSITIVE_KEYS: readonly string[];
|
|
4
|
+
export declare const DEFAULT_SENSITIVE_PATTERNS: readonly RegExp[];
|
|
5
|
+
export interface MaskingConfig {
|
|
6
|
+
keys?: readonly string[];
|
|
7
|
+
patterns?: readonly RegExp[];
|
|
8
|
+
replacement?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class Masker {
|
|
11
|
+
private readonly keys;
|
|
12
|
+
private readonly patterns;
|
|
13
|
+
private readonly replacement;
|
|
14
|
+
constructor(config?: MaskingConfig);
|
|
15
|
+
maskString(value: string): string;
|
|
16
|
+
mask(value: unknown): unknown;
|
|
17
|
+
maskRecord(value: Record<string, unknown>): Record<string, unknown>;
|
|
18
|
+
private isSensitiveKey;
|
|
19
|
+
private visit;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=masking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masking.d.ts","sourceRoot":"","sources":["../src/masking.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,eAAe,CAAC;AAC9C,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAkBnD,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAIvD,CAAC;AAEF,MAAM,WAAW,aAAa;IAE5B,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEzB,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAE7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,MAAM,CAAC,EAAE,aAAa;IAU3B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAQjC,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAI7B,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI1E,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,KAAK;CAiCd"}
|
package/dist/masking.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const DEFAULT_REDACTION = '[REDACTED]';
|
|
2
|
+
export const CIRCULAR_REFERENCE = '[Circular]';
|
|
3
|
+
export const DEFAULT_SENSITIVE_KEYS = [
|
|
4
|
+
'password',
|
|
5
|
+
'passwd',
|
|
6
|
+
'secret',
|
|
7
|
+
'token',
|
|
8
|
+
'authorization',
|
|
9
|
+
'cookie',
|
|
10
|
+
'apikey',
|
|
11
|
+
'privatekey',
|
|
12
|
+
'email',
|
|
13
|
+
'phone',
|
|
14
|
+
'firstname',
|
|
15
|
+
'lastname',
|
|
16
|
+
'birthdate',
|
|
17
|
+
'iban',
|
|
18
|
+
'creditcard',
|
|
19
|
+
'cardnumber',
|
|
20
|
+
'cvv',
|
|
21
|
+
];
|
|
22
|
+
export const DEFAULT_SENSITIVE_PATTERNS = [
|
|
23
|
+
/[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}/g,
|
|
24
|
+
/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/g,
|
|
25
|
+
/Bearer\s+[A-Za-z0-9._~+/=-]+/gi,
|
|
26
|
+
];
|
|
27
|
+
const normalizeKey = (key) => key.toLowerCase().replace(/[-_\s]/g, '');
|
|
28
|
+
const withGlobalFlag = (pattern) => pattern.global ? pattern : new RegExp(pattern.source, `${pattern.flags}g`);
|
|
29
|
+
export class Masker {
|
|
30
|
+
keys;
|
|
31
|
+
patterns;
|
|
32
|
+
replacement;
|
|
33
|
+
constructor(config) {
|
|
34
|
+
this.keys = [...DEFAULT_SENSITIVE_KEYS, ...(config?.keys ?? [])]
|
|
35
|
+
.map(normalizeKey)
|
|
36
|
+
.filter((key) => key.length > 0);
|
|
37
|
+
this.patterns = [...DEFAULT_SENSITIVE_PATTERNS, ...(config?.patterns ?? [])].map(withGlobalFlag);
|
|
38
|
+
this.replacement = config?.replacement ?? DEFAULT_REDACTION;
|
|
39
|
+
}
|
|
40
|
+
maskString(value) {
|
|
41
|
+
return this.patterns.reduce((masked, pattern) => masked.replace(pattern, this.replacement), value);
|
|
42
|
+
}
|
|
43
|
+
mask(value) {
|
|
44
|
+
return this.visit(value, new WeakSet());
|
|
45
|
+
}
|
|
46
|
+
maskRecord(value) {
|
|
47
|
+
return this.visit(value, new WeakSet());
|
|
48
|
+
}
|
|
49
|
+
isSensitiveKey(key) {
|
|
50
|
+
const normalized = normalizeKey(key);
|
|
51
|
+
return this.keys.some((fragment) => normalized.includes(fragment));
|
|
52
|
+
}
|
|
53
|
+
visit(value, ancestors) {
|
|
54
|
+
if (typeof value === 'string') {
|
|
55
|
+
return this.maskString(value);
|
|
56
|
+
}
|
|
57
|
+
if (typeof value !== 'object' || value === null || value instanceof Date) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
if (ancestors.has(value)) {
|
|
61
|
+
return CIRCULAR_REFERENCE;
|
|
62
|
+
}
|
|
63
|
+
ancestors.add(value);
|
|
64
|
+
try {
|
|
65
|
+
if (Array.isArray(value)) {
|
|
66
|
+
return value.map((item) => this.visit(item, ancestors));
|
|
67
|
+
}
|
|
68
|
+
if (value instanceof Error) {
|
|
69
|
+
return {
|
|
70
|
+
name: value.name,
|
|
71
|
+
message: this.maskString(value.message),
|
|
72
|
+
stack: value.stack === undefined ? undefined : this.maskString(value.stack),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const masked = {};
|
|
76
|
+
for (const [key, child] of Object.entries(value)) {
|
|
77
|
+
masked[key] = this.isSensitiveKey(key) ? this.replacement : this.visit(child, ancestors);
|
|
78
|
+
}
|
|
79
|
+
return masked;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
ancestors.delete(value);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=masking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masking.js","sourceRoot":"","sources":["../src/masking.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger-masking.unit.spec.d.ts","sourceRoot":"","sources":["../../../src/test/core/logger-masking.unit.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
+
import { Logger } from '../../logger.js';
|
|
3
|
+
import { DEFAULT_REDACTION } from '../../masking.js';
|
|
4
|
+
describe('Logger masking', () => {
|
|
5
|
+
let consoleLogSpy;
|
|
6
|
+
let consoleErrorSpy;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
9
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
jest.restoreAllMocks();
|
|
13
|
+
});
|
|
14
|
+
const lastOutput = () => consoleLogSpy.mock.calls[0][0];
|
|
15
|
+
it('should mask metadata in json format', () => {
|
|
16
|
+
const logger = new Logger({ format: 'json' });
|
|
17
|
+
logger.info('Login', { email: 'jane@example.com', password: 'hunter2', userId: 7 });
|
|
18
|
+
const payload = JSON.parse(lastOutput());
|
|
19
|
+
expect(payload).toMatchObject({
|
|
20
|
+
email: DEFAULT_REDACTION,
|
|
21
|
+
password: DEFAULT_REDACTION,
|
|
22
|
+
userId: 7,
|
|
23
|
+
});
|
|
24
|
+
expect(lastOutput()).not.toContain('hunter2');
|
|
25
|
+
expect(lastOutput()).not.toContain('jane@example.com');
|
|
26
|
+
});
|
|
27
|
+
it('should mask metadata in text format', () => {
|
|
28
|
+
const logger = new Logger({ format: 'text' });
|
|
29
|
+
logger.info('Login', { nested: { token: 'abc123' } });
|
|
30
|
+
expect(lastOutput()).toContain(DEFAULT_REDACTION);
|
|
31
|
+
expect(lastOutput()).not.toContain('abc123');
|
|
32
|
+
});
|
|
33
|
+
it('should mask patterns inside the message and error details', () => {
|
|
34
|
+
const logger = new Logger({ format: 'json' });
|
|
35
|
+
logger.error('Failed for jane@example.com', new Error('boom jane@example.com'));
|
|
36
|
+
const output = consoleErrorSpy.mock.calls[0][0];
|
|
37
|
+
expect(output).toContain(DEFAULT_REDACTION);
|
|
38
|
+
expect(output).not.toContain('jane@example.com');
|
|
39
|
+
});
|
|
40
|
+
it('should mask positional params', () => {
|
|
41
|
+
const logger = new Logger({ format: 'json' });
|
|
42
|
+
logger.info('Params', 'a@b.io', 3);
|
|
43
|
+
expect(lastOutput()).not.toContain('a@b.io');
|
|
44
|
+
});
|
|
45
|
+
it('should not mutate the metadata passed by the caller', () => {
|
|
46
|
+
const logger = new Logger({ format: 'json' });
|
|
47
|
+
const meta = { password: 'hunter2' };
|
|
48
|
+
logger.info('Login', meta);
|
|
49
|
+
expect(meta.password).toBe('hunter2');
|
|
50
|
+
});
|
|
51
|
+
it('should apply the configured keys and replacement', () => {
|
|
52
|
+
const logger = new Logger({ format: 'json', masking: { keys: ['ssn'], replacement: '***' } });
|
|
53
|
+
logger.info('Profile', { ssn: '123', password: 'x' });
|
|
54
|
+
expect(JSON.parse(lastOutput())).toMatchObject({ ssn: '***', password: '***' });
|
|
55
|
+
});
|
|
56
|
+
it('should let masking be disabled explicitly', () => {
|
|
57
|
+
const logger = new Logger({ format: 'json', masking: false });
|
|
58
|
+
logger.info('Login', { password: 'hunter2' });
|
|
59
|
+
expect(JSON.parse(lastOutput())).toMatchObject({ password: 'hunter2' });
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=logger-masking.unit.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger-masking.unit.spec.js","sourceRoot":"","sources":["../../../src/test/core/logger-masking.unit.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,IAAI,aAAqD,CAAC;IAC1D,IAAI,eAAyD,CAAC;IAE9D,UAAU,CAAC,GAAG,EAAE;QACd,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACxE,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,GAAW,EAAE,CAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAc,CAAC,CAAC,CAAC,CAAC;IAE9E,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAE9C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAEpF,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAe,CAAC;QACvD,MAAM,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC;YAC5B,KAAK,EAAE,iBAAiB;YACxB,QAAQ,EAAE,iBAAiB;YAC3B,MAAM,EAAE,CAAC;SACV,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC9C,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAE9C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QAEtD,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAClD,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAE9C,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;QAEhF,MAAM,MAAM,GAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAc,CAAC,CAAC,CAAC,CAAC;QAC9D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAE9C,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAEnC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;QAErC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAE3B,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAE9F,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QAEtD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAE9D,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;QAE9C,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masking.unit.spec.d.ts","sourceRoot":"","sources":["../../../src/test/core/masking.unit.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { CIRCULAR_REFERENCE, DEFAULT_REDACTION, Masker } from '../../masking.js';
|
|
3
|
+
const JWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U';
|
|
4
|
+
describe('Masker', () => {
|
|
5
|
+
describe('keys', () => {
|
|
6
|
+
it('should mask sensitive keys whatever their casing or separators', () => {
|
|
7
|
+
const masked = new Masker().mask({
|
|
8
|
+
password: 'hunter2',
|
|
9
|
+
'x-internal-token': 'abc',
|
|
10
|
+
Authorization: 'Bearer abc',
|
|
11
|
+
refresh_token: 'def',
|
|
12
|
+
userEmail: 'jane@example.com',
|
|
13
|
+
phoneNumber: '0612345678',
|
|
14
|
+
});
|
|
15
|
+
expect(masked).toEqual({
|
|
16
|
+
password: DEFAULT_REDACTION,
|
|
17
|
+
'x-internal-token': DEFAULT_REDACTION,
|
|
18
|
+
Authorization: DEFAULT_REDACTION,
|
|
19
|
+
refresh_token: DEFAULT_REDACTION,
|
|
20
|
+
userEmail: DEFAULT_REDACTION,
|
|
21
|
+
phoneNumber: DEFAULT_REDACTION,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
it('should keep non sensitive keys untouched', () => {
|
|
25
|
+
const input = { port: 3000, host: 'localhost', ok: true, nothing: null };
|
|
26
|
+
expect(new Masker().mask(input)).toEqual(input);
|
|
27
|
+
});
|
|
28
|
+
it('should accept extra keys and a custom replacement', () => {
|
|
29
|
+
const masker = new Masker({ keys: ['ssn'], replacement: '***' });
|
|
30
|
+
expect(masker.mask({ SSN: '123', password: 'x' })).toEqual({ SSN: '***', password: '***' });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('patterns', () => {
|
|
34
|
+
it('should mask emails and JWTs found inside strings', () => {
|
|
35
|
+
const masked = new Masker().maskString(`user jane.doe+tag@example.co.uk sent ${JWT}`);
|
|
36
|
+
expect(masked).toBe(`user ${DEFAULT_REDACTION} sent ${DEFAULT_REDACTION}`);
|
|
37
|
+
});
|
|
38
|
+
it('should mask bearer credentials', () => {
|
|
39
|
+
expect(new Masker().maskString('header Bearer abc.def-123')).toBe(`header ${DEFAULT_REDACTION}`);
|
|
40
|
+
});
|
|
41
|
+
it('should accept extra patterns, even without the global flag', () => {
|
|
42
|
+
const masker = new Masker({ patterns: [/\d{4}-\d{4}/] });
|
|
43
|
+
expect(masker.maskString('a 1234-5678 b 8765-4321')).toBe(`a ${DEFAULT_REDACTION} b ${DEFAULT_REDACTION}`);
|
|
44
|
+
});
|
|
45
|
+
it('should be stable across repeated calls', () => {
|
|
46
|
+
const masker = new Masker();
|
|
47
|
+
expect(masker.maskString('a@b.io')).toBe(DEFAULT_REDACTION);
|
|
48
|
+
expect(masker.maskString('a@b.io')).toBe(DEFAULT_REDACTION);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('nested structures', () => {
|
|
52
|
+
it('should mask recursively in objects and arrays', () => {
|
|
53
|
+
const masked = new Masker().mask({
|
|
54
|
+
request: {
|
|
55
|
+
headers: { authorization: 'secret-value', accept: 'json' },
|
|
56
|
+
users: [
|
|
57
|
+
{ id: 1, email: 'a@b.io' },
|
|
58
|
+
{ id: 2, note: 'contact c@d.io' },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
expect(masked).toEqual({
|
|
63
|
+
request: {
|
|
64
|
+
headers: { authorization: DEFAULT_REDACTION, accept: 'json' },
|
|
65
|
+
users: [
|
|
66
|
+
{ id: 1, email: DEFAULT_REDACTION },
|
|
67
|
+
{ id: 2, note: `contact ${DEFAULT_REDACTION}` },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
it('should not mutate the original object', () => {
|
|
73
|
+
const input = { password: 'hunter2', nested: { token: 'abc' }, list: ['a@b.io'] };
|
|
74
|
+
const snapshot = JSON.stringify(input);
|
|
75
|
+
new Masker().mask(input);
|
|
76
|
+
expect(JSON.stringify(input)).toBe(snapshot);
|
|
77
|
+
});
|
|
78
|
+
it('should mask class instances', () => {
|
|
79
|
+
class UserDto {
|
|
80
|
+
id;
|
|
81
|
+
email;
|
|
82
|
+
constructor(id, email) {
|
|
83
|
+
this.id = id;
|
|
84
|
+
this.email = email;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
expect(new Masker().mask(new UserDto(1, 'a@b.io'))).toEqual({
|
|
88
|
+
id: 1,
|
|
89
|
+
email: DEFAULT_REDACTION,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
it('should mask errors and keep their shape', () => {
|
|
93
|
+
const error = new Error('failed for a@b.io');
|
|
94
|
+
const masked = new Masker().mask(error);
|
|
95
|
+
expect(masked.name).toBe('Error');
|
|
96
|
+
expect(masked.message).toBe(`failed for ${DEFAULT_REDACTION}`);
|
|
97
|
+
});
|
|
98
|
+
it('should keep dates and primitives', () => {
|
|
99
|
+
const date = new Date('2026-10-07T00:00:00.000Z');
|
|
100
|
+
expect(new Masker().mask({ date, count: 3, flag: false })).toEqual({
|
|
101
|
+
date,
|
|
102
|
+
count: 3,
|
|
103
|
+
flag: false,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
it('should survive circular references', () => {
|
|
107
|
+
const input = { id: 1 };
|
|
108
|
+
input.self = input;
|
|
109
|
+
expect(new Masker().mask(input)).toEqual({ id: 1, self: CIRCULAR_REFERENCE });
|
|
110
|
+
});
|
|
111
|
+
it('should not flag a shared reference as circular', () => {
|
|
112
|
+
const shared = { id: 1 };
|
|
113
|
+
expect(new Masker().mask({ a: shared, b: shared })).toEqual({ a: shared, b: shared });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=masking.unit.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masking.unit.spec.js","sourceRoot":"","sources":["../../../src/test/core/masking.unit.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-context.unit.spec.d.ts","sourceRoot":"","sources":["../../../src/test/core/trace-context.unit.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
+
import { trace } from '@opentelemetry/api';
|
|
3
|
+
import { Logger } from '../../logger.js';
|
|
4
|
+
import { getTraceFields } from '../../trace-context.js';
|
|
5
|
+
import { SPAN_ID_HEX, TRACE_ID_HEX, createSpanContext } from '../factories/span-context.factory.js';
|
|
6
|
+
const DD_TRACE_ID = '9532127138774266268';
|
|
7
|
+
const DD_SPAN_ID = '13235353014750950193';
|
|
8
|
+
describe('Trace correlation', () => {
|
|
9
|
+
let consoleLogSpy;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
jest.restoreAllMocks();
|
|
15
|
+
});
|
|
16
|
+
const activateSpan = (overrides) => {
|
|
17
|
+
jest
|
|
18
|
+
.spyOn(trace, 'getActiveSpan')
|
|
19
|
+
.mockReturnValue(trace.wrapSpanContext(createSpanContext(overrides)));
|
|
20
|
+
};
|
|
21
|
+
describe('getTraceFields', () => {
|
|
22
|
+
it('should return undefined without an active span', () => {
|
|
23
|
+
expect(getTraceFields()).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
it('should convert the ids to the Datadog decimal format', () => {
|
|
26
|
+
activateSpan();
|
|
27
|
+
expect(getTraceFields()).toEqual({
|
|
28
|
+
trace_id: TRACE_ID_HEX,
|
|
29
|
+
'dd.trace_id': DD_TRACE_ID,
|
|
30
|
+
'dd.span_id': DD_SPAN_ID,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
it('should ignore an invalid span context', () => {
|
|
34
|
+
activateSpan({ traceId: '0'.repeat(32), spanId: '0'.repeat(16) });
|
|
35
|
+
expect(getTraceFields()).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('Logger json output', () => {
|
|
39
|
+
const lastPayload = () => JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
|
40
|
+
it('should add the trace fields inside an active span', () => {
|
|
41
|
+
activateSpan();
|
|
42
|
+
new Logger({ format: 'json' }).info('inside span');
|
|
43
|
+
expect(lastPayload()).toMatchObject({
|
|
44
|
+
message: 'inside span',
|
|
45
|
+
trace_id: TRACE_ID_HEX,
|
|
46
|
+
'dd.trace_id': DD_TRACE_ID,
|
|
47
|
+
'dd.span_id': DD_SPAN_ID,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
it('should not add the trace fields outside a span', () => {
|
|
51
|
+
new Logger({ format: 'json' }).info('outside span');
|
|
52
|
+
const payload = lastPayload();
|
|
53
|
+
expect(payload).not.toHaveProperty('trace_id');
|
|
54
|
+
expect(payload).not.toHaveProperty('dd.trace_id');
|
|
55
|
+
expect(payload).not.toHaveProperty('dd.span_id');
|
|
56
|
+
});
|
|
57
|
+
it('should not let caller metadata override the trace fields', () => {
|
|
58
|
+
activateSpan();
|
|
59
|
+
new Logger({ format: 'json' }).info('spoof', { 'dd.trace_id': '1' });
|
|
60
|
+
expect(lastPayload()['dd.trace_id']).toBe(DD_TRACE_ID);
|
|
61
|
+
});
|
|
62
|
+
it('should leave the text format untouched', () => {
|
|
63
|
+
activateSpan();
|
|
64
|
+
new Logger({ format: 'text' }).info('text');
|
|
65
|
+
expect(consoleLogSpy.mock.calls[0][0]).not.toContain(SPAN_ID_HEX);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=trace-context.unit.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-context.unit.spec.js","sourceRoot":"","sources":["../../../src/test/core/trace-context.unit.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SpanContext } from '@opentelemetry/api';
|
|
2
|
+
export declare const TRACE_ID_HEX = "0af7651916cd43dd8448eb211c80319c";
|
|
3
|
+
export declare const SPAN_ID_HEX = "b7ad6b7169203331";
|
|
4
|
+
export declare const createSpanContext: (overrides?: Partial<SpanContext>) => SpanContext;
|
|
5
|
+
//# sourceMappingURL=span-context.factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-context.factory.d.ts","sourceRoot":"","sources":["../../../src/test/factories/span-context.factory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,eAAO,MAAM,YAAY,qCAAqC,CAAC;AAC/D,eAAO,MAAM,WAAW,qBAAqB,CAAC;AAE9C,eAAO,MAAM,iBAAiB,eAAe,OAAO,CAAC,WAAW,CAAC,KAAQ,WAKvE,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TraceFlags } from '@opentelemetry/api';
|
|
2
|
+
export const TRACE_ID_HEX = '0af7651916cd43dd8448eb211c80319c';
|
|
3
|
+
export const SPAN_ID_HEX = 'b7ad6b7169203331';
|
|
4
|
+
export const createSpanContext = (overrides = {}) => ({
|
|
5
|
+
traceId: TRACE_ID_HEX,
|
|
6
|
+
spanId: SPAN_ID_HEX,
|
|
7
|
+
traceFlags: TraceFlags.SAMPLED,
|
|
8
|
+
...overrides,
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=span-context.factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-context.factory.js","sourceRoot":"","sources":["../../../src/test/factories/span-context.factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,MAAM,CAAC,MAAM,YAAY,GAAG,kCAAkC,CAAC;AAC/D,MAAM,CAAC,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAE9C,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,YAAkC,EAAE,EAAe,EAAE,CAAC,CAAC;IACvF,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,UAAU,EAAE,UAAU,CAAC,OAAO;IAC9B,GAAG,SAAS;CACb,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-context.d.ts","sourceRoot":"","sources":["../src/trace-context.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAQD,wBAAgB,cAAc,IAAI,WAAW,GAAG,SAAS,CAYxD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { trace } from '@opentelemetry/api';
|
|
2
|
+
const DATADOG_ID_HEX_LENGTH = 16;
|
|
3
|
+
const toDatadogId = (hex) => BigInt(`0x${hex.slice(-DATADOG_ID_HEX_LENGTH)}`).toString();
|
|
4
|
+
export function getTraceFields() {
|
|
5
|
+
const spanContext = trace.getActiveSpan()?.spanContext();
|
|
6
|
+
if (spanContext === undefined || !trace.isSpanContextValid(spanContext)) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
trace_id: spanContext.traceId,
|
|
11
|
+
'dd.trace_id': toDatadogId(spanContext.traceId),
|
|
12
|
+
'dd.span_id': toDatadogId(spanContext.spanId),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=trace-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-context.js","sourceRoot":"","sources":["../src/trace-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAQ3C,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAGjC,MAAM,WAAW,GAAG,CAAC,GAAW,EAAU,EAAE,CAC1C,MAAM,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;AAE9D,MAAM,UAAU,cAAc;IAC5B,MAAM,WAAW,GAAG,KAAK,CAAC,aAAa,EAAE,EAAE,WAAW,EAAE,CAAC;IAEzD,IAAI,WAAW,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;QACxE,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,WAAW,CAAC,OAAO;QAC7B,aAAa,EAAE,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC;QAC/C,YAAY,EAAE,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC;KAC9C,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volontariapp/logger",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-snap-271697b",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"provenance": true
|
|
@@ -33,11 +33,14 @@
|
|
|
33
33
|
"test": "node --experimental-vm-modules $(yarn bin jest)",
|
|
34
34
|
"test:coverage": "node --experimental-vm-modules $(yarn bin jest) --coverage --coverageReporters=\"text\" --coverageReporters=\"json-summary\" --coverageReporters=\"lcov\""
|
|
35
35
|
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@opentelemetry/api": "^1.9.0"
|
|
38
|
+
},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"@jest/globals": "^30.3.0",
|
|
38
41
|
"@types/jest": "^30.0.0",
|
|
39
42
|
"@types/node": "^25.5.2",
|
|
40
|
-
"@volontariapp/testing": "1.0.
|
|
43
|
+
"@volontariapp/testing": "1.0.3",
|
|
41
44
|
"jest": "^30.3.0",
|
|
42
45
|
"ts-jest": "^29.4.9",
|
|
43
46
|
"typescript": "5.7.3"
|