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

Merge branch 'express-logger' into 'master'

Express logger

See merge request javascript/log-schema-node!2
parents 6f6100fb 9da74271
Pipeline #17163 passed with stages
in 30 seconds
......@@ -7,51 +7,19 @@ variables:
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:8-alpine
script:
- echo $NEXUS_NPM_TOKEN > ~/.npmrc
- echo 'always-auth=true' >> ~/.npmrc
- npm set registry https://nexus.nsd.no/repository/npm-group/
- npm set @nsd: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
publish:
stage: publish
only:
- tags
image: node:7.8-alpine
image: node:8-alpine
script:
- echo $NEXUS_NPM_PUB_TOKEN > ~/.npmrc
- echo 'always-auth=true' >> ~/.npmrc
......
lib/test/
log-schema/
!log-schema/log-schema.json
\ No newline at end of file
*
!log-schema/log-schema.json
!lib/*.js
!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 = {
console.log(js.validate(logEvent, schema));
```
Currently this package only exposes the schema and the Typescript interface. In
the future it might also provide facilities for validation, etc.
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
}
});
});
```
By using the ExpressLogger middleware basic info about each request and response will be logged.
\ No newline at end of file
import * as jsonSchema from "../log-schema/log-schema.json";
const schema = require("../log-schema/log-schema.json");
import ExpressLogger = require("./ExpressLogger");
export { schema, ExpressLogger };
export interface LogSchema {
/**
......@@ -148,8 +152,4 @@ export interface LogSchema {
*/
body?: string;
};
}
const schema: LogSchema = <LogSchema> jsonSchema;
export { schema };
\ No newline at end of file
}
\ No newline at end of file
This diff is collapsed.
{
"name": "@nsd/log-schema-node",
"version": "3.0.0",
"version": "3.1.0",
"main": "lib/index.js",
"types": "lib/index",
"scripts": {
......@@ -9,16 +9,19 @@
"repository": "git@gitlab.nsd.uib.no:nsd-commons/log-schema-node.git",
"author": "Snorre Magnus Davøen <snorre.davoen@nsd.uib.no>",
"license": "See LICENSE",
"files": [
"lib/index.d.ts",
"lib/index.js",
"log-schema/log-schema.json"
],
"dependencies": {
"dotenv": "6.2.0",
"express": "4.16.4",
"jsonschema": "1.2.4",
"lodash": "4.17.11"
},
"devDependencies": {
"@types/express": "4.16.1",
"@types/lodash": "4.14.121",
"jayschema": "^0.3.1",
"json-schema-to-typescript": "^4.2.0",
"tslint": "^5.2.0",
"typescript": "^2.3.2"
"typescript": "3.3.3333"
},
"publishConfig": {
"registry": "https://nexus.nsd.no/repository/npm-hosted/"
......
......@@ -2,7 +2,8 @@
"compilerOptions": {
"declaration": true,
"outDir": "lib",
"target": "es5"
"target": "es6",
"module": "commonjs"
},
"exclude": [
"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