meta2-logger
TypeScript icon, indicating that this package has built-in type declarations

2.1.1 • Public • Published

meta2-logger

Simple logging library for NodeJS with support of facilities and multiple transports.

Logger has also first-class support for TypeScript and is fully documented.

Table of Contents:

Features

Built-in transports:

  • Console
  • Text file
  • JSON file
  • Memory
  • GrayLog

Log levels:

Library supports all of standard syslog levels.

  • debug
  • info
  • notice
  • warn
  • error
  • critical
  • alert
  • emergency

Other features:

  • Facilities
  • Meta-data
  • Custom targets
  • Colorized console output
  • TypeScript decorators to simply enable logging on classes and methods
  • Remote management server with UI and REST API provided by meta2-logger-server package

Installation

# Using NPM 
npm install meta2-logger
 
# To save as dependency 
npm install --save meta2-logger

Usage (TypeScript)

import {Logger, LOG_LEVEL, default as logger} from "meta2-logger";
 
// Use global logger
logger.info("From global logger");
 
// Or create new instance
const myLogger = new Logger({
    level: LOG_LEVEL.DEBUG
});
 
// Setup targets
logger.toConsole({
    level: LOG_LEVEL.INFO,
    timestamp: true,
    colorize: true
}).toFile("demo.log", {
    level: LOG_LEVEL.WARN,
    timestamp: true,
    facilities: [ "test" ]
}).toJsonFile("demo.json", {
    level: LOG_LEVEL.ERROR
}).toGrayLog({
    level: LOG_LEVEL.DEBUG,
    graylogHostname: "localhost"
    host"myApp"
});
 
// Log some messages
logger.debug("Hello %s", "debug");
logger.info("Hello %s", "info");
logger.notice("Hello %s", "notice");
logger.warn("Hello %s", "warn"); // or logger.warning("Hello %s", "warn");
logger.error("Hello %s", "error");
logger.crit("Hello %s", "critical");
logger.alert("Hello %s", "alert");
logger.emerg("Hello %s", "emergency"); // or logger.panic("Hello %s", "emergency");
 
// Create facility
const facility = logger.facility("http", {
    level: LOG_LEVEL.INFO
});
 
facility.notice("Server started on port %d", 8080);
 
// Passing meta-data using object as first argument
facility.warn({ reqId: 123 }, "Bad request");

Usage (vanilla JS)

const Logger = require("meta2-logger");
 
// Accessing global logger instance
const logger = Logger.default;
 
// Or create new instance
const logger = new Logger.Logger();
 
// Setup targets
logger.toConsole({
    level: Logger.LOG_LEVEL.INFO,
    timestamp: true,
    colorize: true
}).toFile("demo.log", {
    level: Logger.LOG_LEVEL.WARN,
    timestamp: true,
    facilities: [ "test" ]
}).toJsonFile("demo.json", {
    level: Logger.LOG_LEVEL.ERROR
}).toGrayLog({
    level: Logger.LOG_LEVEL.DEBUG,
    graylogHostname: "localhost"
    host: "myApp"
});
 
// Log some messages
logger.debug("Hello %s", "debug");
logger.info("Hello %s", "info");
logger.notice("Hello %s", "notice");
logger.warn("Hello %s", "warn"); // or logger.warning("Hello %s", "warn");
logger.error("Hello %s", "error");
logger.crit("Hello %s", "critical");
logger.alert("Hello %s", "alert");
logger.emerg("Hello %s", "emergency"); // or logger.panic("Hello %s", "emergency");
 
// Create facility
const facility = logger.facility("http");
 
facility.notice("Server started on port %d", 8080);
 
// Passing meta-data using object as first argument
facility.warn({ reqId: 123 }, "Bad request");

Facilities

Logging facility provides a way to decouple logging based on their purpose.

Creating facility from logger:

import {default as logger, LOG_LEVEL} from "meta2-logger";
 
// Create facility from main logger - options are optional
const facility = logger.facility("facilityName", {
    level: LOG_LEVEL.INFO
});
 
// We can change facility log level later
facility.setLevel(LOG_LEVEL.NOTICE);
facility.getLevel(); // -> LOG_LEVEL.NOTICE
 
// Log to facility
facility.info("Message");
 
// We can get existing facility instance by calling facility method again
const facilityAgain = logger.facility("facilityName");
 
// We can get map of all registered facilities
logger.getAllFacilities(); // -> { facilityName: LoggerFacility }

Creating facility manually:

import {default as logger, LOG_LEVEL, LoggerFacility} from "meta2-logger";
 
// Create facility and pass logger as first argument, options are optional
const facility = new LoggerFacility(logger, "facilityName", {
    level: LOG_LEVEL.INFO
});
 
// Log to facility
facility.info("Message");

Setting log level

Log levels can be set on logger, facility and on targets.

Classes Logger, LoggerFacility and logging targets support setLevel and getLevel methods.

Example:

import {default as logger, LOG_LEVEL} from "meta2-logger";
 
logger.setLevel(LOG_LEVEL.INFO);
 
logger.debug("Message"); //Does not log
logger.warn("Message"); //Does log
 
logger.getLevel(); //Returns LOG_LEVEL.INFO;
 
// Changing log level on target(s)
logger.toConsole({
    level: LOG_LEVEL.NOTICE
});
 
logger.getTarget("__console__").setLevel(LOG_LEVEL.DEBUG);
logger.getTarget("__console__").getLevel(); // -> LOG_LEVEL.DEBUG
 
// Also works on LoggerFacility
const facility = logger.facility("http");
 
facility.setLevel(LOG_LEVEL.WARN);
facility.getLevel(); // -> LOG_LEVEL.WARN
 

Configuring Logging Targets

Logging target represents destination transport for log messages.

import {default as logger, ConsoleTarget, LOG_LEVEL} from "meta2-logger";
 
logger.to("uniqueLoggerId", new ConsoleTarget({
    level: Logger.LOG_LEVEL.INFO,
    timestamp: true,
    colorize: true
}));

See built-in targets below. Logging target class must implement ILoggerTarget interface.

Console Target

Prints log messages to stdout (console).

import {default as logger, ConsoleTarget, LOG_LEVEL} from "meta2-logger";
 
logger.toConsole({
    // Log level
    level: LOG_LEVEL.DEBUG,
 
    // Facilities - null to accept all facilities
    facilities: [ "http", "broker", "etc" ],
 
    // If to print time and date
    timestamp: true,
 
    // If to print with colors
    colorize: true
});
 
// Or
logger.to("myConsole", new ConsoleTarget(opts));

Sample output:

2018-01-10 14:24:30 warn: [http] (reqId=123) Bad request
2018-01-10 14:24:30 info: Something happend

eg.:
date time level: [facility] (meta=data) Formatted message

Notice: Method toConsole overrides previous console target settings. Use logger.to(...) method to define more targets.

File Target

Appends log messages to specified file.

Messages are formatted the same way as to the console.

File target can be set for multiple files with different configurations.

import {default as logger, FileTarget, LOG_LEVEL} from "meta2-logger";
 
logger.toFile("filename.log", {
    // Log level
    level: LOG_LEVEL.DEBUG,
 
    // Facilities - null to accept all facilities
    facilities: [ "http", "broker", "etc" ],
 
    // If to print time and date
    timestamp: true,
});
 
// Or
logger.to("myFile", new FileTarget("filename.log", opts));

Sample output:

2018-01-10 14:24:30 warn: [http] (reqId=123) Bad request
2018-01-10 14:24:30 info: Something happend

JSON File Target

Appends log messages to specified JSON file.

File target can be set for multiple files with different configurations.

import {default as logger, JsonFileTarget, LOG_LEVEL} from "meta2-logger";
 
logger.toJsonFile("filename.json", {
    // Log level
    level: LOG_LEVEL.DEBUG,
 
    // Facilities - null to accept all facilities
    facilities: [ "http", "broker", "etc" ]
});
 
// Or
logger.to("myJsonFile", new JsonFileTarget("filename.json", opts));

Sample output:

{},
{ timestamp: 1515557050.342, level: 5, facility: "http", msg: "Bad request", meta: { reqId: 123 } },
{ timestamp: 1515557086.342, level: 7, facility: null, msg: "Something happend", meta: {} }

Recommended way to parse JSON log file:

const fs = require("fs");
 
const logFile = fs.readFileSync("filename.json", { encoding: "utf-8" });
const logMessages = JSON.parse("[" + logFile + "]").slice(1);

Memory Target

Stores log messages in memory.

import {default as logger, MemoryTarget, LOG_LEVEL} from "meta2-logger";
 
logger.toMemory({
    // Log level
    level: LOG_LEVEL.DEBUG,
 
    // Facilities - null to accept all facilities
    facilities: [ "http", "broker", "etc" ],
 
    // How many messages to store, default 1000
    limit: 1000
});
 
// Get messages
logger.getTarget("__memory__").getMessages();
 
// Or
const memTarget = new MemoryTarget(opts);
 
logger.to("myMemory", memTarget);
 
memTarget.getMessages();

GrayLog Target

Sends log messages to GrayLog server using GELF protocol.

import {default as logger, GraylogTarget, LOG_LEVEL} from "meta2-logger";
 
logger.toGrayLog({
    // Log level
    level: LOG_LEVEL.DEBUG,
 
    // Facilities - null to accept all facilities
    facilities: [ "http", "broker", "etc" ],
 
    // GrayLog server port
    graylogPort: 12201,
 
    // GrayLog server hostname
    graylogHostname: "localhost",
 
    // Connection type, 'lan' or 'wan'
    connection: "lan",
 
    // Max chunk size for WAN type
    maxChunkSizeWan: 1420,
 
    // Max chunk size for LAN type
    maxChunkSizeLan: 8154,
 
    // Host (application) identifier
    host: "_unspecified_",
 
    // GELF protocol version
    version: "1.0",
 
    // Facility prefix string - is added before facility name
    facilityPrefix: "",
 
    // If to log gelf client debug messages to stdout
    debugGelfClient: false,
 
    // Additional static message fields
    additionalFields: { "myfield": "myValue" }
});
 
// Or
logger.to("myGrayLogTarget", new GraylogTarget(opts));

Notice: Message meta-data are added as additional fields.

Custom Logging Target

To create custom logging target define class which implements ILoggerTarget interface. Or extend class BaseTarget.

import * as util from "util";
 
import {
    LOG_LEVEL, ILoggerTarget, ILoggerMetaData, BaseTarget, IBaseTargetOptions,
    default as logger
} from "./interfaces";
 
export interface ISuchTargetOptions extends IBaseTargetOptions {
    soFunny?: boolean;
}
 
export class SuchTarget extends BaseTarget {
 
    protected soFunny: boolean:
 
    public constructor(options: ISuchTargetOptions) {
 
        super(options);
 
        this.soFunny = options.soFunny || false;
 
    }
 
    /**
     * Log message
     *
     * @param level Log level
     * @param facility Facility
     * @param args Message arguments
     * @param meta Meta-data
     */
    public log(level: LOG_LEVEL, facility: string, args: any, meta: ILoggerMetaData) {
 
        // Check such level
        if (level > this.level) return;
        if (this.facilities.length > 0 && this.facilities.indexOf(facility) < 0) return;
 
        // Format wow message
        const wowMessage = util.format.apply(this, args);
 
        // Create message parts
        const messageParts = [
            "Wow", Date.now(),
            "such", this.levelLabels[LOG_LEVEL.DEBUG].toUpperCase(),
            "many", facility,
            wowMessage,
            "plz",
            JSON.stringify(meta)
        ];
 
        if(this.soFunny)
            messageParts.push("so funny");
 
        // Write message
        this.write(level, facility, messageParts, meta);
 
    }
 
    /**
     * Write formatted log message
     *
     * @param level Log level
     * @param facility Facility
     * @param msg Formated message parts
     * @param meta Meta-data
     */
    protected write(level: LOG_LEVEL, facility: string, message: Array<string>, meta: ILoggerMetaData) {
 
        console.log("So scare", message.join(" "));
 
    }
 
}
 
// Use such target
logger.to("suchTarget", new SuchTarget({
    level: LOG_LEVEL.DEBUG,
    soFunny: true
}));

Utility Functions

parseLogLevel

This function parses log level from string to number. Is case insensitive.

Usage:

import {default as logger, parseLogLevel} from "meta2-logger";
 
logger.toConsole({
    level: parseLogLevel("criTIcal")
});

@Logging decorator

Decorator to configure and assign LoggerFacility instance to a class.

Note: decorators are experimental TypeScript feature.

Usage:

import {Logger, Logging, LoggerFacility, LOG_LEVEL} from "meta2-logger";
 
const myLogger = new Logger();
 
// Second argument can be omitted
@Logging("facilityName", {
    logger: myLogger, // When omitted default logger will be used
    level: LOG_LEVEL.DEBUG
})
class MyClass {
 
    public log: LoggerFacility;
 
    public doSomething(){
 
        this.log.info("Hello");
 
    }
 
}
 
const obj = new MyClass();
obj.doSomething(); // -> will log debug: [facilityName] Hello

@LogMethodCall decorator

Decorator to log every method call. When @Logging decorator is applied it's configuration will be used.

Note: decorators are experimental TypeScript feature.

Usage:

import {Logger, Logging, LogMethodCall, LoggerFacility, LOG_LEVEL} from "meta2-logger";
 
const myLogger = new Logger();
 
// Second argument can be omitted
@Logging("facilityName", {
    logger: myLogger, // When omitted default logger will be used
    level: LOG_LEVEL.DEBUG
})
class MyClass {
 
    public log: LoggerFacility;
 
    /*
     * Decorator arguments (all are optional):
     * - LOG_LEVEL
     * - If to capture arguments
     * - Message prefix
     */
    @LogMethodCall(LOG_LEVEL.DEBUG, true, "Hey")
    public doSomething(...args){
        return true;
    }
 
}
 
const obj = new MyClass();
obj.doSomething("hello", "world");

will log:

debug: [facilityName] (method=doSomething) (class=MyClass) Hey Method MyClass.doSomething called with arguments [ 'hello', 'world' ]
    at class_1.descriptor.value [as doSomething] (/path/to/app/node_modules/meta2-logger/dist/src/util.ts:90:38)
    at Logger.info (/path/to/app/node_modules/meta2-logger/dist/src/Logger.js:286:14)
    at Object.<anonymous> (/path/to/app/index.js:30:24)
    at Module._compile (module.js:571:32)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)
    at Function.Module._load (module.js:439:3)
    at Module.runMain (module.js:605:10)

Logging Stack Trace (experimental)

Logger has built-in feature to capture stack trace for every log message. If enabled call stack will be available as trace meta value. Note that internal logger function calls are excluded.

Warning: Capturing of stack traces has a significant impact on performance and should be used only for temporary debugging.

This feature is currently experimental.

Also, note that this feature does not affect possibility to log stack trace by passing an error object as an argument to a log method - eg ... catch(err){ logger.error("Operation failed:", err); } will work always.

Following example:

import {default as logger} from "meta2-logger";
 
logger.enableTrace();
logger.toConsole();
 
logger.info("Hello!");
 
// To turn it off
logger.enableTrace(false);

will print to console:

info: hello
  >>
    at Logger.info (/path/to/app/node_modules/meta2-logger/dist/src/Logger.js:286:14)
    at Object.<anonymous> (/path/to/app/index.js:30:24)
    at Module._compile (module.js:571:32)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)
    at Function.Module._load (module.js:439:3)
    at Module.runMain (module.js:605:10)

API Reference

class Logger {
    getLevel(): LOG_LEVEL;
    setLevel(level: LOG_LEVEL);
    log(level: LOG_LEBEL, ...args): void;
    debug(...args): void;
    info(...args): void;
    notice(...args): void;
    warn(...args): void;
    warning(...args): void;
    error(...args): void;
    crit(...args): void;
    alert(...args): void;
    emerg(...args): void;
    panic(...args): void;
    facility(name: string): LoggerFacility;
    getAllFacilities(): { [K: string]: LoggerFacility };
    to(id: string, target: ILoggerTarget): Logger;
    getAllTargets(): { [K: string]: ILoggerTarget };
    getTarget(id: string): ILoggerTarget|null;
    toConsole(options: IConsoleTargetOptions = {}): Logger;
    toFile(filename: string, options: IFileTargetOptions = {}): Logger;
    toJsonFile(filename: string, options: IFileTargetOptions = {}): Logger;
    toGrayLog(options: IGraylogTargetOptions): Logger;
    enableTrace(enabled: boolean);
    isTraceEnabled(): boolean;
 
    // Close all I/O and socket handles
    close();
}
 
class LoggerFacility {
    constructor(protected logger: Logger, protected prefix: string);
    getLevel(): LOG_LEVEL;
    setLevel(level: LOG_LEVEL);
    debug(...args): void;
    info(...args): void;
    notice(...args): void;
    warn(...args): void;
    warning(...args): void;
    error(...args): void;
    crit(...args): void;
    alert(...args): void;
    emerg(...args): void;
    panic(...args): void;
}
 
enum LOG_LEVEL {
    DEBUG = 7,
    INFO = 6,
    NOTICE = 5,
    WARN = 4,
    ERROR = 3,
    CRITICAL = 2,
    ALERT = 1,
    EMERGENCY = 0
}
 
interface ILogger {
    log(...args);
    debug(...args);
    info(...args);
    notice(...args);
    warn(...args);
    warning(...args);
    error(...args);
    crit(...args);
    alert(...args);
    emerg(...args);
    panic(...args);
    getLevel(): LOG_LEVEL;
    setLevel(level: LOG_LEVEL);
}
 
interface ILoggerMetaData {
    [ K: string ]: string|number|boolean|Date;
    [ K: number ]: string|number|boolean|Date;
}
 
interface ILoggerTarget {
    log: (level: LOG_LEVEL, facility: string, args: Array<any>, meta: ILoggerMetaData) => void;
    close: () => void;
    getLevel(): LOG_LEVEL;
    setLevel(level: LOG_LEVEL);
}
 
function parseLogLevel(level: string): LOG_LEVEL;

Development

# Install dependencies 
npm install
 
# Transpile TypeScript 
npm run build
 
# Run linter 
npm run lint
 
# Run tests 
npm test

License

This library is published under MIT license.

Copyright (c) 2017 - 2018 Jiri Hybek, jiri@hybek.cz

Dependents (1)

Package Sidebar

Install

npm i meta2-logger

Weekly Downloads

22

Version

2.1.1

License

MIT

Unpacked Size

120 kB

Total Files

36

Last publish

Collaborators

  • metaplatform