Skip to content

Commit 2dca77e

Browse files
yokuzejthomerson
authored andcommitted
feat: Add basic logger (#20)
1 parent a7b2af1 commit 2dca77e

10 files changed

Lines changed: 617 additions & 5 deletions

src/Request.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import { ILogger } from './logging/logging-types';
12
import _ from 'underscore';
23
import qs from 'qs';
34
import cookie from 'cookie';
45
import Application from './Application';
5-
import { RequestEvent, HandlerContext, RequestEventRequestContext } from './request-response-types';
6+
import { RequestEvent, HandlerContext, RequestEventRequestContext, LambdaEventSourceType } from './request-response-types';
67
import { StringMap, KeyValueStringObject, StringArrayOfStringsMap, StringUnknownMap } from './utils/common-types';
8+
import ConsoleLogger from './logging/ConsoleLogger';
79

810
export default class Request {
911

10-
public static readonly SOURCE_ALB = 'ALB';
11-
public static readonly SOURCE_APIGW = 'APIGW';
12+
public static readonly SOURCE_ALB: LambdaEventSourceType = 'ALB';
13+
public static readonly SOURCE_APIGW: LambdaEventSourceType = 'APIGW';
1214

1315
/**
1416
* The application that is running this request.
@@ -204,7 +206,7 @@ export default class Request {
204206
* Load Balancer, `ALB`, or API Gateway, `APIGW`). See `Request.SOURCE_ALB` and
205207
* `Request.SOURCE_APIGW`.
206208
*/
207-
public readonly eventSourceType: ('ALB' | 'APIGW');
209+
public readonly eventSourceType: LambdaEventSourceType;
208210

209211
/**
210212
* The body of the request. If the body is an empty value (e.g. `''`), `req.body` will
@@ -215,6 +217,8 @@ export default class Request {
215217
*/
216218
public body?: unknown;
217219

220+
public readonly log: ILogger;
221+
218222
protected _parentRequest?: Request;
219223
protected _url: string;
220224
protected _path: string;
@@ -269,6 +273,17 @@ export default class Request {
269273
// more details.
270274
this.originalUrl = event.path;
271275
this.params = Object.freeze(params);
276+
277+
if (this._parentRequest) {
278+
this.log = this._parentRequest.log;
279+
} else {
280+
this.log = new ConsoleLogger({
281+
level: app.routerOptions.logging.level,
282+
interface: this.eventSourceType,
283+
fnStartTime: Date.now(),
284+
getTimeUntilFnTimeout: () => { return context.getRemainingTimeInMillis(); },
285+
});
286+
}
272287
}
273288

274289
/** PUBLIC PROPERTIES: GETTERS AND SETTERS */

src/Router.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ import Route from './Route';
1919

2020
const DEFAULT_OPTS: RouterOptions = {
2121
caseSensitive: false,
22+
logging: { level: 'info' },
2223
};
2324

2425
export default class Router implements IRouter {
2526

2627
public readonly routerOptions: RouterOptions;
2728
private readonly _processors: IRequestMatchingProcessorChain[] = [];
2829

29-
public constructor(options?: RouterOptions) {
30+
public constructor(options?: Partial<RouterOptions>) {
3031
this.routerOptions = _.defaults(options, DEFAULT_OPTS);
3132
}
3233

src/interfaces.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import Request from './Request';
44
import Response from './Response';
5+
import { LogLevel } from './logging/logging-types';
56

67
/**
78
* The function that is passed to request processors for them to signal that they are done
@@ -91,6 +92,10 @@ export interface RouteMatchingProcessorAppender<T> {
9192
(path: PathParams, ...handlers: ProcessorOrProcessors[]): T;
9293
}
9394

95+
export interface ApplicationLoggingOptions {
96+
level: LogLevel;
97+
}
98+
9499
export interface RouterOptions {
95100

96101
/**
@@ -100,6 +105,8 @@ export interface RouterOptions {
100105
* case-sensitivity enabled, only the second request would match that route.
101106
*/
102107
caseSensitive: boolean;
108+
109+
logging: ApplicationLoggingOptions;
103110
}
104111

105112
export interface IRouter {

src/logging/ConsoleLogger.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import _ from 'underscore';
2+
import {
3+
ILogger,
4+
LogObject,
5+
LoggerConfig,
6+
LogLevel,
7+
DebugLogObject,
8+
} from './logging-types';
9+
import levels from './levels';
10+
import isDebugLevelOrMoreVerbose from './is-debug-level-or-more-verbose';
11+
import { LambdaEventSourceType } from '../request-response-types';
12+
13+
export default class ConsoleLogger implements ILogger {
14+
15+
protected _level: LogLevel;
16+
protected _interface: LambdaEventSourceType;
17+
protected _getTimeUntilFnTimeout: () => number;
18+
protected _fnStartTime: number;
19+
20+
public constructor(config: LoggerConfig) {
21+
this._level = config.level || 'info';
22+
this._interface = config.interface;
23+
this._fnStartTime = typeof config.fnStartTime === 'undefined' ? Date.now() : config.fnStartTime;
24+
this._getTimeUntilFnTimeout = config.getTimeUntilFnTimeout;
25+
}
26+
27+
public trace(msg: string, data?: unknown): void {
28+
this._log('trace', msg, data);
29+
}
30+
31+
public debug(msg: string, data?: unknown): void {
32+
this._log('debug', msg, data);
33+
}
34+
35+
public info(msg: string, data?: unknown): void {
36+
this._log('info', msg, data);
37+
}
38+
39+
public warn(msg: string, data?: unknown): void {
40+
this._log('warn', msg, data);
41+
}
42+
43+
public error(msg: string, data?: unknown): void {
44+
this._log('error', msg, data);
45+
}
46+
47+
public fatal(msg: string, data?: unknown): void {
48+
this._log('fatal', msg, data);
49+
}
50+
51+
public getLevel(): LogLevel {
52+
return this._level;
53+
}
54+
55+
public setLevel(level: LogLevel): void {
56+
this._level = level;
57+
}
58+
59+
/**
60+
* Perform the actual message logging
61+
*/
62+
protected _log(level: LogLevel, msg: string, data?: unknown): void {
63+
if (this._shouldLog(level)) {
64+
// eslint-disable-next-line no-console
65+
console.log(JSON.stringify(this._makeLogObject(level, msg, data)));
66+
}
67+
}
68+
69+
/**
70+
* @returns `true` if the given level should be logged at this logger's current log
71+
* level setting.
72+
*/
73+
protected _shouldLog(level: LogLevel): boolean {
74+
// Log if the level is higher priority than the current log level setting.
75+
// e.g. error (50) >= info (30)
76+
return levels[level] >= levels[this._level];
77+
}
78+
79+
/**
80+
* Creates an object to be logged
81+
*/
82+
protected _makeLogObject(level: LogLevel, msg: string, data?: unknown): LogObject | DebugLogObject {
83+
let logLine: LogObject = { level, msg };
84+
85+
if (!_.isUndefined(data)) {
86+
logLine.data = data;
87+
}
88+
89+
if (isDebugLevelOrMoreVerbose(level)) {
90+
let debugLogLine = logLine as DebugLogObject;
91+
92+
debugLogLine.int = this._interface;
93+
debugLogLine.remaining = this._getTimeUntilFnTimeout();
94+
debugLogLine.timer = this._getTimeSinceFnStart();
95+
96+
return debugLogLine;
97+
}
98+
99+
return logLine;
100+
}
101+
102+
/**
103+
* The approximate time, in milliseconds, since the Lambda function started executing.
104+
*/
105+
protected _getTimeSinceFnStart(): number {
106+
return Date.now() - this._fnStartTime;
107+
}
108+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { LogLevel } from './logging-types';
2+
import levels from './levels';
3+
4+
/**
5+
* @returns `true` if the given log level is `'debug'` or a more verbose level (e.g.
6+
* `'trace'`).
7+
*/
8+
export default function isDebugLevelOrMoreVerbose(level: LogLevel): boolean {
9+
// More verbose levels have lower priority numbers
10+
return levels[level] <= levels.debug;
11+
}

src/logging/levels.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { LogLevels } from './logging-types';
2+
3+
/**
4+
* A map of all available log levels. The key is the `LogLevelLabel` and the value is the
5+
* "priority". The logger's log level is set to one of these keys. When a message logging
6+
* function such as `debug` is called, it is only logged if the message's "priority" is
7+
* greater than or equal to the priority of the current log level. For example:
8+
*
9+
* ```
10+
* const logger = new ConsoleLogger({
11+
* level: 'info',
12+
* interface: 'ALB',
13+
* getTimeUntilFnTimeout: () => { return 0; }
14+
* });
15+
*
16+
* // error (priority: 50) is >= info (priority: 30), so this message is logged
17+
* logger.error('error');
18+
*
19+
* // debug (priority: 20) is NOT >= info (priority 30), so this message is NOT logged
20+
* logger.debug('debug');
21+
* ```
22+
*
23+
* Logging level priorities are for internal use and are not exposed on the public API.
24+
* Users of the public API adjust the logging level using the `LogLevel` strings.
25+
*/
26+
const levels: LogLevels = {
27+
trace: 10,
28+
debug: 20,
29+
info: 30,
30+
warn: 40,
31+
error: 50,
32+
fatal: 60,
33+
silent: Number.MAX_SAFE_INTEGER,
34+
};
35+
36+
export default levels;

0 commit comments

Comments
 (0)