🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

qlogger

Package Overview
Dependencies
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

qlogger

fast configurable logger and newline delimited data transport

Source
npmnpm
Version
1.3.11
Version published
Weekly downloads
856
7033.33%
Maintainers
1
Weekly downloads
 
Created
Source

qlogger

quick nodejs logging and newline delimited data transport

QLogger is a toolkit for building very fast loggers. It can be used out of the box as-is, or it can be reassembled in new ways for custom loggers. It's lean, fast, flexible, and easy to use.

It can log in any format, eg space-separated text or json bundles. The formatters and writers are pluggable, use one of the defaults or use your own.

How fast? On my system I get 650k 200 byte lines per second saved to a shared logfile under LOCK_EX mutex (using qfputs as the writer and logging only 2 lines per continuable; 1.1m per sec if logging 5 lines per continuable).

A slow logger can report on the data being processed. A fast logger is a data streaming engine, and can itself process data.

    var QLogger = require('qlogger');
    var logger = new QLogger('info', 'file:///var/log/myapp/app.log');
    logger.addFilter(require('qlogger/filters').filterBasic);

Installation

    npm install qlogger

Unit test:

    npm test qlogger

Speed test (log 100k timestamped 200 byte lines):

    node node_modules/qlogger/benchmark.js

Structure

QLogger sends newline-delimited strings (messages) to writers. The strings may be edited in flight by filters. Filters return the modified string, and can annotate it with timestamp, loglevel, hostname, etc., or serialize objects for export. Writers deliver the strings to their detaination.

Writers can be added to write to file, send over TCP/IP, send to syslog, etc. The strings can be modified in flight by filters, which will write the altered string. Common filters would be to add a timestap and the message loglevel. Writers and filters must be configured explicitly, there is no default.

QLogger exports a simplified subset of the traditional logging methods: error, info, and debug. Each log message is appended to the logfile as a newline terminated string.

Newline delimited text is a universally compatible, easy-to-parse and very very fast way to stream and process data.

Points to keep in mind when using logfiles for general-purpose data transport:

  • the logfile might have multiple writers and unix writes are not atomic, ie writes need a mutex (write-write mutex, eg flock(LOCK_EX))
  • the logfile might be consumed by simple readers that do not tolerate partial writes, so each write should be a complete newline terminated message (ie, hold the lock for the duration of the write)
  • the reader might itself modify the logfile (eg compact it), so writes need a mutex (read-write mutex, eg flock(LOCK_EX))
  • the logfile might get consumed (renamed or removed), ie cannot reuse the file handle indefinitely, must reopen the file periodically
  • the logfile could be used for low-latency buffering, so the reopen interval should be pretty short (all consumers of the logfile must wait out the reopen interval to ensure that activity has settled before moving on from the file)

Examples

Log to stdout, formatting the log lines with the basic plaintext filter. Step by step:

    QLogger = require('qlogger');
    logger = new QLogger();
    logger.loglevel('info');
    logger.addWriter(process.stdout);
    filterBasic = require('qlogger/filters').filterBasic;
    logger.addFilter(filterBasic);

And then

    logger.info("Hello, world.");
    // => 2014-11-22 15:03:38.482 [info] Hello, world.
    logger.debug("debug messages not on");
    // =>
    logger.error("Hello again.");
    // => 2014-11-22 15:03:38.483 [error] Hello again.

Same as above, but more succinctly:

    logger = require('qlogger')('info', process.stdout);
    logger.addFilter(require('qlogger/filters').filterBasic);

Log to file using a write stream, formatting the log lines with a quick inline function (note: this is just as an example, file write streams are too slow to use where speed matters):

    fs = require('fs');
    QLogger = require('qlogger');
    logger = new QLogger('info', fs.createWriteStream('app.log', 'a'));
    logger.addFilter(function(msg, level) {
        return new Date().toISOString + " " + msg;
    });

Log to file using a qfputs FileWriter, without any additional formatting. This can stream over 100MB/sec of data one line at a time to a mutex-controlled shared logfile.

    QLogger = require('qlogger');
    logger = new QLogger('info', QLogger.createWriter('file://app.log', 'a'));

Methods

new QLogger( [loglevel], [writer] )

Create a logger that will log messages of importance loglevel or above. It is an error if the loglevel is not recognized.

Loglevel can be specified as a string 'error', 'info' or 'debug'. If omitted, it defaults to 'info'. Internally, they are converted to the standard unix syslog loglevels 3, 6 and 7. The higher syslog logging levels (emerg, alert, and crit) and warning and notice were deliberately omitted, leaving just the three essential message classes: human attention required, useful statistics, and everything available for debugging.

The optional writer may be a writerObject (see addWriter below), or a writer specification string. The latter will create one of the built-in writers using QLogger.createWriter(). If no writerSpec is given, the logger will be created without a writer. It is an error if the writer specification is not recognized. The built-in writers are those supported by createWriter().

QLogger.createWriter( writerSpec )

This class method will create a new writer corresponding to the spec. The recognized writer specifications are:

    file://</path/to/file>          // absolute filepath
    file://<file/name>              // relative filename
    stdout://                       // process.stdout
    stderr://                       // process.stderr
    tcp://<host>:<port>             // net.connect() tcp connection
    udp://<host>:<port>             // datagram

Logging Methods

log( message [, ...] )

Log a multi-argument message. The message is passed to the filters using the current loglevel (so a logger that has loglevel 'debug' will write log() messages as if they were from debug()). Multiple arguments are gathered into an array, and the filter is expected to convert them to a string. (This last allows printf-like formatted output).

error( message )

Log an error message. Error messages will be logged by all loglevels, 'error', 'info' and 'debug'.

info( message )

Log an informational message. The logger must have loglevel 'info' or 'debug'.

