winston-papertrail-transport
Version:
A Papertrail transport for Winston 3
295 lines (294 loc) • 12 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PapertrailConnection = exports.PapertrailTransport = void 0;
var tls_1 = __importDefault(require("tls"));
var os_1 = __importDefault(require("os"));
var winston_1 = __importDefault(require("winston"));
var triple_beam_1 = require("triple-beam");
var glossy_1 = require("glossy");
var winston_transport_1 = __importDefault(require("winston-transport"));
var events_1 = require("events");
var net = __importStar(require("net"));
var KEEPALIVE_INTERVAL = 15 * 1000;
if (Number(winston_1.default.version.split('.')[0]) < 3) {
throw new Error('winston-papertrail-transport requires winston >= 3.0.0');
}
var PapertrailTransport = /** @class */ (function (_super) {
__extends(PapertrailTransport, _super);
function PapertrailTransport(options) {
var _this = _super.call(this, options) || this;
var defaultOptions = {
host: 'localhost',
port: 417,
program: 'default',
facility: 'daemon',
hostname: os_1.default.hostname(),
levels: {
debug: 7,
info: 6,
notice: 5,
warning: 4,
warn: 4,
error: 3,
err: 3,
crit: 2,
alert: 1,
emerg: 0,
},
attemptsBeforeDecay: 5,
maximumAttempts: 25,
connectionDelay: 1000,
handleExceptions: false,
maxDelayBetweenReconnection: 60000,
flushOnClose: true,
disableTls: false,
};
_this.options = Object.assign({}, defaultOptions, options);
_this.connection = new PapertrailConnection(_this.options);
_this.producer = new glossy_1.Produce({
facility: _this.options.facility,
});
return _this;
}
Object.defineProperty(PapertrailTransport.prototype, "name", {
get: function () {
return 'papertrail';
},
enumerable: false,
configurable: true
});
PapertrailTransport.prototype.log = function (info, callback) {
var _this = this;
setImmediate(function () {
_this.emit('logged', info);
});
var _a = info, _b = triple_beam_1.LEVEL, level = _a[_b], _c = triple_beam_1.MESSAGE, output = _a[_c];
this.sendMessage(level, output, callback);
};
PapertrailTransport.prototype.sendMessage = function (level, message, callback) {
var lines = [''];
var msg = '';
// Only split if we actually have a message
if (message) {
lines = message.split('\n');
}
// If the incoming message has multiple lines, break them and format each
// line as its own message
for (var i = 0; i < lines.length; i++) {
// don't send extra message if our message ends with a newline
if (lines[i].length === 0 && i === lines.length - 1) {
break;
}
// Needs a valid severity - default to "notice" if the map failed.
var severity = this.options.levels[level] || 5;
msg +=
this.producer.produce({
severity: severity,
host: this.options.hostname,
appName: this.options.program,
date: new Date(),
message: lines[i],
}) + '\r\n';
}
this.connection.write(msg, callback);
};
PapertrailTransport.prototype.close = function () {
this.connection.close();
};
return PapertrailTransport;
}(winston_transport_1.default));
exports.PapertrailTransport = PapertrailTransport;
var PapertrailConnection = /** @class */ (function (_super) {
__extends(PapertrailConnection, _super);
function PapertrailConnection(options) {
var _this = _super.call(this) || this;
_this.options = options;
if (!_this.options.host || !_this.options.port) {
throw new Error('Missing required parameters: host and port');
}
_this.connectionDelay = _this.options.connectionDelay;
_this.currentRetries = 0;
_this.totalRetries = 0;
_this.loggingEnabled = true;
_this._shutdown = false;
_this._erroring = false;
/**
* Dev could instantiate a new logger and then call logger.log immediately.
* We need a way to put incoming strings (from multiple transports) into
* a buffer queue.
*/
_this.deferredQueue = [];
_this.connect();
return _this;
}
PapertrailConnection.prototype.connect = function () {
var _this = this;
if (this._shutdown || this._erroring) {
return;
}
this.close();
try {
if (this.options.disableTls) {
this.stream = net.createConnection(this.options.port, this.options.host, function () { return _this.onConnected(); });
this.stream.setKeepAlive(true, KEEPALIVE_INTERVAL);
this.stream.once('error', function (err) { return _this.onErrored(err); });
this.stream.once('end', function () { return _this.connect(); });
}
else {
this.socket = net.createConnection(this.options.port, this.options.host, function () {
_this.socket.setKeepAlive(true, KEEPALIVE_INTERVAL);
_this.stream = tls_1.default.connect({
socket: _this.socket,
rejectUnauthorized: false,
}, function () { return _this.onConnected(); });
_this.stream.once('error', function (err) {
_this.onErrored(err);
});
_this.stream.once('end', function () { return _this.connect(); });
});
this.socket.once('error', function (err) { return _this.onErrored(err); });
}
}
catch (err) {
this.onErrored(err);
}
};
PapertrailConnection.prototype.write = function (text, callback) {
var _a;
// If we cannot write at the moment, we add it to the deferred queue for later processing
if ((_a = this.stream) === null || _a === void 0 ? void 0 : _a.writable) {
try {
this.stream.write(text, callback);
}
catch (e) {
this.deferredQueue.push({ buffer: text, callback: callback });
}
}
else {
this.deferredQueue.push({ buffer: text, callback: callback });
}
};
PapertrailConnection.prototype.processBuffer = function () {
if (this.deferredQueue.length === 0 || !this.stream || !this.stream.writable) {
return;
}
var msg = this.deferredQueue.pop();
while (msg) {
this.stream.write(msg.buffer, msg.callback);
msg = this.deferredQueue.pop();
}
this.stream.emit('empty');
};
PapertrailConnection.prototype.onConnected = function () {
this.loggingEnabled = true;
this.currentRetries = 0;
this.totalRetries = 0;
this.connectionDelay = this.options.connectionDelay;
this.processBuffer();
this.emit('connect', "Connected to Papertrail at " + this.options.host + ":" + this.options.port);
};
PapertrailConnection.prototype.onErrored = function (err) {
var _this = this;
this._erroring = true;
this.emitSilentError(err);
setTimeout(function () {
if (_this.connectionDelay < _this.options.maxDelayBetweenReconnection &&
_this.currentRetries >= _this.options.attemptsBeforeDecay) {
_this.connectionDelay = _this.connectionDelay * 2;
_this.currentRetries++;
}
if (_this.loggingEnabled && _this.totalRetries >= _this.options.maximumAttempts) {
_this.loggingEnabled = false;
_this.emitSilentError(new Error('Maximum number of retries exceeded, disabling buffering'));
}
_this._erroring = false;
_this.connect();
}, this.connectionDelay);
};
PapertrailConnection.prototype.emitSilentError = function (err) {
if (this.listenerCount('error') > 0) {
this.emit('error', err);
}
else {
console.error("Papertrail connection error: " + err);
}
};
PapertrailConnection.prototype.closeSockets = function () {
if (this.socket) {
this.socket.destroy();
this.socket = undefined;
}
if (this.stream) {
this.stream.removeAllListeners('end');
this.stream.removeAllListeners('error');
this.stream.destroy();
this.stream = undefined;
}
};
PapertrailConnection.prototype.close = function () {
var _this = this;
// if we encounter errors while closing, we wait and try to close three more times
var max = 3;
var attempts = 0;
this._shutdown = true;
var _close = function () {
try {
if (_this.stream) {
if (_this.options.flushOnClose && _this.deferredQueue.length > 0) {
_this.stream.on('empty', function () {
_this.closeSockets();
});
}
else {
_this.closeSockets();
}
}
_this._shutdown = false;
}
catch (e) {
attempts++;
if (attempts < max) {
setTimeout(_close, 100 + attempts);
}
}
};
_close();
};
return PapertrailConnection;
}(events_1.EventEmitter));
exports.PapertrailConnection = PapertrailConnection;