Removing all Node specific stuff from NodeLogging.

This commit is contained in:
Fabian 2019-03-31 23:35:23 -04:00
commit e489a15892
11 changed files with 3971 additions and 0 deletions

5
.editorconfig Normal file
View File

@ -0,0 +1,5 @@
charset = utf-8
indent_style = space
indent_size = 3
trim_trailing_whitespace = true
end_of_line = lf

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
logs/
yarn.lock
out/

3102
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "@hibas123/logging",
"version": "1.0.0",
"description": "",
"main": "out/index.js",
"types": "out/index.d.ts",
"scripts": {
"prepublish": "tsc",
"build": "tsc",
"watch": "tsc --watch"
},
"repository": {
"type": "git",
"url": "https://git.stamm.me/OpenServer/Logging.git"
},
"author": "Fabian Stamm",
"license": "MIT",
"devDependencies": {
"typescript": "^3.3.4000"
},
"dependencies": {
"@hibas123/utils": "^2.0.2"
}
}

94
readme.md Normal file
View File

@ -0,0 +1,94 @@
Simple logging module, that supports terminal coloring and different plugins
# Getting Started
``` javascript
const Logging = require("@hibas123/logging").Logging;
Logging.log("Hello there");
```
There are different Logging levels, that also apply terminal coloring:
``` javascript
Logging.debug("Debug message")
Logging.log("Log message")
Logging.warning("Warning")
Logging.error(new Error("To less creativity"))
Logging.error("Just an simple message as error")
Logging.errorMessage("Nearly the same as error")
```
All Logging types except the simple error take as many arguments as you want. These will be joined with spaces and serialized with the node util.inspect function.
# Setup
This logging module doesn't require any setup per default, but sometimes it makes sense to configure a few things.
For example can you disable the console output. This may be helpful, if you insert a custom adapters as described below.
Also you can set a name. All messages that are send with this instance are prefixed by this name.
``` javascript
const CustomLogging = new LoggingBase(name | {
name: "custom", // default undefined
console: false // default true
});
```
# Plugins
There is a Plugin API available, that makes is possible to add custom Logging Adapter.
``` javascript
const Demo = new LoggingBase("Demo");
Demo.addAdapter(new DemoAdapter({ color: "rainbow" }));
```
The adapters need to provide a very simple Interface:
``` typescript
interface Adapter {
init(observable: ObservableInterface<Message>, name?: string): void | Promise<void>;
flush(sync: true): void;
flush(sync: false): void | Promise<void>;
}
interface Message {
type: LoggingTypes;
name?:string;
text: {
raw: string[],
formatted: string[]
};
date: Date;
file: string;
customColors?:string;
}
enum LoggingTypes {
Log,
Warning,
Error,
Debug
}
```
The `ObservableInterface` comes from `@hibas123/utils`. It provides a very simple api for subscribing and unsubscribing from the message events.
More Details on Observable [git](https://git.stamm.me/OpenServer/Utils) or [npm](https://www.npmjs.com/package/@hibas123/utils)
# License
MIT
Copyright (c) 2018 Fabian Stamm
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

234
src/base.ts Normal file
View File

@ -0,0 +1,234 @@
import { Observable } from "@hibas123/utils";
import { ConsoleWriter } from "./consolewriter";
import inspect from "./inspect";
import { Adapter, LoggingTypes, Message } from "./types";
export const Colors = {
Reset: "\x1b[0m",
Bright: "\x1b[1m",
Dim: "\x1b[2m",
Underscore: "\x1b[4m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
Hidden: "\x1b[8m",
FgBlack: "\x1b[30m",
FgRed: "\x1b[31m",
FgGreen: "\x1b[32m",
FgYellow: "\x1b[33m",
FgBlue: "\x1b[34m",
FgMagenta: "\x1b[35m",
FgCyan: "\x1b[36m",
FgWhite: "\x1b[37m",
BgBlack: "\x1b[40m",
BgRed: "\x1b[41m",
BgGreen: "\x1b[42m",
BgYellow: "\x1b[43m",
BgBlue: "\x1b[44m",
BgMagenta: "\x1b[45m",
BgCyan: "\x1b[46m",
BgWhite: "\x1b[47m"
}
export interface LoggingBaseOptions {
/**
* Name will be prefixed on Console output and added to logfiles, if not specified here
*/
name: string,
/**
* Prints output to console
*/
console: boolean;
}
export class LoggingBase {
private logFile: any;
private errorFile: any;
private adapter: Adapter[] = [];
private adapter_init: Promise<void>[] = [];
private messageObservable = new Observable<Message>();
protected name: string;
constructor(options?: Partial<LoggingBaseOptions> | string) {
let opt: Partial<LoggingBaseOptions>;
if (!options) opt = {}
else if (typeof options === "string") {
opt = { name: options };
} else {
opt = options;
}
let config = {
name: undefined,
console: true,
files: true,
...opt
};
if (config.name)
this.name = config.name;
for (let key in this) {
if (typeof this[key] === "function") this[key] = (<any>this[key]).bind(this);
}
if (config.console) {
this.addAdapter(new ConsoleWriter());
}
}
addAdapter(adapter: Adapter) {
this.adapter.push(adapter);
let prms = Promise.resolve(adapter.init(this.messageObservable.getPublicApi(), this.name));
this.adapter_init.push(prms);
}
flush(sync: true): void;
flush(sync: false): Promise<void>;
flush(sync: boolean): void | Promise<void> {
if (sync) {
this.adapter.forEach(elm => elm.flush(true));
} else {
return Promise.all(this.adapter.map(elm => elm.flush(false))).then(() => { });
}
}
public waitForSetup() {
return Promise.all(this.adapter_init);
}
debug(...message: any[]) {
this.message(LoggingTypes.Debug, message);
}
log(...message: any[]) {
this.message(LoggingTypes.Log, message);
}
warning(...message: any[]) {
this.message(LoggingTypes.Warning, message);
}
logWithCustomColors(type: LoggingTypes, colors: string, ...message: any[]) {
this.message(type, message, colors);
}
error(error: Error | string) {
if (!error) error = "Empty ERROR was passed, so no informations available";
if (typeof error === "string") {
let e = new Error()
this.message(LoggingTypes.Error, [error, "\n", e.stack]);
} else {
this.message(LoggingTypes.Error, [error.message, "\n", error.stack], undefined, getCallerFromExisting(error));
}
}
errorMessage(...message: any[]) {
this.message(LoggingTypes.Error, message);
}
private message(type: LoggingTypes, message: any[] | string, customColors?: string, caller?: { file: string, line: number }) {
let file_raw = caller || getCallerFile();
let file = `${file_raw.file}:${String(file_raw.line).padEnd(3, " ")}`;
let mb = "";
if (typeof message === "string") {
mb = message;
} else {
message.forEach((e, i) => {
if (typeof e !== "string") e = inspect(e, { colors: true, showHidden: true, depth: 3 });
if (e.endsWith("\n") || i === message.length - 1) {
mb += e;
} else {
mb += e + " ";
}
});
}
let lines = mb.split("\n");
let date = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
let prefix = `[ ${date} ][${LoggingTypes[type].toUpperCase().padEnd(5, " ")}][${file}]: `;
let formatted = lines.map(line => prefix + line);
let msg: Message = {
date: new Date(),
file,
name: this.name,
text: {
raw: lines,
formatted
},
type,
customColors
}
this.messageObservable.send(msg);
}
}
function getStack() {
// Save original Error.prepareStackTrace
let origPrepareStackTrace = (<any>Error).prepareStackTrace;
// Override with function that just returns `stack`
(<any>Error).prepareStackTrace = function (_, stack) {
return stack
}
// Create a new `Error`, which automatically gets `stack`
let err = new Error();
// Evaluate `err.stack`, which calls our new `Error.prepareStackTrace`
let stack: any[] = <any>err.stack;
// Restore original `Error.prepareStackTrace`
(<any>Error).prepareStackTrace = origPrepareStackTrace;
// Remove superfluous function call on stack
stack.shift(); // getStack --> Error
return stack
}
function baseName(path) {
return path.split(/[\\/]/).pop();
}
function getCallerFile() {
try {
let stack = getStack()
let current_file = stack.shift().getFileName();
while (stack.length) {
let caller_file = stack.shift();
if (current_file !== caller_file.getFileName())
return {
file: baseName(caller_file.getFileName()),
line: caller_file.getLineNumber()
};
}
} catch (err) { }
return { file: undefined, line: 0 };
}
function getCallerFromExisting(err: Error): { file: string, line: number } {
if (!err || !err.stack) return { file: "NOFILE", line: 0 };
let lines = err.stack.split("\n");
lines.shift();// removing first line
while (lines.length > 0) {
let line = lines.shift();
let matches = line.match(/[a-zA-Z_-]+[.][a-zA-Z_-]+[:][0-9]+/g)
if (matches && matches.length > 0) {
let [f, line] = matches[0].split(":")
return {
file: f, line: Number(line)
};
}
}
}

39
src/consolewriter.ts Normal file
View File

@ -0,0 +1,39 @@
import { ObservableInterface } from "@hibas123/utils";
import { Colors } from "./index";
import { Adapter, LoggingTypes, Message } from "./types";
export class ConsoleWriter implements Adapter {
init(observable: ObservableInterface<Message>) {
observable.subscribe(this.onMessage.bind(this));
}
flush() { }
onMessage(message: Message) {
let consoleLogFormat = Colors.Reset;
if (!message.customColors) {
switch (message.type) {
case LoggingTypes.Log:
//m += FgWhite + BgBlack;
break;
case LoggingTypes.Error:
consoleLogFormat += Colors.FgRed;//FgWhite + BgRed + FgWhite;
break;
case LoggingTypes.Debug:
consoleLogFormat += Colors.FgCyan;
break;
case LoggingTypes.Warning:
consoleLogFormat += Colors.FgYellow;
break;
}
} else {
consoleLogFormat += message.customColors;
}
let lines = message.text.formatted;
let name = "";
if (message.name) name = `[${message.name}]=>`;
lines.forEach(line => console.log(consoleLogFormat + name + line + Colors.Reset))
}
}

8
src/index.ts Normal file
View File

@ -0,0 +1,8 @@
import { LoggingBase } from "./base";
export { Colors, LoggingBase } from "./base";
export { Adapter, LoggingTypes, Message } from "./types";
export { ObservableInterface } from "@hibas123/utils";
export let Logging: LoggingBase = undefined;
Logging = new LoggingBase();
export default Logging;

415
src/inspect.ts Normal file
View File

@ -0,0 +1,415 @@
/**
* Module exports.
*/
interface InspectOptions {
depth: number;
colors: boolean;
showHidden: boolean;
}
/**
* Echos the value of a value. Trys to print the value out
* in the best way possible given the different types.
*
* @param {Object} obj The object to print out.
* @param {Object} opts Optional options object that alters the output.
* @license MIT (© Joyent)
*/
/* legacy: obj, showHidden, depth, colors*/
export default function inspect(obj: any, opts: Partial<InspectOptions>) {
// default options
let ctx = {
seen: [],
stylize: stylizeNoColor,
depth: undefined,
colors: undefined,
showHidden: undefined,
customInspect: undefined
};
// legacy...
if (arguments.length >= 3) ctx.depth = arguments[2];
if (arguments.length >= 4) ctx.colors = arguments[3];
if (isBoolean(opts)) {
// legacy...
ctx.showHidden = opts;
} else if (opts) {
// got an "options" object
_extend(ctx, opts);
}
// set default options
if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
if (isUndefined(ctx.depth)) ctx.depth = 2;
if (isUndefined(ctx.colors)) ctx.colors = false;
if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
if (ctx.colors) ctx.stylize = stylizeWithColor;
return formatValue(ctx, obj, ctx.depth);
}
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
inspect.colors = {
'bold': [1, 22],
'italic': [3, 23],
'underline': [4, 24],
'inverse': [7, 27],
'white': [37, 39],
'grey': [90, 39],
'black': [30, 39],
'blue': [34, 39],
'cyan': [36, 39],
'green': [32, 39],
'magenta': [35, 39],
'red': [31, 39],
'yellow': [33, 39]
};
// Don't use 'blue' not visible on cmd.exe
inspect.styles = {
'special': 'cyan',
'number': 'yellow',
'boolean': 'yellow',
'undefined': 'grey',
'null': 'bold',
'string': 'green',
'date': 'magenta',
// "name": intentionally not styling
'regexp': 'red'
};
function stylizeNoColor(str, styleType) {
return str;
}
function isBoolean(arg) {
return typeof arg === 'boolean';
}
function isUndefined(arg) {
return arg === void 0;
}
function stylizeWithColor(str, styleType) {
var style = inspect.styles[styleType];
if (style) {
return '\u001b[' + inspect.colors[style][0] + 'm' + str +
'\u001b[' + inspect.colors[style][1] + 'm';
} else {
return str;
}
}
function isFunction(arg) {
return typeof arg === 'function';
}
function isString(arg) {
return typeof arg === 'string';
}
function isNumber(arg) {
return typeof arg === 'number';
}
function isNull(arg) {
return arg === null;
}
function hasOwn(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function isRegExp(re) {
return isObject(re) && objectToString(re) === '[object RegExp]';
}
function isObject(arg) {
return typeof arg === 'object' && arg !== null;
}
function isError(e) {
return isObject(e) &&
(objectToString(e) === '[object Error]' || e instanceof Error);
}
function isDate(d) {
return isObject(d) && objectToString(d) === '[object Date]';
}
function objectToString(o) {
return Object.prototype.toString.call(o);
}
function arrayToHash(array) {
var hash = {};
array.forEach(function (val, idx) {
hash[val] = true;
});
return hash;
}
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
var output = [];
for (var i = 0, l = value.length; i < l; ++i) {
if (hasOwn(value, String(i))) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
String(i), true));
} else {
output.push('');
}
}
keys.forEach(function (key) {
if (!key.match(/^\d+$/)) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
key, true));
}
});
return output;
}
function formatError(value) {
return '[' + Error.prototype.toString.call(value) + ']';
}
function formatValue(ctx, value, recurseTimes) {
// Provide a hook for user-specified inspect functions.
// Check that value is an object with an inspect function on it
if (ctx.customInspect &&
value &&
isFunction(value.inspect) &&
// Filter out the util module, it's inspect function is special
value.inspect !== inspect &&
// Also filter out any prototype objects using the circular check.
!(value.constructor && value.constructor.prototype === value)) {
var ret = value.inspect(recurseTimes, ctx);
if (!isString(ret)) {
ret = formatValue(ctx, ret, recurseTimes);
}
return ret;
}
// Primitive types cannot have properties
var primitive = formatPrimitive(ctx, value);
if (primitive) {
return primitive;
}
// Look up the keys of the object.
var keys = Object.keys(value);
var visibleKeys = arrayToHash(keys);
try {
if (ctx.showHidden && Object.getOwnPropertyNames) {
keys = Object.getOwnPropertyNames(value);
}
} catch (e) {
// ignore
}
// IE doesn't make error fields non-enumerable
// http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
if (isError(value)
&& (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
return formatError(value);
}
// Some type of object without properties can be shortcutted.
if (keys.length === 0) {
if (isFunction(value)) {
var name = value.name ? ': ' + value.name : '';
return ctx.stylize('[Function' + name + ']', 'special');
}
if (isRegExp(value)) {
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
}
if (isDate(value)) {
return ctx.stylize(Date.prototype.toString.call(value), 'date');
}
if (isError(value)) {
return formatError(value);
}
}
var base = '', array = false, braces = ['{', '}'];
// Make Array say that they are Array
if (Array.isArray(value)) {
array = true;
braces = ['[', ']'];
}
// Make functions say that they are functions
if (isFunction(value)) {
var n = value.name ? ': ' + value.name : '';
base = ' [Function' + n + ']';
}
// Make RegExps say that they are RegExps
if (isRegExp(value)) {
base = ' ' + RegExp.prototype.toString.call(value);
}
// Make dates with properties first say the date
if (isDate(value)) {
base = ' ' + Date.prototype.toUTCString.call(value);
}
// Make error with message first say the error
if (isError(value)) {
base = ' ' + formatError(value);
}
if (keys.length === 0 && (!array || value.length == 0)) {
return braces[0] + base + braces[1];
}
if (recurseTimes < 0) {
if (isRegExp(value)) {
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
} else {
return ctx.stylize('[Object]', 'special');
}
}
ctx.seen.push(value);
var output;
if (array) {
output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
} else {
output = keys.map(function (key) {
return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
});
}
ctx.seen.pop();
return reduceToSingleString(output, base, braces);
}
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
var name, str, desc;
desc = { value: void 0 };
try {
// ie6 navigator.toString
// throws Error: Object doesn't support this property or method
desc.value = value[key];
} catch (e) {
// ignore
}
try {
// ie10 Object.getOwnPropertyDescriptor(window.location, 'hash')
// throws TypeError: Object doesn't support this action
if (Object.getOwnPropertyDescriptor) {
desc = Object.getOwnPropertyDescriptor(value, key) || desc;
}
} catch (e) {
// ignore
}
if (desc.get) {
if (desc.set) {
str = ctx.stylize('[Getter/Setter]', 'special');
} else {
str = ctx.stylize('[Getter]', 'special');
}
} else {
if (desc.set) {
str = ctx.stylize('[Setter]', 'special');
}
}
if (!hasOwn(visibleKeys, key)) {
name = '[' + key + ']';
}
if (!str) {
if (ctx.seen.indexOf(desc.value) < 0) {
if (isNull(recurseTimes)) {
str = formatValue(ctx, desc.value, null);
} else {
str = formatValue(ctx, desc.value, recurseTimes - 1);
}
if (str.indexOf('\n') > -1) {
if (array) {
str = str.split('\n').map(function (line) {
return ' ' + line;
}).join('\n').substr(2);
} else {
str = '\n' + str.split('\n').map(function (line) {
return ' ' + line;
}).join('\n');
}
}
} else {
str = ctx.stylize('[Circular]', 'special');
}
}
if (isUndefined(name)) {
if (array && key.match(/^\d+$/)) {
return str;
}
name = JSON.stringify('' + key);
if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
name = name.substr(1, name.length - 2);
name = ctx.stylize(name, 'name');
} else {
name = name.replace(/'/g, "\\'")
.replace(/\\"/g, '"')
.replace(/(^"|"$)/g, "'");
name = ctx.stylize(name, 'string');
}
}
return name + ': ' + str;
}
function formatPrimitive(ctx, value) {
if (isUndefined(value))
return ctx.stylize('undefined', 'undefined');
if (isString(value)) {
var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
.replace(/'/g, "\\'")
.replace(/\\"/g, '"') + '\'';
return ctx.stylize(simple, 'string');
}
if (isNumber(value))
return ctx.stylize('' + value, 'number');
if (isBoolean(value))
return ctx.stylize('' + value, 'boolean');
// For some reason typeof null is "object", so special case here.
if (isNull(value))
return ctx.stylize('null', 'null');
}
function reduceToSingleString(output, base, braces) {
var numLinesEst = 0;
var length = output.reduce(function (prev, cur) {
numLinesEst++;
if (cur.indexOf('\n') >= 0) numLinesEst++;
return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
}, 0);
if (length > 60) {
return braces[0] +
(base === '' ? '' : base + '\n ') +
' ' +
output.join(',\n ') +
' ' +
braces[1];
}
return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
}
function _extend<T extends Y, Y>(origin: T, add: Y) {
// Don't do anything if add isn't an object
if (!add || !isObject(add)) return origin;
var keys = Object.keys(add);
var i = keys.length;
while (i--) {
origin[keys[i]] = add[keys[i]];
}
return origin;
}

27
src/types.ts Normal file
View File

@ -0,0 +1,27 @@
import { ObservableInterface } from "@hibas123/utils";
export enum LoggingTypes {
Log,
Warning,
Error,
Debug
}
export interface Message {
type: LoggingTypes;
name?: string;
text: {
raw: string[],
formatted: string[]
};
date: Date;
file: string;
customColors?: string;
}
export interface Adapter {
init(observable: ObservableInterface<Message>, name?: string): void | Promise<void>;
flush(sync: true): void;
flush(sync: false): void | Promise<void>;
}

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"noImplicitAny": false,
"sourceMap": true,
"outDir": "out",
"declaration": true,
"typeRoots": [
"node_modules/@types"
]
},
"exclude": [
"node_modules"
],
"include": [
"src"
]
}