Source: index.js

/*
 * Copyright 2015 Splunk, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"): you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

var Stream = require("stream").Writable;
var util = require("util");

var SplunkLogging = require("splunk-logging");
var SplunkLogger = SplunkLogging.Logger;

/**
 * A class that implements a raw writable stream.
 *
 * @property {object} config - Configuration settings for this <code>SplunkStream</code> instance.
 * @property {object[]} contextQueue - Queue of <code>context</code> objects to be sent to Splunk.
 * @property {function} error - A callback function for errors: <code>function(err, context)</code>.
 * Defaults to <code>console.log</code> both values;
 *
 * @param {object} config - Configuration settings for a new [SplunkLogger]{@link SplunkLogger}.
 * @param {string} config.token - HTTP Event Collector token, required.
 * @param {string} [config.name=splunk-javascript-logging/0.9.3] - Name for this logger.
 * @param {string} [config.host=localhost] - Hostname or IP address of Splunk server.
 * @param {string} [config.maxRetries=0] - How many times to retry when HTTP POST to Splunk fails.
 * @param {string} [config.path=/services/collector/event/1.0] - URL path to send data to on the Splunk server.
 * @param {string} [config.protocol=https] - Protocol used to communicate with the Splunk server, <code>http</code> or <code>https</code>.
 * @param {number} [config.port=8088] - HTTP Event Collector port on the Splunk server.
 * @param {string} [config.url] - URL string to pass to {@link https://nodejs.org/api/url.html#url_url_parsing|url.parse}. This will try to set
 * <code>host</code>, <code>path</code>, <code>protocol</code>, <code>port</code>, <code>url</code>. Any of these values will be overwritten if 
 * the corresponding property is set on <code>config</code>.
 * @param {string} [config.level=info] - Logging level to use, will show up as the <code>severity</code> field of an event, see
 *  [SplunkLogger.levels]{@link SplunkLogger#levels} for common levels.
 * @param {number} [config.batchInterval=0] - Automatically flush events after this many milliseconds.
 * When set to a non-positive value, events will be sent one by one. This setting is ignored when non-positive.
 * @param {number} [config.maxBatchSize=0] - Automatically flush events after the size of queued
 * events exceeds this many bytes. This setting is ignored when non-positive.
 * @param {number} [config.maxBatchCount=1] - Automatically flush events after this many
 * events have been queued. Defaults to flush immediately on sending an event. This setting is ignored when non-positive.
 * @constructor
 * @implements {@link https://nodejs.org/api/stream.html#stream_class_stream_writable|Stream.Writable}
 */
var SplunkStream = function (config) {
    /** @type {SplunkLogger} */
    this.logger = new SplunkLogger(config);

    // If using the common logger's default name, change it
    if (this.logger.config.name.match("splunk-javascript-logging/\\d\\.\\d\\.\\d")) {
        this.logger.config.name = "splunk-bunyan-logger/0.9.3";
    }

    // Overwrite the common library's error callback
    var that = this;
    this.logger.error = function(err, context) {
        that.emit("error", err, context);
    };

    /* jshint unused:false */
    /**
     * A callback function called after sending a request to Splunk:
     *  <code>function(err, response, body)</code>. Defaults
     * to an empty function.
     *
     * @type {function}
     */
    this.send = function(err, resp, body) {};
};
util.inherits(SplunkStream, Stream);

/**
 * Returns the configuration for this logger.
 * See {@link SplunkStream}.
 *
 * @returns {Object} Configuration for this logger.
 * @public
 */
SplunkStream.prototype.config = function() {
    return this.logger.config;
};

/**
 * The <code>write()</code> function for <code>SplunkStream</code>.
 *
 * Bunyan will call this function when a user logs a message.
 * See [Bunyan raw streams]{@link https://github.com/trentm/node-bunyan#stream-type-raw}.
 *
 * @param {object} data - The data object is provided by Bunyan.
 * @public
 */
SplunkStream.prototype.write = function (data) {
    if (!data) {
        this.emit("error", new Error("Must pass a parameter to write."));
        return;
    }

    var context = {
        message: data,
        severity: module.exports.severityFromLevel(data.level),
        metadata: {
            time: data.time,
            host: data.hostname
        }
    };
    
    // Remove properties already added to the context
    delete context.message.level;
    delete context.message.time;
    delete context.message.hostname;

    // Clean up any existing metadata
    if (data.hasOwnProperty("host")) {
        context.metadata.host = data.host;
        delete data.host;
    }
    if (data.hasOwnProperty("source")) {
        context.metadata.source = data.source;
        delete data.source;
    }
    if (data.hasOwnProperty("sourcetype")) {
        context.metadata.sourcetype = data.sourcetype;
        delete data.sourcetype;
    }
    if (data.hasOwnProperty("index")) {
        context.metadata.index = data.index;
        delete data.index;
    }
    
    var that = this;
    this.logger.send(context, that.send);
};

/**
 * Splunk Bunyan Logger module, to be used with [Bunyan]{@link https://www.npmjs.com/package/bunyan}.
 *
 * See {@link https://github.com/splunk/splunk-bunyan-logger/tree/master/examples|examples} for usage.
 *
 * @module SplunkBunyanLogger
 * @namespace SplunkBunyanLogger
 */
module.exports =  {
    /**
     * Bunyan's logging levels.
     *
     * @default info
     * @readonly
     * @enum {string}
     * @memberof SplunkBunyanLogger
     */
    levels: {
        TRACE: "trace",
        DEBUG: "debug",
        INFO: "info",
        WARN: "warn",
        ERROR: "error",
        FATAL: "fatal"
    },
    /**
     * Translates a Bunyan logging level number to the name of the level.
     *
     * @param {number} level - A Bunyan logging level integer. See {@link SplunkBunyanLogger.levels}
     * @returns {string}
     * @memberof SplunkBunyanLogger
     */
    severityFromLevel: function (level) {
        switch(level) {
            case 10:
                return module.exports.levels.TRACE;
            case 20:
                return module.exports.levels.DEBUG;
            case 40:
                return module.exports.levels.WARN;
            case 50:
                return module.exports.levels.ERROR;
            case 60:
                return module.exports.levels.FATAL;
            default:
                return module.exports.levels.INFO;
        }
    },
    
    /**
     * Creates a Bunyan Stream object with the provided <code>config</code>.
     *
     * @param {object} config - See {@link SplunkStream}.
     * @returns {SplunkBunyanStream} A Bunyan Stream object.
     * @memberof SplunkBunyanLogger
     */
    createStream: function (config) {
        var stream =  new SplunkStream(config);
        /**
         * @typedef SplunkBunyanStream
         * @property {string} level The logging level for Bunyan.
         * @property {string} type Always <code>raw</code>.
         * @property {function} on Takes an <code>event</code> string, and a callback function.
         * The most useful event to listen for is <code>error</code>.
         * See {@link https://nodejs.org/api/events.html#events_emitter_on_event_listener|Node.js events} documentation.
         * @property {function} setEventFormatter Overrides the eventFormatter for the underlying SplunkLogger.
         * Takes a callback function parameter: <code>function(message, severity)</code>, where message
         * is an object, and severity is a string.
         * @property {function} on Adds a listener to to the SplunkStream object, typically used for the error event.
         * @property {function} flush Manually sends all queued events to Splunk in a single HTTP request.
         * Takes a callback function parameter: <code>function(err, response, body)</code>.
         * @property {SplunkStream} stream See {@link SplunkStream}
         */
        return {
            level: config.level || module.exports.levels.INFO,
            type: "raw",
            setEventFormatter: function(formatter) {
                this.stream.logger.eventFormatter = formatter;
            },
            on: function(event, callback) {
                this.stream.on(event, callback);
            },
            flush: function(callback) {
                // If flush is called with no param, use the send() callback
                callback = callback || this.stream.send;
                this.stream.logger.flush(callback);
            },
            stream: stream
        };
    }
};