debug( message )

Log a debug message. The logger must have loglevel of 'debug'.

fflush( callback )

Tries to force all writers to write out any buffered data. Invokes the callback once the writes have all finished. This is a half-hearted implementation, since fflush can only flush write streams, tcp sockets and FileWriter (file://) objects.

loglevel( [newLoglevel] )

returns the current loglevel. The loglevel controls the log sensitivity; a loglevel of 'info' would write info() and error() messages but not debug(). If a new loglevel is specified, the logger will change the loglevel and returns the old loglevel.

Configuration Methods

addWriter( writerObject )

Have the logger write log messages with the writer object. The writerObject must have a method write( string, callback ). The writer will be called with the already formatted log line. Multiple writers are supported. Writers are run in the order added, but are not serialized, and writers may complete out of order.

addFilter( filterFunction( message, loglevel ) )

A filter modifies the log message before writing it, and returns the filtered string. A final built-in filter makes sure that the string ends in a newline. Filters are applied in the order they were added.

By using filters it is possible to daisy-chain or fan out loggers to have messages be observed by multiple loggers or logged by multiple agents. The very first filter added sees the raw unfiltered message.

    QLogger = require('qlogger');
    logger = new QLogger('info', process.stdout);
    logger.addFilter(
        function(msg, loglevel) {
            return new Date().toISOString() + " [" + QLogger.LEVELNAMES[loglevel]+ "] " + msg;
        }
    );
    logger.info("Hello, world.");
    logger.error("Done.");
    logger.debug("debug messages not enabled");
    // => 2014-10-18T12:34:56.667Z [info] Hello, world.
    // => 2014-10-18T12:34:56.668Z [error] Done.
    // => 

Two very simple filters are included; each adds a timestamp and the loglevel. filterBasic() produces a plaintext logline, the JsonFilter a json bundle with fields "time", "level" and "message". The json filter can log text or objects, and can merge fields from a static template object into each logline. The standard fields "time", "level" and "message" in the template object are overwritten with the run-time values; this can be used to control the order of the fields in the output.

Built-In Filters

filterBasic = require('qlogger/filters').filterBasic

filterBasic() produces a plaintext logline with a human-readable timestamp and the logelevel.

    var filterBasic = require('qlogger/filters').filterBasic;
    logger.addFilter(filterBasic);
    logger.info("Hello, world.")
    // 2014-10-19 01:23:45.678 [info] Hello, world.

filterJson = require('qlogger/filters').JsonFilter.makeFilter( opts )

filterJson() logs a stringified json bundle that will always have fields "time", "level" and "message". The time is a millisecond timestamp. Other fields are copied from the message object being logged (unless a string). filterJson is constructed by the JsonFilter class.

The json filter can merge fields from a static template into each logline. The logged bundle fields will contain the template fields, the standard fields, then all other fields on the logged object, in that order. The template can be used to add static info to each logline (e.g. host, version) and to control the order of the fields in the output.

The standard fields "time", "level" and "message" (and "error" if logging an Error object), are replaced with run-time values. If the message itself contains time, level or message, the fields from the message will be the ones output.

    var JsonFilter = require('qlogger/filters').JsonFilter;
    var loglineTemplate = {
        // the template defines the basic set of fields to log
        // and the order they will appear in.  If logging objects,
        // any additional fields from the object will be appended.
        // If logging Error objects, the message will be set to
        // the error message, and error:{code:, message:, stack:}
        // will be copied from the Error object.
        time: 'provide',
        level: 'provide',
        custom1: 123,
        message: 'will provide'
    };
    filterJson = JsonFilter.makeFilter(loglineTemplate);
    logger.addFilter(filterJson);
    logger.info("Hello, world.");
    // {"time":1414627805981,"level":"info","custom1":123,"message":"Hello, world."}
    logger.info(new Error("oops"));

The json encoding function to use can be specified in opts.encode. The default is JSON.stringify, but for simple json logging json-simple is 2x faster.

Timestamp formatting

QLogger exports the simple timestamp formatter used by filterBasic. It takes a millisecond precision timestamp as returned by Date.now(), and formats an SQL-type ISO 9075 datetime string (YYYY-mm-dd HH:ii:ss, whole seconds, no timezone). It's much faster than Date.toISOString, and much much faster than general-purpose timestamp formatters like moment or phpdate.

filterBasic appends the milliseconds to the formatted timestamp separately, to save having to repeatedly format the same time during busts. Something like

    now = Date.now();
    msec = now % 1000;
    str = formatIsoDate(now - msec);
    str += "." + (msec >= 100 ? msec : msec >= 10 ? "0" + msec : "00" + msec);

Note that although formatting the timestamp takes only .5 microseconds, logging a line to a file itself is just 1.5 microseconds (per line, average). Timing it, reusing a formatted timestamp results in 28% faster throughput.

formatIsoDate( timestamp )

    var formatIsoDate = require('qlogger/filters').formatIsoDate;
    var timestamp = Date.now();
    // => 1414627805981
    var time = formatIsoDate(timestamp);
    // => 2014-10-29 20:10:05

formatIsoDateUtc( timestamp )

    var formatIsoDateUTC = require('qlogger/filters').formatIsoDateUtc;
    var time = formatIsoDate(1414627805981);
    // => 2014-10-30 00:10:05

For pure streaming line-oriented data transport, see qfputs for high-speed batched fputs(), and qfgets for batched fgets().

TODO

  • only insert time/level/message into json logs if specified in template (ie, if using the default template or present in the user-supplied template) Omit them if not present in the supplied template.
  • maybe log to process.stdout by default instead of not writing?
  • make JsonFilter time use a function specified in the template

Keywords

Andras

FAQs

Package last updated on 01 Mar 2016

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts