@@ -86,13 +86,20 @@ Define `Binding` keys here for the component as well as any constants for the
86
86
user (for this extension that'll be the logLevel ` enum ` ).
87
87
88
88
``` ts
89
+ /**
90
+ * Binding keys used by this component.
91
+ */
89
92
export namespace EXAMPLE_LOG_BINDINGS {
90
93
export const METADATA = ' example.log.metadata' ;
91
94
export const APP_LOG_LEVEL = ' example.log.level' ;
92
95
export const TIMER = ' example.log.timer' ;
96
+ export const LOGGER = ' example.log.logger' ;
93
97
export const LOG_ACTION = ' example.log.action' ;
94
98
}
95
99
100
+ /**
101
+ * Enum to define the supported log levels
102
+ */
96
103
export enum LOG_LEVEL {
97
104
DEBUG ,
98
105
INFO ,
@@ -108,56 +115,93 @@ Define TypeScript type definitions / interfaces for complex types and functions
108
115
``` ts
109
116
import {ParsedRequest , OperationArgs } from ' @loopback/rest' ;
110
117
118
+ /**
119
+ * A function to perform REST req/res logging action
120
+ */
111
121
export interface LogFn {
112
122
(
113
123
req : ParsedRequest ,
114
124
args : OperationArgs ,
125
+ // tslint:disable-next-line:no-any
115
126
result : any ,
116
127
startTime ? : HighResTime ,
117
128
): Promise <void >;
118
129
119
130
startTimer(): HighResTime ;
120
131
}
121
132
133
+ /**
134
+ * Log level metadata
135
+ */
122
136
export type LevelMetadata = {level: number };
137
+
138
+ /**
139
+ * High resolution time as [seconds, nanoseconds]. Used by process.hrtime().
140
+ */
123
141
export type HighResTime = [number , number ]; // [seconds, nanoseconds]
142
+
143
+ /**
144
+ * Log writing function
145
+ */
146
+ export type LogWriterFn = (msg : string , level : number ) => void ;
147
+
148
+ /**
149
+ * Timer function for logging
150
+ */
124
151
export type TimerFn = (start ? : HighResTime ) => HighResTime ;
125
152
```
126
153
127
154
### ` src/decorators/log.decorator.ts `
128
- Extension users can use decorators to provide "hints" (or metadata) for our
129
- component. These "hints" allow the extension to modify behaviour accordingly.
155
+ Extension developers can create decorators to provide "hints" (or metadata) to
156
+ user artifacts such as controllers and their methods. These "hints" allow the
157
+ extension to add extra processing accordingly.
130
158
131
159
For this extension, the decorator marks which controller methods should be
132
- logged (and optionally at which level they should be logged).
133
- ` Reflector ` from ` @loopback/context ` is used to store and retrieve the metadata
134
- by the extension.
160
+ logged (and optionally at which level they should be logged). We leverage
161
+ ` @loopback/metadata ` module to implement the decorator and inspection function.
135
162
136
163
``` ts
137
164
import {LOG_LEVEL , EXAMPLE_LOG_BINDINGS } from ' ../keys' ;
138
- import {Constructor , Reflector } from ' @loopback/context' ;
165
+ import {
166
+ Constructor ,
167
+ MethodDecoratorFactory ,
168
+ MetadataInspector ,
169
+ } from ' @loopback/context' ;
139
170
import {LevelMetadata } from ' ../types' ;
140
171
172
+ /**
173
+ * Mark a controller method as requiring logging (input, output & timing)
174
+ * if it is set at or greater than Application LogLevel.
175
+ * LOG_LEVEL.DEBUG < LOG_LEVEL.INFO < LOG_LEVEL.WARN < LOG_LEVEL.ERROR < LOG_LEVEL.OFF
176
+ *
177
+ * @param level The Log Level at or above it should log
178
+ */
141
179
export function log(level ? : number ) {
142
- return function (target : Object , methodName : string ): void {
143
- if (level === undefined ) level = LOG_LEVEL .WARN ;
144
- Reflector .defineMetadata (
145
- EXAMPLE_LOG_BINDINGS .METADATA ,
146
- {level },
147
- target ,
148
- methodName ,
149
- );
150
- };
180
+ if (level === undefined ) level = LOG_LEVEL .WARN ;
181
+ return MethodDecoratorFactory .createDecorator <LevelMetadata >(
182
+ EXAMPLE_LOG_BINDINGS .METADATA ,
183
+ {
184
+ level ,
185
+ },
186
+ );
151
187
}
152
188
189
+ /**
190
+ * Fetch log level stored by `@log` decorator.
191
+ *
192
+ * @param controllerClass Target controller
193
+ * @param methodName Target method
194
+ */
153
195
export function getLogMetadata(
154
196
controllerClass : Constructor <{}>,
155
197
methodName : string ,
156
198
): LevelMetadata {
157
- return Reflector .getMetadata (
158
- EXAMPLE_LOG_BINDINGS .METADATA ,
159
- controllerClass .prototype ,
160
- methodName ,
199
+ return (
200
+ MetadataInspector .getMethodMetadata <LevelMetadata >(
201
+ EXAMPLE_LOG_BINDINGS .METADATA ,
202
+ controllerClass .prototype ,
203
+ methodName ,
204
+ ) || {level : LOG_LEVEL .OFF }
161
205
);
162
206
}
163
207
```
@@ -214,23 +258,6 @@ export class TimerProvider implements Provider<TimerFn> {
214
258
}
215
259
```
216
260
217
- ### ` src/providers/log-level.provider.ts `
218
- A provider can set the default binding value for ` example.log.level ` so it's
219
- easier to get started with the extension. User's can override the value by
220
- binding a new value or using the mixin.
221
-
222
- ``` ts
223
- import {Provider } from ' @loopback/context' ;
224
- import {LOG_LEVEL } from ' ../keys' ;
225
-
226
- export class LogLevelProvider implements Provider <number > {
227
- constructor () {}
228
- value(): number {
229
- return LOG_LEVEL .WARN ;
230
- }
231
- }
232
- ```
233
-
234
261
### ` src/providers/log-action.provider.ts `
235
262
This will be the most important provider for the extension as it is responsible
236
263
for actually logging the request. The extension will retrieve the metadata
@@ -245,24 +272,36 @@ import {CoreBindings} from '@loopback/core';
245
272
import {OperationArgs , ParsedRequest } from ' @loopback/rest' ;
246
273
import {getLogMetadata } from ' ../decorators/log.decorator' ;
247
274
import {EXAMPLE_LOG_BINDINGS , LOG_LEVEL } from ' ../keys' ;
248
- import {LogFn , TimerFn , HighResTime , LevelMetadata } from ' ../types' ;
275
+ import {
276
+ LogFn ,
277
+ TimerFn ,
278
+ HighResTime ,
279
+ LevelMetadata ,
280
+ LogWriterFn ,
281
+ } from ' ../types' ;
249
282
import chalk from ' chalk' ;
250
283
251
284
export class LogActionProvider implements Provider <LogFn > {
285
+ // LogWriteFn is an optional dependency and it falls back to `logToConsole`
286
+ @inject (EXAMPLE_LOG_BINDINGS .LOGGER , {optional: true })
287
+ private logWriter: LogWriterFn = logToConsole ;
288
+
289
+ @inject (EXAMPLE_LOG_BINDINGS .APP_LOG_LEVEL , {optional: true })
290
+ private logLevel: number = LOG_LEVEL .WARN ;
291
+
252
292
constructor (
253
293
@inject .getter (CoreBindings .CONTROLLER_CLASS )
254
294
private readonly getController : Getter <Constructor <{}>>,
255
295
@inject .getter (CoreBindings .CONTROLLER_METHOD_NAME )
256
296
private readonly getMethod : Getter <string >,
257
- @inject (EXAMPLE_LOG_BINDINGS .APP_LOG_LEVEL )
258
- private readonly logLevel : number ,
259
297
@inject (EXAMPLE_LOG_BINDINGS .TIMER ) public timer : TimerFn ,
260
298
) {}
261
299
262
300
value(): LogFn {
263
301
const fn = <LogFn >((
264
302
req : ParsedRequest ,
265
303
args : OperationArgs ,
304
+ // tslint:disable-next-line:no-any
266
305
result : any ,
267
306
start ? : HighResTime ,
268
307
) => {
@@ -279,11 +318,13 @@ export class LogActionProvider implements Provider<LogFn> {
279
318
private async action(
280
319
req : ParsedRequest ,
281
320
args : OperationArgs ,
321
+ // tslint:disable-next-line:no-any
282
322
result : any ,
283
323
start ? : HighResTime ,
284
324
): Promise <void > {
285
325
const controllerClass = await this .getController ();
286
326
const methodName: string = await this .getMethod ();
327
+
287
328
const metadata: LevelMetadata = getLogMetadata (controllerClass , methodName );
288
329
const level: number | undefined = metadata ? metadata .level : undefined ;
289
330
@@ -294,36 +335,42 @@ export class LogActionProvider implements Provider<LogFn> {
294
335
level !== LOG_LEVEL .OFF
295
336
) {
296
337
if (! args ) args = [];
297
- let log = ` ${req .url } :: ${controllerClass .name }. ` ;
298
- log += ` ${methodName }(${args .join (' , ' )}) => ` ;
338
+ let msg = ` ${req .url } :: ${controllerClass .name }. ` ;
339
+ msg += ` ${methodName }(${args .join (' , ' )}) => ` ;
299
340
300
- if (typeof result === ' object' ) log += JSON .stringify (result );
301
- else log += result ;
341
+ if (typeof result === ' object' ) msg += JSON .stringify (result );
342
+ else msg += result ;
302
343
303
344
if (start ) {
304
345
const timeDiff: HighResTime = this .timer (start );
305
346
const time: number =
306
347
timeDiff [0 ] * 1000 + Math .round (timeDiff [1 ] * 1e-4 ) / 100 ;
307
- log = ` ${time }ms: ${log } ` ;
348
+ msg = ` ${time }ms: ${msg } ` ;
308
349
}
309
350
310
- switch (level ) {
311
- case LOG_LEVEL .DEBUG :
312
- console .log (chalk .white (` DEBUG: ${log } ` ));
313
- break ;
314
- case LOG_LEVEL .INFO :
315
- console .log (chalk .green (` INFO: ${log } ` ));
316
- break ;
317
- case LOG_LEVEL .WARN :
318
- console .log (chalk .yellow (` WARN: ${log } ` ));
319
- break ;
320
- case LOG_LEVEL .ERROR :
321
- console .log (chalk .red (` ERROR: ${log } ` ));
322
- break ;
323
- }
351
+ this .logWriter (msg , level );
324
352
}
325
353
}
326
354
}
355
+
356
+ function logToConsole(msg : string , level : number ) {
357
+ let output;
358
+ switch (level ) {
359
+ case LOG_LEVEL .DEBUG :
360
+ output = chalk .white (` DEBUG: ${msg } ` );
361
+ break ;
362
+ case LOG_LEVEL .INFO :
363
+ output = chalk .green (` INFO: ${msg } ` );
364
+ break ;
365
+ case LOG_LEVEL .WARN :
366
+ output = chalk .yellow (` WARN: ${msg } ` );
367
+ break ;
368
+ case LOG_LEVEL .ERROR :
369
+ output = chalk .red (` ERROR: ${msg } ` );
370
+ break ;
371
+ }
372
+ if (output ) console .log (output );
373
+ }
327
374
```
328
375
329
376
### ` src/index.ts `
@@ -333,7 +380,6 @@ Export all the files to ensure a user can import the necessary components.
333
380
export * from ' ./decorators/log.decorator' ;
334
381
export * from ' ./mixins/log-level.mixin' ;
335
382
export * from ' ./providers/log-action.provider' ;
336
- export * from ' ./providers/log-level.provider' ;
337
383
export * from ' ./providers/timer.provider' ;
338
384
export * from ' ./component' ;
339
385
export * from ' ./types' ;
@@ -347,13 +393,12 @@ they are automatically bound when a user adds the component to their application
347
393
``` ts
348
394
import {EXAMPLE_LOG_BINDINGS } from ' ./keys' ;
349
395
import {Component , ProviderMap } from ' @loopback/core' ;
350
- import {TimerProvider , LogActionProvider , LogLevelProvider } from ' ./' ;
396
+ import {TimerProvider , LogActionProvider } from ' ./' ;
351
397
352
398
export class LogComponent implements Component {
353
399
providers? : ProviderMap = {
354
400
[EXAMPLE_LOG_BINDINGS .TIMER ]: TimerProvider ,
355
401
[EXAMPLE_LOG_BINDINGS .LOG_ACTION ]: LogActionProvider ,
356
- [EXAMPLE_LOG_BINDINGS .APP_LOG_LEVEL ]: LogLevelProvider ,
357
402
};
358
403
}
359
404
```
0 commit comments