UNPKG

glossy

Version:

Syslog parser and producer

460 lines (388 loc) 14.8 kB
/* * Glossy Producer - Generate valid syslog messages * * Copyright Squeeks <privacymyass@gmail.com>. * This is free software licensed under the MIT License - * see the LICENSE file that should be included with this package. */ /* * These values replace the integers in message that define the facility. */ var FacilityIndex = { 'kern': 0, // kernel messages 'user': 1, // user-level messages 'mail': 2, // mail system 'daemon': 3, // system daemons 'auth': 4, // security/authorization messages 'syslog': 5, // messages generated internally by syslogd 'lpr': 6, // line printer subsystem 'news': 7, // network news subsystem 'uucp': 8, // UUCP subsystem 'clock': 9, // clock daemon 'sec': 10, // security/authorization messages 'ftp': 11, // FTP daemon 'ntp': 12, // NTP subsystem 'audit': 13, // log audit 'alert': 14, // log alert // 'clock': 15, // clock daemon (note 2) 'local0': 16, // local use 0 (local0) 'local1': 17, // local use 1 (local1) 'local2': 18, // local use 2 (local2) 'local3': 19, // local use 3 (local3) 'local4': 20, // local use 4 (local4) 'local5': 21, // local use 5 (local5) 'local6': 22, // local use 6 (local6) 'local7': 23 // local use 7 (local7) }; // Note 1 - Various operating systems have been found to utilize // Facilities 4, 10, 13 and 14 for security/authorization, // audit, and alert messages which seem to be similar. // Note 2 - Various operating systems have been found to utilize // both Facilities 9 and 15 for clock (cron/at) messages. /* * These values replace the integers in message that define the severity. */ var SeverityIndex = { 'emerg': 0, // Emergency: system is unusable 'emergency': 0, 'alert': 1, // Alert: action must be taken immediately 'crit': 2, // Critical: critical conditions 'critical': 2, 'err': 3, // Error: error conditions 'error': 3, 'warn': 4, // Warning: warning conditions 'warning': 4, 'notice': 5, // Notice: normal but significant condition 'info': 6 , // Informational: informational messages 'information': 6, 'informational': 6, 'debug': 7 // Debug: debug-level messages }; /* * Defines the range matching BSD style months to integers. */ var BSDDateIndex = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; /* * GlossyProducer class * @param {Object} provides persistent details of all messages: * facility: The facility index * severity: Severity index * host: Host address, either name or IP * appName: Application/Process name * pid: Process ID * msgID: Message ID (RFC5424 only) * type: RFC3164/RFC5424 message type * @return {Object} GlossyProducer object */ var GlossyProducer = function(options) { if(options && typeof options =='object' && options.type) { this.type = options.type.match(/bsd|3164/i) ? "RFC3164" : "RFC5424"; } else if(options && typeof options == 'string') { this.type = options.match(/bsd|3164/i) ? "RFC3164" : "RFC5424"; } else { this.type = "RFC5424"; } if(options && options.facility && FacilityIndex[options.facility]) { this.facility = options.facility; } if(options && options.pid && parseInt(options.pid, 10)) { this.pid = options.pid; } if(options && options.host) this.host = options.host.replace(/\s+/g, ''); if(options && options.appName) this.appName = options.appName.replace(/\s+/g, ''); if(options && options.msgID) this.msgID = options.msgID.replace(/\s+/g, ''); }; /* * @param {Object} options object containing details of the message: * facility: The facility index * severity: Severity index * prival: RFC5424 PRIVAL field - will override facility/severity if in valid [0-191] range and both provided * see ABNF at: (http://tools.ietf.org/html/rfc5424#section-6) * host: Host address, either name or IP * appName: Application ID * pid: Process ID * date: Timestamp to be applied, uses current GMT by default * time: Optional Date() argument may be used in lieu of 'date' - allows parse() output to be used for produce args * msgID: Message ID (RFC5424 only) * structuredData: Object of structured data (RFC5424 only) * message: The message to be sent * * @param {Function} callback a callback run once the message is built * @return {String} compiledMessage on completion, false on failure */ GlossyProducer.prototype.produce = function(options, callback) { // TODO: next breaking api change make key output from parse() consistent with produce input options if(options.time instanceof Date && !options.date) options.date = options.time; var msgData = []; if(!options.date instanceof Date) { options.date = new Date(Date()); } if(!options.facility) options.facility = this.facility; if(this.type == 'RFC5424') { if(options.hasOwnProperty('prival') && options.prival >= 0 && options.prival <= 191) { var prival = '<' + options.prival + '>1'; } else { var prival = calculatePrival({ facility: options.facility, severity: options.severity, version: 1 }); } if(prival === false) return false; msgData.push(prival); msgData.push(generateDate(options.date)); msgData.push(options.host || this.host || '-'); msgData.push(options.appName || this.appName || '-'); msgData.push(options.pid || this.pid || '-'); msgData.push(options.msgID || this.msgID || '-'); if(options.structuredData) { msgData.push(generateStructuredData(options.structuredData) || '-'); } else { msgData.push('-'); } if(!options.message) options.message = '-'; } else { options.timestamp = generateBSDDate(options.date); msgData.push( calculatePrival({ facility: options.facility, severity: options.severity }) + options.timestamp ); msgData.push(options.host || this.host); msgData.push(); if(options.appName || this.appName) { var app = options.appName || this.appName; var pid = options.pid || this.pid; if(parseInt(pid, 10)) { msgData.push(app + '[' + pid + ']:'); } else { msgData.push(app + ':'); } } } var compiledMessage = msgData.filter(function (messageElement) { // Filter null/ undefined values return messageElement; }).map(function (messageElement) { // Trim messages to remove successive whitespace return String(messageElement).trim(); }).join(' '); compiledMessage += ' ' + options.message || ''; msgData.push(compiledMessage); if(callback) { return callback(compiledMessage); } else { return compiledMessage; } }; /* * @param {Object} options object containing details of the message with * the severity as 'debug' * @param {Function} callback a callback run once the message is built * @return {String} compiledMessage on completion, false on failure */ GlossyProducer.prototype.debug = function(options, callback) { options.severity = 'debug'; return this.produce(options, callback); }; /* * @param {Object} options object containing details of the message with * the severity as 'info' * @param {Function} callback a callback run once the message is built * @return {String} compiledMessage on completion, false on failure */ GlossyProducer.prototype.info = function(options, callback) { options.severity = 'info'; return this.produce(options, callback); }; /* * @param {Object} options object containing details of the message with * the severity as 'notice' * @param {Function} callback a callback run once the message is built * @return {String} compiledMessage on completion, false on failure */ GlossyProducer.prototype.notice = function(options, callback) { options.severity = 'notice'; return this.produce(options, callback); }; /* * @param {Object} options object containing details of the message with * the severity as 'warn' * @param {Function} callback a callback run once the message is built * @return {String} compiledMessage on completion, false on failure */ GlossyProducer.prototype.warn = function(options, callback) { options.severity = 'warn'; return this.produce(options, callback); }; /* * @param {Object} options object containing details of the message with * the severity as 'crit' * @param {Function} callback a callback run once the message is built * @return {String} compiledMessage on completion, false on failure */ GlossyProducer.prototype.crit = function(options, callback) { options.severity = 'crit'; return this.produce(options, callback); }; /* * @param {Object} options object containing details of the message with * the severity as 'alert' * @param {Function} callback a callback run once the message is built * @return {String} compiledMessage on completion, false on failure */ GlossyProducer.prototype.alert = function(options, callback) { options.severity = 'alert'; return this.produce(options, callback); }; /* * @param {Object} options object containing details of the message with * the severity as 'emergency' * @param {Function} callback a callback run once the message is built * @return {String} compiledMessage on completion, false on failure */ GlossyProducer.prototype.emergency = function(options, callback) { options.severity = 'emergency'; return this.produce(options, callback); }; /* * Prepend a zero to a number less than 10 * @param {Number} n * @return {String} * * Where's sprintf when you need it? */ function leadZero(n) { if(typeof n != 'number') return n; n = n < 10 ? '0' + n : n ; return n; } /* * Get current date in RFC 3164 format. If no date is supplied, the default * is the current time in GMT + 0. * @param {Date} dateObject optional Date object * @returns {String} * * Features code taken from https://github.com/akaspin/ain */ function generateBSDDate(dateObject) { if(!(dateObject instanceof Date)) dateObject = new Date(Date()); var hours = leadZero(dateObject.getHours()); var minutes = leadZero(dateObject.getMinutes()); var seconds = leadZero(dateObject.getSeconds()); var month = dateObject.getMonth(); var day = dateObject.getDate(); if(day < 10) (day = ' ' + day); return BSDDateIndex[month] + " " + day + " " + hours + ":" + minutes + ":" + seconds; } /* * Generate date in RFC 3339 format. If no date is supplied, the default is * the current time in GMT + 0. * @param {Date} dateObject optional Date object * @returns {String} formatted date */ function generateDate(dateObject) { if(!(dateObject instanceof Date)) dateObject = new Date(Date()); // Calcutate the offset var timeOffset; var minutes = Math.abs(dateObject.getTimezoneOffset()); var hours = 0; while(minutes >= 60) { hours++; minutes -= 60; } if(dateObject.getTimezoneOffset() < 0) { // Ahead of UTC timeOffset = '+' + leadZero(hours) + '' + ':' + leadZero(minutes); } else if(dateObject.getTimezoneOffset() > 0) { // Behind UTC timeOffset = '-' + leadZero(hours) + '' + ':' + leadZero(minutes); } else { // UTC timeOffset = 'Z'; } // Date formattedDate = dateObject.getUTCFullYear() + '-' + // N.B. Javascript Date objects return months of the year indexed from // zero, while the RFC 5424 syslog standard expects months indexed from // one. leadZero(dateObject.getUTCMonth() + 1) + '-' + // N.B. Javascript Date objects return days of the month indexed from one // (unlike months of year), so this does not need any correction. leadZero(dateObject.getUTCDate()) + 'T' + // Time leadZero(dateObject.getUTCHours()) + ':' + leadZero(dateObject.getUTCMinutes()) + ':' + leadZero(dateObject.getUTCSeconds()) + '.' + leadZero(dateObject.getUTCMilliseconds()) + timeOffset; return formattedDate; } /* * Calculate the PRIVAL for a given facility * @param {Object} values Contains the three key arguments * facility {Number}/{String} the Facility Index * severity {Number} * version {Number} For RFC 5424 messages, this should be 1 * * @return {String} */ function calculatePrival(values) { var pri = {}; // Facility if(typeof values.facility == 'string' && !values.facility.match(/^\d+$/)) { pri.facility = FacilityIndex[values.facility.toLowerCase()]; } else if( parseInt(values.facility, 10) && parseInt(values.facility, 10) < 24) { pri.facility = parseInt(values.facility, 10); } //Severity if(typeof values.severity == 'string' && !values.severity.match(/^\d+$/)) { pri.severity = SeverityIndex[values.severity.toLowerCase()]; } else if( parseInt(values.severity, 10) && parseInt(values.severity, 10) < 8) { pri.severity = parseInt(values.severity, 10); } if(!isNaN(pri.severity) && !isNaN(pri.facility)) { pri.prival = (pri.facility * 8) + pri.severity; pri.str = values.version ? '<' + pri.prival + '>' + values.version : '<' + pri.prival + '>'; return pri.str; } else { return false; } } /* * Serialise objects into the structured data segment * @param {Object} struct The object to serialise * @return {String} structuredData the serialised data */ function generateStructuredData(struct) { if(typeof struct != 'object') return false; var structuredData = ''; for(var sdID in struct) { sdElement = struct[sdID]; structuredData += '[' + sdID; for(var key in sdElement) { sdElement[key] = sdElement[key].toString().replace(/(\]|\\|")/g, '\\$1'); structuredData += ' ' + key + '="' + sdElement[key] + '"'; } structuredData += ']'; } return structuredData; } if(typeof module == 'object') { module.exports = GlossyProducer; }