bunyan-logstash-tcp
Version:
Logstash TCP plugin for Bunyan
253 lines (216 loc) • 6.27 kB
JavaScript
'use strict';
var bunyan = require('bunyan');
var net = require('net');
var fs = require('fs');
var os = require('os');
var tls = require('tls');
var CBuffer = require('CBuffer');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var levels = new Map([[10, 'trace'], [20, 'debug'], [30, 'info'], [40, 'warn'], [50, 'error'], [60, 'fatal']]);
/**
* Creates a new instance of LogstashStream from the options.
*
* @param {objects} options The constructions options. See the constructor for details.
* TODO: Improve this doc.
*
* @returns {LogstashStream} The bunyan stream that sends data to logstash
*/
function createLogstashStream(options) {
return new LogstashStream(options);
}
/**
* This class implements the bunyan stream contract with a stream that
* sends data to logstash.
*
* @param {objects} options The constructions options. See the constructor for details.
* TODO: Improve this doc.
*
* @constructor
*/
function LogstashStream(options) {
EventEmitter.call(this);
options = options || {};
this.name = 'bunyan';
this.level = options.level || 'info';
this.server = options.server || os.hostname();
this.host = options.host || '127.0.0.1';
this.port = options.port || 9999;
this.application = options.appName || process.title;
this.pid = options.pid || process.pid;
this.tags = options.tags || ['bunyan'];
this.type = options.type;
this.client = null;
// ssl
this.ssl_enable = options.ssl_enable || false;
this.ssl_key = options.ssl_key || '';
this.ssl_cert = options.ssl_cert || '';
this.ca = options.ca || '';
this.ssl_passphrase = options.ssl_passphrase || '';
this.cbuffer_size = options.cbuffer_size || 10;
// Connection state
this.log_queue = new CBuffer(this.cbuffer_size);
this.connected = false;
this.socket = null;
this.retries = -1;
this.max_connect_retries = typeof options.max_connect_retries === 'number' ? options.max_connect_retries : 4;
this.retry_interval = options.retry_interval || 100;
this.connect();
}
util.inherits(LogstashStream, EventEmitter);
/**
* Writes a log entry to the steam.
*
* @param {object} entry The entry to write.
* @returns {void}
*/
LogstashStream.prototype.write = function logstashWrite(entry) {
var level = void 0;
if (typeof entry === 'string') {
entry = JSON.parse(entry);
}
var rec = Object.assign({}, entry);
level = rec.level;
if (levels.has(level)) {
level = levels.get(level);
}
var msg = {
'@timestamp': new Date(rec.time).toISOString(),
message: rec.msg,
tags: this.tags,
source: this.server + '/' + this.application,
level: level
};
if (typeof this.type === 'string') {
msg.type = this.type;
}
delete rec.time;
delete rec.msg;
// Remove internal bunyan fields that won't mean anything outside of
// a bunyan context.
delete rec.v;
delete rec.level;
rec.pid = this.pid;
this.send(JSON.stringify(Object.assign({}, msg, rec), bunyan.safeCycles()));
};
/**
* Connects the stream to the remote logstash server specified in the options.
*
* @returns {void}
*/
LogstashStream.prototype.connect = function connect() {
var options = {};
var self = this;
this.retries += 1;
this.connecting = true;
if (this.ssl_enable) {
options = {
key: this.ssl_key ? fs.readFileSync(this.ssl_key) : null,
cert: this.ssl_cert ? fs.readFileSync(this.ssl_cert) : null,
passphrase: this.ssl_passphrase ? this.ssl_passphrase : null,
ca: this.ca ? this.ca.map(function (filePath) {
return fs.readFileSync(filePath);
}) : null
};
this.socket = tls.connect(this.port, this.host, options, function () {
self.socket.setEncoding('UTF-8');
self.announce();
self.connecting = false;
});
} else {
this.socket = new net.Socket();
}
this.socket.unref();
this.socket.on('error', function (err) {
self.connecting = false;
self.connected = false;
self.socket.destroy();
self.socket = null;
self.emit('error', err);
});
this.socket.on('timeout', function () {
if (self.socket.readyState !== 'open') {
self.socket.destroy();
}
self.emit('timeout');
});
this.socket.on('connect', function () {
self.retries = 0;
self.emit('connect');
});
this.socket.on('close', function () {
self.connected = false;
if (self.max_connect_retries < 0 || self.retries < self.max_connect_retries) {
if (!self.connecting) {
setTimeout(function () {
self.connect();
}, self.retry_interval).unref();
}
} else {
self.log_queue = new CBuffer(self.cbuffer_size);
self.silent = true;
}
self.emit('close');
});
if (!this.ssl_enable) {
this.socket.connect(self.port, self.host, function () {
self.announce();
self.connecting = false;
});
}
};
/**
* Announces that the stream is connected. Will flush any messages in the queue.
*
* @returns {void}
*/
LogstashStream.prototype.announce = function announce() {
var self = this;
self.connected = true;
self.flush();
};
/**
* Flushes the queue, sending all messages that have not been sent yet to the remote
* destination.
*
* @returns {void}
*/
LogstashStream.prototype.flush = function flush() {
var self = this;
var message = self.log_queue.pop();
while (message) {
self.sendLog(message.message);
message = self.log_queue.pop();
}
self.log_queue.empty();
};
/**
* Immediately writes a string to the undelying socket.
*
* @param {string} message The string to write.
* @returns {void}
*/
LogstashStream.prototype.sendLog = function sendLog(message) {
this.socket.write(message + '\n');
};
/**
* Sends a string message. The message will be immediately sent if the stream
* is already connected, or queued if the stream is not connected yet.
* @param {string} message The string to send
* @returns {void}
*/
LogstashStream.prototype.send = function logstashSend(message) {
var self = this;
// send tcp logs
if (!self.connected) {
self.log_queue.push({
message: message
});
} else {
self.sendLog(message);
}
};
module.exports = {
createStream: createLogstashStream,
LogstashStream: LogstashStream
};