text: /*
   * 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};
    //
    // 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,
      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
    //
    if (tags) {
      // 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 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() {}
  