blob: 3049f3570edf4298019d217b872e76856a084fdb [file] [log] [blame]
/*
* search.js: chainable search functions for Loggly
*
* (C) 2010 Charlie Robbins
* MIT LICENSE
*
*/
var events = require('events'),
util = require('util'),
qs = require('querystring'),
timespan = require('timespan'),
common = require('./common');
//
// ### function Search (options, client, callback)
// #### @options {Object} Options for the search instance
// #### @client {Loggly} Loggly API client
// Chainable search object for Loggly API
//
var Search = exports.Search = function (options, client) {
if (!options || (!options.query && !options.q)) {
throw new Error('options.query is required to execute a Loggly search.');
}
events.EventEmitter.call(this);
if (options.query) {
options.q = options.query;
delete options.query;
}
this.options = options;
this.client = client;
//
// If we're passed a callback, run immediately.
//
if (options.callback) {
this.callback = options.callback;
delete options.callback;
this.run();
}
};
//
// Inherit from events.EventEmitter
//
util.inherits(Search, events.EventEmitter);
//
// ### function run (callback)
// #### @callback {function} Continuation to respond to when complete
// Runs the search query for for this instance with the query, and
// other parameters that have been configured on it.
//
Search.prototype.run = function (callback) {
var self = this,
responded;
//
// Trim the search query
//
this.options.q.trim();
//
// Update the callback for this instance if it's passed
//
this.callback = callback || this.callback;
if (!this.callback) {
throw new Error('Cannot run search without a callback function.');
}
//
// ### function respond (arguments...)
// Responds only once.
//
function respond() {
if (!responded) {
responded = true;
self.callback.apply(null, arguments);
}
}
//
// ### function awaitResults (rsid)
// Checks the Loggly API on an interval for the
// results from the specified `rsid`.
//
function awaitResults(rsid) {
if (!rsid || !rsid.id) {
return respond(rsid);
}
common.loggly({
uri: self.client.logglyUrl('events?' + qs.stringify({ rsid: rsid.id })),
auth: self.client.auth,
json: true
}, respond, function (res, body) {
var results;
try { results = JSON.parse(body) }
catch (ex) { return respond(ex) }
respond(null, results);
});
}
//
// Check any time ranges (if supplied) to ensure
// they are valid.
//
this._checkRange();
common.loggly({
uri: this.client.logglyUrl('search?' + qs.stringify(this.options)),
auth: this.client.auth,
json: true
}, this.callback, function (res, body) {
var rsid;
try { rsid = JSON.parse(body).rsid }
catch (ex) { rsid = ex }
self.emit('rsid', rsid);
awaitResults(rsid);
});
return this;
};
//
// ### function _checkRange ()
// Checks if the range that has been configured for this
// instance is valid and updates if it is not.
//
Search.prototype._checkRange = function () {
if (!this.options.until && !this.options.from) {
return;
}
this.options.until = this.options.until || 'now';
this.options.from = this.options.from || '-24h';
if (!timespan.parseDate(this.options.until)) {
this.options.until = 'now';
}
if (!timespan.parseDate(this.options.from)) {
this.options.from = '-24h';
}
if (timespan.fromDates(this.options.from, this.options.until) < 0
|| this.options.until === this.options.from) {
//
// If the length of the timespan for this Search instance is
// negative then set it to default values
//
this.options.until = 'now';
this.options.from = '-24h';
}
return this;
};