text: var merge = require('./merge');
  
  var RollbarJSON = {};
  var __initRollbarJSON = false;
  function setupJSON() {
    if (__initRollbarJSON) {
      return;
    }
    __initRollbarJSON = true;
  
    if (isDefined(JSON)) {
      if (isNativeFunction(JSON.stringify)) {
        RollbarJSON.stringify = JSON.stringify;
      }
      if (isNativeFunction(JSON.parse)) {
        RollbarJSON.parse = JSON.parse;
      }
    }
    if (!isFunction(RollbarJSON.stringify) || !isFunction(RollbarJSON.parse)) {
      var setupCustomJSON = require('../vendor/JSON-js/json3.js');
      setupCustomJSON(RollbarJSON);
    }
  }
  setupJSON();
  
  /*
   * isType - Given a Javascript value and a string, returns true if the type of the value matches the
   * given string.
   *
   * @param x - any value
   * @param t - a lowercase string containing one of the following type names:
   *    - undefined
   *    - null
   *    - error
   *    - number
   *    - boolean
   *    - string
   *    - symbol
   *    - function
   *    - object
   *    - array
   * @returns true if x is of type t, otherwise false
   */
  function isType(x, t) {
    return t === typeName(x);
  }
  
  /*
   * typeName - Given a Javascript value, returns the type of the object as a string
   */
  function typeName(x) {
    var name = typeof x;
    if (name !== 'object') {
      return name;
    }
    if (!x) {
      return 'null';
    }
    if (x instanceof Error) {
      return 'error';
    }
    return ({}).toString.call(x).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
  }
  
  /* isFunction - a convenience function for checking if a value is a function
   *
   * @param f - any value
   * @returns true if f is a function, otherwise false
   */
  function isFunction(f) {
    return isType(f, 'function');
  }
  
  /* isNativeFunction - a convenience function for checking if a value is a native JS function
   *
   * @param f - any value
   * @returns true if f is a native JS function, otherwise false
   */
  function isNativeFunction(f) {
    var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
    var funcMatchString = Function.prototype.toString.call(Object.prototype.hasOwnProperty)
      .replace(reRegExpChar, '\\$&')
      .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?');
    var reIsNative = RegExp('^' + funcMatchString + '$');
    return isObject(f) && reIsNative.test(f);
  }
  
  /* isObject - Checks if the argument is an object
   *
   * @param value - any value
   * @returns true is value is an object function is an object)
  */
  function isObject(value) {
    var type = typeof value;
    return value != null && (type == 'object' || type == 'function');
  }
  
  /*
   * isDefined - a convenience function for checking if a value is not equal to undefined
   *
   * @param u - any value
   * @returns true if u is anything other than undefined
   */
  function isDefined(u) {
    return !isType(u, 'undefined');
  }
  
  /*
   * isIterable - convenience function for checking if a value can be iterated, essentially
   * whether it is an object or an array.
   *
   * @param i - any value
   * @returns true if i is an object or an array as determined by `typeName`
   */
  function isIterable(i) {
    var type = typeName(i);
    return (type === 'object' || type === 'array');
  }
  
  /*
   * isError - convenience function for checking if a value is of an error type
   *
   * @param e - any value
   * @returns true if e is an error
   */
  function isError(e) {
    return isType(e, 'error');
  }
  
  function traverse(obj, func, seen) {
    var k, v, i;
    var isObj = isType(obj, 'object');
    var isArray = isType(obj, 'array');
    var keys = [];
  
    if (isObj && seen.indexOf(obj) !== -1) {
      return obj;
    }
    seen.push(obj);
  
    if (isObj) {
      for (k in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, k)) {
          keys.push(k);
        }
      }
    } else if (isArray) {
      for (i = 0; i < obj.length; ++i) {
        keys.push(i);
      }
    }
  
    var result = isObj ? {} : [];
    var same = true;
    for (i = 0; i < keys.length; ++i) {
      k = keys[i];
      v = obj[k];
      result[k] = func(k, v, seen);
      same = same && result[k] === obj[k];
    }
  
    return (keys.length != 0 && !same) ? result : obj;
  }
  
  function redact() {
    return '********';
  }
  
  // from http://stackoverflow.com/a/8809472/1138191
  function uuid4() {
    var d = now();
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = (d + Math.random() * 16) % 16 | 0;
      d = Math.floor(d / 16);
      return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16);
    });
    return uuid;
  }
  
  var LEVELS = {
    debug: 0,
    info: 1,
    warning: 2,
    error: 3,
    critical: 4
  };
  
  function sanitizeUrl(url) {
    var baseUrlParts = parseUri(url);
    if (!baseUrlParts) {
      return '(unknown)';
    }
  
    // remove a trailing # if there is no anchor
    if (baseUrlParts.anchor === '') {
      baseUrlParts.source = baseUrlParts.source.replace('#', '');
    }
  
    url = baseUrlParts.source.replace('?' + baseUrlParts.query, '');
    return url;
  }
  
  var parseUriOptions = {
    strictMode: false,
    key: [
      'source',
      'protocol',
      'authority',
      'userInfo',
      'user',
      'password',
      'host',
      'port',
      'relative',
      'path',
      'directory',
      'file',
      'query',
      'anchor'
    ],
    q: {
      name: 'queryKey',
      parser: /(?:^|&)([^&=]*)=?([^&]*)/g
    },
    parser: {
      strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
      loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
    }
  };
  
  function parseUri(str) {
    if (!isType(str, 'string')) {
      return undefined;
    }
  
    var o = parseUriOptions;
    var m = o.parser[o.strictMode ? 'strict' : 'loose'].exec(str);
    var uri = {};
    var i = o.key.length;
  
    while (i--) {
      uri[o.key[i]] = m[i] || '';
    }
  
    uri[o.q.name] = {};
    uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
      if ($1) {
        uri[o.q.name][$1] = $2;
      }
    });
  
    return uri;
  }
  
  function addParamsAndAccessTokenToPath(accessToken, options, params) {
    params = params || {};
    params.access_token = accessToken;
    var paramsArray = [];
    var k;
    for (k in params) {
      if (Object.prototype.hasOwnProperty.call(params, k)) {
        paramsArray.push([k, params[k]].join('='));
      }
    }
    var query = '?' + paramsArray.sort().join('&');
  
    options = options || {};
    options.path = options.path || '';
    var qs = options.path.indexOf('?');
    var h = options.path.indexOf('#');
    var p;
    if (qs !== -1 && (h === -1 || h > qs)) {
      p = options.path;
      options.path = p.substring(0,qs) + query + '&' + p.substring(qs+1);
    } else {
      if (h !== -1) {
        p = options.path;
        options.path = p.substring(0,h) + query + p.substring(h);
      } else {
        options.path = options.path + query;
      }
    }
  }
  
  function formatUrl(u, protocol) {
    protocol = protocol || u.protocol;
    if (!protocol && u.port) {
      if (u.port === 80) {
        protocol = 'http:';
      } else if (u.port === 443) {
        protocol = 'https:';
      }
    }
    protocol = protocol || 'https:';
  
    if (!u.hostname) {
      return null;
    }
    var result = protocol + '//' + u.hostname;
    if (u.port) {
      result = result + ':' + u.port;
    }
    if (u.path) {
      result = result + u.path;
    }
    return result;
  }
  
  function stringify(obj, backup) {
    var value, error;
    try {
      value = RollbarJSON.stringify(obj);
    } catch (jsonError) {
      if (backup && isFunction(backup)) {
        try {
          value = backup(obj);
        } catch (backupError) {
          error = backupError;
        }
      } else {
        error = jsonError;
      }
    }
    return {error: error, value: value};
  }
  
  function jsonParse(s) {
    var value, error;
    try {
      value = RollbarJSON.parse(s);
    } catch (e) {
      error = e;
    }
    return {error: error, value: value};
  }
  
  function makeUnhandledStackInfo(
    message,
    url,
    lineno,
    colno,
    error,
    mode,
    backupMessage,
    errorParser
  ) {
    var location = {
      url: url || '',
      line: lineno,
      column: colno
    };
    location.func = errorParser.guessFunctionName(location.url, location.line);
    location.context = errorParser.gatherContext(location.url, location.line);
    var href = document && document.location && document.location.href;
    var useragent = window && window.navigator && window.navigator.userAgent;
    return {
      'mode': mode,
      'message': error ? String(error) : (message || backupMessage),
      'url': href,
      'stack': [location],
      'useragent': useragent
    };
  }
  
  function wrapCallback(logger, f) {
    return function(err, resp) {
      try {
        f(err, resp);
      } catch (e) {
        logger.error(e);
      }
    };
  }
  
  function createItem(args, logger, notifier, requestKeys, lambdaContext) {
    var message, err, custom, callback, request;
    var arg;
    var extraArgs = [];
  
    for (var i = 0, l = args.length; i < l; ++i) {
      arg = args[i];
  
      var typ = typeName(arg);
      switch (typ) {
        case 'undefined':
          break;
        case 'string':
          message ? extraArgs.push(arg) : message = arg;
          break;
        case 'function':
          callback = wrapCallback(logger, arg);
          break;
        case 'date':
          extraArgs.push(arg);
          break;
        case 'error':
        case 'domexception':
          err ? extraArgs.push(arg) : err = arg;
          break;
        case 'object':
        case 'array':
          if (arg instanceof Error || (typeof DOMException !== 'undefined' && arg instanceof DOMException)) {
            err ? extraArgs.push(arg) : err = arg;
            break;
          }
          if (requestKeys && typ === 'object' && !request) {
            for (var j = 0, len = requestKeys.length; j < len; ++j) {
              if (arg[requestKeys[j]] !== undefined) {
                request = arg;
                break;
              }
            }
            if (request) {
              break;
            }
          }
          custom ? extraArgs.push(arg) : custom = arg;
          break;
        default:
          if (arg instanceof Error || (typeof DOMException !== 'undefined' && arg instanceof DOMException)) {
            err ? extraArgs.push(arg) : err = arg;
            break;
          }
          extraArgs.push(arg);
      }
    }
  
    if (extraArgs.length > 0) {
      // if custom is an array this turns it into an object with integer keys
      custom = merge(custom);
      custom.extraArgs = extraArgs;
    }
  
    var item = {
      message: message,
      err: err,
      custom: custom,
      timestamp: now(),
      callback: callback,
      uuid: uuid4()
    };
    if (custom && custom.level !== undefined) {
      item.level = custom.level;
      delete custom.level;
    }
    if (requestKeys && request) {
      item.request = request;
    }
    if (lambdaContext) {
      item.lambdaContext = lambdaContext;
    }
    item._originalArgs = args;
    return item;
  }
  
  /*
   * get - given an obj/array and a keypath, return the value at that keypath or
   *       undefined if not possible.
   *
   * @param obj - an object or array
   * @param path - a string of keys separated by '.' such as 'plugin.jquery.0.message'
   *    which would correspond to 42 in `{plugin: {jquery: [{message: 42}]}}`
   */
  function get(obj, path) {
    if (!obj) {
      return undefined;
    }
    var keys = path.split('.');
    var result = obj;
    try {
      for (var i = 0, len = keys.length; i < len; ++i) {
        result = result[keys[i]];
      }
    } catch (e) {
      result = undefined;
    }
    return result;
  }
  
  function set(obj, path, value) {
    if (!obj) {
      return;
    }
    var keys = path.split('.');
    var len = keys.length;
    if (len < 1) {
      return;
    }
    if (len === 1) {
      obj[keys[0]] = value;
      return;
    }
    try {
      var temp = obj[keys[0]] || {};
      var replacement = temp;
      for (var i = 1; i < len-1; i++) {
        temp[keys[i]] = temp[keys[i]] || {};
        temp = temp[keys[i]];
      }
      temp[keys[len-1]] = value;
      obj[keys[0]] = replacement;
    } catch (e) {
      return;
    }
  }
  
  function scrub(data, scrubFields) {
    scrubFields = scrubFields || [];
    var paramRes = _getScrubFieldRegexs(scrubFields);
    var queryRes = _getScrubQueryParamRegexs(scrubFields);
  
    function redactQueryParam(dummy0, paramPart, dummy1, dummy2, dummy3, valPart) {
      return paramPart + redact(valPart);
    }
  
    function paramScrubber(v) {
      var i;
      if (isType(v, 'string')) {
        for (i = 0; i < queryRes.length; ++i) {
          v = v.replace(queryRes[i], redactQueryParam);
        }
      }
      return v;
    }
  
    function valScrubber(k, v) {
      var i;
      for (i = 0; i < paramRes.length; ++i) {
        if (paramRes[i].test(k)) {
          v = redact(v);
          break;
        }
      }
      return v;
    }
  
    function scrubber(k, v, seen) {
      var tmpV = valScrubber(k, v);
      if (tmpV === v) {
        if (isType(v, 'object') || isType(v, 'array')) {
          return traverse(v, scrubber, seen);
        }
        return paramScrubber(tmpV);
      } else {
        return tmpV;
      }
    }
  
    return traverse(data, scrubber, []);
  }
  
  function _getScrubFieldRegexs(scrubFields) {
    var ret = [];
    var pat;
    for (var i = 0; i < scrubFields.length; ++i) {
      pat = '^\\[?(%5[bB])?' + scrubFields[i] + '\\[?(%5[bB])?\\]?(%5[dD])?$';
      ret.push(new RegExp(pat, 'i'));
    }
    return ret;
  }
  
  
  function _getScrubQueryParamRegexs(scrubFields) {
    var ret = [];
    var pat;
    for (var i = 0; i < scrubFields.length; ++i) {
      pat = '\\[?(%5[bB])?' + scrubFields[i] + '\\[?(%5[bB])?\\]?(%5[dD])?';
      ret.push(new RegExp('(' + pat + '=)([^&\\n]+)', 'igm'));
    }
    return ret;
  }
  
  function formatArgsAsString(args) {
    var i, len, arg;
    var result = [];
    for (i = 0, len = args.length; i < len; i++) {
      arg = args[i];
      switch (typeName(arg)) {
        case 'object':
          arg = stringify(arg);
          arg = arg.error || arg.value;
          if (arg.length > 500) {
            arg = arg.substr(0, 497) + '...';
          }
          break;
        case 'null':
          arg = 'null';
          break;
        case 'undefined':
          arg = 'undefined';
          break;
        case 'symbol':
          arg = arg.toString();
          break;
      }
      result.push(arg);
    }
    return result.join(' ');
  }
  
  function now() {
    if (Date.now) {
      return +Date.now();
    }
    return +new Date();
  }
  
  function filterIp(requestData, captureIp) {
    if (!requestData || !requestData['user_ip'] || captureIp === true) {
      return;
    }
    var newIp = requestData['user_ip'];
    if (!captureIp) {
      newIp = null;
    } else {
      try {
        var parts;
        if (newIp.indexOf('.') !== -1) {
          parts = newIp.split('.');
          parts.pop();
          parts.push('0');
          newIp = parts.join('.');
        } else if (newIp.indexOf(':') !== -1) {
          parts = newIp.split(':');
          if (parts.length > 2) {
            var beginning = parts.slice(0, 3);
            var slashIdx = beginning[2].indexOf('/');
            if (slashIdx !== -1) {
              beginning[2] = beginning[2].substring(0, slashIdx);
            }
            var terminal = '0000:0000:0000:0000:0000';
            newIp = beginning.concat(terminal).join(':');
          }
        } else {
          newIp = null;
        }
      } catch (e) {
        newIp = null;
      }
    }
    requestData['user_ip'] = newIp;
  }
  
  function handleOptions(current, input, payload) {
    var result = merge(current, input, payload);
    if (!input || input.overwriteScrubFields) {
      return result;
    }
    if (input.scrubFields) {
      result.scrubFields = (current.scrubFields || []).concat(input.scrubFields);
    }
    return result;
  }
  
  module.exports = {
    addParamsAndAccessTokenToPath: addParamsAndAccessTokenToPath,
    createItem: createItem,
    filterIp: filterIp,
    formatArgsAsString: formatArgsAsString,
    formatUrl: formatUrl,
    get: get,
    handleOptions: handleOptions,
    isError: isError,
    isFunction: isFunction,
    isIterable: isIterable,
    isNativeFunction: isNativeFunction,
    isType: isType,
    jsonParse: jsonParse,
    LEVELS: LEVELS,
    makeUnhandledStackInfo: makeUnhandledStackInfo,
    merge: merge,
    now: now,
    redact: redact,
    sanitizeUrl: sanitizeUrl,
    scrub: scrub,
    set: set,
    stringify: stringify,
    traverse: traverse,
    typeName: typeName,
    uuid4: uuid4
  };
  