Commit a5aae7f2 authored by Eirik Alvær's avatar Eirik Alvær

Adds ExpressLogger module which contains utility functions to log at the...

Adds ExpressLogger module which contains utility functions to log at the correct format when using an express server.
It also provides middleware for express servers that logs basic info about requests and responses.
parent 6f6100fb
Pipeline #17145 failed with stage
in 17 seconds
...@@ -7,29 +7,6 @@ variables: ...@@ -7,29 +7,6 @@ variables:
test: test:
stage: test stage: test
except:
- tags
image: node:7.8-alpine
script:
- echo $NEXUS_NPM_TOKEN > ~/.npmrc
- echo 'always-auth=true' >> ~/.npmrc
- npm set registry https://nexus.nsd.no/repository/npm-group/
- npm install
- npm run compile
artifacts:
expire_in: 1 week
paths:
- lib/index.d.ts
- lib/index.js
- log-schema/log-schema.json
- README.md
- LICENSE
- package.json
test-release:
stage: test
only:
- tags
image: node:7.8-alpine image: node:7.8-alpine
script: script:
- echo $NEXUS_NPM_TOKEN > ~/.npmrc - echo $NEXUS_NPM_TOKEN > ~/.npmrc
...@@ -37,15 +14,6 @@ test-release: ...@@ -37,15 +14,6 @@ test-release:
- npm set registry https://nexus.nsd.no/repository/npm-group/ - npm set registry https://nexus.nsd.no/repository/npm-group/
- npm install - npm install
- npm run compile - npm run compile
artifacts:
expire_in: 1 week
paths:
- lib/index.d.ts
- lib/index.js
- log-schema/log-schema.json
- README.md
- LICENSE
- package.json
publish: publish:
stage: publish stage: publish
......
lib/test/ *
log-schema/ !log-schema/log-schema.json
!log-schema/log-schema.json !lib/*.js
\ No newline at end of file !lib/*.d.ts
!package.json
\ No newline at end of file
import { LogSchema, schema } from "./index";
import Express = require("express");
import Http = require("http");
import JsonSchema = require("jsonschema");
import _ = require("lodash");
const serviceName = process.env.npm_package_name;
const serviceVersion = process.env.npm_package_version;
const maxStringSize = 1000;
export interface LogInput {
message: string;
data?: any;
error?: {
type: string;
code: number;
serviceName?: string;
stack?: string;
data?: any;
originalErrorObject?: any;
};
file?: string;
logRequestPayload?: boolean;
responseTime?: number;
response?: {
body?: string;
headers?: Http.OutgoingHttpHeaders
};
req?: Express.Request;
tags?: string[];
statusCode?: number;
serviceName?: string; // Defaults to name from package.json file when program is started using npm
serviceVersion?: string; // Defaults to version from package.json file when program is started using npm
maxStringSize?: number; // Defaults to 1000
}
export interface FullLogInput extends LogInput {
levelName: LogSchema["levelName"];
}
function limitStringSize(str: string, limit?: number) {
if (!limit) {
limit = maxStringSize;
}
if (str.length > limit) {
return str.substr(0, limit) + "..";
} else {
return str;
}
}
function isDev() {
return process.env.NODE_ENV !== "production";
}
function stringify(data, limit?: number) {
try {
let str = "";
if (isDev()) {
str = JSON.stringify(data, null, 2);
} else {
str = JSON.stringify(data);
}
return limitStringSize(str, limit);
} catch (err) {
return "Unable to stringify.";
}
}
function extractHeaders(headers: Http.IncomingHttpHeaders | Http.OutgoingHttpHeaders, limit?: number): { [k: string]: string } {
let res = {};
_.each(headers, (value, key) => {
if (key === "authorization") {
return;
}
if (key === "cookie") {
return;
}
res[key] = limitStringSize(value.toString(), limit);
});
return res;
}
function getLogData(input: FullLogInput): LogSchema {
const res: LogSchema = {
"@timestamp": new Date().toISOString(),
file: input.file,
levelName: input.levelName,
loggerName: "ExpressLogger",
message: input.message,
responseTime: input.responseTime,
schemaVersion: "v3",
serviceName: input.serviceName || serviceName || "unknown",
serviceVersion: input.serviceVersion || serviceVersion || "unknown",
tags: input.tags,
statusCode: input.statusCode
};
const req = input.req;
if (req) {
res.host = req.hostname;
res.method = req.method;
res.port = req.connection.localPort;
res.request = {
headers: extractHeaders(req.headers, input.maxStringSize),
query: req.query,
path: req.params
}
if (input.logRequestPayload) {
res.request.payload = stringify(req.body, input.maxStringSize);
}
res.url = req.url;
res.xRequestId = _.get(req, "id");
}
const response = input.response;
if (response) {
res.response = {
headers: extractHeaders(response.headers),
body: limitStringSize(response.body, input.maxStringSize)
};
}
if (input.data) {
res.data = stringify(input.data, input.maxStringSize);
}
const error = input.error;
if (error) {
res.error = {
type: error.type,
code: error.code + "",
serviceName: error.serviceName,
stack: error.stack,
data: stringify(error.data, input.maxStringSize),
originalErrorObject: stringify(error.originalErrorObject, input.maxStringSize)
}
}
return res;
}
function log(levelName: LogSchema["levelName"], input: LogInput) {
if (isDev()) {
console.log("----------------------------------------------------------------------------------");
}
try {
const fullLogInput = _.merge({ levelName}, input);
const data = getLogData(fullLogInput);
const str = stringify(data, Number.MAX_SAFE_INTEGER);
console.log(str);
if (isDev()) {
JsonSchema.validate(data, schema, { throwError: true });
}
} catch (err) {
const errData = getLogData({
levelName: "ERROR",
message: "Invalid log format!",
data: {
error: err
},
req: input.req
});
const str = stringify(errData, Number.MAX_SAFE_INTEGER);
console.error(str);
}
}
export function debug(input: LogInput) {
log ("DEBUG", input);
}
export function info(input: LogInput) {
log ("INFO", input);
}
export function warn(input: LogInput) {
log ("WARN", input);
}
export function error(input: LogInput) {
log ("ERROR", input);
}
export function middleware(req: Express.Request, res: Express.Response, next: Express.NextFunction) {
const startTime = Date.now();
const reqInfoString = (req: Express.Request) => req.method.toUpperCase() + " " + req.path;
const message = reqInfoString(req) + " request received."
const file = "ExpressLogger.ts";
info({ message, file, req, logRequestPayload: true })
const send = res.send;
let responseLogged = false;
res.send = function (data) {
if (!responseLogged) {
responseLogged = true;
const responseTime = Date.now() - startTime;
const message = reqInfoString(req) + " " + res.statusCode + " took " + responseTime + " ms.";
const body = _.isString(data)? data : JSON.stringify(data);
const response = {
headers: res.getHeaders(),
body
}
info({ responseTime, message, file, req, response });
}
send.apply(res, arguments);
return res;
}
next();
}
\ No newline at end of file
...@@ -44,5 +44,39 @@ const logEvent: LogSchema = { ...@@ -44,5 +44,39 @@ const logEvent: LogSchema = {
console.log(js.validate(logEvent, schema)); console.log(js.validate(logEvent, schema));
``` ```
Example using the ExpressLogger:
```typescript
import Express = require("express");
import {ExpressLogger as Logger} from "@nsd/log-schema-node";
const app = Express();
app.use(Logger.middleware);
app.get("/hello", (req, res) => {
Logger.info({
message: "Hello, Logger!",
req
});
res.send("Hello, World!");
});
const port = 1337;
const server = app.listen(port, () => {
Logger.info({ message: "Server started on port " + port});
});
server.on("error", (err) => {
Logger.error({
message: "Server has encountered an error.",
error: {
code: 100,
type: "SERVER_ERROR",
originalErrorObject: err
}
});
});
```
Currently this package only exposes the schema and the Typescript interface. In Currently this package only exposes the schema and the Typescript interface. In
the future it might also provide facilities for validation, etc. the future it might also provide facilities for validation, etc.
import * as jsonSchema from "../log-schema/log-schema.json"; import * as schema from "../log-schema/log-schema.json";
import ExpressLogger = require("./ExpressLogger");
export { schema, ExpressLogger };
export interface LogSchema { export interface LogSchema {
/** /**
...@@ -148,8 +151,4 @@ export interface LogSchema { ...@@ -148,8 +151,4 @@ export interface LogSchema {
*/ */
body?: string; body?: string;
}; };
} }
\ No newline at end of file
const schema: LogSchema = <LogSchema> jsonSchema;
export { schema };
\ No newline at end of file
This diff is collapsed.
...@@ -9,16 +9,20 @@ ...@@ -9,16 +9,20 @@
"repository": "git@gitlab.nsd.uib.no:nsd-commons/log-schema-node.git", "repository": "git@gitlab.nsd.uib.no:nsd-commons/log-schema-node.git",
"author": "Snorre Magnus Davøen <snorre.davoen@nsd.uib.no>", "author": "Snorre Magnus Davøen <snorre.davoen@nsd.uib.no>",
"license": "See LICENSE", "license": "See LICENSE",
"files": [ "dependencies": {
"lib/index.d.ts", "dotenv": "6.2.0",
"lib/index.js", "express": "4.16.4",
"log-schema/log-schema.json" "jsonschema": "1.2.4",
], "lodash": "4.17.11"
},
"devDependencies": { "devDependencies": {
"@types/express": "4.16.1",
"@types/lodash": "4.14.121",
"dtslint": "Microsoft/dtslint#production",
"jayschema": "^0.3.1", "jayschema": "^0.3.1",
"json-schema-to-typescript": "^4.2.0", "json-schema-to-typescript": "^4.2.0",
"tslint": "^5.2.0", "tslint": "^5.2.0",
"typescript": "^2.3.2" "typescript": "3.3.3333"
}, },
"publishConfig": { "publishConfig": {
"registry": "https://nexus.nsd.no/repository/npm-hosted/" "registry": "https://nexus.nsd.no/repository/npm-hosted/"
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
"compilerOptions": { "compilerOptions": {
"declaration": true, "declaration": true,
"outDir": "lib", "outDir": "lib",
"target": "es5" "target": "es6",
"module": "commonjs"
}, },
"exclude": [ "exclude": [
"lib", "lib",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment