bifocal/node_modules/node-loggly-bulk/lib/loggly/client.js

293 lines
8.4 KiB
JavaScript

/*
* client.js: Core client functions for accessing Loggly
*
* (C) 2010 Charlie Robbins
* MIT LICENSE
*
*/
//
// Setting constant value of EVENT_SIZE variable
//
var EVENT_SIZE = 1000 * 1000;
var events = require('events'),
util = require('util'),
qs = require('querystring'),
common = require('./common'),
loggly = require('../loggly'),
Search = require('./search').Search,
stringifySafe = require('json-stringify-safe');
function stringify(msg) {
var payload;
try { payload = JSON.stringify(msg) }
catch (ex) { payload = stringifySafe(msg, null, null, noop) }
return payload;
}
//
// function to truncate message over 1 MB
//
function truncateLargeMessage(message) {
var maximumBytesAllowedToLoggly = EVENT_SIZE;
var bytesLengthOfLogMessage = Buffer.byteLength(message);
var isMessageTruncated = false;
if(bytesLengthOfLogMessage > maximumBytesAllowedToLoggly) {
message = message.slice(0, maximumBytesAllowedToLoggly);
isMessageTruncated = true;
}
return {
message: message,
isMessageTruncated: isMessageTruncated
};
}
//
// function createClient (options)
// Creates a new instance of a Loggly client.
//
exports.createClient = function (options) {
return new Loggly(options);
};
//
// ### function Loggly (options)
// #### @options {Object} Options for this Loggly client
// #### @subdomain
// #### @token
// #### @json
// #### @auth
// #### @tags
// Constructor for the Loggly object
//
var Loggly = exports.Loggly = function (options) {
if (!options || !options.subdomain || !options.token) {
throw new Error('options.subdomain and options.token are required.');
}
events.EventEmitter.call(this);
this.subdomain = options.subdomain;
this.token = options.token;
this.host = options.host || 'logs-01.loggly.com';
this.json = options.json || null;
this.auth = options.auth || null;
this.proxy = options.proxy || null;
this.userAgent = 'node-loggly ' + loggly.version;
this.useTagHeader = 'useTagHeader' in options ? options.useTagHeader : true;
this.isBulk = options.isBulk || false;
this.bufferOptions = options.bufferOptions || {size: 500, retriesInMilliSeconds: 30 * 1000};
this.networkErrorsOnConsole = options.networkErrorsOnConsole || false;
//
// Set the tags on this instance.
//
this.tags = options.tags
? this.tagFilter(options.tags)
: null;
var url = 'https://' + this.host,
api = options.api || 'apiv2';
this.urls = {
default: url,
log: [url, 'inputs', this.token].join('/'),
bulk: [url, 'bulk', this.token].join('/'),
api: 'https://' + [this.subdomain, 'loggly', 'com'].join('.') + '/' + api
};
};
//
// Inherit from events.EventEmitter
//
util.inherits(Loggly, events.EventEmitter);
//
// ### function log (msg, tags, callback)
// #### @msg {string|Object} Data to log
// #### @tags {Array} **Optional** Tags to send with this msg
// #### @callback {function} Continuation to respond to when complete.
// Logs the message to the token associated with this instance. If
// the message is an Object we will attempt to serialize it. If any
// `tags` are supplied they will be passed via the `X-LOGGLY-TAG` header.
// - http://www.loggly.com/docs/api-sending-data/
//
Loggly.prototype.log = function (msg, tags, callback) {
// typeof msg is string when we are using node-loggly-bulk to send logs.
// If we are sending logs using winston-loggly-bulk, msg is object.
// Check if 'msg' is an object, if yes then stringify it to truncate it over 1MB.
var truncatedMessageObject = null;
if(typeof(msg) === 'object'){
var stringifiedMessage = JSON.stringify(msg)
truncatedMessageObject = truncateLargeMessage(stringifiedMessage);
msg = truncatedMessageObject.isMessageTruncated ? truncatedMessageObject.message : msg;
}else if (typeof(msg) === 'string') {
truncatedMessageObject = truncateLargeMessage(msg);
msg = truncatedMessageObject.isMessageTruncated ? truncatedMessageObject.message : msg;
}
if (!callback && typeof tags === 'function') {
callback = tags;
tags = null;
}
var self = this,
logOptions;
//
// Remark: Have some extra logic for detecting if we want to make a bulk
// request to loggly
//
function serialize(msg) {
if (msg instanceof Object) {
return self.json ? stringify(msg) : common.serialize(msg);
}
else {
return self.json ? stringify({ message: msg }) : msg;
}
}
msg = this.isBulk && Array.isArray(msg) ? msg.map(serialize) : serialize(msg);
logOptions = {
uri: this.isBulk ? this.urls.bulk : this.urls.log,
method: 'POST',
body: msg,
proxy: this.proxy,
isBulk: this.isBulk,
bufferOptions: this.bufferOptions,
networkErrorsOnConsole: this.networkErrorsOnConsole,
headers: {
host: this.host,
accept: '*/*',
'user-agent': this.userAgent,
'content-type': this.json ? 'application/json' : 'text/plain'
}
};
//
// Remark: if tags are passed in run the filter on them and concat
// with any tags that were passed or just use default tags if they exist
//
tags = tags
? (this.tags ? this.tags.concat(this.tagFilter(tags)) : this.tagFilter(tags))
: this.tags;
//
// Optionally send `X-LOGGLY-TAG` if we have them.
// Set the 'X-LOGGLY-TAG' only when we have actually some tag value.
// The library receives "400 Bad Request" in response when the
// value of 'X-LOGGLY-TAG' is empty string in request header.
//
if (tags && tags.length) {
// Decide whether to add tags as http headers or add them to the URI.
if (this.useTagHeader) {
logOptions.headers['X-LOGGLY-TAG'] = tags.join(',');
}
else {
logOptions.uri += '/tag/' + tags.join(',') + '/';
}
}
common.loggly(logOptions, callback, function (res, body) {
try {
if(body && res.statusCode.toString() === '200'){
var result = JSON.parse(body);
self.emit('log', result);
if (callback) {
callback(null, result);
}
}
else
console.log('Error Code- ' + res.statusCode + ' "' + res.statusMessage + '"');
}
catch (ex) {
if (callback) {
callback(new Error('Unspecified error from Loggly: ' + ex));
}
}
});
return this;
};
//
// ### function tag (tags)
// #### @tags {Array} Tags to use for `X-LOGGLY-TAG`
// Sets the tags on this instance
//
Loggly.prototype.tagFilter = function (tags) {
var isSolid = /^[\w\d][\w\d-_.]+/;
tags = !Array.isArray(tags)
? [tags]
: tags;
//
// TODO: Filter against valid tag names with some Regex
// http://www.loggly.com/docs/tags/
// Remark: Docs make me think we dont need this but whatevs
//
return tags.filter(function (tag) {
//
// Remark: length may need to use Buffer.byteLength?
//
return tag && isSolid.test(tag) && tag.length <= 64;
});
};
//
// ### function customer (callback)
// ### @callback {function} Continuation to respond to.
// Retrieves the customer information from the Loggly API:
// - http://www.loggly.com/docs/api-account-info/
//
Loggly.prototype.customer = function (callback) {
common.loggly({
uri: this.logglyUrl('customer'),
auth: this.auth
}, callback, function (res, body) {
var customer;
try { customer = JSON.parse(body) }
catch (ex) { return callback(ex) }
callback(null, customer);
});
};
//
// function search (query, callback)
// Returns a new search object which can be chained
// with options or called directly if @callback is passed
// initially.
//
// Sample Usage:
//
// client.search('404', function () { /* ... */ })
// .on('rsid', function (rsid) { /* ... */ })
//
// client.search({ query: '404', rows: 100 })
// .on('rsid', function (rsid) { /* ... */ })
// .run(function () { /* ... */ });
//
Loggly.prototype.search = function (query, callback) {
var options = typeof query === 'string'
? { query: query }
: query;
options.callback = callback;
return new Search(options, this);
};
//
// function logglyUrl ([path, to, resource])
// Helper method that concats the string params into a url
// to request against a loggly serverUrl.
//
Loggly.prototype.logglyUrl = function (/* path, to, resource */) {
var args = Array.prototype.slice.call(arguments);
return [this.urls.api].concat(args).join('/');
};
//
// Simple noop function for reusability
//
function noop() {}