Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 977d061

Browse files
author
Michal Vlasák
committed
Add stricter compilation options and types
1 parent 8939d89 commit 977d061

9 files changed

Lines changed: 157 additions & 80 deletions

File tree

package.json

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,33 @@
44
"description": "Ackee Node Logger",
55
"main": "./dist/index.js",
66
"scripts": {
7-
"build": "tsc",
7+
"build": "tsc --strict --noImplicitReturns --noUnusedLocals --noUnusedParameters",
88
"lint-staged": "lint-staged",
99
"test": "jest",
1010
"test-watch": "jest --watch",
11-
"lint": "tslint -p . --force --format verbose; exit 0",
11+
"lint": "tslint -p . --force --format codeFrame; exit 0",
1212
"pretty": "prettier --write 'src/*.ts'",
1313
"precommit": "lint-staged && npm test",
1414
"check": "npm-check -i components -i config -i controllers -i enums -i errors -i middleware -i routes -i repositories -i services -i husky & exit 0"
1515
},
1616
"lint-staged": {
17-
"*.js": ["eslint", "git add"]
17+
"*.js": [
18+
"eslint"
19+
]
1820
},
1921
"author": "Michal Vlasák <michal.vlasak@ackee.cz>",
2022
"license": "ISC",
2123
"engines": {
2224
"node": "6.14.3"
2325
},
2426
"dependencies": {
27+
"@types/express": "^4.16.0",
28+
"@types/lodash.isobject": "^3.0.4",
29+
"@types/lodash.isstring": "^4.0.4",
30+
"@types/on-finished": "^2.3.1",
31+
"@types/on-headers": "^1.0.0",
32+
"@types/pino": "^5.6.1",
33+
"@types/pino-multi-stream": "^3.1.1",
2534
"lodash.difference": "^4.5.0",
2635
"lodash.foreach": "^4.5.0",
2736
"lodash.isempty": "^4.4.0",

src/declarations.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
declare module 'omit-deep' {
2+
function omitDeep(value: object | object[], keys: string | string[]): object;
3+
export = omitDeep;
4+
}
5+
6+
declare module 'pick-deep' {
7+
function pickDeep(obj: object, paths: string | string[], separator?: string): object;
8+
export = pickDeep;
9+
}
10+
11+
// there is a @typed/pino-multi-stream package, but it has wrong type in its Streams definition. So until its fixed, we use this
12+
declare module 'pino-multi-stream' {
13+
import {
14+
LevelWithSilent as PinoLevel,
15+
Logger as PinoLogger,
16+
LoggerOptions as PinoLoggerOptions,
17+
stdSerializers as pinoStdSerializers,
18+
} from 'pino';
19+
import stream = require('stream');
20+
21+
type Streams = Array<{ stream: NodeJS.WritableStream; level?: Level }>;
22+
interface LoggerOptions extends PinoLoggerOptions {
23+
streams?: Streams;
24+
}
25+
const stdSerializers: typeof pinoStdSerializers;
26+
27+
function multistream(streams: Streams): stream.Writable;
28+
type Level = PinoLevel;
29+
type Logger = PinoLogger;
30+
31+
function pinoms(options: LoggerOptions): Logger;
32+
}

src/express.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
1+
import { ErrorRequestHandler, Request, RequestHandler, Response } from 'express';
12
import * as onFinished from 'on-finished';
2-
import * as onHeaders from 'on-headers';
3+
import onHeaders = require('on-headers');
4+
import { AckeeLogger } from '.';
35

4-
const expressMiddleware = function(req, response, next) {
6+
const errorSymbol = Symbol.for('error');
7+
8+
export type AckeeLoggerExpressMiddleware = (
9+
this: AckeeLogger,
10+
req: Request & { _startAt?: [number, number]; ackId?: string },
11+
response: Response & { _startAt?: [number, number]; time?: string; out?: object; [errorSymbol]?: any },
12+
next: any
13+
) => void;
14+
15+
const expressMiddleware: RequestHandler = function(
16+
this: AckeeLogger,
17+
req: Request & { _startAt?: [number, number]; ackId?: string },
18+
response: Response & { _startAt?: [number, number]; time?: string; out?: object; [errorSymbol]?: any },
19+
next: any
20+
) {
521
const reqIn = `--- ${req.method} ${req.originalUrl} ${req.headers['user-agent']}`;
622
this.debug({ req, ackId: req.ackId }, `${reqIn} - Request accepted`);
723
req._startAt = process.hrtime();
824
onHeaders(response, () => {
925
response._startAt = process.hrtime();
10-
const diffFromSeconds = (response._startAt[0] - req._startAt[0]) * 1e3;
11-
const diffFromNanoseconds = (response._startAt[1] - req._startAt[1]) * 1e-6;
26+
const diffFromSeconds = (response._startAt[0] - req._startAt![0]) * 1e3;
27+
const diffFromNanoseconds = (response._startAt[1] - req._startAt![1]) * 1e-6;
1228
const ms = diffFromSeconds + diffFromNanoseconds;
1329
response.time = ms.toFixed(3);
1430
});
15-
onFinished(response, (err, res) => {
16-
const error = res[Symbol.for('error')];
31+
onFinished(response, (_err, res) => {
32+
const error = res[errorSymbol];
1733
const reqOut = `${res.statusCode} ${req.method} ${req.originalUrl} ${res.time} ms ${req.headers['user-agent']}`;
18-
if (this.options.ignoredHttpMethods.includes(req.method)) {
34+
if (this.options.ignoredHttpMethods && this.options.ignoredHttpMethods.includes(req.method)) {
1935
return;
2036
}
2137
if (error) {
@@ -29,9 +45,10 @@ const expressMiddleware = function(req, response, next) {
2945
next();
3046
};
3147

32-
const expressErrorMiddleware = (error, req, res, next) => {
33-
res[Symbol.for('error')] = error;
48+
const expressErrorMiddleware: ErrorRequestHandler = (error, _req, res, next) => {
49+
(res as any)[Symbol.for('error')] = error;
3450
next(error);
3551
};
3652

3753
export { expressErrorMiddleware, expressMiddleware };
54+

src/index.ts

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
1-
import * as isObject from 'lodash.isobject';
2-
import * as isString from 'lodash.isstring';
1+
import { ErrorRequestHandler } from 'express';
2+
import isObject = require('lodash.isobject');
3+
import isString = require('lodash.isstring');
34
import * as pino from 'pino';
4-
import { multistream } from 'pino-multi-stream';
5-
import { expressErrorMiddleware, expressMiddleware } from './express';
6-
import { levels } from './levels';
5+
import { BaseLogger as PinoLogger } from 'pino';
6+
import { Level, LoggerOptions, multistream } from 'pino-multi-stream';
7+
import { AckeeLoggerExpressMiddleware, expressErrorMiddleware, expressMiddleware } from './express';
78
import * as serializers from './serializers';
89
import { StackDriverFormatStream } from './stackdriver';
9-
import { decorateStreams, DefaultTransformStream } from './streams';
10-
11-
interface LoggerOptions {
12-
disableFields: string[];
13-
enableFields: string[];
14-
defaultLevel: string;
15-
disableStackdriverFormat: boolean;
16-
streams: any[];
17-
ignoredHttpMethods: string[];
18-
config: any;
19-
pretty: boolean;
10+
import { AckeeLoggerStream, decorateStreams, DefaultTransformStream } from './streams';
11+
12+
export interface AckeeLoggerOptions {
13+
disableFields?: string[];
14+
enableFields?: string[];
15+
defaultLevel?: Level;
16+
disableStackdriverFormat?: boolean;
17+
streams?: AckeeLoggerStream[];
18+
ignoredHttpMethods?: string[];
19+
config?: LoggerOptions;
20+
pretty?: boolean;
21+
}
22+
23+
export interface AckeeLogger extends PinoLogger {
24+
warning: pino.LogFn;
25+
options: AckeeLoggerOptions;
26+
express: AckeeLoggerExpressMiddleware;
27+
expressError: ErrorRequestHandler;
2028
}
2129

2230
// This is a custom slightly edited version of pino-multistream's wirte method, whch adds support for maximum log level
2331
// The original version was pino-multistream 3.1.2 (commit 71d98ae) - https://github.com/pinojs/pino-multi-stream/blob/71d98ae191e02c56e39e849d2c30d59c8c6db1b9/multistream.js#L43
24-
const maxLevelWrite = function(data) {
32+
const maxLevelWrite: pino.WriteFn = function(this: any, data: object): void {
2533
let stream;
2634
const needsMetadata = Symbol.for('needsMetadata');
2735
const level = this.lastLevel;
@@ -44,7 +52,7 @@ const maxLevelWrite = function(data) {
4452
}
4553
};
4654

47-
const defaultLogger = (options: LoggerOptions = ({} as any) as LoggerOptions) => {
55+
const defaultLogger = (options: AckeeLoggerOptions = {}): AckeeLogger => {
4856
const pretty = pino.pretty();
4957
pretty.pipe(process.stdout);
5058
const prettyErr = pino.pretty();
@@ -54,7 +62,7 @@ const defaultLogger = (options: LoggerOptions = ({} as any) as LoggerOptions) =>
5462
serializers.enablePaths(options.enableFields);
5563

5664
const isTesting = process.env.NODE_ENV === 'test';
57-
let defaultLevel = 'debug';
65+
let defaultLevel: Level = 'debug';
5866

5967
if (isTesting) {
6068
defaultLevel = 'silent';
@@ -64,20 +72,17 @@ const defaultLogger = (options: LoggerOptions = ({} as any) as LoggerOptions) =>
6472
defaultLevel = options.defaultLevel;
6573
}
6674

67-
let streams;
75+
let streams: AckeeLoggerStream[];
6876
let defaultMessageKey = 'message'; // best option for Google Stackdriver
6977
if (options.streams) {
7078
streams = options.streams;
7179
} else if (options.pretty) {
72-
streams = [
73-
{ level: defaultLevel, stream: pretty, maxLevel: levels.warn },
74-
{ level: levels.warn, stream: prettyErr },
75-
];
80+
streams = [{ level: defaultLevel, maxLevel: 'warn', stream: pretty }, { level: 'warn', stream: prettyErr }];
7681
defaultMessageKey = 'msg'; // default pino - best option for pretty print
7782
} else {
7883
streams = [
79-
{ level: defaultLevel, stream: process.stdout, maxLevel: levels.warn },
80-
{ level: levels.warn, stream: process.stderr },
84+
{ level: defaultLevel, maxLevel: 'warn', stream: process.stdout },
85+
{ level: 'warn', stream: process.stderr },
8186
];
8287
}
8388
if (!options.disableStackdriverFormat) {
@@ -106,28 +111,27 @@ const defaultLogger = (options: LoggerOptions = ({} as any) as LoggerOptions) =>
106111
multistream(streams)
107112
);
108113
logger.warning = logger.warn;
109-
logger.options = options;
114+
(logger as any).options = options;
110115

111116
// Add maxLevel support to pino-multi-stream
112117
// This could be replaced with custom pass-through stream being passed to multistream, which would filter the messages
113-
logger.stream.write = maxLevelWrite.bind(logger.stream);
114-
118+
(logger as any).stream.write = maxLevelWrite.bind(logger.stream);
115119
logger.express = expressMiddleware.bind(logger);
116-
logger.expressError = expressErrorMiddleware;
120+
logger.expressError = expressErrorMiddleware as any;
117121

118-
return logger;
122+
return (logger as any) as AckeeLogger;
119123
};
120124

121-
let rootLogger;
125+
let rootLogger: AckeeLogger;
122126

123-
const loggerFactory = (data = {}) => {
124-
let moduleName;
125-
let options;
127+
const loggerFactory = (data: string | AckeeLoggerOptions = {}): AckeeLogger => {
128+
let moduleName: string | undefined;
129+
let options: AckeeLoggerOptions = {};
126130
if (data) {
127131
if (isString(data)) {
128-
moduleName = data;
132+
moduleName = data as string;
129133
} else if (isObject(data)) {
130-
options = data;
134+
options = data as AckeeLoggerOptions;
131135
} else {
132136
throw new TypeError(`Invalid argument of type ${typeof data}`);
133137
}
@@ -139,11 +143,11 @@ const loggerFactory = (data = {}) => {
139143
if (!moduleName) {
140144
return rootLogger;
141145
}
142-
return rootLogger.child({ name: moduleName });
146+
return (rootLogger.child({ name: moduleName }) as any) as AckeeLogger;
143147
};
144148

145149
const factoryProxy = new Proxy(loggerFactory, {
146-
get: (target, key) => target()[key],
150+
get: (target, key) => (target() as any)[key],
147151
});
148152

149153
export default factoryProxy;

src/serializers.ts

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
import * as forEach from 'lodash.foreach';
2-
import * as omit from 'omit-deep';
3-
import * as pick from 'pick-deep';
1+
import { Dictionary } from 'lodash';
2+
import forEach = require('lodash.foreach');
3+
import omit = require('omit-deep');
4+
import pick = require('pick-deep');
45
import { removeEmpty } from './utils';
56

6-
const serializers = {
7-
error(obj) {
7+
type SerializerFn = (obj: Dictionary<any>) => Dictionary<any>;
8+
9+
const serializers: Dictionary<SerializerFn> = {
10+
error(obj: Dictionary<any>): Dictionary<any> {
811
return {
912
code: obj.code,
1013
data: obj.data,
1114
message: obj.message,
1215
stack: obj.stack,
1316
};
1417
},
15-
process(obj) {
18+
process(obj: Dictionary<any>): Dictionary<any> {
1619
if (!obj.env) {
1720
return obj;
1821
}
@@ -23,7 +26,7 @@ const serializers = {
2326
omit(rest, 'env');
2427
return removeEmpty(Object.assign({}, filteredEnv, rest));
2528
},
26-
req(obj) {
29+
req(obj: Dictionary<any>): Dictionary<any> {
2730
const pickHeaders = ['x-deviceid', 'authorization', 'user-agent'];
2831
const [body, query] = ['body', 'query'].map(name => {
2932
const source = obj[name];
@@ -43,51 +46,53 @@ const serializers = {
4346
url: obj.originalUrl || obj.url,
4447
});
4548
},
46-
res(obj) {
49+
res(obj: Dictionary<any>): Dictionary<any> {
4750
return {
4851
out: obj.out,
4952
time: obj.time,
5053
};
5154
},
5255
};
5356

54-
const disablePaths = paths => {
57+
const disablePaths = (paths: string[] | undefined) => {
5558
if (!paths || paths.length <= 0) {
5659
return;
5760
}
5861
forEach(serializers, (value, key) => {
5962
const matcher = new RegExp(`^${key}.(.*)`);
60-
const affectedFields = [];
63+
const affectedFields: string[] = [];
6164
paths.forEach(field => {
62-
field.replace(matcher, (match, p1) => {
63-
affectedFields.push(p1);
64-
});
65+
const res = field.match(matcher);
66+
if (res !== null) {
67+
affectedFields.push(res[1]);
68+
}
6569
});
6670

6771
if (affectedFields.length > 0) {
68-
const newSerializer = obj => {
72+
const newSerializer: SerializerFn = (obj: Dictionary<any>) => {
6973
return omit(value(obj), affectedFields);
7074
};
7175
serializers[key] = newSerializer;
7276
}
7377
});
7478
};
7579

76-
const enablePaths = paths => {
80+
const enablePaths = (paths: string[] | undefined) => {
7781
if (!paths || paths.length <= 0) {
7882
return;
7983
}
8084
forEach(serializers, (value, key) => {
8185
const matcher = new RegExp(`^${key}.(.*)`);
82-
const affectedFields = [];
86+
const affectedFields: string[] = [];
8387
paths.forEach(field => {
84-
field.replace(matcher, (match, p1) => {
85-
affectedFields.push(p1);
86-
});
88+
const res = field.match(matcher);
89+
if (res !== null) {
90+
affectedFields.push(res[1]);
91+
}
8792
});
8893

8994
if (affectedFields.length > 0) {
90-
const newSerializer = obj => {
95+
const newSerializer: SerializerFn = (obj: Dictionary<any>) => {
9196
const newFields = pick(obj, affectedFields);
9297
const originalResult = value(obj);
9398
return Object.assign({}, originalResult, newFields);

src/stackdriver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Transform } from 'stream';
22

3-
const PINO_TO_STACKDRIVER = {
3+
const PINO_TO_STACKDRIVER: { [key: number]: string } = {
44
10: 'DEBUG',
55
20: 'DEBUG',
66
30: 'INFO',
@@ -10,7 +10,7 @@ const PINO_TO_STACKDRIVER = {
1010
};
1111

1212
class StackDriverFormatStream extends Transform {
13-
public _transform(chunk, encoding, callback) {
13+
public _transform(chunk: any, _encoding: string, callback: (error?: Error | undefined, data?: any) => void) {
1414
const obj = JSON.parse(chunk);
1515
obj.severity = PINO_TO_STACKDRIVER[obj.level] || 'UNKNOWN';
1616

0 commit comments

Comments
 (0)