Skip to content

Commit

Permalink
feat: Add basic logger (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
yokuze committed Mar 25, 2019
1 parent a7b2af1 commit f915a35
Show file tree
Hide file tree
Showing 11 changed files with 620 additions and 5 deletions.
23 changes: 19 additions & 4 deletions src/Request.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { ILogger } from './logging/logging-types';
import _ from 'underscore';
import qs from 'qs';
import cookie from 'cookie';
import Application from './Application';
import { RequestEvent, HandlerContext, RequestEventRequestContext } from './request-response-types';
import { RequestEvent, HandlerContext, RequestEventRequestContext, LambdaEventSourceType } from './request-response-types';
import { StringMap, KeyValueStringObject, StringArrayOfStringsMap, StringUnknownMap } from './utils/common-types';
import ConsoleLogger from './logging/ConsoleLogger';

export default class Request {

public static readonly SOURCE_ALB = 'ALB';
public static readonly SOURCE_APIGW = 'APIGW';
public static readonly SOURCE_ALB: LambdaEventSourceType = 'ALB';
public static readonly SOURCE_APIGW: LambdaEventSourceType = 'APIGW';

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

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

public readonly log: ILogger;

protected _parentRequest?: Request;
protected _url: string;
protected _path: string;
Expand Down Expand Up @@ -269,6 +273,17 @@ export default class Request {
// more details.
this.originalUrl = event.path;
this.params = Object.freeze(params);

if (this._parentRequest) {
this.log = this._parentRequest.log;
} else {
this.log = new ConsoleLogger({
level: app.routerOptions.logging.level,
interface: this.eventSourceType,
fnStartTime: Date.now(),
getTimeUntilFnTimeout: () => { return context.getRemainingTimeInMillis(); },
});
}
}

/** PUBLIC PROPERTIES: GETTERS AND SETTERS */
Expand Down
4 changes: 3 additions & 1 deletion src/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@ import { RouteMatchingProcessorChain } from './chains/RouteMatchingProcessorChai
import { MatchAllRequestsProcessorChain } from './chains/MatchAllRequestsProcessorChain';
import { SubRouterProcessorChain } from './chains/SubRouterProcessorChain';
import Route from './Route';
import { Optional } from './utils/common-types';

const DEFAULT_OPTS: RouterOptions = {
caseSensitive: false,
logging: { level: 'info' },
};

export default class Router implements IRouter {

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

public constructor(options?: RouterOptions) {
public constructor(options?: Optional<RouterOptions>) {
this.routerOptions = _.defaults(options, DEFAULT_OPTS);
}

Expand Down
7 changes: 7 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import Request from './Request';
import Response from './Response';
import { LogLevel } from './logging/logging-types';

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

export interface ApplicationLoggingOptions {
level: LogLevel;
}

export interface RouterOptions {

/**
Expand All @@ -100,6 +105,8 @@ export interface RouterOptions {
* case-sensitivity enabled, only the second request would match that route.
*/
caseSensitive: boolean;

logging: ApplicationLoggingOptions;
}

export interface IRouter {
Expand Down
108 changes: 108 additions & 0 deletions src/logging/ConsoleLogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import _ from 'underscore';
import {
ILogger,
LogObject,
LoggerConfig,
LogLevel,
DebugLogObject,
} from './logging-types';
import levels from './levels';
import isDebugLevelOrMoreVerbose from './is-debug-level-or-more-verbose';
import { LambdaEventSourceType } from '../request-response-types';

export default class ConsoleLogger implements ILogger {

protected _level: LogLevel;
protected _interface: LambdaEventSourceType;
protected _getTimeUntilFnTimeout: () => number;
protected _fnStartTime: number;

public constructor(config: LoggerConfig) {
this._level = config.level || 'info';
this._interface = config.interface;
this._fnStartTime = typeof config.fnStartTime === 'undefined' ? Date.now() : config.fnStartTime;
this._getTimeUntilFnTimeout = config.getTimeUntilFnTimeout;
}

public trace(msg: string, data?: unknown): void {
this._log('trace', msg, data);
}

public debug(msg: string, data?: unknown): void {
this._log('debug', msg, data);
}

public info(msg: string, data?: unknown): void {
this._log('info', msg, data);
}

public warn(msg: string, data?: unknown): void {
this._log('warn', msg, data);
}

public error(msg: string, data?: unknown): void {
this._log('error', msg, data);
}

public fatal(msg: string, data?: unknown): void {
this._log('fatal', msg, data);
}

public getLevel(): LogLevel {
return this._level;
}

public setLevel(level: LogLevel): void {
this._level = level;
}

/**
* Perform the actual message logging
*/
protected _log(level: LogLevel, msg: string, data?: unknown): void {
if (this._shouldLog(level)) {
// eslint-disable-next-line no-console
console.log(JSON.stringify(this._makeLogObject(level, msg, data)));
}
}

/**
* @returns `true` if the given level should be logged at this logger's current log
* level setting.
*/
protected _shouldLog(level: LogLevel): boolean {
// Log if the level is higher priority than the current log level setting.
// e.g. error (50) >= info (30)
return levels[level] >= levels[this._level];
}

/**
* Creates an object to be logged
*/
protected _makeLogObject(level: LogLevel, msg: string, data?: unknown): LogObject | DebugLogObject {
let logLine: LogObject = { level, msg };

if (!_.isUndefined(data)) {
logLine.data = data;
}

if (isDebugLevelOrMoreVerbose(level)) {
let debugLogLine = logLine as DebugLogObject;

debugLogLine.int = this._interface;
debugLogLine.remaining = this._getTimeUntilFnTimeout();
debugLogLine.timer = this._getTimeSinceFnStart();

return debugLogLine;
}

return logLine;
}

/**
* The approximate time, in milliseconds, since the Lambda function started executing.
*/
protected _getTimeSinceFnStart(): number {
return Date.now() - this._fnStartTime;
}
}
11 changes: 11 additions & 0 deletions src/logging/is-debug-level-or-more-verbose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { LogLevel } from './logging-types';
import levels from './levels';

/**
* @returns `true` if the given log level is `'debug'` or a more verbose level (e.g.
* `'trace'`).
*/
export default function isDebugLevelOrMoreVerbose(level: LogLevel): boolean {
// More verbose levels have lower priority numbers
return levels[level] <= levels.debug;
}
36 changes: 36 additions & 0 deletions src/logging/levels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { LogLevels } from './logging-types';

/**
* A map of all available log levels. The key is the `LogLevelLabel` and the value is the
* "priority". The logger's log level is set to one of these keys. When a message logging
* function such as `debug` is called, it is only logged if the message's "priority" is
* greater than or equal to the priority of the current log level. For example:
*
* ```
* const logger = new ConsoleLogger({
* level: 'info',
* interface: 'ALB',
* getTimeUntilFnTimeout: () => { return 0; }
* });
*
* // error (priority: 50) is >= info (priority: 30), so this message is logged
* logger.error('error');
*
* // debug (priority: 20) is NOT >= info (priority 30), so this message is NOT logged
* logger.debug('debug');
* ```
*
* Logging level priorities are for internal use and are not exposed on the public API.
* Users of the public API adjust the logging level using the `LogLevel` strings.
*/
const levels: LogLevels = {
trace: 10,
debug: 20,
info: 30,
warn: 40,
error: 50,
fatal: 60,
silent: Number.MAX_SAFE_INTEGER,
};

export default levels;
Loading

0 comments on commit f915a35

Please sign in to comment.