Skip to content

Instantly share code, notes, and snippets.

@boneskull
Last active August 29, 2015 14:22
Show Gist options
  • Save boneskull/e0dcbade0c6dc2d4740f to your computer and use it in GitHub Desktop.
Save boneskull/e0dcbade0c6dc2d4740f to your computer and use it in GitHub Desktop.
decorate AngularJS' $log service to send data to Loggly
/* 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._);
(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);
@boneskull
Copy link
Author

this requires LoDash, but you can probably pull it out

@boneskull
Copy link
Author

If you don't need benchmarks, pull out the stopWatch() stuff. it requires momentjs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment