Implement custom logger to replace scribe.js.
* scribe.js is unmaintained and adds unnecessary complexity.
This commit is contained in:
parent
2df8539c46
commit
002ae4419f
|
@ -116,10 +116,8 @@ Dann die `config.json` anpassen nach belieben. Es gibt die folgenden Konfigurati
|
||||||
|
|
||||||
* **`server.logging.enabled`** Ob geloggt werden soll (Achtung: `false` sorgt dafür, dass überhaupt kein Output mehr auf
|
* **`server.logging.enabled`** Ob geloggt werden soll (Achtung: `false` sorgt dafür, dass überhaupt kein Output mehr auf
|
||||||
`stdout` oder in den Logs erscheint und überschreibt damit alle anderen Logging-Optionen!), z. B.: `false`
|
`stdout` oder in den Logs erscheint und überschreibt damit alle anderen Logging-Optionen!), z. B.: `false`
|
||||||
* **`server.logging.directory`** Verzeichnis unter dem Log-Files abgelegt werden, z. B.: `"$FFFFNG_HOME/logs"`
|
|
||||||
* **`server.logging.debug`** Gibt an, ob Debug-Output geloggt werden soll (Achtung, viel!), z. B.: `false`
|
* **`server.logging.debug`** Gibt an, ob Debug-Output geloggt werden soll (Achtung, viel!), z. B.: `false`
|
||||||
* **`server.logging.profile`** Gibt an, ob Profiling-Output geloggt werden soll (Achtung, viel!), z. B.: `false`
|
* **`server.logging.profile`** Gibt an, ob Profiling-Output geloggt werden soll (Achtung, viel!), z. B.: `false`
|
||||||
* **`server.logging.logRequests`** Gib an, ob HTTP-Requests geloggt werden sollen (Achtung, Datenschutz!), z. B.: `false`
|
|
||||||
|
|
||||||
* **`server.internal.active`** Gibt an, ob interne URLs, wie Admin-Panel und Logging-Interface, erreichbar sein sollen,
|
* **`server.internal.active`** Gibt an, ob interne URLs, wie Admin-Panel und Logging-Interface, erreichbar sein sollen,
|
||||||
z. B.: `true`
|
z. B.: `true`
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
* Integrate typescript in the build and start migrating the server code.
|
* Integrate typescript in the build and start migrating the server code.
|
||||||
* Find a nice way to integrate typescript with grunt.
|
* Find a nice way to integrate typescript with grunt.
|
||||||
* Replace logging framework.
|
|
||||||
* Bluebird for promises?
|
* Bluebird for promises?
|
||||||
* Yarn instead of NPM?
|
* Yarn instead of NPM?
|
||||||
|
|
||||||
|
|
|
@ -453,14 +453,6 @@ angular.module('ffffngAdmin').config(function(NgAdminConfigurationProvider, Rest
|
||||||
.menu(tasks)
|
.menu(tasks)
|
||||||
.icon('<span class="fa fa-cog"></span>')
|
.icon('<span class="fa fa-cog"></span>')
|
||||||
)
|
)
|
||||||
.addChild(nga
|
|
||||||
.menu()
|
|
||||||
.template(
|
|
||||||
'<a href="' + pathPrefix + '/internal/logs" target="_blank">' +
|
|
||||||
'<span class="fa fa-list"></span> Logs' +
|
|
||||||
'</a>'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
admin.dashboard(nga.dashboard()
|
admin.dashboard(nga.dashboard()
|
||||||
|
|
|
@ -8,10 +8,8 @@
|
||||||
|
|
||||||
"logging": {
|
"logging": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"directory": "/tmp/logs",
|
|
||||||
"debug": false,
|
"debug": false,
|
||||||
"profile": false,
|
"profile": false
|
||||||
"logRequests": false
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"internal": {
|
"internal": {
|
||||||
|
|
52
package-lock.json
generated
52
package-lock.json
generated
|
@ -29,7 +29,6 @@
|
||||||
"nodemailer": "^6.7.2",
|
"nodemailer": "^6.7.2",
|
||||||
"nodemailer-html-to-text": "^3.2.0",
|
"nodemailer-html-to-text": "^3.2.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"scribe-js": "^2.0.4",
|
|
||||||
"serve-static": "^1.14.1",
|
"serve-static": "^1.14.1",
|
||||||
"sparkson": "^1.3.6",
|
"sparkson": "^1.3.6",
|
||||||
"sqlite": "^3.0.6"
|
"sqlite": "^3.0.6"
|
||||||
|
@ -3897,14 +3896,6 @@
|
||||||
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
|
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/callsite": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
|
@ -4305,14 +4296,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
},
|
},
|
||||||
"node_modules/colors": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.1.90"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
@ -15819,19 +15802,6 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/scribe-js": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/scribe-js/-/scribe-js-2.0.4.tgz",
|
|
||||||
"integrity": "sha1-a1GbdVdqCXjCd/ghanRSVs/yTQs=",
|
|
||||||
"deprecated": "This package is no longer maintained. Please find an alternative solution. Sorry for inconvenience.",
|
|
||||||
"dependencies": {
|
|
||||||
"callsite": "^1.0.0",
|
|
||||||
"colors": "^1.0.0",
|
|
||||||
"express": "^4.0.0",
|
|
||||||
"mkdirp": "^0.5.0",
|
|
||||||
"moment": "^2.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/seek-bzip": {
|
"node_modules/seek-bzip": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
|
||||||
|
@ -21575,11 +21545,6 @@
|
||||||
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
|
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"callsite": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
|
|
||||||
},
|
|
||||||
"callsites": {
|
"callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
|
@ -21904,11 +21869,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
},
|
},
|
||||||
"colors": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
|
|
||||||
},
|
|
||||||
"combined-stream": {
|
"combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
@ -30830,18 +30790,6 @@
|
||||||
"xmlchars": "^2.2.0"
|
"xmlchars": "^2.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scribe-js": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/scribe-js/-/scribe-js-2.0.4.tgz",
|
|
||||||
"integrity": "sha1-a1GbdVdqCXjCd/ghanRSVs/yTQs=",
|
|
||||||
"requires": {
|
|
||||||
"callsite": "^1.0.0",
|
|
||||||
"colors": "^1.0.0",
|
|
||||||
"express": "^4.0.0",
|
|
||||||
"mkdirp": "^0.5.0",
|
|
||||||
"moment": "^2.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"seek-bzip": {
|
"seek-bzip": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
"nodemailer": "^6.7.2",
|
"nodemailer": "^6.7.2",
|
||||||
"nodemailer-html-to-text": "^3.2.0",
|
"nodemailer-html-to-text": "^3.2.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"scribe-js": "^2.0.4",
|
|
||||||
"serve-static": "^1.14.1",
|
"serve-static": "^1.14.1",
|
||||||
"sparkson": "^1.3.6",
|
"sparkson": "^1.3.6",
|
||||||
"sqlite": "^3.0.6"
|
"sqlite": "^3.0.6"
|
||||||
|
|
|
@ -104,7 +104,7 @@ test("should get messages for no tag", () => {
|
||||||
|
|
||||||
// when
|
// when
|
||||||
logger.tag().debug("message");
|
logger.tag().debug("message");
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(logger.getMessages("debug")).toEqual([["message"]]);
|
expect(logger.getMessages("debug")).toEqual([["message"]]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {Logger, TaggedLogger} from "../types";
|
import {Logger, TaggedLogger, LogLevel} from '../types';
|
||||||
|
import {ActivatableLogger} from '../logger';
|
||||||
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'profile';
|
|
||||||
|
|
||||||
export type MockLogMessages = any[][];
|
export type MockLogMessages = any[][];
|
||||||
type TaggedLogMessages = {
|
type TaggedLogMessages = {
|
||||||
|
@ -8,7 +7,7 @@ type TaggedLogMessages = {
|
||||||
logs: {[key: string]: MockLogMessages}
|
logs: {[key: string]: MockLogMessages}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockLogger implements Logger {
|
export class MockLogger implements ActivatableLogger {
|
||||||
private taggedLogMessages: TaggedLogMessages = MockLogger.emptyTaggedLogMessages();
|
private taggedLogMessages: TaggedLogMessages = MockLogger.emptyTaggedLogMessages();
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
@ -37,7 +36,7 @@ export class MockLogger implements Logger {
|
||||||
return taggedLogMessages.logs[level] || [];
|
return taggedLogMessages.logs[level] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {}
|
init(...args: any[]): void {}
|
||||||
|
|
||||||
private doLog(taggedLogMessages: TaggedLogMessages, level: LogLevel, tags: string[], args: any[]): void {
|
private doLog(taggedLogMessages: TaggedLogMessages, level: LogLevel, tags: string[], args: any[]): void {
|
||||||
if (tags.length > 0) {
|
if (tags.length > 0) {
|
||||||
|
@ -55,17 +54,27 @@ export class MockLogger implements Logger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private log(level: LogLevel, tags: string[], args: any[]): void {
|
|
||||||
this.doLog(this.taggedLogMessages, level, tags, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
tag(...tags: string[]): TaggedLogger {
|
tag(...tags: string[]): TaggedLogger {
|
||||||
|
const logger: MockLogger = this;
|
||||||
return {
|
return {
|
||||||
debug: (...args: any[]): void => this.log('debug', tags, args),
|
log(level: LogLevel, ...args: any[]): void {
|
||||||
info: (...args: any[]): void => this.log('info', tags, args),
|
logger.doLog(logger.taggedLogMessages, level, tags, args);
|
||||||
warn: (...args: any[]): void => this.log('warn', tags, args),
|
},
|
||||||
error: (...args: any[]): void => this.log('error', tags, args),
|
debug(...args: any[]): void {
|
||||||
profile: (...args: any[]): void => this.log('profile', tags, args),
|
this.log('debug', ...args);
|
||||||
|
},
|
||||||
|
info(...args: any[]): void {
|
||||||
|
this.log('info', ...args);
|
||||||
|
},
|
||||||
|
warn(...args: any[]): void {
|
||||||
|
this.log('warn', ...args);
|
||||||
|
},
|
||||||
|
error(...args: any[]): void {
|
||||||
|
this.log('error', ...args);
|
||||||
|
},
|
||||||
|
profile(...args: any[]): void {
|
||||||
|
this.log('profile', ...args);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
190
server/logger.test.ts
Normal file
190
server/logger.test.ts
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
import {LogLevel, LogLevels, isLogLevel} from "./types";
|
||||||
|
import {ActivatableLoggerImpl} from "./logger";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
class TestableLogger extends ActivatableLoggerImpl {
|
||||||
|
private logs: any[][] = [];
|
||||||
|
|
||||||
|
constructor(enabled?: boolean) {
|
||||||
|
super();
|
||||||
|
this.init(
|
||||||
|
enabled === false ? false : true, // default is true
|
||||||
|
(...args: any[]): void => this.doLog(...args)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
doLog(...args: any[]): void {
|
||||||
|
this.logs.push(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogs(): any[][] {
|
||||||
|
return this.logs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParsedLogEntry = {
|
||||||
|
level: LogLevel,
|
||||||
|
tags: string[],
|
||||||
|
message: string,
|
||||||
|
args: any[],
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseLogEntry(logEntry: any[]): ParsedLogEntry {
|
||||||
|
if (!logEntry.length) {
|
||||||
|
throw new Error(
|
||||||
|
`Empty log entry. Should always start with log message: ${logEntry}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const logMessage = logEntry[0];
|
||||||
|
if (typeof logMessage !== "string") {
|
||||||
|
throw new Error(
|
||||||
|
`Expected log entry to start with string, but got: ${logMessage}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const regexp = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ([A-Z]+) - (\[[^\]]*\])? *(.*)$/;
|
||||||
|
const groups = logMessage.match(regexp);
|
||||||
|
if (groups === null || groups.length < 4) {
|
||||||
|
throw new Error(
|
||||||
|
`Malformed log message.\n\nExpected format: ${regexp}.\nGot: ${logMessage}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const level = groups[1].toLowerCase();
|
||||||
|
if (!isLogLevel(level)) {
|
||||||
|
throw new Error(
|
||||||
|
`Unknown log level "${level}" in log message: ${logMessage}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagsStr = groups[2].substring(1, groups[2].length - 1);
|
||||||
|
const tags = tagsStr ? _.split(tagsStr, ", ") : [];
|
||||||
|
const message = groups[3];
|
||||||
|
const args = logEntry.slice(1);
|
||||||
|
|
||||||
|
return {
|
||||||
|
level: level as LogLevel,
|
||||||
|
tags,
|
||||||
|
message,
|
||||||
|
args,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLogs(logs: any[][]): ParsedLogEntry[] {
|
||||||
|
const parsedLogs: ParsedLogEntry[] = [];
|
||||||
|
for (const logEntry of logs) {
|
||||||
|
parsedLogs.push(parseLogEntry(logEntry));
|
||||||
|
}
|
||||||
|
return parsedLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const level of LogLevels) {
|
||||||
|
test(`should log single untagged ${level} message without parameters`, () => {
|
||||||
|
// given
|
||||||
|
const logger = new TestableLogger();
|
||||||
|
|
||||||
|
// when
|
||||||
|
logger.tag()[level]("message");
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
||||||
|
level,
|
||||||
|
tags: [],
|
||||||
|
message: "message",
|
||||||
|
args: [],
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should log single tagged ${level} message without parameters`, () => {
|
||||||
|
// given
|
||||||
|
const logger = new TestableLogger();
|
||||||
|
|
||||||
|
// when
|
||||||
|
logger.tag("tag1", "tag2")[level]("message");
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
||||||
|
level,
|
||||||
|
tags: ["tag1", "tag2"],
|
||||||
|
message: "message",
|
||||||
|
args: [],
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should log single tagged ${level} message with parameters`, () => {
|
||||||
|
// given
|
||||||
|
const logger = new TestableLogger();
|
||||||
|
|
||||||
|
// when
|
||||||
|
logger.tag("tag1", "tag2")[level]("message", 1, {}, [false]);
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
||||||
|
level,
|
||||||
|
tags: ["tag1", "tag2"],
|
||||||
|
message: "message",
|
||||||
|
args: [1, {}, [false]],
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should escape tags for ${level} message without parameters`, () => {
|
||||||
|
// given
|
||||||
|
const logger = new TestableLogger();
|
||||||
|
|
||||||
|
// when
|
||||||
|
logger.tag("%s", "%d", "%f", "%o", "%")[level]("message");
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
||||||
|
level,
|
||||||
|
tags: ["%%s", "%%d", "%%f", "%%o", "%%"],
|
||||||
|
message: "message",
|
||||||
|
args: [],
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should not escape ${level} message itself`, () => {
|
||||||
|
// given
|
||||||
|
const logger = new TestableLogger();
|
||||||
|
|
||||||
|
// when
|
||||||
|
logger.tag("tag")[level]("%s %d %f %o %%");
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
||||||
|
level,
|
||||||
|
tags: ["tag"],
|
||||||
|
message: "%s %d %f %o %%",
|
||||||
|
args: [],
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should not escape ${level} message arguments`, () => {
|
||||||
|
// given
|
||||||
|
const logger = new TestableLogger();
|
||||||
|
|
||||||
|
// when
|
||||||
|
logger.tag("tag")[level]("message", 1, "%s", "%d", "%f", "%o", "%");
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
||||||
|
level,
|
||||||
|
tags: ["tag"],
|
||||||
|
message: "message",
|
||||||
|
args: [1, "%s", "%d", "%f", "%o", "%"],
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should not log ${level} message on disabled logger`, () => {
|
||||||
|
// given
|
||||||
|
const logger = new TestableLogger(false);
|
||||||
|
|
||||||
|
// when
|
||||||
|
logger.tag("tag")[level]("message");
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(parseLogs(logger.getLogs())).toEqual([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
137
server/logger.ts
137
server/logger.ts
|
@ -1,104 +1,71 @@
|
||||||
import {Logger, TaggedLogger} from "./types";
|
import {Logger, TaggedLogger, LogLevel} from './types';
|
||||||
|
import moment from 'moment';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
function procConsole() {
|
export type LoggingFunction = (...args: any[]) => void;
|
||||||
// @ts-ignore
|
|
||||||
return process.console;
|
|
||||||
}
|
|
||||||
|
|
||||||
const noopTaggedLogger: TaggedLogger = {
|
const noopTaggedLogger: TaggedLogger = {
|
||||||
debug(...args: any): void {},
|
log(level: LogLevel, ...args: any[]): void {},
|
||||||
info(...args: any): void {},
|
debug(...args: any[]): void {},
|
||||||
warn(...args: any): void {},
|
info(...args: any[]): void {},
|
||||||
error(...args: any): void {},
|
warn(...args: any[]): void {},
|
||||||
profile(...args: any): void {},
|
error(...args: any[]): void {},
|
||||||
|
profile(...args: any[]): void {},
|
||||||
};
|
};
|
||||||
|
|
||||||
class ActivatableLogger implements Logger {
|
export interface ActivatableLogger extends Logger {
|
||||||
|
init(enabled: boolean, loggingFunction?: LoggingFunction): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ActivatableLoggerImpl implements ActivatableLogger {
|
||||||
private enabled: boolean = false;
|
private enabled: boolean = false;
|
||||||
|
private loggingFunction: LoggingFunction = console.info;
|
||||||
|
|
||||||
init(): void {
|
init(enabled: boolean, loggingFunction?: LoggingFunction): void {
|
||||||
const app = require('./app').app;
|
|
||||||
const config = require('./config').config;
|
const config = require('./config').config;
|
||||||
|
|
||||||
const enabled = config.server.logging.enabled;
|
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
|
this.loggingFunction = loggingFunction || console.info;
|
||||||
// Hack to allow proper logging of Error.
|
|
||||||
Object.defineProperty(Error.prototype, 'message', {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true
|
|
||||||
});
|
|
||||||
Object.defineProperty(Error.prototype, 'stack', {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const scribe = require('scribe-js')({
|
|
||||||
rootPath: config.server.logging.directory,
|
|
||||||
});
|
|
||||||
|
|
||||||
function addLogger(name: string, color: string, active: boolean) {
|
|
||||||
if (enabled && active) {
|
|
||||||
procConsole().addLogger(name, color, {
|
|
||||||
logInConsole: false
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
procConsole()[name] = function () {
|
|
||||||
this._reset(); // forget tags, etc. for this logging event
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addLogger('debug', 'grey', config.server.logging.debug);
|
|
||||||
addLogger('profile', 'blue', config.server.logging.profile);
|
|
||||||
|
|
||||||
if (enabled && config.server.logging.logRequests) {
|
|
||||||
app.use(scribe.express.logger());
|
|
||||||
}
|
|
||||||
if (config.server.internal.active) {
|
|
||||||
const prefix = config.server.rootPath === '/' ? '' : config.server.rootPath;
|
|
||||||
app.use(prefix + '/internal/logs', scribe.webPanel());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hack to allow correct logging of node.js Error objects.
|
|
||||||
// See: https://github.com/bluejamesbond/Scribe.js/issues/70
|
|
||||||
Object.defineProperty(Error.prototype, 'toJSON', {
|
|
||||||
configurable: true,
|
|
||||||
value: function () {
|
|
||||||
const alt: {[key: string]: any} = {};
|
|
||||||
const storeKey = (key: string) => {
|
|
||||||
alt[key] = this[key];
|
|
||||||
};
|
|
||||||
Object.getOwnPropertyNames(this).forEach(storeKey, this);
|
|
||||||
return alt;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
for (const key of Object.keys(procConsole())) {
|
|
||||||
// @ts-ignore
|
|
||||||
module.exports[key] = enabled ? procConsole()[key] : (...args: any) => {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tag(...tags: any): TaggedLogger {
|
tag(...tags: string[]): TaggedLogger {
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
|
const loggingFunction = this.loggingFunction;
|
||||||
return {
|
return {
|
||||||
debug(...args: any): void {
|
log(level: LogLevel, ...args: any[]): void {
|
||||||
procConsole().tag(...tags).debug(...args);
|
const timeStr = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
const levelStr = level.toUpperCase();
|
||||||
|
const tagsStr = tags ? '[' + _.join(tags, ', ') + ']' : '';
|
||||||
|
const messagePrefix = `${timeStr} ${levelStr} - ${tagsStr}`;
|
||||||
|
|
||||||
|
// Make sure to only replace %s, etc. in real log message
|
||||||
|
// but not in tags.
|
||||||
|
const escapedMessagePrefix = messagePrefix.replace(/%/g, '%%');
|
||||||
|
|
||||||
|
let message = '';
|
||||||
|
if (args && _.isString(args[0])) {
|
||||||
|
message = args[0];
|
||||||
|
args.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
const logStr = message
|
||||||
|
? `${escapedMessagePrefix} ${message}`
|
||||||
|
: escapedMessagePrefix;
|
||||||
|
loggingFunction(logStr, ...args);
|
||||||
},
|
},
|
||||||
info(...args: any): void {
|
debug(...args: any[]): void {
|
||||||
procConsole().tag(...tags).info(...args);
|
this.log('debug', ...args);
|
||||||
},
|
},
|
||||||
warn(...args: any): void {
|
info(...args: any[]): void {
|
||||||
procConsole().tag(...tags).warn(...args);
|
this.log('info', ...args);
|
||||||
},
|
},
|
||||||
error(...args: any): void {
|
warn(...args: any[]): void {
|
||||||
procConsole().tag(...tags).error(...args);
|
this.log('warn', ...args);
|
||||||
},
|
},
|
||||||
profile(...args: any): void {
|
error(...args: any[]): void {
|
||||||
procConsole().tag(...tags).profile(...args);
|
this.log('error', ...args);
|
||||||
|
},
|
||||||
|
profile(...args: any[]): void {
|
||||||
|
this.log('profile', ...args);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -107,4 +74,4 @@ class ActivatableLogger implements Logger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ActivatableLogger() as Logger;
|
export default new ActivatableLoggerImpl() as ActivatableLogger;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import * as router from "./router"
|
||||||
import * as app from "./app"
|
import * as app from "./app"
|
||||||
|
|
||||||
app.init();
|
app.init();
|
||||||
Logger.init();
|
Logger.init(config.server.logging.enabled);
|
||||||
Logger.tag('main', 'startup').info('Server starting up...');
|
Logger.tag('main', 'startup').info('Server starting up...');
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
|
|
@ -7,10 +7,8 @@ export type Version = string;
|
||||||
export class LoggingConfig {
|
export class LoggingConfig {
|
||||||
constructor(
|
constructor(
|
||||||
@Field("enabled") public enabled: boolean,
|
@Field("enabled") public enabled: boolean,
|
||||||
@Field("directory") public directory: string,
|
|
||||||
@Field("debug") public debug: boolean,
|
@Field("debug") public debug: boolean,
|
||||||
@Field("profile") public profile: boolean,
|
@Field("profile") public profile: boolean,
|
||||||
@Field("logRequests") public logRequests: boolean,
|
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,20 @@
|
||||||
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'profile';
|
||||||
|
export const LogLevels: LogLevel[] = ['debug', 'info', 'warn', 'error', 'profile'];
|
||||||
|
|
||||||
|
export function isLogLevel(arg: any): arg is LogLevel {
|
||||||
|
if (typeof arg !== "string") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const level of LogLevels) {
|
||||||
|
if (level === arg) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TaggedLogger {
|
export interface TaggedLogger {
|
||||||
|
log(level: LogLevel, ...args: any[]): void;
|
||||||
debug(...args: any[]): void;
|
debug(...args: any[]): void;
|
||||||
info(...args: any[]): void;
|
info(...args: any[]): void;
|
||||||
warn(...args: any[]): void;
|
warn(...args: any[]): void;
|
||||||
|
@ -7,6 +23,5 @@ export interface TaggedLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Logger {
|
export interface Logger {
|
||||||
init(): void;
|
|
||||||
tag(...tags: string[]): TaggedLogger;
|
tag(...tags: string[]): TaggedLogger;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue