// INDEX.JS
/** Copyright (c) 2018 Craig Yamato */
/**
* @fileoverview The SyslogPro module for sending syslog messages
* Most APIs will return a promise. These APIs can be used using
* `then(...)/catch(...)`
*
* Syslog formatting classes can be used as input into a Syslog class to be used
* simultaneously to the same Syslog server. The Syslog Class with a configured
* Syslog server target can also be used as the input into each of the
* formatting classes so that they may run independently.
* @author Craig Yamato <craig@kentik.com>
* @copyright (c) 2018 - Craig Yamato
* @version 0.1.0
* @exports Syslog
* @exports LEEF
* @exports CEF
* @module SyslogPro
*/
'use strict';
const moment = require('moment');
const os = require('os');
const dns = require('dns');
let dnsPromises = dns.promises;
const fs = require('fs');
/**
* Format the ANSI foreground color code from a RGB hex code or ANSI color code
* @private
* @param {string} hex - The color hex code in the form of #FFFFFF or Number of
* the ANSI color code (30-37 Standard & 0-255 Extended)
* @returns {Promise} - The formatted ANSI color code
* @throws {Error} - A Format Error
*/
function rgbToAnsi(hex,
extendedColor) {
return new Promise((resolve, reject) => {
let colorCode = 0; // Var to hold color code
// Break HEX Code up into RGB
const hexParts = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (hexParts || typeof hex === 'number') {
if (typeof hex === 'number') {
if (extendedColor && hex < 256) {
resolve(hex);
} else if ((hex > 29 && hex < 38) || (hex > 89 && hex < 98)) {
resolve(hex);
} else {
reject(new Error('FORMAT ERROR: Color code not in range'));
}
} else {
const r = parseInt(hexParts[1], 16);
const g = parseInt(hexParts[2], 16);
const b = parseInt(hexParts[3], 16);
if (extendedColor) {
if (r === g && g === b) {
// Gray Scale Color
if (r < 8) {
colorCode = 16;
} else if (r > 248) {
colorCode = 231;
} else {
colorCode = Math.round(((r - 8) / 247) * 24) + 232;
}
} else {
colorCode = 16
+ (36 * Math.round(r / 255 * 5))
+ (6 * Math.round(g / 255 * 5))
+ Math.round(b / 255 * 5);
}
} else {
colorCode = 30;
const red = r / 255;
const green = g / 255;
const blue = b / 255;
let v = Math.max(red, green, blue) * 100;
v = Math.round(v / 50);
if (v === 1) {
colorCode += ((Math.round(b / 255) << 2)
| (Math.round(g / 255) << 1)
| Math.round(r / 255));
}
if (v === 2) {
colorCode += 60;
}
}
}
resolve(colorCode);
return;
} else {
reject(new Error('TYPE ERROR: Not in RGB color hex or color code'));
return;
}
});
}
/**
* A class to work with syslog messages using UDP, TCP, or TLS transport.
* There is support for Syslog message formatting RFC-3164, RFC-5424 including
* Structured Data, IBM LEEF (Log Event Extended Format), and HP CEF (Common
* Event Format).
* Syslog formatting classes can be used as input into a Syslog class to be used
* simultaneously to the same Syslog server. *
* @requires moment
* @version 0.0.0
* @since 0.0.0
*/
class Syslog {
/**
* Construct a new Syslog transport object with user options
* @public
* @version 0.0.0
* @since 0.0.0
* @this Syslog
* @param {object} [options] - Options object
* >>>Transport Configuration
* @param {string} [options.target='localhost'] - The IP Address|FQDN of the
* Syslog Server, this option if set will take presidents over any target
* set in a formatting object
* @param {string} [options.protocol='udp'] - L4 transport protocol
* (udp|tcp|tls), this option if set will take presidents over any
* transport set in a formatting object
* @param {number} [options.port=514] - IP port, this option if set will take
* presidents over any IP Port set in a formatting object
* @param {number} [options.tcpTimeout=10000] - Ignored for all other
* transports, this option if set will take presidents over any timeout
* set in a formatting object
* @param {string[]} [options.tlsServerCerts] - Array of authorized TLS server
* certificates file locations, this option if set will take presidents
* over any certificates set in a formatting object
* @param {string} [options.tlsClientCert] - Client TLS certificate file
* location that this client should use, this option if set will take
* presidents over any certificates set in a formatting object
* @param {string} [options.tlsClientKey] - Client TLS key file
* location that this client should use, this option if set will take
* presidents over any certificates set in a formatting object
* >>>Syslog Format Settings
* @param {string} [options.format='none'] - Valid syslog format options for
* this module are 'none', 'rfc3164', 'rfc5424', 'leef', 'cef'
* @param {RFC3164} [options.rfc5424] - {@link module:SyslogPro~RFC5424|
* RFC5424 related settings}
* @param {RFC5424} [options.rfc5424] - {@link module:SyslogPro~RFC5424|
* RFC5424 related settings}
* @param {LEEF} [options.leef] - {@link module:SyslogPro~LEEF|IBM LEEF
* (Log Event Extended Format) object}
* @param {CEF} [options.cef] - {@link module:SyslogPro~CEF|HP CEF
* (Common Event Format) formatting object}
*/
constructor(options) {
this.constructor__ = true;
if (!options) {
options = {};
}
// Basic transport setup
/** @type {string} */
this.target = options.target || 'localhost';
/** @type {string} */
this.protocol = options.protocol || 'udp';
this.protocol = this.protocol.toLowerCase();
/** @type {number} */
this.port = options.port || 514;
/** @type {number} */
this.tcpTimeout = options.tcpTimeout || 10000;
if ((typeof options.tlsServerCerts === 'object'
&& Array.isArray(options.tlsServerCerts))
|| typeof options.tlsServerCerts === 'string') {
this.addTlsServerCerts(options.tlsServerCerts);
} else {
/** @type {string[]} */
this.tlsServerCerts = [];
}
if (options.tlsClientCert) {
/** @type {string} */
this.tlsClientCert = options.tlsClientCert;
}
if (options.tlsClientKey) {
/** @type {string} */
this.tlsClientKey = options.tlsClientKey;
}
// Syslog Format
if (typeof options.format === 'string') {
/** @type {string} */
this.format = options.format.toLowerCase();
} else {
this.format = options.format || 'none';
}
if (options.rfc3164) {
if (options.rfc3164.constructor__) {
/** @type {RFC3164} */
this.rfc3164 = options.rfc3164;
} else {
this.rfc3164 = new RFC3164(options);
}
}
if (options.rfc5424) {
if (options.rfc5424.constructor__) {
/** @type {RFC5424} */
this.rfc5424 = options.rfc5424;
} else {
this.rfc5424 = new RFC5424(options);
}
}
if (options.leef) {
if (options.leef.constructor__) {
/** @type {LEEF} */
this.leef = options.leef;
} else {
this.leef = new LEEF(options);
}
}
if (options.cef) {
if (options.cef.constructor__) {
/** @type {CEF} */
this.cef = options.cef;
} else {
this.cef = new CEF(options);
}
}
if (this.format === 'rfc3164' && !this.rfc3164) {
this.rfc3164 = new RFC3164();
}
if (this.format === 'rfc5424' && !this.rfc5424) {
this.rfc5424 = new RFC5424();
}
if (this.format === 'leef' && !this.leef) {
this.leef = new LEEF();
}
if (this.format === 'cef' && !this.cef) {
this.cef = new CEF();
}
}
/**
* Add a TLS server certificate which can be used to authenticate the server
* this syslog client is connecting too. This function will validate the
* input as a file location string and add it to an array of certificates
* @private
* @version 0.0.0
* @since 0.0.0
* @param {string|string[]} certs - File location of the certificate(s)
* @returns {Promise} - True
* @throws {Error} - A Type Error
*/
addTlsServerCerts(certs) {
return new Promise((resolve, reject) => {
if (typeof certs === 'object' && Array.isArray(certs)) {
/** @private @type {string[]} */
this.tlsServerCerts = certs;
} else if (typeof certs === 'string') {
this.tlsServerCerts = [certs];
} else {
let errMsg =
'TYPE ERROR: Server Cert file locations should be a string';
errMsg += ' or array of strings';
reject(new Error(errMsg));
}
resolve(true);
});
}
/**
* Send the Syslog message over UDP
* @private
* @param {string} msg - The formatted Syslog Message
* @returns {Promise} - The Syslog formatted string sent
* @throws {Error} - Network Error
*/
udpMessage(msg) {
return new Promise((resolve, reject) => {
// Test for target DNS and Address Family (IPv4/6) by looking up the DNS
const dgram = require('dgram');
const dnsOptions = {
verbatim: true,
};
dnsPromises.lookup(this.target, dnsOptions)
.then((result) => {
const udpType = result.family === 4 ? 'udp4' : 'udp6';
let client = dgram.createSocket(udpType);
// Turn msg in to a UTF8 buffer
let msgBuffer = Buffer.from(msg, 'utf8');
client.send(msgBuffer, this.port, this.target, () => {
client.close();
resolve(msg);
});
})
.catch((error) => {
reject(error); // Reject out of the sendMessage function promise
});
});
}
/**
* Send the Syslog message over TCP
* @private
* @param {string} msg - The formatted Syslog Message
* @returns {Promise} - The Syslog formatted string sent
* @throws {Error} - Timeout error for TCP and TLS connections
* @throws {Error} - Network Error
*/
tcpMessage(msg) {
return new Promise((resolve, reject) => {
const net = require('net');
const dnsOptions = {
verbatim: true,
};
dnsPromises.lookup(this.target, dnsOptions)
.then((result) => {
const tcpOptions = {
host: this.target,
port: this.port,
family: result.family,
};
const client = net.createConnection(tcpOptions, () => {
// Turn msg in to a UTF8 buffer
let msgBuffer = Buffer.from(msg, 'utf8');
client.write(msgBuffer, () => {
client.end();
});
});
client.setTimeout(this.tcpTimeout);
client.on('end', () => {
resolve(msg);
});
client.on('timeout', () => {
client.end();
reject(new Error('TIMEOUT ERROR: Syslog server TCP timeout'));
});
client.on('error', (error) => {
client.destroy();
reject(error);
});
})
.catch((error) => {
reject(error);
});
});
}
/**
* Send the Syslog message over TLS
* @private
* @param {string} msg - The formatted Syslog Message
* @returns {Promise} - The Syslog formatted string sent
* @throws {Error} - Timeout error for TCP and TLS connections
* @throws {Error} - Network Error
*/
tlsMessage(msg) {
return new Promise((resolve, reject) => {
const tls = require('tls');
const tlsOptions = {
host: this.target,
port: this.port,
};
// Load client cert and key if requested
if (typeof this.tlsClientKey === 'string'
&& typeof this.tlsClientCert === 'string') {
tlsOptions.key = fs.readFileSync(this.tlsClientKey);
tlsOptions.cert = fs.readFileSync(this.tlsClientCert);
} else if (typeof this.tlsClientKey !== 'string'
&& typeof this.tlsClientKey !== 'undefined') {
let errMsg = 'TYPE ERROR: TLS Client Key is not a file';
errMsg += 'location string';
reject(new Error(errMsg));
return;
} else if (typeof this.tlsClientCert !== 'string'
&& typeof this.tlsClientCert !== 'undefined') {
let errMsg = 'TYPE ERROR: TLS Client Cert is not a file';
errMsg += 'location string';
reject(new Error(errMsg));
return;
}
// Load any server certs if provided
let tlsCerts = this.tlsServerCerts.length;
if (tlsCerts > 0) {
let tlsOptionsCerts = [];
for (let certIndex = 0; certIndex < tlsCerts; certIndex++) {
if (typeof this.tlsServerCerts[certIndex] !== 'string') {
let errMsg = 'TYPE ERROR: TLS Server Cert is not a file';
errMsg += 'location string';
reject(new Error(errMsg));
}
let cert = fs.readFileSync(this.tlsServerCerts[certIndex]);
tlsOptionsCerts.push(cert);
}
tlsOptions.ca = tlsOptionsCerts;
tlsOptions.rejectUnauthorized = true;
}
const client = tls.connect(tlsOptions, () => {
// Turn msg in to a UTF8 buffer
let msgBuffer = Buffer.from(msg, 'utf8');
client.write(msgBuffer, () => {
client.end();
});
});
client.setTimeout(this.tcpTimeout);
client.on('end', () => {
resolve(msg);
});
client.on('timeout', () => {
client.end();
reject(new Error('TIMEOUT ERROR: Syslog server TLS timeout'));
});
client.on('error', (error) => {
client.destroy();
reject(error);
});
});
}
/**
* Send the Syslog message to the selected target Syslog server using the
* selected transport.
* @private
* @param {string} msg - The formatted Syslog Message
* @returns {Promise} - The Syslog formatted string sent
* @throws {Error} - Timeout error for TCP and TLS connections
* @throws {Error} - Network Error
*/
send(msg) {
return new Promise((resolve, reject) => {
if (typeof msg !== 'string') {
reject(new Error('TYPE ERROR: Syslog message must be a string'));
return;
}
this.protocol = this.protocol.toLowerCase();
if (this.protocol === 'udp') {
this.udpMessage(msg)
.then((result) => {
resolve(result);
})
.catch((reson) => {
reject(reson);
});
} else if (this.protocol === 'tcp') {
this.tcpMessage(msg)
.then((result) => {
resolve(result);
})
.catch((reson) => {
reject(reson);
});
} else if (this.protocol === 'tls') {
this.tlsMessage(msg)
.then((result) => {
resolve(result);
})
.catch((reson) => {
reject(reson);
});
} else {
let errorMsg = 'FORMAT ERROR: Protocol not recognized, should be ';
errorMsg += 'udp|tcp|tls';
reject(new Error(errorMsg));
}
});
}
}
/**
* A class to work with RFC3164 formatted syslog messages. The messaging is
* fully configurable and ANSI foreground colors can be added. Both ANSI 8 and
* ANSI 256 color are fully supported. Most APIs will return a promise. These
* APIs can be used using `then(...)/catch(...)`
*
* A Syslog class with a configured
* Syslog server target can also be used as the input into the formatting
* classes so that it may run independently.
*
* The RFC3164 Syslog logging format is meant to be used as a stream of log data
* from a service or application. This class is designed to be used in this
* fashion where new messages are written to the class as needed.
* @requires moment
* @version 0.0.0
* @since 0.0.0
*/
class RFC3164 {
/**
* Construct a new RFC3164 formatted Syslog object with user options
* @public
* @this RFC3164
* @param {object} [options] - Options object
* @param {string} [options.applicationName='NodeJSLogger'] - Application
* @param {string} [options.hostname=os.hostname] - The name of this server
* @param {number} [options.facility=23] - Facility code to use sending this
* message
* @param {boolean} [options.color=false] - Apply color coding encoding tag
* with syslog message text
* @param {boolean} [options.extendedColor=false] - Use the extended ANSI
* color set encoding tag with syslog message text
* @param {object} [options.colors] - User defended colors for
* severities
* @param {string} [options.colors.emergencyColor] - A RGB Hex coded color in
* the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
* & 0-255 Extended)
* @param {string} [options.colors.alertColor] - A RGB Hex coded color in the
* form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
* 0-255 Extended)
* @param {string} [options.colors.criticalColor] - A RGB Hex coded color in
* the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
* & 0-255 Extended)
* @param {string} [options.colors.errorColor] - A RGB Hex coded color in the
* form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
* 0-255 Extended)
* @param {string} [options.colors.warningColor] - A RGB Hex coded color in
* the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
* & 0-255 Extended)
* @param {string} [options.colors.noticeColor] - A RGB Hex coded color in the
* form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
* 0-255 Extended)
* @param {string} [options.colors.informationalColor] - A RGB Hex coded color
* in the form of #FFFFFF or as or the ANSI color code number (30-37
* Standard & 0-255 Extended)
* @param {string} [options.colors.debugColor] - A RGB Hex coded color in the
* form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
* 0-255 Extended)
* @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog|
* Syslog server connection} that should be used to send messages directly
* from this class. @see SyslogPro~Syslog
*/
constructor(options) {
/** @private @type {boolean} */
this.constructor__ = true;
options = options || {};
this.hostname = options.hostname || os.hostname();
this.applicationName = options.applicationName || '';
this.facility = options.facility || 23;
if (options.color) {
/** @type {boolean} */
this.color = true;
} else {
this.color = false;
}
if (options.extendedColor) {
/** @type {boolean} */
this.extendedColor = true;
} else {
this.extendedColor = false;
}
if (options.server) {
if (!options.server.constructor__) {
/** @private @type {Syslog} */
this.server = new Syslog(options.server);
} else {
this.server = options.server;
}
}
if (this.extendedColor) {
/** @private @type {number} */
this.emergencyColor = 1; // Red foreground color
/** @private @type {number} */
this.alertColor = 202; // Dark Orange foreground color
/** @private @type {number} */
this.criticalColor = 208; // Orange foreground color
/** @private @type {number} */
this.errorColor = 178; // Light Orange foreground color
/** @private @type {number} */
this.warningColor = 226; // Yellow foreground color
/** @private @type {number} */
this.noticeColor = 117; // Light Blue foreground color
/** @private @type {number} */
this.informationalColor = 45; // Blue foreground color
/** @private @type {number} */
this.debugColor = 27; // Dark Blue foreground color
} else {
this.emergencyColor = 31; // Red foreground color
this.alertColor = 31; // Red foreground color
this.criticalColor = 31; // Red foreground color
this.errorColor = 33; // Yellow foreground color
this.warningColor = 33; // Yellow foreground color
this.noticeColor = 36; // Blue foreground color
this.informationalColor = 36; // Blue foreground color
this.debugColor = 34; // Dark Blue foreground color
}
if (typeof options.colors === 'object') {
this.setColor(options.colors, this.extendedColor);
}
}
/**
* Sets the color to be used for messages at a set priority
* @public
* @param {string} [colors.emergencyColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.alertColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.criticalColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.errorColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.warningColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.noticeColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.informationalColor] - A RGB Hex coded color in the
* form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
* 0-255 Extended)
* @param {string} [colors.debugColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @throws {Error} A standard error object
*/
setColor(colors, extendedColor) {
return new Promise((resolve, reject) => {
let colorPromises = [];
if (colors.emergencyColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.emergencyColor, this.extendedColor)
.then((result) => {
this.emergencyColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'emergencyColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.alertColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.alertColor, this.extendedColor)
.then((result) => {
this.alertColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'alertColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.criticalColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.criticalColor, this.extendedColor)
.then((result) => {
this.criticalColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'criticalColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.errorColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.errorColor, this.extendedColor)
.then((result) => {
this.errorColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'errorColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.warningColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.warningColor, this.extendedColor)
.then((result) => {
this.warningColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'warningColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.noticeColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.noticeColor, this.extendedColor)
.then((result) => {
this.noticeColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'noticeColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.informationalColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.informationalColor, this.extendedColor)
.then((result) => {
this.informationalColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'informationalColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.debugColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.debugColor, this.extendedColor)
.then((result) => {
this.debugColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'debugColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
Promise.all(colorPromises)
.then((results) => {
resolve(true);
})
.catch((reson) => {
reject(reson);
});
});
}
/**
* Building a formatted message. Returns a promise with a formatted message
* @public
* @param {string} msg - The Syslog Message
* @param {object} [options] - Options object
* @param {number} [options.severity=7] - An array of structure
* @param {number} [options.colorCode=36] - The ANSI color code to use if
* message coloration is selected
* @returns {Promise} A Syslog formatted string according to the selected RFC
* @throws {Error} A standard error object
*/
buildMessage(msg, options) {
return new Promise((resolve, reject) => {
options = options || {};
let severity = typeof options.severity === 'number' ?
options.severity : 6;
if (typeof msg !== 'string' || options.msgSeverity > 7) {
let errMsg = 'FORMAT ERROR: Syslog message must be a string';
errMsg += ' msgSeverity must be a number between 0 and 7';
reject(new Error(errMsg));
return;
}
let fmtMsg = ''; // Formatted Syslog message string var
const newLine = '\n';
const newLineRegEx = /(\r|\n|(\r\n))/;
const escapeCode = '\u001B';
const resetColor = '\u001B[0m';
// The PRI is common to both RFC formats
const pri = (this.facility * 8) + severity;
// Remove any newline character
msg = msg.replace(newLineRegEx, '');
// Add requested color
if (this.color) {
options.msgColor = options.msgColor || 36;
let colorCode = '[';
if (this.extendedColor) {
colorCode += '38;5;'; // Extended 256 Colors ANSI Code
}
if (typeof options.msgColor === 'number') {
colorCode += options.msgColor;
colorCode += 'm'; // ANSI Color Closer
} else {
colorCode = '[39m'; // Use terminal's default color
}
msg = escapeCode + colorCode + msg + resetColor;
}
// RegEx to find a leading 0 in the day of a DateTime for RFC3164 RFC3164
// uses BSD timeformat
const rfc3164DateRegEx =
/((A|D|F|J|M|N|O|S)(a|c|e|p|o|u)(b|c|g|l|n|p|r|t|v|y)\s)0(\d\s\d\d:\d\d:\d\d)/;
const timestamp = moment()
.format('MMM DD hh:mm:ss')
.replace(rfc3164DateRegEx, '$1 $5');
// Build message
fmtMsg = '<' + pri + '>';
fmtMsg += timestamp;
fmtMsg += ' ' + this.hostname;
fmtMsg += ' ' + this.applicationName;
fmtMsg += ' ' + msg;
fmtMsg += newLine;
resolve(fmtMsg);
});
}
/**
* send a RFC5424 formatted message. Returns a promise with the formatted
* message that was sent. If no server connection was defined when the
* class was created a default Syslog connector will be used.
* @see SyslogPro~Syslog
* @public
* @param {string} msg - The unformatted Syslog message to send
* @param {object} [options] - Options object
* @param {number} [options.severity=7] - An array of structure
* @param {number} [options.colorCode=36] - The ANSI color code to use if
* @returns {Promise} A Syslog formatted string according to the selected RFC
* @throws {Error} A standard error object
*/
send(msg, options) {
return new Promise((resolve, reject) => {
if (!this.server) {
this.server = new Syslog();
}
this.buildMessage(msg, options)
.then((result) => {
this.server.send(result)
.then((sendResult) => {
resolve(sendResult);
})
.catch((error) => {
reject(error);
});
})
.catch((error) => {
reject(error);
});
});
}
/**
* Send a syslog message with a security level of 0 (Emergency)
* @public
* @param {string} msg - The emergency message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
emergency(msg) {
return this.send(msg, {
severity: 0,
colorCode: this.emergencyColor,
});
}
/**
* Send a syslog message with a security level of 0 (Emergency)
* @public
* @param {string} msg - The emergency message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
emer(msg) {
return this.emergency(msg);
}
/**
* Send a syslog message with a severity level of 1 (Alert)
* @public
* @param {string} msg - The alert message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
alert(msg) {
return this.send(msg, {
severity: 1,
colorCode: this.alertColor,
});
}
/**
* Send a syslog message with a severity level of 2 (Critical)
* @public
* @param {string} msg - The critical message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
critical(msg) {
return this.send(msg, {
severity: 2,
colorCode: this.criticalColor,
});
}
/**
* Send a syslog message with a severity level of 2 (Critical)
* @public
* @param {string} msg - The critical message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
crit(msg) {
return this.critical(msg);
}
/**
* Send a syslog message with a severity level of 3 (Error)
* @public
* @param {string} msg - The error message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
error(msg) {
return this.send(msg, {
severity: 3,
colorCode: this.errorColor,
});
}
/**
* Send a syslog message with a severity level of 3 (Error)
* @public
* @param {string} msg - The error message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
err(msg) {
return this.error(msg);
}
/**
* Send a syslog message with a severity level of 4 (Warning)
* @public
* @param {string} msg - The warning message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
warning(msg) {
return this.send(msg, {
severity: 4,
colorCode: this.warningColor,
});
}
/**
* Send a syslog message with a severity level of 4 (Warning)
* @public
* @param {string} msg - The warning message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
warn(msg) {
return this.warning(msg);
}
/**
* Send a syslog message with a severity level of 5 (Notice)
* @public
* @param {string} msg - The notice message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
notice(msg) {
return this.send(msg, {
severity: 5,
colorCode: this.noticeColor,
});
}
/**
* Send a syslog message with a severity level of 5 (Notice)
* @public
* @param {string} msg - The notice message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
note(msg) {
return this.notice(msg);
}
/**
* Send a syslog message with a severity level of 6 (Informational)
* @public
* @param {string} msg - The informational message to send to the Syslog
* server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
informational(msg) {
return this.send(msg, {
severity: 6,
colorCode: this.informationalColor,
});
}
/**
* Send a syslog message with a severity level of 6 (Informational)
* @public
* @param {string} msg - The informational message to send to the Syslog
* server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
info(msg) {
return this.informational(msg);
}
/**
* Send a syslog message with a severity level of 6 (Informational)
* @public
* @param {string} msg - The informational message to send to the Syslog
* server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
log(msg) {
return this.informational(msg);
}
/**
* Send a syslog message with a severity level of 7 (Debug)
* @public
* @param {string} msg - The debug message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
debug(msg) {
return this.send(msg, {
severity: 7,
colorCode: this.debugColor,
});
}
}
/**
* A class to work with RFC5424 formatted syslog messages. The messaging is
* fully configurable and ANSI foreground * colors can be added. Both ANSI 8
* and ANSI 256 color are fully supported.
*Most APIs will return a promise. These APIs can be used using
* `then(...)/catch(...)`
*
* A Syslog class with a configured
* Syslog server target can also be used as the input into the formatting
* classes so that it may run independently.
*
* The RFC5424 Syslog logging format is meant to be used as a stream of log data
* from a service or application. This class is designed to be used in this
* fashion where new messages are written to the class as needed.
* @requires moment
* @version 0.0.0
* @since 0.0.0
*/
class RFC5424 {
/**
* Construct a new RFC5424 formatted Syslog object with user options
* @public
* @this RFC5424
* @param {object} [options] - Options object
* @param {string} [options.applicationName='NodeJSLogger'] - Application
* @param {string} [options.hostname=os.hostname] - The name of this server
* @param {boolean} [options.timestamp=false] - Included a Timestamp
* @param {boolean} [options.timestampUTC=false] - RFC standard is for
* local time
* @param {boolean} [options.timestampMS=false] - Timestamp with ms
* resolution
* @param {boolean} [options.timestampTZ=true] - Should the timestamp
* included time zone
* @param {boolean} [options.includeStructuredData=false] - Included
* any provided structured data
* @param {boolean} [options.utf8BOM=true] - Included the UTF8
* @param {boolean} [options.color=false] - Included the UTF8
* @param {boolean} [options.extendedColor=false] - Included the UTF8
* encoding tag with syslog message text
* @param {object} [options.colors] - User defended colors for
* severities
* @param {string} [options.colors.emergencyColor] - A RGB Hex coded color in
* the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
* & 0-255 Extended)
* @param {string} [options.colors.alertColor] - A RGB Hex coded color in the
* form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
* 0-255 Extended)
* @param {string} [options.colors.criticalColor] - A RGB Hex coded color in
* the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
* & 0-255 Extended)
* @param {string} [options.colors.errorColor] - A RGB Hex coded color in the
* form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
* 0-255 Extended)
* @param {string} [options.colors.warningColor] - A RGB Hex coded color in
* the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
* & 0-255 Extended)
* @param {string} [options.colors.noticeColor] - A RGB Hex coded color in the
* form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
* 0-255 Extended)
* @param {string} [options.colors.informationalColor] - A RGB Hex coded color
* in the form of #FFFFFF or as or the ANSI color code number (30-37
* Standard & 0-255 Extended)
* @param {string} [options.colors.debugColor] - A RGB Hex coded color in the
* form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
* 0-255 Extended)
* @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog|
* Syslog server connection} that should be used to send messages directly
* from this class. @see SyslogPro~Syslog
*/
constructor(options) {
/** @private @type {boolean} */
this.constructor__ = true;
options = options || {};
this.hostname = options.hostname || os.hostname();
this.applicationName = options.applicationName || '';
if (typeof options.timestamp === 'undefined' || options.timestamp) {
/** @type {boolean} */
this.timestamp = true;
} else {
this.timestamp = false;
}
if (options.timestampUTC) {
/** @type {boolean} */
this.timestampUTC = true;
} else {
this.timestampUTC = false;
}
if (typeof options.timestampTZ === 'undefined' || options.timestampTZ) {
/** @type {boolean} */
this.timestampTZ = true;
} else {
this.timestampTZ = false;
}
if (options.timestampMS) {
/** @type {boolean} */
this.timestampMS = true;
} else {
this.timestampMS = false;
}
if (options.includeStructuredData) {
/** @type {boolean} */
this.includeStructuredData = true;
} else {
this.includeStructuredData = false;
}
if (typeof options.utf8BOM === 'undefined' || options.utf8BOM) {
/** @type {boolean} */
this.utf8BOM = true;
} else {
this.utf8BOM = false;
}
if (options.color) {
/** @type {boolean} */
this.color = true;
} else {
this.color = false;
}
if (options.extendedColor) {
/** @type {boolean} */
this.extendedColor = true;
} else {
this.extendedColor = false;
}
if (options.server) {
if (!options.server.constructor__) {
/** @private @type {Syslog} */
this.server = new Syslog(options.server);
} else {
this.server = options.server;
}
}
if (this.extendedColor) {
/** @private @type {number} */
this.emergencyColor = 1; // Red foreground color
/** @private @type {number} */
this.alertColor = 202; // Dark Orange foreground color
/** @private @type {number} */
this.criticalColor = 208; // Orange foreground color
/** @private @type {number} */
this.errorColor = 178; // Light Orange foreground color
/** @private @type {number} */
this.warningColor = 226; // Yellow foreground color
/** @private @type {number} */
this.noticeColor = 117; // Light Blue foreground color
/** @private @type {number} */
this.informationalColor = 45; // Blue foreground color
/** @private @type {number} */
this.debugColor = 27; // Dark Blue foreground color
} else {
this.emergencyColor = 31; // Red foreground color
this.alertColor = 31; // Red foreground color
this.criticalColor = 31; // Red foreground color
this.errorColor = 33; // Yellow foreground color
this.warningColor = 33; // Yellow foreground color
this.noticeColor = 36; // Blue foreground color
this.informationalColor = 36; // Blue foreground color
this.debugColor = 34; // Dark Blue foreground color
}
if (typeof options.colors === 'object') {
this.setColor(options.colors, this.extendedColor);
}
}
/**
* Sets the color to be used for messages at a set priority
* @public
* @param {string} [colors.emergencyColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.alertColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.criticalColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.errorColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.warningColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.noticeColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @param {string} [colors.informationalColor] - A RGB Hex coded color in the
* form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
* 0-255 Extended)
* @param {string} [colors.debugColor] - A RGB Hex coded color in the form
* of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
* Extended)
* @throws {Error} A standard error object
*/
setColor(colors, extendedColor) {
return new Promise((resolve, reject) => {
let colorPromises = [];
if (colors.emergencyColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.emergencyColor, this.extendedColor)
.then((result) => {
this.emergencyColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'emergencyColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.alertColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.alertColor, this.extendedColor)
.then((result) => {
this.alertColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'alertColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.criticalColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.criticalColor, this.extendedColor)
.then((result) => {
this.criticalColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'criticalColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.errorColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.errorColor, this.extendedColor)
.then((result) => {
this.errorColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'errorColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.warningColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.warningColor, this.extendedColor)
.then((result) => {
this.warningColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'warningColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.noticeColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.noticeColor, this.extendedColor)
.then((result) => {
this.noticeColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'noticeColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.informationalColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.informationalColor, this.extendedColor)
.then((result) => {
this.informationalColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'informationalColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
if (colors.debugColor) {
colorPromises.push(
new Promise((resolve, reject) => {
rgbToAnsi(colors.debugColor, this.extendedColor)
.then((result) => {
this.debugColor = result;
resolve(true);
})
.catch((reson) => {
reson.message = 'TYPE ERROR: ';
reson.message += 'debugColor';
reson.message += ' Not in RGB color hex or color code';
reject(reson);
});
}));
}
Promise.all(colorPromises)
.then((results) => {
resolve(true);
})
.catch((reson) => {
reject(reson);
});
});
}
/**
* Building a formatted message. Returns a promise with a formatted message
* @public
* @param {string} msg - The Syslog Message
* @param {object} [options] - Options object
* @param {number} [options.severity=7] - An array of structure
* @param {number} [options.facility=23] - Facility code to use sending this
* message
* @param {string} [options.pid='-'] - The process id of the service sending
* this message
* @param {string[]} [options.structuredData] - An array of structure
* data strings conforming to the IETF/IANA defined SD-IDs or IANA
* registered SMI Network Management Private Enterprise Code SD-ID
* conforming to the format
* [name@<private enterprise number> parameter=value]
* @param {number} [options.colorCode=36] - The ANSI color code to use if
* message coloration is selected
* @returns {Promise} A Syslog formatted string according to the selected RFC
* @throws {Error} A standard error object
*/
buildMessage(msg, options) {
return new Promise((resolve, reject) => {
options = options || {};
let severity = typeof options.severity === 'number' ?
options.severity : 6;
if (typeof msg !== 'string' || options.severity > 7) {
let errMsg = 'FORMAT ERROR: Syslog message must be a string';
errMsg += ' msgSeverity must be a number between 0 and 7';
reject(new Error(errMsg));
return;
}
let facility = options.facility || 23;
let pid = options.pid || '-';
let id = options.id || '-';
let msgStructuredData = options.msgStructuredData || [];
let fmtMsg = ''; // Formated Syslog message string var
const newLine = '\n';
const newLineRegEx = /(\r|\n|(\r\n))/;
const escapeCode = '\u001B';
const resetColor = '\u001B[0m';
// The PRI is common to both RFC formats
const pri = (facility * 8) + severity;
// Remove any newline character
msg = msg.replace(newLineRegEx, '');
// Add requested color
if (this.color) {
options.msgColor = options.msgColor || 36;
let colorCode = '[';
if (this.extendedColor) {
colorCode += '38;5;'; // Extended 256 Colors ANSI Code
}
if (typeof options.msgColor === 'number') {
colorCode += options.msgColor;
colorCode += 'm'; // ANSI Color Closer
} else {
colorCode = '[39m'; // Use terminal's default color
}
msg = escapeCode + colorCode + msg + resetColor;
}
// RFC5424 timestamp formating
let timestamp = '-';
if (this.timestamp) {
let timeQuality = '[timeQuality';
if (this.timestampUTC) {
timeQuality += ' tzKnown=1';
if (this.timestampMS) {
if (this.timestampTZ) {
timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ss.SSSSSSZ');
} else {
timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ss.SSSSSS');
}
} else {
if (this.timestampTZ) {
timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ssZ');
} else {
timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ss');
}
}
} else {
if (this.timestampTZ) {
timeQuality += ' tzKnown=1';
if (this.timestampMS) {
timeQuality += ' isSynced=1';
timeQuality += ' syncAccuracy=0';
timestamp = moment().format('YYYY-MM-DDThh:mm:ss.SSSSSSZ');
} else {
timestamp = moment().format('YYYY-MM-DDThh:mm:ssZ');
}
} else {
timeQuality += ' tzKnown=0';
if (this.timestampMS) {
timeQuality += ' isSynced=1';
timeQuality += ' syncAccuracy=0';
timestamp = moment().format('YYYY-MM-DDThh:mm:ss.SSSSSS');
} else {
timestamp = moment().format('YYYY-MM-DDThh:mm:ss');
}
}
}
timeQuality += ']';
msgStructuredData.push(timeQuality);
}
// Build Structured Data string
let structuredData = '-';
const sdElementCount = msgStructuredData.length;
if (this.includeStructuredData && sdElementCount > 0) {
let sdElementNames = [];
let sdElements = [];
const sdElementNameRegEx = /(\[)(\S*)(\s|\])/;
// Loop to drop duplicates of the same SD Element name
for (let elementIndex = 0;
elementIndex < sdElementCount;
elementIndex++) {
let elementName =
msgStructuredData[elementIndex]
.match(sdElementNameRegEx)[2];
if (!sdElementNames.includes(elementName)) {
sdElementNames.push(elementName);
sdElements.push(msgStructuredData[elementIndex]);
}
}
structuredData = sdElements.join('');
}
// Build the message
fmtMsg = '<' + pri + '>';
fmtMsg += '1'; // Version number
fmtMsg += ' ' + timestamp;
fmtMsg += ' ' + this.hostname;
fmtMsg += ' ' + this.applicationName;
fmtMsg += ' ' + pid;
fmtMsg += ' ' + id;
fmtMsg += ' ' + structuredData;
if (this.utf8BOM) {
fmtMsg += ' BOM' + msg;
} else {
fmtMsg += ' ' + msg;
}
fmtMsg += newLine;
resolve(fmtMsg);
});
}
/**
* send a RFC5424 formatted message. Returns a promise with the formatted
* message that was sent. If no server connection was defined when the
* class was created a default Syslog connector will be used.
* @see SyslogPro~Syslog
* @public
* @param {string} msg - The unformatted Syslog message to send
* @returns {Promise} A Syslog formatted string according to the selected RFC
* @throws {Error} A standard error object
*/
send(msg, options) {
return new Promise((resolve, reject) => {
if (!this.server) {
this.server = new Syslog();
}
this.buildMessage(msg, options)
.then((result) => {
this.server.send(result)
.then((sendResult) => {
resolve(sendResult);
})
.catch((error) => {
reject(error);
});
})
.catch((error) => {
reject(error);
});
});
}
/**
* Send a syslog message with a severity level of 0 (Emergency)
* @public
* @param {string} msg - The emergency message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
emergency(msg) {
return this.send(msg, {
severity: 0,
colorCode: this.emergencyColor,
});
}
/**
* Send a syslog message with a severity level of 0 (Emergency)
* @public
* @param {string} msg - The emergency message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
emer(msg) {
return this.emergency(msg);
}
/**
* Send a syslog message with a severity level of 1 (Alert)
* @public
* @param {string} msg - The alert message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
alert(msg) {
return this.send(msg, {
severity: 1,
colorCode: this.alertColor,
});
}
/**
* Send a syslog message with a severity level of 2 (Critical)
* @public
* @param {string} msg - The critical message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
critical(msg) {
return this.send(msg, {
severity: 2,
colorCode: this.criticalColor,
});
}
/**
* Send a syslog message with a severity level of 2 (Critical)
* @public
* @param {string} msg - The critical message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
crit(msg) {
return this.critical(msg);
}
/**
* Send a syslog message with a severity level of 3 (Error)
* @public
* @param {string} msg - The error message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
error(msg) {
return this.send(msg, {
severity: 3,
colorCode: this.errorColor,
});
}
/**
* Send a syslog message with a severity level of 3 (Error)
* @public
* @param {string} msg - The error message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
err(msg) {
return this.error(msg);
}
/**
* Send a syslog message with a severity level of 4 (Warning)
* @public
* @param {string} msg - The warning message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
warning(msg) {
return this.send(msg, {
severity: 4,
colorCode: this.warningColor,
});
}
/**
* Send a syslog message with a severity level of 4 (Warning)
* @public
* @param {string} msg - The warning message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
warn(msg) {
return this.warning(msg);
}
/**
* Send a syslog message with a severity level of 5 (Notice)
* @public
* @param {string} msg - The notice message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
notice(msg) {
return this.send(msg, {
severity: 5,
colorCode: this.noticeColor,
});
}
/**
* Send a syslog message with a severity level of 5 (Notice)
* @public
* @param {string} msg - The notice message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
note(msg) {
return this.notice(msg);
}
/**
* Send a syslog message with a severity level of 6 (Informational)
* @public
* @param {string} msg - The informational message to send to the Syslog
* server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
informational(msg) {
return this.send(msg, {
severity: 6,
colorCode: this.informationalColor,
});
}
/**
* Send a syslog message with a severity level of 6 (Informational)
* @public
* @param {string} msg - The informational message to send to the Syslog
* server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
info(msg) {
return this.informational(msg);
}
/**
* Send a syslog message with a severity level of 6 (Informational)
* @public
* @param {string} msg - The informational message to send to the Syslog
* server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
log(msg) {
return this.informational(msg);
}
/**
* Send a syslog message with a severity level of 7 (Debug)
* @public
* @param {string} msg - The debug message to send to the Syslog server
* @returns {Promise} - The formatted syslog message sent to the Syslog server
* @throws {Error} - Any bubbled-up error
*/
debug(msg) {
return this.send(msg, {
severity: 7,
colorCode: this.debugColor,
});
}
}
/**
* A class to work with IBM LEEF (Log Event Extended Format) messages this form
* of system messages are designed to work with security systems. Messages can
* be saved to file (Saving to file if not part of this module but a LEEF
* formatted message produced by this module can be saved externally to it) or
* sent via Syslog.
* Most APIs will return a promise. These APIs can be used using
* `then(...)/catch(...)`
*
* A Syslog class with a configured Syslog server target can also be used as
* the input into the formatting classes so that it may run independently. The
* LEEF format is designed to send event data to a SIEM system and should not
* be as a logging stream. This class is meant to be used once per message.
* @requires moment
* @version 0.0.0
* @since 0.0.0
*/
class LEEF {
/**
* Construct a new LEEF formatting object with user options
* @public
* @param {object} [options] - Options object
* @param {string} [options.vendor='unknown'] - The vendor of the system that
* generated the event being reported
* @param {string} [options.product='unknown'] - The product name of the
* system that genrated the event being reported
* @param {string} [options.version='unknown'] - The version name of the
* system that genrated the event being reported
* @param {string} [options.eventId='unknown'] - The eventId of the
* system that genrated the event being reported
* @param {object} [options.attributes] - LEEF message attributes which
* defaults to all base attributes with null values, new attributes should
* be added as new elements to this object
* @param {boolean} [options.syslogHeader='true'] - Should the LEEF message
* include a Syslog header with Timestamp and source
* @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog|
* Syslog server connection} that should be used to send messages directly
* from this class. @see SyslogPro~Syslog
*/
constructor(options) {
/** @private @type {boolean} */
this.constructor__ = true;
options = options || {};
/** @type {string} */
this.vendor = options.vendor || 'unknown';
/** @type {string} */
this.product = options.product || 'unknown';
/** @type {string} */
this.version = options.version || 'unknown';
/** @type {string} */
this.eventId = options.eventId || 'unknown';
/** @type {boolean} */
this.syslogHeader = typeof options.syslogHeader === 'boolean'
? options.syslogHeader : true;
/** @type {object} */
this.attributes = options.attributes || {
cat: null,
devTime: null,
devTimeFormat: null,
proto: null,
sev: null,
src: null,
dst: null,
srcPort: null,
dstPort: null,
srcPreNAT: null,
dstPreNAT: null,
srcPostNAT: null,
dstPostNAT: null,
usrName: null,
srcMAC: null,
dstMAC: null,
srcPreNATPort: null,
dstPreNATPort: null,
srcPostNATPort: null,
dstPostNATPort: null,
identSrc: null,
identHostName: null,
identNetBios: null,
identGrpName: null,
identMAC: null,
vSrc: null,
vSrcName: null,
accountName: null,
srcBytes: null,
dstBytes: null,
srcPackets: null,
dstPackets: null,
totalPackets: null,
role: null,
realm: null,
policy: null,
resource: null,
url: null,
groupID: null,
domain: null,
isLoginEvent: null,
isLogoutEvent: null,
identSecondlp: null,
calLanguage: null,
AttributeLimits: null,
calCountryOrRegion: null,
};
if (options.server) {
if (options.server.constructor__) {
/** @private @type {Syslog} */
this.server = options.server;
} else {
this.server = new Syslog(options.server);
}
}
}
/**
*Build a formatted message
* @public
* @return {Promise} - string with formatted message
*/
buildMessage() {
return new Promise((resolve, reject) => {
let fmtMsg = 'LEEF:2.0';
fmtMsg += '|' + this.vendor;
fmtMsg += '|' + this.product;
fmtMsg += '|' + this.version;
fmtMsg += '|' + this.eventId;
fmtMsg += '|';
// Build LEEF Attributes
const Tab = '\x09';
const leefAttribs = Object.entries(this.attributes);
const leefAttribsLen = leefAttribs.length;
for (let attrib = 0; attrib < leefAttribsLen; attrib++) {
if (leefAttribs[attrib][1] !== null) {
fmtMsg += leefAttribs[attrib][0] + '=' + leefAttribs[attrib][1] + Tab;
}
}
resolve(fmtMsg);
});
}
/**
* @public
* @param {Syslog} [options=false] - A {@link module:SyslogPro~Syslog|
* Syslog server connection} that should be used to send messages directly
* from this class. @see SyslogPro~Syslog
*/
send(options) {
return new Promise((resolve, reject) => {
this.buildMessage()
.then((result) => {
if (!this.server) {
this.server = new Syslog(options);
}
this.server.send(result)
.then((sendResult) => {
resolve(sendResult);
})
.catch((reson) => {
reject(reson);
});
});
});
}
}
/**
* A class to work with HP CEF (Common Event Format) messages. This form
* of system messages are designed to work with security systems. Messages can
* be saved to file (Saving to file if not part of this module but a CEF
* formatted message produced by this module can be saved externally to it) or
* sent via Syslog.
* Most APIs will return a promise. These APIs can be used using
* `then(...)/catch(...)`
*
* A Syslog class with a configured Syslog server target can also be used as
* the input into the formatting classes so that it may run independently. The
* CEF format is designed to send event data to a SIEM system and should not be
* as a logging stream. This class is meant to be used once per message.
* @requires moment
* @version 0.0.0
* @since 0.0.0
*/
class CEF {
/**
* Construct a new CEF formatting object with user options
* @public
* @param {object} [options] - Options object
* @param {string} [options.deviceVendor='unknown'] - The vendor of the system
* that generated the event being reported
* @param {string} [options.deviceProduct='unknown'] - The product name of the
* system that genrated the event being reported
* @param {string} [options.deviceVersion='unknown'] - The version name of the
* system that genrated the event being reported
* @param {string} [options.deviceEventClassId='unknown'] - The eventId of the
* system that genrated the event being reported
* @param {string} [options.name='unknown'] - Name of the service generating
* the notice
* @param {string} [options.severity='unknown'] - Severity of the notification
* @param {string} [options.extensions={}] - Any CEF Key=Value extensions
* @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog|
* Syslog server connection} that should be used to send messages directly
* from this class. @see SyslogPro~Syslog
*/
constructor(options) {
/** @private @type {boolean} */
this.constructor__ = true;
options = options || {};
/** @type {string} */
this.deviceVendor = options.deviceVendor || 'Unknown';
/** @type {string} */
this.deviceProduct = options.deviceProduct || 'Unknown';
/** @type {string} */
this.deviceVersion = options.deviceVersion || 'Unknown';
/** @type {string} */
this.deviceEventClassId = options.deviceEventClassId || 'Unknown';
/** @type {string} */
this.name = options.name || 'Unknown';
/** @type {string} */
this.severity = options.severity || 'Unknown';
/** @type {object} */
this.extensions = options.extensions || {
deviceAction: null,
applicationProtocol: null,
deviceCustomIPv6Address1: null,
'deviceCustomIPv6 Address1Label': null,
deviceCustomIPv6Address3: null,
'deviceCustomIPv6Address3 Label': null,
'deviceCustomIPv6 Address4': null,
'deviceCustomIPv6 Address4Label': null,
deviceEventCategory: null,
deviceCustomFloatingPoint1: null,
'deviceCustom FloatingPoint1Label': null,
deviceCustomFloatingPoint2: null,
'deviceCustomFloatingPoint2 Label': null,
deviceCustomFloatingPoint3: null,
'deviceCustom FloatingPoint3Label': null,
deviceCustomFloatingPoint4: null,
'deviceCustom FloatingPoint4Label': null,
deviceCustomNumber1: null,
deviceCustomNumber1Label: null,
DeviceCustomNumber2: null,
deviceCustomNumber2Label: null,
deviceCustomNumber3: null,
deviceCustomNumber3Label: null,
baseEventCount: null,
deviceCustomString1: null,
deviceCustomString1Label: null,
deviceCustomString2: null,
deviceCustomString2Label: null,
deviceCustomString3: null,
deviceCustomString3Label: null,
deviceCustomString4: null,
deviceCustomString4Label: null,
deviceCustomString5: null,
deviceCustomString5Label: null,
deviceCustomString6: null,
deviceCustomString6Label: null,
destinationDnsDomain: null,
destinationServiceName: null,
'destinationTranslated Address': null,
destinationTranslatedPort: null,
deviceCustomDate1: null,
deviceCustomDate1Label: null,
deviceCustomDate2: null,
deviceCustomDate2Label: null,
deviceDirection: null,
deviceDnsDomain: null,
deviceExternalId: null,
deviceFacility: null,
deviceInboundInterface: null,
deviceNtDomain: null,
deviceOutboundInterface: null,
devicePayloadId: null,
deviceProcessName: null,
deviceTranslatedAddress: null,
destinationHostName: null,
destinationMacAddress: null,
destinationNtDomain: null,
destinationProcessId: null,
destinationUserPrivileges: null,
destinationProcessName: null,
destinationPort: null,
destinationAddress: null,
deviceTimeZone: null,
destinationUserId: null,
destinationUserName: null,
deviceAddress: null,
deviceHostName: null,
deviceMacAddress: null,
deviceProcessId: null,
endTime: null,
externalId: null,
fileCreateTime: null,
fileHash: null,
fileId: null,
fileModificationTime: null,
filePath: null,
filePermission: null,
fileType: null,
flexDate1: null,
flexDate1Label: null,
flexString1: null,
flexString1Label: null,
flexString2: null,
flexString2Label: null,
filename: null,
fileSize: null,
bytesIn: null,
message: null,
oldFileCreateTime: null,
oldFileHash: null,
oldFileId: null,
oldFileModificationTime: null,
oldFileName: null,
oldFilePath: null,
oldFileSize: null,
oldFileType: null,
bytesOut: null,
eventOutcome: null,
transportProtocol: null,
Reason: null,
requestUrl: null,
requestClientApplication: null,
requestContext: null,
requestCookies: null,
requestMethod: null,
deviceReceiptTime: null,
sourceHostName: null,
sourceMacAddress: null,
sourceNtDomain: null,
sourceDnsDomain: null,
sourceServiceName: null,
sourceTranslatedAddress: null,
sourceTranslatedPort: null,
sourceProcessId: null,
sourceUserPrivileges: null,
sourceProcessName: null,
sourcePort: null,
sourceAddress: null,
startTime: null,
sourceUserId: null,
sourceUserName: null,
type: null,
agentDnsDomain: null,
agentNtDomain: null,
agentTranslatedAddress: null,
'agentTranslatedZone ExternalID': null,
agentTranslatedZoneURI: null,
agentZoneExternalID: null,
agentZoneURI: null,
agentAddress: null,
agentHostName: null,
agentId: null,
agentMacAddress: null,
agentReceiptTime: null,
agentType: null,
agentTimeZone: null,
agentVersion: null,
customerExternalID: null,
customerURI: null,
'destinationTranslated ZoneExternalID': null,
'destinationTranslated ZoneURI': null,
destinationZoneExternalID: null,
destinationZoneURI: null,
'deviceTranslatedZone ExternalID': null,
deviceTranslatedZoneURI: null,
deviceZoneExternalID: null,
deviceZoneURI: null,
destinationGeoLatitude: null,
destinationGeoLongitude: null,
eventId: null,
rawEvent: null,
sourceGeoLatitude: null,
sourceGeoLongitude: null,
'sourceTranslatedZone ExternalID': null,
sourceTranslatedZoneURI: null,
sourceZoneExternalID: null,
sourceZoneURI: null,
};
if (options.server) {
if (options.server.constructor__) {
/** @private @type {Syslog} */
this.server = options.server;
} else {
this.server = new Syslog(options.server);
}
}
}
/**
* Validate this CEF object
* @public
* @return {Promise} - True if validated
* @throws {Error} - First element to fail validation
*/
validate() {
return new Promise((resolve, reject) => {
const Extensions = {
deviceAction: {
key: 'act',
type: 'String',
len: 63,
discription: 'Action taken by the device.',
},
applicationProtocol: {
key: 'app',
type: 'String',
len: 31,
discription: 'Application level protocol, example values are HTTP, ' +
'HTTPS, SSHv2, Telnet, POP, IMPA, IMAPS, and so on.',
},
deviceCustomIPv6Address1: {
key: 'c6a1',
type: 'String',
len: null,
discription: 'One of four IPv6 address fields available to map ' +
'fields that do not apply to any other in this dictionary. ' +
'TIP: See the guidelines under “User-Defined Extensions” for ' +
'tips on using these fields.',
},
'deviceCustomIPv6 Address1Label': {
key: 'c6a1Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomIPv6Address3: {
key: 'c6a3',
type: 'String',
len: null,
discription: 'One of four IPv6 address fields available to map ' +
'fields that do not apply to any other in this dictionary. ' +
'TIP: See the guidelines under “User-Defined Extensions” for ' +
'tips on using these fields.',
},
'deviceCustomIPv6Address3 Label': {
key: 'c6a3Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
'deviceCustomIPv6 Address4': {
key: 'c6a4',
type: 'String',
len: null,
discription: 'One of four IPv6 address fields available to map ' +
'fields that do not apply to any other in this dictionary. ' +
'TIP: See the guidelines under “User-Defined Extensions” for ' +
'tips on using these fields.',
},
'deviceCustomIPv6 Address4Label': {
key: 'C6a4Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceEventCategory: {
key: 'cat',
type: 'String',
len: 1023,
discription: 'Represents the category assigned by the originating ' +
'device. Devices often use their own categorization schema to ' +
'classify event. Example: “/Monitor/Disk/Read”',
},
deviceCustomFloatingPoint1: {
key: 'cfp1',
type: 'Number',
len: null,
discription: 'One of four floating point fields available to map ' +
'fields that do not apply to any other in this dictionary.',
},
'deviceCustom FloatingPoint1Label': {
key: 'cfp1Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomFloatingPoint2: {
key: 'cfp2',
type: 'Number',
len: null,
discription: 'One of four floating point fields available to map ' +
'fields that do not apply to any other in this dictionary.',
},
'deviceCustomFloatingPoint2 Label': {
key: 'cfp2Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomFloatingPoint3: {
key: 'cfp3',
type: 'Number',
len: null,
discription: 'One of four floating point fields available to map ' +
'fields that do not apply to any other in this dictionary.',
},
'deviceCustom FloatingPoint3Label': {
key: 'cfp3Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomFloatingPoint4: {
key: 'cfp4',
type: 'Number',
len: null,
discription: 'One of four floating point fields available to map ' +
'fields that do not apply to any other in this dictionary.',
},
'deviceCustom FloatingPoint4Label': {
key: 'cfp4Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomNumber1: {
key: 'cn1',
type: 'Number',
len: null,
discription: 'One of three number fields available to map fields ' +
'that do not apply to any other in this dictionary. Use ' +
'sparingly and seek a more specific dictionary supplied field ' +
'when possible.',
},
deviceCustomNumber1Label: {
key: 'cn1Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
DeviceCustomNumber2: {
key: 'cn2',
type: 'Number',
len: null,
discription: 'One of three number fields available to map fields ' +
'that do not apply to any other in this dictionary. Use ' +
'sparingly and seek a more specific, dictionary supplied field ' +
'when possible.',
},
deviceCustomNumber2Label: {
key: 'cn2Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomNumber3: {
key: 'cn3',
type: 'Number',
len: null,
discription: 'One of three number fields available to map fields ' +
'that do not apply to any other in this dictionary. Use ' +
'sparingly and seek a more specific, dictionary supplied field ' +
'when possible.',
},
deviceCustomNumber3Label: {
key: 'cn3Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
baseEventCount: {
key: 'cnt',
type: 'Number',
len: null,
discription: 'A count associated with this event. How many times ' +
'was this same event observed? Count can be omitted if it is 1.',
},
deviceCustomString1: {
key: 'cs1',
type: 'String',
len: 4000,
discription: 'One of six strings available to map fields that do ' +
'not apply to any other in this dictionary. Use sparingly and ' +
'seek a more specific, dictionary supplied field when ' +
'possible. TIP: See the guidelines under “User-Defined ' +
'Extensions” for tips on using these fields.',
},
deviceCustomString1Label: {
key: 'cs1Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomString2: {
key: 'cs2',
type: 'String',
len: 4000,
discription: 'One of six strings available to map fields that do ' +
'not apply to any other in this dictionary. Use sparingly and ' +
'seek a more specific, dictionary supplied field when ' +
'possible. TIP: See the guidelines under “User-Defined ' +
'Extensions” for tips on using these fields.',
},
deviceCustomString2Label: {
key: 'cs2Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomString3: {
key: 'cs3',
type: 'String',
len: 4000,
discription: 'One of six strings available to map fields that do ' +
'not apply to any other in this dictionary. Use sparingly and ' +
'seek a more specific, dictionary supplied field when ' +
'possible. TIP: See the guidelines under “User-Defined ' +
'Extensions” for tips on using these fields.',
},
deviceCustomString3Label: {
key: 'cs3Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomString4: {
key: 'cs4',
type: 'String',
len: 4000,
discription: 'One of six strings available to map fields that do ' +
'not apply to any other in this dictionary. Use sparingly and ' +
'seek a more specific, dictionary supplied field when ' +
'possible. TIP: See the guidelines under “User-Defined ' +
'Extensions” for tips on using these fields.',
},
deviceCustomString4Label: {
key: 'cs4Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomString5: {
key: 'cs5',
type: 'String',
len: 4000,
discription: 'One of six strings available to map fields that do ' +
'not apply to any other in this dictionary. Use sparingly and ' +
'seek a more specific, dictionary supplied field when ' +
'possible. TIP: See the guidelines under “User-Defined ' +
'Extensions” for tips on using these fields.',
},
deviceCustomString5Label: {
key: 'cs5Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomString6: {
key: 'cs6',
type: 'String',
len: 4000,
discription: 'One of six strings available to map fields that do ' +
'not apply to any other in this dictionary. Use sparingly and ' +
'seek a more specific, dictionary supplied field when ' +
'possible. TIP: See the guidelines under “User-Defined ' +
'Extensions” for tips on using these fields.',
},
deviceCustomString6Label: {
key: 'cs6Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
destinationDnsDomain: {
key: 'destination DnsDomain',
type: 'String',
len: 255,
discription: 'The DNS domain part of the complete fully qualified ' +
'domain name (FQDN).',
},
destinationServiceName: {
key: 'destination ServiceName',
type: 'String',
len: 1023,
discription: 'The service targeted by this event. Example: “sshd”',
},
'destinationTranslated Address': {
key: 'Destination Translated Address',
type: 'String',
len: null,
discription: 'Identifies the translated destination that the event ' +
'refers to in an IP network. The format is an IPv4 address. ' +
'Example: “192.168.10.1”',
},
destinationTranslatedPort: {
key: 'Destination TranslatedPort',
type: 'Number',
len: null,
discription: 'Port after it was translated; for example, a ' +
'firewall. Valid port numbers are 0 to 65535.',
},
deviceCustomDate1: {
key: 'deviceCustom Date1',
type: 'String',
len: null,
discription: 'One of two timestamp fields available to map fields ' +
'that do not apply to any other in this dictionary. Use ' +
'sparingly and seek a more specific, dictionary supplied field ' +
'when possible. TIP: See the guidelines under “User-Defined ' +
'Extensions” for tips on using these fields.',
},
deviceCustomDate1Label: {
key: 'deviceCustom Date1Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceCustomDate2: {
key: 'deviceCustom Date2',
type: 'String',
len: null,
discription: 'One of two timestamp fields available to map fields ' +
'that do not apply to any other in this dictionary. Use ' +
'sparingly and seek a more specific, dictionary supplied field ' +
'when possible. TIP: See the guidelines under “User-Defined ' +
'Extensions” for tips on using these fields.',
},
deviceCustomDate2Label: {
key: 'deviceCustom Date2Label',
type: 'String',
len: 1023,
discription: 'All custom fields have a corresponding label field. ' +
'Each of these fields is a string and describes the purpose of ' +
'the custom field.',
},
deviceDirection: {
key: 'deviceDirection',
type: 'Number',
len: null,
discription: 'Any information about what direction the observed ' +
'communication has taken. The following values are supported: ' +
'“0” for inbound or “1” for outbound',
},
deviceDnsDomain: {
key: 'deviceDns Domain',
type: 'String',
len: 255,
discription: 'The DNS domain part of the complete fully qualified ' +
'domain name (FQDN).',
},
deviceExternalId: {
key: 'device ExternalId',
type: 'String',
len: 255,
discription: 'A name that uniquely identifies the device ' +
'generating this event.',
},
deviceFacility: {
key: 'deviceFacility',
type: 'String',
len: 1023,
discription: 'The facility generating this event. For example, ' +
'Syslog has an explicit facility associated with every event.',
},
deviceInboundInterface: {
key: 'deviceInbound Interface',
type: 'String',
len: 128,
discription: 'Interface on which the packet or data entered the ' +
'device.',
},
deviceNtDomain: {
key: 'deviceNt Domain',
type: 'String',
len: 255,
discription: 'The Windows domain name of the device address.',
},
deviceOutboundInterface: {
key: 'Device Outbound Interface',
type: 'String',
len: 128,
discription: 'Interface on which the packet or data left the ' +
'device.',
},
devicePayloadId: {
key: 'Device PayloadId',
type: 'String',
len: 128,
discription: 'Unique identifier for the payload associated with ' +
'the event.',
},
deviceProcessName: {
key: 'deviceProcess Name',
type: 'String',
len: 1023,
discription: 'Process name associated with the event. An example ' +
'might be the process generating the syslog entry in UNIX.',
},
deviceTranslatedAddress: {
key: 'device Translated Address',
type: 'String',
len: null,
discription: 'Identifies the translated device address that the ' +
'event refers to in an IP network. The format is an IPv4 ' +
'address. Example: “192.168.10.1”',
},
destinationHostName: {
key: 'dhost',
type: 'String',
len: 1023,
discription: 'Identifies the destination that an event refers to ' +
'in an IP network. The format should be a fully qualified ' +
'domain name (FQDN) associated with the destination node, when ' +
'a node is available. Examples: “host.domain.com” or “host”.',
},
destinationMacAddress: {
key: 'dmac',
type: 'String',
len: null,
discription: 'Six colon-seperated hexadecimal numbers. Example: ' +
'“00:0D:60:AF:1B:61”',
},
destinationNtDomain: {
key: 'dntdom',
type: 'String',
len: 255,
discription: 'The Windows domain name of the destination address.',
},
destinationProcessId: {
key: 'dpid',
type: 'Number',
len: null,
discription: 'Provides the ID of the destination process ' +
'associated with the event. For example, if an event contains ' +
'process ID 105, 105” is the process ID.',
},
destinationUserPrivileges: {
key: 'dpriv',
type: 'String',
len: 1023,
discription: 'The typical values are “Administrator”, “User”, and ' +
'“Guest”. This identifies the destination user’s privileges. ' +
'In UNIX, for example, activity executed on the root user ' +
'would be identified with destinationUser Privileges of ' +
'“Administrator”.',
},
destinationProcessName: {
key: 'dproc',
type: 'String',
len: 1023,
discription: 'The name of the event’s destination process. ' +
'Example: “telnetd” or “sshd”.',
},
destinationPort: {
key: 'dpt',
type: 'Number',
len: null,
discription: 'The valid port numbers are between 0 and 65535.',
},
destinationAddress: {
key: 'dst',
type: 'String',
len: null,
discription: 'Identifies the destination address that the event ' +
'refers to in an IP network. The format is an IPv4 address. ' +
'Example: “192.168.10.1”',
},
deviceTimeZone: {
key: 'dtz',
type: 'String',
len: 255,
discription: 'The timezone for the device generating the event.',
},
destinationUserId: {
key: 'duid',
type: 'String',
len: 1023,
discription: 'Identifies the destination user by ID. For example, ' +
'in UNIX, the root user is generally associated with user ' +
'ID 0.',
},
destinationUserName: {
key: 'duser',
type: 'String',
len: 1023,
discription: 'Identifies the destination user by name. This is the ' +
'user associated with the event’s destination. Email addresses ' +
'are often mapped into the UserName fields. The recipient is a ' +
'candidate to put into this field.',
},
deviceAddress: {
key: 'dvc',
type: 'String',
len: null,
discription: 'Identifies the device address that an event refers ' +
'to in an IP network. The format is an IPv4 address. Example: ' +
'“192.168.10.1”.',
},
deviceHostName: {
key: 'dvchost',
type: 'String',
len: 100,
discription: 'The format should be a fully qualified domain name ' +
'(FQDN) associated with the device node, when a node is ' +
'available. Example: “host.domain.com” or “host”.',
},
deviceMacAddress: {
key: 'dvcmac',
type: 'String',
len: null,
discription: 'Six colon-separated hexadecimal numbers. Example: ' +
'“00:0D:60:AF:1B:61”',
},
deviceProcessId: {
key: 'dvcpid',
type: 'Number',
len: null,
discription: 'Provides the ID of the process on the device ' +
'generating the event.',
},
endTime: {
key: 'end',
type: 'String',
len: null,
discription: 'The time at which the activity related to the event ' +
'ended. The format is MMM dd yyyy HH:mm:ss or milliseconds ' +
'since epoch (Jan 1st1970). An example would be reporting the ' +
'end of a session.',
},
externalId: {
key: 'externalId',
type: 'String',
len: 40,
discription: 'The ID used by an originating device. They are ' +
'usually increasing numbers, associated with events.',
},
fileCreateTime: {
key: 'fileCreateTime',
type: 'String',
len: null,
discription: 'Time when the file was created.',
},
fileHash: {
key: 'fileHash',
type: 'String',
len: 255,
discription: 'Hash of a file.',
},
fileId: {
key: 'fileId',
type: 'String',
len: 1023,
discription: 'An ID associated with a file could be the inode.',
},
fileModificationTime: {
key: 'fileModification Time',
type: 'String',
len: null,
discription: 'Time when the file was last modified.',
},
filePath: {
key: 'filePath',
type: 'String',
len: 1023,
discription: 'Full path to the file, including file name itself. ' +
'Example: C:\Program Files \WindowsNT\Accessories\ wordpad.exe ' +
'or /usr/bin/zip',
},
filePermission: {
key: 'filePermission',
type: 'String',
len: 1023,
discription: 'Permissions of the file.',
},
fileType: {
key: 'fileType',
type: 'String',
len: 1023,
discription: 'Type of file (pipe, socket, etc.)',
},
flexDate1: {
key: 'flexDate1',
type: 'String',
len: null,
discription: 'A timestamp field available to map a timestamp that ' +
'does not apply to any other defined timestamp field in this ' +
'dictionary. Use all flex fields sparingly and seek a more ' +
'specific, dictionary supplied field when possible. These ' +
'fields are typically reserved for customer use and should not ' +
'be set by vendors unless necessary.',
},
flexDate1Label: {
key: 'flexDate1Label',
type: 'String',
len: 128,
discription: 'The label field is a string and describes the ' +
'purpose of the flex field.',
},
flexString1: {
key: 'flexString1',
type: 'String',
len: 1023,
discription: 'One of four floating point fields available to map ' +
'fields that do not apply to any other in this dictionary. Use ' +
'sparingly and seek a more specific, dictionary supplied field ' +
'when possible. These fields are typically reserved for ' +
'customer use and should not be set by vendors unless ' +
'necessary.',
},
flexString1Label: {
key: 'flexString1 Label',
type: 'String',
len: 128,
discription: 'The label field is a string and describes the ' +
'purpose of the flex field.',
},
flexString2: {
key: 'flexString2',
type: 'String',
len: 1023,
discription: 'One of four floating point fields available to map ' +
'fields that do not apply to any other in this dictionary. Use ' +
'sparingly and seek a more specific, dictionary supplied field ' +
'when possible. These fields are typically reserved for ' +
'customer use and should not be set by vendors unless ' +
'necessary.',
},
flexString2Label: {
key: 'flex String2Label',
type: 'String',
len: 128,
discription: 'The label field is a string and describes the ' +
'purpose of the flex field.',
},
filename: {
key: 'fname',
type: 'String',
len: 1023,
discription: 'Name of the file only (without its path).',
},
fileSize: {
key: 'fsize',
type: 'Number',
len: null,
discription: 'Size of the file.',
},
bytesIn: {
key: 'in',
type: 'Number',
len: null,
discription: 'Number of bytes transferred inbound, relative to the ' +
'source to destination relationship, meaning that data was ' +
'flowing from source to destination.',
},
message: {
key: 'msg',
type: 'String',
len: 1023,
discription: 'An arbitrary message giving more details about the ' +
'event. Multi-line entries can be produced by using \n as the ' +
'new line separator.',
},
oldFileCreateTime: {
key: 'oldFileCreate Time',
type: 'String',
len: null,
discription: 'Time when old file was created.',
},
oldFileHash: {
key: 'oldFileHash',
type: 'String',
len: 255,
discription: 'Hash of the old file.',
},
oldFileId: {
key: 'oldFileId',
type: 'String',
len: 1023,
discription: 'An ID associated with the old file could be the ' +
'inode.',
},
oldFileModificationTime: {
key: 'oldFile Modification Time',
type: 'String',
len: null,
discription: 'Time when old file was last modified.',
},
oldFileName: {
key: 'oldFileName',
type: 'String',
len: 1023,
discription: 'Name of the old file.',
},
oldFilePath: {
key: 'oldFilePath',
type: 'String',
len: 1023,
discription: 'Full path to the old fiWindowsNT\\Accessories le, ' +
'including the file name itself. Examples: c:\\Program ' +
'Files\\wordpad.exe or /usr/bin/zip',
},
oldFileSize: {
key: 'oldFileSize',
type: 'Number',
len: null,
discription: 'Size of the old file.',
},
oldFileType: {
key: 'oldFileType',
type: 'String',
len: 1023,
discription: 'Type of the old file (pipe, socket, etc.)',
},
bytesOut: {
key: 'out',
type: 'Number',
len: null,
discription: 'Number of bytes transferred outbound relative to the ' +
'source to destination relationship. For example, the byte ' +
'number of data flowing from the destination to the source.',
},
eventOutcome: {
key: 'outcome',
type: 'String',
len: 63,
discription: 'Displays the outcome, usually as ‘success’ or ' +
'‘failure’.',
},
transportProtocol: {
key: 'proto',
type: 'String',
len: 31,
discription: 'Identifies the Layer-4 protocol used. The possible ' +
'values are protocols such as TCP or UDP.',
},
Reason: {
key: 'reason',
type: 'String',
len: 1023,
discription: 'The reason an audit event was generated. For ' +
'example “badd password” or “unknown user”. This could also be ' +
'an error or return code. Example: “0x1234”',
},
requestUrl: {
key: 'request',
type: 'String',
len: 1023,
discription: 'In the case of an HTTP request, this field contains ' +
'the URL accessed. The URL should contain the protocol as ' +
'well. Example: “http://www/secure.com”',
},
requestClientApplication: {
key: 'requestClient Application',
type: 'String',
len: 1023,
discription: 'The User-Agent associated with the request.',
},
requestContext: {
key: 'requestContext',
type: 'String',
len: 2048,
discription: 'Description of the content from which the request ' +
'originated (for example, HTTP Referrer)',
},
requestCookies: {
key: 'requestCookies',
type: 'String',
len: 1023,
discription: 'Cookies associated with the request.',
},
requestMethod: {
key: 'requestMethod',
type: 'String',
len: 1023,
discription: 'The method used to access a URL. Possible values: ' +
'“POST”, “GET”, etc.',
},
deviceReceiptTime: {
key: 'rt',
type: 'String',
len: null,
discription: 'The time at which the event related to the activity ' +
'was received. The format is MMM dd yyyy HH:mm:ss or ' +
'milliseconds since epoch (Jan 1st 1970)',
},
sourceHostName: {
key: 'shost',
type: 'String',
len: 1023,
discription: 'Identifies the source that an event refers to in an ' +
'IP network. The format should be a fully qualified domain ' +
'name (DQDN) associated with the source node, when a mode is ' +
'available. Examples: “host” or “host.domain.com”.',
},
sourceMacAddress: {
key: 'smac',
type: 'String',
len: null,
discription: 'Six colon-separated hexadecimal numbers. Example: ' +
'“00:0D:60:AF:1B:61”',
},
sourceNtDomain: {
key: 'sntdom',
type: 'String',
len: 255,
discription: 'The Windows domain name for the source address.',
},
sourceDnsDomain: {
key: 'sourceDns Domain',
type: 'String',
len: 255,
discription: 'The DNS domain part of the complete fully qualified ' +
'domain name (FQDN).',
},
sourceServiceName: {
key: 'source ServiceName',
type: 'String',
len: 1023,
discription: 'The service that is responsible for generating this ' +
'event.',
},
sourceTranslatedAddress: {
key: 'source Translated Address',
type: 'String',
len: null,
discription: 'Identifies the translated source that the event ' +
'refers to in an IP network. The format is an IPv4 address. ' +
'Example: “192.168.10.1”.',
},
sourceTranslatedPort: {
key: 'source TranslatedPort',
type: 'Number',
len: null,
discription: 'A port number after being translated by, for ' +
'example, a firewall. Valid port numbers are 0 to 65535.',
},
sourceProcessId: {
key: 'spid',
type: 'Number',
len: null,
discription: 'The ID of the source process associated with the ' +
'event.',
},
sourceUserPrivileges: {
key: 'spriv',
type: 'String',
len: 1023,
discription: 'The typical values are “Administrator”, “User”, and ' +
'“Guest”. It identifies the source user’s privileges. In UNIX, ' +
'for example, activity executed by the root user would be ' +
'identified with “Administrator”.',
},
sourceProcessName: {
key: 'sproc',
type: 'String',
len: 1023,
discription: 'The name of the event’s source process.',
},
sourcePort: {
key: 'spt',
type: 'Number',
len: null,
discription: 'The valid port numbers are 0 to 65535.',
},
sourceAddress: {
key: 'src',
type: 'String',
len: null,
discription: 'Identifies the source that an event refers to in an ' +
'IP network. The format is an IPv4 address. Example: ' +
'“192.168.10.1”.',
},
startTime: {
key: 'start',
type: 'String',
len: null,
discription: 'The time when the activity the event referred to ' +
'started. The format is MMM dd yyyy HH:mm:ss or milliseconds ' +
'since epoch (Jan 1st 1970)',
},
sourceUserId: {
key: 'suid',
type: 'String',
len: 1023,
discription: 'Identifies the source user by ID. This is the user ' +
'associated with the source of the event. For example, in ' +
'UNIX, the root user is generally associated with user ID 0.',
},
sourceUserName: {
key: 'suser',
type: 'String',
len: 1023,
discription: 'Identifies the source user by name. Email addresses ' +
'are also mapped into the UserName fields. The sender is a ' +
'candidate to put into this field.',
},
type: {
key: 'type',
type: 'Number',
len: null,
discription: '0 means base event, 1 means aggregated, 2 means ' +
'correlation, and 3 means action. This field can be omitted ' +
'for base events (type 0).',
},
agentDnsDomain: {
key: 'agentDns Domain',
type: 'String',
len: 255,
discription: 'The DNS domain name of the ArcSight connector that ' +
'processed the event.',
},
agentNtDomain: {
key: 'agentNtDomain',
type: 'String',
len: 255,
discription: '',
},
agentTranslatedAddress: {
key: 'agentTranslated Address',
type: 'String',
len: null,
discription: '',
},
'agentTranslatedZone ExternalID': {
key: 'agentTranslated ZoneExternalID',
type: 'String',
len: 200,
discription: '',
},
agentTranslatedZoneURI: {
key: 'agentTranslated Zone URI',
type: 'String',
len: 2048,
discription: '',
},
agentZoneExternalID: {
key: 'agentZone ExternalID',
type: 'String',
len: 200,
discription: '',
},
agentZoneURI: {
key: 'agentZoneURI',
type: 'String',
len: 2048,
discription: '',
},
agentAddress: {
key: 'agt',
type: 'String',
len: null,
discription: 'The IP address of the ArcSight connector that ' +
'processed the event.',
},
agentHostName: {
key: 'ahost',
type: 'String',
len: 1023,
discription: 'The hostname of the ArcSight connector that ' +
'processed the event.',
},
agentId: {
key: 'aid',
type: 'String',
len: 40,
discription: 'The agent ID of the ArcSight connector that ' +
'processed the event.',
},
agentMacAddress: {
key: 'amac',
type: 'String',
len: null,
discription: 'The MAC address of the ArcSight connector that ' +
'processed the event.',
},
agentReceiptTime: {
key: 'art',
type: 'String',
len: null,
discription: 'The time at which information about the event was ' +
'received by the ArcSight connector.',
},
agentType: {
key: 'at',
type: 'String',
len: 63,
discription: 'The agent type of the ArcSight connector that ' +
'processed the event',
},
agentTimeZone: {
key: 'atz',
type: 'String',
len: 255,
discription: 'The agent time zone of the ArcSight connector that ' +
'processed the event.',
},
agentVersion: {
key: 'av',
type: 'String',
len: 31,
discription: 'The version of the ArcSight connector that processed ' +
'the event.',
},
customerExternalID: {
key: 'customer ExternalID',
type: 'String',
len: 200,
discription: '',
},
customerURI: {
key: 'customerURI',
type: 'String',
len: 2048,
discription: '',
},
'destinationTranslated ZoneExternalID': {
key: 'destination TranslatedZone ExternalID',
type: 'String',
len: 200,
discription: '',
},
'destinationTranslated ZoneURI': {
key: 'destination Translated ZoneURI',
type: 'String',
len: 2048,
discription: 'The URI for the Translated Zone that the destination ' +
'asset has been assigned to in ArcSight.',
},
destinationZoneExternalID: {
key: 'destinationZone ExternalID',
type: 'String',
len: 200,
discription: '',
},
destinationZoneURI: {
key: 'destinationZone URI',
type: 'String',
len: 2048,
discription: 'The URI for the Zone that the destination asset has ' +
'been assigned to in ArcSight.',
},
'deviceTranslatedZone ExternalID': {
key: 'device TranslatedZone ExternalID',
type: 'String',
len: 200,
discription: '',
},
deviceTranslatedZoneURI: {
key: 'device TranslatedZone URI',
type: 'String',
len: 2048,
discription: 'The URI for the Translated Zone that the device ' +
'asset has been assigned to in ArcSight.',
},
deviceZoneExternalID: {
key: 'deviceZone ExternalID',
type: 'String',
len: 200,
discription: '',
},
deviceZoneURI: {
key: 'deviceZoneURI',
type: 'String',
len: 2048,
discription: 'Thee URI for the Zone that the device asset has been ' +
'assigned to in ArcSight.',
},
destinationGeoLatitude: {
key: 'dlat',
type: 'Number',
len: null,
discription: 'The latitudinal value from which the ' +
'destination’s IP address belongs.',
},
destinationGeoLongitude: {
key: 'dlong',
type: 'Number',
len: null,
discription: 'The longitudinal value from which the destination’s ' +
'IP address belongs.',
},
eventId: {
key: 'eventId',
type: 'Number',
len: null,
discription: 'This is a unique ID that ArcSight assigns to each ' +
'event.',
},
rawEvent: {
key: 'rawEvent',
type: 'String',
len: 4000,
discription: '',
},
sourceGeoLatitude: {
key: 'slat',
type: 'Number',
len: null,
discription: '',
},
sourceGeoLongitude: {
key: 'slong',
type: 'Number',
len: null,
discription: '',
},
'sourceTranslatedZone ExternalID': {
key: 'source TranslatedZone ExternalID',
type: 'String',
len: 200,
discription: '',
},
sourceTranslatedZoneURI: {
key: 'source TranslatedZone URI',
type: 'String',
len: 2048,
discription: 'The URI for the Translated Zone that the destination ' +
'asset has been assigned to in ArcSight.',
},
sourceZoneExternalID: {
key: 'sourceZone ExternalID',
type: 'String',
len: 200,
discription: '',
},
sourceZoneURI: {
key: 'sourceZoneURI',
type: 'String',
len: 2048,
discription: 'The URI for the Zone that the source asset has been ' +
'assigned to in ArcSight.' },
};
if (typeof this.deviceVendor !== 'string'
|| typeof this.deviceProduct !== 'string'
|| typeof this.deviceVersion !== 'string'
) {
reject(new Error('TYPE ERROR: CEF Device Info must be a string'));
}
if (this.severity
&& (
(
typeof this.severity === 'string'
&& (
this.severity !== 'Unknown'
&& this.severity !== 'Low'
&& this.severity !== 'Medium'
&& this.severity !== 'High'
&& this.severity !== 'Very-High'
)
)
|| (
typeof this.severity === 'number'
&& (
this.severity < 0
|| this.severity > 10
)
)
)
) {
reject(new Error('TYPE ERROR: CEF Severity not set correctly'));
}
const cefExts = Object.entries(this.extensions);
const cefExtsLen = cefExts.length;
for (let ext = 0; ext < cefExtsLen; ext++) {
if (cefExts[ext][1] !== null) {
if (Extensions[cefExts[ext][0]]) {
if (typeof cefExts[ext][1] === Extensions[cefExts[ext][0]]
.type
.toLowerCase()) {
if (Extensions[cefExts[ext][0]].len > 0
&& typeof cefExts[ext][1] === 'string'
&& cefExts[ext][1].length > Extensions[cefExts[ext][0]].len){
let errMsg = 'FORMAT ERROR:';
errMsg += ' CEF Extention Key';
errMsg += ' ' + cefExts[ext][0];
errMsg += ' value length is to long;';
errMsg += ' max length is';
errMsg += ' ' + Extensions[cefExts[ext][0]].len;
reject(new Error(errMsg));
}
} else {
let errMsg = 'TYPE ERROR:';
errMsg += ' CEF Key';
errMsg += ' ' + cefExts[ext][0];
errMsg += ' value type was expected to be';
errMsg += ' ' + Extensions[cefExts[ext][0]].type.toLowerCase();
reject(new Error(errMsg));
}
}
}
}
resolve(true);
});
}
/**
* Build a CEF formated string
* @public
* @return {Promise} - String with formated message
*/
buildMessage() {
return new Promise((resolve,
reject) => {
let fmtMsg = 'CEF:0';
fmtMsg += '|' + this.deviceVendor;
fmtMsg += '|' + this.deviceProduct;
fmtMsg += '|' + this.deviceVersion;
fmtMsg += '|' + this.deviceEventClassId;
fmtMsg += '|' + this.name;
fmtMsg += '|' + this.severity;
fmtMsg += '|';
const cefExts = Object.entries(this.extensions);
const cefExtsLen = cefExts.length;
for (let ext = 0; ext < cefExtsLen; ext++) {
if (cefExts[ext][1] !== null) {
fmtMsg += cefExts[ext][0] + '=' + cefExts[ext][1] + ' ';
}
}
resolve(fmtMsg);
});
}
/**
* @public
* @param {Syslog} [options=false] - A {@link module:SyslogPro~Syslog|
* Syslog server connection} that should be used to send messages directly
* from this class. @see SyslogPro~Syslog
*/
send(options) {
return new Promise((resolve,
reject) => {
this.buildMessage()
.then((result) => {
if (!this.server) {
this.server = new Syslog(options);
}
this.server.send(result)
.then((sendResult) => {
resolve(sendResult);
})
.catch((reson) => {
reject(reson);
});
});
});
}
}
module.exports = {
RgbToAnsi: rgbToAnsi,
RFC3164: RFC3164,
RFC5424: RFC5424,
LEEF: LEEF,
CEF: CEF,
Syslog: Syslog,
};