@villedemontreal/correlation-id 5.3.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/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/src/config/configs.d.ts +16 -0
- package/dist/src/config/configs.js +26 -0
- package/dist/src/config/configs.js.map +1 -0
- package/dist/src/config/constants.d.ts +27 -0
- package/dist/src/config/constants.js +27 -0
- package/dist/src/config/constants.js.map +1 -0
- package/dist/src/config/init.d.ts +15 -0
- package/dist/src/config/init.js +34 -0
- package/dist/src/config/init.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +25 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/middleware/correlationIdMiddleware.d.ts +8 -0
- package/dist/src/middleware/correlationIdMiddleware.js +43 -0
- package/dist/src/middleware/correlationIdMiddleware.js.map +1 -0
- package/dist/src/middleware/correlationIdMiddleware.test.d.ts +1 -0
- package/dist/src/middleware/correlationIdMiddleware.test.js +306 -0
- package/dist/src/middleware/correlationIdMiddleware.test.js.map +1 -0
- package/dist/src/services/correlationIdService.d.ts +68 -0
- package/dist/src/services/correlationIdService.js +166 -0
- package/dist/src/services/correlationIdService.js.map +1 -0
- package/dist/src/services/correlationIdService.test.d.ts +1 -0
- package/dist/src/services/correlationIdService.test.js +215 -0
- package/dist/src/services/correlationIdService.test.js.map +1 -0
- package/dist/src/utils/logger.d.ts +11 -0
- package/dist/src/utils/logger.js +54 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/testingConfigurations.d.ts +9 -0
- package/dist/src/utils/testingConfigurations.js +18 -0
- package/dist/src/utils/testingConfigurations.js.map +1 -0
- package/package.json +62 -0
- package/src/config/configs.ts +26 -0
- package/src/config/constants.ts +40 -0
- package/src/config/init.ts +33 -0
- package/src/index.ts +9 -0
- package/src/middleware/correlationIdMiddleware.test.ts +335 -0
- package/src/middleware/correlationIdMiddleware.ts +45 -0
- package/src/services/correlationIdService.test.ts +255 -0
- package/src/services/correlationIdService.ts +255 -0
- package/src/utils/logger.ts +53 -0
- package/src/utils/testingConfigurations.ts +14 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
2
|
+
import * as cls from 'cls-hooked';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import * as express from 'express';
|
|
5
|
+
import * as semver from 'semver';
|
|
6
|
+
import { v4 as uuid } from 'uuid';
|
|
7
|
+
import { constants } from '../config/constants';
|
|
8
|
+
const oldEmitSlot = Symbol('kOriginalEmit');
|
|
9
|
+
const cidSlot = Symbol('kCorrelationId');
|
|
10
|
+
const storeSlot = Symbol('kCidStore');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* CorrelationId type
|
|
14
|
+
*/
|
|
15
|
+
export type CorrelationId = string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Informations about the correlation ID.
|
|
19
|
+
*/
|
|
20
|
+
export interface ICidInfo {
|
|
21
|
+
/**
|
|
22
|
+
* Current cid
|
|
23
|
+
*/
|
|
24
|
+
current: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Cid received in the request (may be undefined)
|
|
28
|
+
*/
|
|
29
|
+
receivedInRequest: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Cid generated (may be undefined)
|
|
33
|
+
*/
|
|
34
|
+
generated: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* CorrelationId service
|
|
39
|
+
*/
|
|
40
|
+
export interface ICorrelationIdService {
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new correlation ID that can then be passed to the
|
|
43
|
+
* "withId()" function.
|
|
44
|
+
*/
|
|
45
|
+
createNewId(): CorrelationId;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Executes a function inside a context where the correlation ID is defined.
|
|
49
|
+
*
|
|
50
|
+
* @param work the function to run within the cid context.
|
|
51
|
+
* @param cid the correlation ID to use.
|
|
52
|
+
*
|
|
53
|
+
*/
|
|
54
|
+
withId<T>(work: () => T, cid?: CorrelationId): T;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Executes a function inside a context where the correlation ID is defined.
|
|
58
|
+
* This is the promisified version of the `withId` method.
|
|
59
|
+
* @param work a callback to invoke with the submitted correlation ID
|
|
60
|
+
* @param cid the correlation ID to install be before invoking the submitted callback
|
|
61
|
+
*
|
|
62
|
+
* @deprecated `#withId` is preferable instead: if the wrapped operation
|
|
63
|
+
* is asynchronous it will still be properly scoped (correlation context) and
|
|
64
|
+
* can safely be awaited for outside of `#withId`.
|
|
65
|
+
*/
|
|
66
|
+
withIdAsync<T>(work: () => Promise<T>, cid?: string): Promise<T>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* binds the current correlation context to the target
|
|
70
|
+
* @param target the target to bind to
|
|
71
|
+
* @returns either the submitted target (if it is an emitter) or a wrapped target (for a function)
|
|
72
|
+
* @remarks you might have to bind to an emitter in order to maitain
|
|
73
|
+
* the correlation context.
|
|
74
|
+
*/
|
|
75
|
+
bind<T>(target: T): T;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns the correlation ID from the current context
|
|
79
|
+
*/
|
|
80
|
+
getId(): CorrelationId;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Returns all correlation ID info
|
|
84
|
+
*/
|
|
85
|
+
getCidInfo(req: express.Request): ICidInfo;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* CorrelationId service
|
|
90
|
+
*/
|
|
91
|
+
class CorrelationIdServiceWithClsHooked implements ICorrelationIdService {
|
|
92
|
+
private store: cls.Namespace = cls.createNamespace('343c9880-fa2b-4212-a9f3-15f3cc09581d');
|
|
93
|
+
|
|
94
|
+
public createNewId(): CorrelationId {
|
|
95
|
+
return uuid();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public withId<T>(work: () => T, cid?: CorrelationId): T {
|
|
99
|
+
let cidClean = cid;
|
|
100
|
+
if (!cidClean) {
|
|
101
|
+
cidClean = this.createNewId();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return this.store.runAndReturn(() => {
|
|
105
|
+
this.store.set('correlator', cidClean);
|
|
106
|
+
return work();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public async withIdAsync<T>(work: () => Promise<T>, cid?: string): Promise<T> {
|
|
111
|
+
return new Promise<T>((resolve, reject) => {
|
|
112
|
+
this.withId(() => {
|
|
113
|
+
try {
|
|
114
|
+
work()
|
|
115
|
+
.then(resolve)
|
|
116
|
+
.catch(reject);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
reject(err);
|
|
119
|
+
}
|
|
120
|
+
}, cid);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public bind<T>(target: T): T {
|
|
125
|
+
if (target instanceof EventEmitter) {
|
|
126
|
+
return this.bindEmitter(target);
|
|
127
|
+
}
|
|
128
|
+
if (typeof target === 'function') {
|
|
129
|
+
return this.bindFunction(target);
|
|
130
|
+
}
|
|
131
|
+
return target;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public getId() {
|
|
135
|
+
return this.store.get('correlator');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public getCidInfo(req: express.Request): ICidInfo {
|
|
139
|
+
return {
|
|
140
|
+
current: this.getId(),
|
|
141
|
+
receivedInRequest: req[constants.requestExtraVariables.cidReceivedInRequest],
|
|
142
|
+
generated: req[constants.requestExtraVariables.cidNew]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private bindEmitter<T extends EventEmitter>(emitter: T): T {
|
|
147
|
+
// Note that we can't use the following line:
|
|
148
|
+
// this.store.bindEmitter(emitter);
|
|
149
|
+
// because this works only if bindEmitter is called before any
|
|
150
|
+
// call to the "on" method of the emitter, and I don't want to
|
|
151
|
+
// risk having ordering issues.
|
|
152
|
+
// Note however that patching an emitter might not work in 100% cases.
|
|
153
|
+
// patch emit method only once!
|
|
154
|
+
if (!emitter[oldEmitSlot]) {
|
|
155
|
+
emitter[oldEmitSlot] = emitter.emit;
|
|
156
|
+
(emitter as any).emit = (...args: any[]) => {
|
|
157
|
+
// wrap the emit call within a new correlation context
|
|
158
|
+
// with the bound cid.
|
|
159
|
+
this.withId(() => {
|
|
160
|
+
// invoke original emit method
|
|
161
|
+
emitter[oldEmitSlot].apply(emitter, args);
|
|
162
|
+
}, emitter[cidSlot]);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// update the cid bound to the emitter
|
|
166
|
+
emitter[cidSlot] = this.getId();
|
|
167
|
+
return emitter;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// tslint:disable-next-line: ban-types
|
|
171
|
+
private bindFunction<T extends Function>(target: T): T {
|
|
172
|
+
return this.store.bind(target);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// tslint:disable-next-line: max-classes-per-file
|
|
177
|
+
class CorrelationIdServiceWithAsyncLocalStorage implements ICorrelationIdService {
|
|
178
|
+
private storage = new AsyncLocalStorage();
|
|
179
|
+
|
|
180
|
+
public createNewId(): CorrelationId {
|
|
181
|
+
return uuid();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public withId<T>(work: () => T, cid?: CorrelationId): T {
|
|
185
|
+
const correlationId = cid || this.createNewId();
|
|
186
|
+
return this.storage.run({ correlationId }, work);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public async withIdAsync<T>(work: () => Promise<T>, cid?: string): Promise<T> {
|
|
190
|
+
return this.withId(work, cid);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public bind<T>(target: T): T {
|
|
194
|
+
if (target instanceof EventEmitter) {
|
|
195
|
+
return this.bindEmitter(target);
|
|
196
|
+
}
|
|
197
|
+
if (typeof target === 'function') {
|
|
198
|
+
return this.bindFunction(target);
|
|
199
|
+
}
|
|
200
|
+
return target;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public getId() {
|
|
204
|
+
const store = this.storage.getStore();
|
|
205
|
+
if (store) {
|
|
206
|
+
return store.correlationId;
|
|
207
|
+
}
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public getCidInfo(req: express.Request): ICidInfo {
|
|
212
|
+
return {
|
|
213
|
+
current: this.getId(),
|
|
214
|
+
receivedInRequest: req[constants.requestExtraVariables.cidReceivedInRequest],
|
|
215
|
+
generated: req[constants.requestExtraVariables.cidNew]
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private bindEmitter<T extends EventEmitter>(emitter: T): T {
|
|
220
|
+
// patch emit method only once!
|
|
221
|
+
if (!emitter[oldEmitSlot]) {
|
|
222
|
+
emitter[oldEmitSlot] = emitter.emit;
|
|
223
|
+
(emitter as any).emit = (...args: any[]) => {
|
|
224
|
+
// use the store that was bound to this emitter
|
|
225
|
+
const store = emitter[storeSlot];
|
|
226
|
+
if (store) {
|
|
227
|
+
this.storage.enterWith(store);
|
|
228
|
+
}
|
|
229
|
+
// invoke original emit method
|
|
230
|
+
emitter[oldEmitSlot].call(emitter, ...args);
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
// update the store bound to the emitter
|
|
234
|
+
emitter[storeSlot] = this.storage.getStore();
|
|
235
|
+
return emitter;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// tslint:disable-next-line: ban-types
|
|
239
|
+
private bindFunction<T extends Function>(target: T): T {
|
|
240
|
+
const storage = this.storage;
|
|
241
|
+
const store = this.storage.getStore();
|
|
242
|
+
return function(...args: any[]) {
|
|
243
|
+
storage.enterWith(store);
|
|
244
|
+
return target.call(this, ...args);
|
|
245
|
+
} as any;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function canUseAsyncLocalStorage() {
|
|
250
|
+
return semver.satisfies(process.versions.node, '>=13.10.0') && !!AsyncLocalStorage;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export let correlationIdService: ICorrelationIdService = canUseAsyncLocalStorage()
|
|
254
|
+
? new CorrelationIdServiceWithAsyncLocalStorage()
|
|
255
|
+
: new CorrelationIdServiceWithClsHooked();
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ILogger, initLogger, LazyLogger, Logger, LoggerConfigs, LogLevel } from '@villedemontreal/logger';
|
|
2
|
+
import { configs } from '../config/configs';
|
|
3
|
+
|
|
4
|
+
let testingLoggerLibInitialised = false;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a Logger.
|
|
8
|
+
*/
|
|
9
|
+
export function createLogger(name: string): ILogger {
|
|
10
|
+
// ==========================================
|
|
11
|
+
// We use a LazyLogger so the real Logger
|
|
12
|
+
// is only created when the first
|
|
13
|
+
// log is actually performed... At that point,
|
|
14
|
+
// our "configs.loggerCreator" configuration
|
|
15
|
+
// must have been set by the code using our library!
|
|
16
|
+
//
|
|
17
|
+
// This pattern allows calling code to import
|
|
18
|
+
// modules from us in which a logger is
|
|
19
|
+
// created in the global scope :
|
|
20
|
+
//
|
|
21
|
+
// let logger = createLogger('someName');
|
|
22
|
+
//
|
|
23
|
+
// Without a Lazy Logger, the library configurations
|
|
24
|
+
// would at that moment *not* have been set yet
|
|
25
|
+
// (by the calling code) and an Error would be thrown
|
|
26
|
+
// because the "configs.loggerCreator" is required.
|
|
27
|
+
// ==========================================
|
|
28
|
+
return new LazyLogger(name, (nameArg: string) => {
|
|
29
|
+
return configs.loggerCreator(nameArg);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function initTestingLoggerConfigs() {
|
|
34
|
+
const loggerConfig: LoggerConfigs = new LoggerConfigs(() => 'test-cid');
|
|
35
|
+
loggerConfig.setLogLevel(LogLevel.DEBUG);
|
|
36
|
+
initLogger(loggerConfig);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A Logger that uses a dummy cid provider.
|
|
41
|
+
*
|
|
42
|
+
* Only use this when running the tests!
|
|
43
|
+
*/
|
|
44
|
+
export function getTestingLoggerCreator(): (name: string) => ILogger {
|
|
45
|
+
return (name: string): ILogger => {
|
|
46
|
+
if (!testingLoggerLibInitialised) {
|
|
47
|
+
initTestingLoggerConfigs();
|
|
48
|
+
testingLoggerLibInitialised = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return new Logger(name);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { init } from '../config/init';
|
|
2
|
+
import { getTestingLoggerCreator } from '../utils/logger';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Call this when your need to set
|
|
6
|
+
* *Testing* configurations to the current
|
|
7
|
+
* library, without the need for a calling code
|
|
8
|
+
* to do so.
|
|
9
|
+
*
|
|
10
|
+
* A test Correlation Id will be used!
|
|
11
|
+
*/
|
|
12
|
+
export function setTestingConfigurations(): void {
|
|
13
|
+
init(getTestingLoggerCreator());
|
|
14
|
+
}
|