Last active
August 29, 2015 14:22
-
-
Save boneskull/e0dcbade0c6dc2d4740f to your computer and use it in GitHub Desktop.
decorate AngularJS' $log service to send data to Loggly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jshint -W117 */ | |
(function (window, document, angular, _) { | |
'use strict'; | |
/** | |
* URL to remote Loggly tracker | |
* @type {string} | |
*/ | |
var URL = '//cloudfront.loggly.com/js/loggly.tracker.js', | |
/** | |
* Script tag to hold Loggly tracker script. | |
* @type {Tag|*} | |
*/ | |
script = document.createElement('script'); | |
// if you want to emit uncaught errors before AngularJS loads, just | |
// ditch this crap and include the file in a <script> tag. | |
script.setAttribute('async', true); | |
script.setAttribute('src', URL); | |
// append Loggly script to head tag | |
document.querySelector('head').appendChild(script); | |
/** | |
* Loggly Tracker object instance. | |
* @type {Array|*|Window._LTracker} | |
*/ | |
window._LTracker = window._LTracker || []; | |
window._LTracker.push({ | |
logglyKey: 'YOUR_SECRET_KEY', | |
sendConsoleErrors: true | |
}); | |
// create console.debug() if it doesn't exist. | |
window.console.debug = window.console.debug || window.console.log; | |
angular.module('loggly', []) | |
.config(['$provide', function ($provide) { | |
var levels = ['debug', 'log', 'info', 'warn', 'error'], | |
/** | |
* This decorates AngularJS' `$log` service to write data to Loggly. | |
* @param {ng.$LogProvider} $delegate | |
* @param {window} $window | |
* @returns {*} | |
*/ | |
$logDecorator = function $logDecorator($delegate, $window) { | |
var $log, | |
tracker = $window._LTracker, | |
stopWatches = []; | |
if (angular.isUndefined(tracker)) { | |
console.warn('Loggly tracker is not present'); | |
return $delegate; | |
} | |
if (angular.isUndefined(_)) { | |
console.warn('LoDash is not present'); | |
return $delegate; | |
} | |
$log = _.object(levels, _.map(levels, function (level) { | |
return $delegate[level]; | |
})); | |
_.each(levels, function (level) { | |
/** | |
* When you use `$log.foo()`, you use this function. | |
* | |
* It sends information to Loggly for visualization and reporting. | |
* | |
* @param {string} id Log entry identifier. This should be unique | |
* to the "type" of log entry, not the log entry itself. If the | |
* `id` ends with "_BEGIN", the service will start a timer. Once | |
* a corresponding `id` is received ending in "_END", then the timer | |
* is stopped, and a `ms` value is calculated and passed to Loggly. | |
* @param {(...string|Object)} [desc] Description of log entry, | |
* plus any string replacement parameters, if necessary. If an | |
* Object, considered to be the `data` parameter, and the `id` will | |
* also be used for the description. | |
* @param {Object} [data] Extra data to pass. | |
* @returns {*} Whatever the AngularJS log.* functions return. | |
* @example | |
* // log an error with id "INVALID_CONFIG", description | |
* // "Invalid report configuration", and add the serialized config | |
* // object. | |
* $log.error('INVALID_CONFIG', 'Invalid report configuration', | |
* config.serialize()); | |
* // the same as above with a description parameter | |
* $log.error('INVALID_CONFIG', 'Invalid report configuration "%s"', | |
* config.id, config.serialize()); | |
* // log an error with id "UNKNOWN" and description "UNKNOWN" with | |
* // no extra data. probably not very helpful. | |
* $log.error('UNKNOWN'); | |
* // log an informational message. note the log level is passed to | |
* // loggly. | |
* $log.info('Crosstab with id "%s" deleted by user "%d", config.id, | |
* User.id); | |
*/ | |
$delegate[level] = function logglyLog(id, desc, data) { | |
var offset, args, msgArgs, logglyObj, hasData, matches, msg; | |
if (id instanceof Error) { | |
data = { | |
cause: id.cause, | |
message: id.message, | |
stack: id.stack | |
}; | |
msgArgs = arguments; | |
// this allows you to search for uncaught errors with key data.id == UNCAUGHT | |
id = 'UNCAUGHT'; | |
} | |
else if (!_.isString(id)) { | |
console.warn('first parameter to $log.%s() must be a string ' + | |
'or Error for transmission to Loggly', level); | |
return $log[level].apply($delegate, arguments); | |
} | |
else { | |
if (_.isObject(desc)) { | |
data = desc; | |
msgArgs = [id]; | |
} | |
else { | |
args = _.toArray(arguments); | |
hasData = _.isObject(_.last(args)); | |
msgArgs = | |
args.slice(1, hasData ? args.length - 1 : args.length); | |
data = hasData ? _.last(args) : data; | |
} | |
} | |
msg = _.format.apply(_, msgArgs); | |
if ((matches = id.match(/^(.+)_BEGIN$/))) { | |
stopWatches[matches[1]] = _.stopWatch(); | |
} | |
else if ((matches = | |
id.match(/^(.+)_END$/)) && stopWatches[matches[1]]) { | |
offset = stopWatches[matches[1]].stop(); | |
_.extend(data, { | |
ms: offset | |
}); | |
msg += _.format(' (%d)', offset); | |
delete stopWatches[matches[1]]; | |
} | |
/** | |
* This is the object we send to Loggly. `data` is optional. | |
* @type {{level: string, id: string, desc: string, data: Object}} | |
*/ | |
logglyObj = { | |
level: level, | |
id: id, | |
desc: msg, | |
data: data | |
}; | |
tracker.push(logglyObj); | |
// delegate to AngularJS' logger. | |
return $log[level].apply($delegate, msgArgs); | |
}; | |
}); | |
return $delegate; | |
}; | |
$logDecorator.$inject = ['$delegate', '$window']; | |
$provide.decorator('$log', $logDecorator); | |
}]); | |
})(window, window.document, window.angular, window._); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(_, moment) { | |
/** | |
* Offset since Epoch | |
* @typedef {number} offset | |
*/ | |
/** | |
* Starts a StopWatch (used for benchmarking) | |
* @see _.stopWatch | |
* @constructor | |
*/ | |
var StopWatch = function StopWatch() { | |
this.restart(); | |
}; | |
StopWatch.prototype = { | |
/** | |
* Split the StopWatch, but don't stop it. | |
* @returns {offset} | |
*/ | |
split: function split() { | |
return moment().diff(this._now); | |
}, | |
/** | |
* Stop the StopWatch, return the offset, and restart it. | |
* @returns {offset} | |
*/ | |
stop: function stop() { | |
var ms = this.split(); | |
this.restart(); | |
return ms; | |
}, | |
/** | |
* Restart the StopWatch | |
* @returns {StopWatch} | |
*/ | |
restart: function restart() { | |
this._now = moment(); | |
return this; | |
} | |
}; | |
_.mixin({ | |
stopWatch: function() { | |
return new StopWatch(); | |
} | |
}, { chain: false }); | |
}(window._, window.moment); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
this requires LoDash, but you can probably pull it out