<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-example110-production</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script src="LogExtender.js"></script>
<script src="ABApp.js"></script>
<script src="script.js"></script>
<script>
angular.element(document).ready(function() {
angular.bootstrap(document.getElementById("GoogleAngularSampleApp"), ['logExample']);
angular.bootstrap(document.getElementById("ABAngularSampleApp"), ['ABApp']);
});
</script>
</head>
<body>
<h3>Please open your javascript console to view the logging output.</h3>
<div id="GoogleAngularSampleApp" ng-controller="LogController">
<div ng-include='"OriginalLogExample.html"'></div>
</div>
<div id="ABAngularSampleApp" ng-controller="MyController">
<div ng-include='"ExtendedLogExample.html"'></div>
<div ng-include='"ReadMe.html"'></div>
</div>
</body>
</html>
(function(angular) {
'use strict';
angular.module('logExample', [])
.controller('LogController', ['$scope', '$log', function($scope, $log) {
$scope.$log = $log;
$scope.message = 'Hello World!';
$log.log("Hello Log from Default Log Service.");
$log.info("Hello Info from Default Log Service.");
$log.debug("Hello Debug from Default Log Service.");
$log.warn("Hello Warn from Default Log Service.");
$log.error("Hello Error from Default Log Service.");
}]);
})(window.angular);
/*
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
/** Below code is from link provided below. It is a module which uses decorator to modify the
* results of $log.
* Original Code location - https://github.com/lwhiteley/AngularLogExtender/wiki/05.-Set-Component-Class-Name
**/
/**
* Log Unobtrusive Extension v0.0.12-0+sha.afc3898
*
* Used within AngularJS to enhance functionality within the AngularJS $log service.
*
* @original-author Thomas Burleson
* @contributor Layton Whiteley
* @contributor A confused individual <ferronrsmith@gmail.com>
* @website http://www.theSolutionOptimist.com
* (c) 2016 https://github.com/lwhiteley/AngularLogExtender
* License: MIT
*
* Modifications made by @contributor Layton Whiteley:
* - Modified to be a full stand-alone Angular Application for reuse
* - Has global and feature level activation/disabling for $log
* - Supported sensitive field filtering
* - Created and tested with AngularJS versions : 1.0.4, 1.1.0, 1.1.2, 1.2.28, 1.3.14, 1.4.0-rc.0, 1.5.0
*/
angular.module("log.ex.uo", []).provider('logEx', ['$provide', function($provide) {
// Creates an injector function that can be used for retrieving services as well as for dependency injection
var $injector = angular.injector(['ng']);
// Used the $injector defined to retrieve the $filterProvider
var $filter = $injector.get('$filter');
/**
* Used to enable logging globally
* @type {boolean}
*/
var enableGlobally = false;
/**
* Used to enable quiet logger enabling.
* When this feature is enable the config message is not shown
* @type {boolean}
*/
var enabledQuietly = false;
/**
* Used to activate logPrefix overriding
* @type {boolean}
*/
var logPrefixOverride = false;
/**
* Used to force log-ex to use the default log prefix rules
* @type {boolean}
*/
var useDefaultPrefix = false;
/**
* Used to store custom log prefix rules
* @type {null | Function}
*/
var customLogPrefixFn = null;
/**
* current browser's user agent
* @type {string}
*/
var userAgent = navigator.userAgent;
/**
* default log methods available
* @type {string[]}
*/
var defaultLogMethods = ['log', 'info', 'warn', 'debug', 'error', 'getInstance'];
/**
* list of browsers that support colorify
* @type {string[]}
*/
var colorifySupportedBrowsers = ['chrome', 'firefox'];
/**
* flag to activate/deactivate default log method colors
* @type {boolean}
*/
var useDefaultColors = true;
/**
* list of known keys used to style logs
* @type {string[]}
*/
var cssKeys = ['color', 'background', 'font-size', 'border'];
/**
* default string to put in place of filtered values
* @type {string}
*/
var defaultFilterString = '[FILTERED]';
/**
* default configuration for filtering values of provided keys
* @type {object}
*/
var filterConfig = {
filterString: defaultFilterString,
logFilters: []
};
/**
* default colours for each log method
* @type {object}
*/
var defaultLogMethodColors = {
log: 'color: green;',
info: 'color: blue',
warn: 'color: #CC9933;',
debug: 'color: brown;',
error: 'color: red;'
};
/**
* publicly allowed methods for the extended $log object.
* this give the developer the option of using special features
* such as setting a className and overriding log messages.
* More Options to come.
* @type {string[]}
*/
var allowedMethods = defaultLogMethods;
/**
* This is the default method responsible for formatting the prefix of all extended $log messages pushed to the console
* @see overrideLogPrefix to override the logPrefix
* @param {string=} className - name of the component class ($controller, $service etc.)
* @returns {string} - formatted string that will be prepended to log outputs
*/
var defaultLogPrefixFn = function( /**{String=}*/ className) {
var separator = " >> ",
format = "MMM-dd-yyyy-h:mm:ssa",
now = $filter('date')(new Date(), format);
return "" + now + ((itypeof(className) !== 'string') ? "" : "::" + className) + separator;
};
/**
* The itypeof operator returns a string indicating the type of the unevaluated operand.
* @param {*} val - object to be evaluated
* @returns {String} - returns a string with the type of the evaluated operand
*/
var itypeof = function(val) {
return Object.prototype.toString.call(val).replace(/(\[|object|\s|\])/g, "").toLowerCase();
};
/**
* Evaluates an object to verify it is of type `object` or `array`
* @param {*} value - an object to be evaluated
* @returns boolean - returns true if parameter is of type object or array
*/
var isObjectOrArray = function(value) {
return (/(object|array)/.test(itypeof(value)));
};
/**
* Trims whitespace at the beginning and/or end of a string
* @param {String} value - string to be trimmed
* @returns {String} - returns an empty string if the value passed is not of type {String}
*/
var trimString = function(value) {
if (itypeof(value) === 'string') {
return value.replace(/^\s*/, '').replace(/\s*$/, '');
}
return "";
};
/**
* checks if a variable is of @type {boolean}
* @param {boolean} value - flag to be evaluated
* @returns {boolean} - returns true if evaluated object is a boolean
*/
var isBoolean = function(value) {
return itypeof(value) === 'boolean';
};
/**
* This method checks if a variable is of type {string}
* and if the string is not an empty string
* @param {string} value - string to be evaluated
* @returns {*|Boolean|boolean} - returns true if string is not null or empty
*/
var isValidString = function(value) {
return (itypeof(value) === 'string' && trimString(value) !== "");
};
/**
* checks if @param1 is a substring of @param2
* @param {string} sub - partial string that may be a sub string
* @param {string} full - full string that may have the unevaluated substring
* @returns {boolean} - returns true if a substring is found in the ful string
*/
var isSubString = function(sub, full) {
if (itypeof(sub) === 'string' && itypeof(full) === 'string') {
if (full.toLowerCase().indexOf(sub.toLowerCase()) !== -1) {
return true;
}
}
return false;
};
/**
* This method is responsible for generating the prefix of all extended $log messages pushed to the console
* @param {string=} className - name of the component class ($controller, $service etc.)
* @returns {string} - formatted string that will be prepended to log outputs
*/
var getLogPrefix = function( /**{String=}*/ className) {
var prefix = '';
if (!useDefaultPrefix && logPrefixOverride) {
prefix = customLogPrefixFn(className);
} else {
prefix = defaultLogPrefixFn(className);
}
return prefix;
};
/**
* Checks if the current browser is a part of the supported browser list for adding colors
* @returns {boolean} - returns true if the current browser supports colorify
*/
var isColorifySupported = function() {
for (var i = 0; i < colorifySupportedBrowsers.length; i++) {
if (isSubString(colorifySupportedBrowsers[i], userAgent)) {
return true;
}
}
return false;
};
/**
* Stores flag to know if current browser is colorify supported
* @type {boolean}
*/
//TODO: Need to refactor this into a self-invoking function
var isColorifySupportedBrowser = isColorifySupported();
/**
* The following method checks if the log arguments array length is one and the element is a string
* @param {*[]} args - unevaluated log method arguments array that should contain only one element of type {string}
* @returns {boolean} - returns true if args match the above criteria
*/
var validateColorizeInputs = function(args) {
return (args.length === 1 &&
itypeof(args[0]) === 'string');
};
/**
* The following method does partial validation to ensure css string contains known keys
* @param {string} css - css string to be evaluated
* @returns {boolean} - returns true if string contains any supported keys
*/
var containsColorCssKeys = function(css) {
for (var x = 0; x < cssKeys.length; x++) {
if (isSubString(cssKeys[x], css)) {
return true;
}
}
return false;
};
/**
* The following method does partial validation to ensure css string is valid
* @param {string} value - css string to be evaluated
* @returns {boolean} - returns true if string has css format
*/
var validateColorCssString = function(value) {
return (itypeof(value) === 'string' && isSubString(':', value) &&
trimString(value).length > 6) && containsColorCssKeys(value);
};
/**
* The following takes a string a returns an array as parameter if browser is supported
* e.g. Expected outcome $log.log('%c Oh my heavens! ', 'background: #222; color: #bada55');
* @param {string} message - string to be coloured
* @param {string} colorCSS - css string to apply to message
* @param {string} prefix - log prefix to be prepended to message
* @returns {*[]} - returns colorify formatted array if all inputs are valid else returns array with the original message
*/
var colorify = function(message, colorCSS, prefix) {
prefix = (itypeof(prefix) === 'string' ? prefix : '');
var canProcess = isColorifySupportedBrowser && validateColorCssString(colorCSS) && itypeof(message) === 'string';
var output = canProcess ? ('' + prefix + message) : message;
return canProcess ? (["%c" + output, colorCSS]) : [output];
};
/**
* The following method checks if useTemplate value is true and
* if the log arguments array length is two
* @param {boolean} useTemplate - flag that configures the usage of the template engine
* @param {*[]} args - list of log arguments that should match pattern creating template strings
* @returns {boolean} - returns true if log arguments match template pattern and useTemplate is set to true
*/
var validateTemplateInputs = function(useTemplate, args) {
return isBoolean(useTemplate) && useTemplate && args.length === 2;
};
/**
* supplant is a string templating engine that replaces patterns
* in a string with values from a template object
* @example:
* for `var template = 'i am template string - {descriptor}';`
* `var values = {descriptor: 'awesome'};`
*
* when `var result = supplant(template, values);`
* then `result` will be `i am template string - awesome`
*
* @param {string} template - string with patterns to be replaced by values
* @param {object} values - object with values to replace in template string
* @param {RegExp=} pattern - custom regular expression of pattern to replace in template string
* @returns {string|Array} - returns formatted string if template and values match the required pattern
*/
var supplant = function(template, values, /*{RegExp=}*/ pattern) {
var criteria1 = itypeof(template) !== 'string' && itypeof(values) !== 'object';
var criteria2 = itypeof(template) !== 'string' || itypeof(values) !== 'object';
if (criteria1 || criteria2) {
return Array.prototype.slice.call(arguments);
}
pattern = itypeof(pattern) === 'regexp' ? pattern : /\{([^\{\}]*)\}/g;
return template.replace(pattern, function(patternToReplace, replacementKey) {
var replacementKeyList = replacementKey.split('.'),
replacements = values;
try {
angular.forEach(replacementKeyList, function(value, key) {
replacements = replacements[replacementKeyList[key]];
});
} catch (e) {
replacements = patternToReplace;
}
return (itypeof(replacements) === 'string' || itypeof(replacements) === 'number') ? replacements : patternToReplace;
});
};
/**
* Evaluates an array of log arguments to be filtered using the provided or default filter keys
* @param {[] | Object} logArguments - array to be processed
* @returns {[] | Object} - returns a processed array with configured filter values replaced by filterString
*/
var filterSensitiveValues = function(logArguments) {
if (isObjectOrArray(logArguments) && filterConfig.logFilters.length > 0) {
angular.forEach(logArguments, function(logValue, logKey) {
angular.forEach(filterConfig.logFilters, function(filterValue) {
// replace filtered values here
if (itypeof(logValue) === 'object' &&
logValue.hasOwnProperty(filterValue) && !isObjectOrArray(logValue[filterValue])) {
logValue[filterValue] = filterConfig.filterString;
} else if (isObjectOrArray(logValue)) {
logArguments[logKey] = filterSensitiveValues(logValue);
}
});
});
return logArguments;
}
return logArguments;
};
// Register $log decorator with AngularJS $provider
$provide.decorator('$log', ["$delegate", function($delegate) {
/**
* Encapsulates functionality to extends $log and expose additional functionality
**/
var logEnhancerObj = function() {
/**
* processUseOverride returns true if the override flag is set.
* this is used to activate the override functionality.
* @param {boolean} override - unevaluated override flag
* @returns {boolean} - returns true if override is a boolean
*/
var processUseOverride = function(override) {
return isBoolean(override);
};
/**
* processOverride only takes true or false as valid input.
* any other input will resolve as true.
* this function is used to override the global flag for displaying logs
* @param {boolean} override - unevaluated override flag
* @returns {boolean} - returns true if override is not equal to false
*/
var processOverride = function(override) {
return override !== false;
};
/**
* The following method checks if the global enabled flag and the override flag are set as type {boolean}
* variables. If both are set it returns the value of the override flag to control $log outputs
* @param {boolean} enabled - global flag that activates/deactivates logging
* @param {boolean} override - flag that overrides the global enabled flag
* @returns {boolean} - returns override if both params are booleans else returns {boolean=} false
*/
var activateLogs = function(enabled, override) {
if (isBoolean(enabled) && isBoolean(override)) {
return override;
}
return false;
};
/**
* The following method handles printing a message to the console indicating
* if a $log instance is using an override.
* If logging is disabled globally & an override of true is set,
* then a message will be displayed for the specific $log instance
* if logging is enabled globally & an override of false is set,
* then a message will be displayed for the specific $log instance
* @private for internal use only
* @param _$log - $log instance
* @param useOverride - flag that defines logic to regard using the override
* @param _override - flag that overrides the global enabled flag
* @param className - name of the component class ($controller, $service etc.)
* @param enabled - global flag that activates/deactivates logging
*/
var printOverrideLogs = function(_$log, useOverride, _override, className, enabled) {
var instance = (isValidString(className)) ? className : "this instance";
if (!enabled && useOverride && _override) {
_$log.log(getLogPrefix() + "[OVERRIDE] LOGGING ENABLED - $log enabled for " + instance);
} else if (enabled && useOverride && !_override) {
_$log.log(getLogPrefix() + "[OVERRIDE] LOGGING DISABLED - $log disabled for " + instance);
}
};
/**
* Converts an array to a object literal & assign a no operation function as the value
* @private for internal use only
* @param {*[]} arr - array to be transformed to object literal
* @returns {Object} - converted object
*/
var arrToObject = function(arr) {
var result = {};
if (angular.isArray(arr)) {
result = {
getInstance: angular.noop
};
angular.forEach(arr, function(value) {
result[value] = angular.noop;
});
}
return result;
};
/**
* General purpose method for building $log objects.
* This method also provides the capability to specify the log methods to expose
* @private for internal use only
* @param {Object} oSrc - $log instance
* @param {Array=} aMethods - list of $log methods
* @param {Function=} func - function that defines rules for custom $log instance
* @param {Array=} aParams - parameters to be used in prepareLogFn
* @returns {Object} - returns a $log instance
*/
var createLogObj = function(oSrc, aMethods, /**{Function=}*/ func, /**{*Array=}*/ aParams) {
var resultSet = {},
oMethods = arrToObject(aMethods);
angular.forEach(defaultLogMethods, function(value) {
var res;
if (angular.isDefined(aParams)) {
var params = [];
angular.copy(aParams, params);
params.unshift(oSrc[value]);
if (isColorifySupportedBrowser && useDefaultColors) {
params[5] = validateColorCssString(params[5]) ? params[5] : defaultLogMethodColors[value];
}
res = func.apply(null, params);
} else {
res = oSrc[value];
}
resultSet[value] = angular.isUndefined(oMethods[value]) ? angular.noop : res;
});
return resultSet;
};
/**
* Contains functionality for transforming the AngularJS $log
* @param $log {Object} - original angular $log to be enhanced
* @returns {Object} - extended $log object
**/
var enhanceLogger = function($log) {
/**
* Partial application to pre-capture a logger function
* @param {Function} logFn - $log method
* @param {*} className - name of the component class ($controller, $service etc.)
* @param {boolean} override - flag that overrides the global enable flag
* @param {boolean} useOverride - flag that defines logic to consider using the override
* @param {string} colorCss - css styles for coloring log methods
* @param {boolean} useTemplate - enables/disables the template engine
* @returns {Function} - returns function with specific rules for a log metod
*/
var prepareLogFn = function(logFn, className, override, useOverride, useTemplate, colorCss) {
var enhancedLogFn = function() {
var activate = (useOverride) ? activateLogs(enabled, override) : enabled;
if (activate) {
var args = Array.prototype.slice.call(arguments);
// perform filter of sensitive values within objects and arrays
// if at least one filter key is available
if (filterConfig.logFilters.length > 0) {
args = filterSensitiveValues(args);
}
var prefix = getLogPrefix(className);
if (validateTemplateInputs(useTemplate, args)) {
var data = (supplant.apply(null, args));
data = (itypeof(data) === 'string') ? [data] : data;
args = data;
}
if (itypeof(colorCss) === 'string' && validateColorizeInputs(args)) {
args = colorify(args[0], colorCss, prefix);
} else {
args.unshift(prefix);
}
if (logFn) {
logFn.apply(null, args);
}
}
};
// Only needed to support angular-mocks expectations
enhancedLogFn.logs = [];
return enhancedLogFn;
};
/**
* Capture the original $log functions; for use in enhancedLogFn()
* @type {*}
* @private
*/
var _$log = createLogObj($log, allowedMethods);
/**
* Support to generate class-specific logger instance with/without className or override
* @param {*} className - Name of object in which $log.<function> calls is invoked.
* @param {boolean=} override - activates/deactivates component level logging
* @param {boolean=} useTemplate - enables/disables the template engine
* @param {String=} colorCss - css styles for coloring log methods
* @returns {*} $log instance - returns a custom log instance
*/
var getInstance = function( /*{*=}*/ className, /*{boolean=}*/ override, /*{boolean=}*/ useTemplate, /*{String=}*/ colorCss) {
if (isBoolean(className)) {
override = className;
className = null;
} else if (itypeof(className) === 'string') {
className = trimString(className);
} else {
className = null;
}
var useOverride = processUseOverride(override);
override = processOverride(override);
printOverrideLogs(_$log, useOverride, override, className, enabled);
return createLogObj(_$log, allowedMethods, prepareLogFn, [className, override, useOverride, useTemplate, colorCss]);
};
//declarations and functions , extensions
/**
* Used to enable/disable logging
* @type {boolean}
*/
var enabled = false;
/**
* Used to enable quiet logger enabling.
* @type {boolean}
*/
var quiet = false;
/**
* Extends the $log object with the transformed native methods
* @param $log - $log instance
* @param {function} createLogObj - defines transformation rules
*/
angular.extend($log, createLogObj($log, allowedMethods, prepareLogFn, [null, false, false, false, null]));
/**
* Extend the $log with the {@see getInstance} method
* @type {getInstance}
*/
$log.getInstance = getInstance;
/**
* The following method enable/disable logging globally
* @param {boolean} flag - boolean flag specifying if log should be enabled/disabled
* @param {boolean} verbose - flag that sets whether logging should be enabled quietly
*/
$log.enableLog = function(flag, verbose) {
enabled = flag;
quiet = verbose;
};
/**
* The following returns the status of the {@see enabled}
* @returns {boolean} - returns global enabled flag
*/
$log.logEnabled = function() {
return enabled && !quiet;
};
return $log;
};
//---------------------------------------//
/**
* The following function exposes the $decorated logger to allow the defaults to be overridden
* @param $log - $log instance
* @returns {*} - returns $log instance fitted for external configurations and regular use
*/
var exposeSafeLog = function($log) {
return createLogObj($log, allowedMethods);
};
// add public methods to logEnhancerObj
this.enhanceLogger = enhanceLogger;
this.exposeSafeLog = exposeSafeLog;
};
//=======================================================================//
// Configuration Section
//=======================================================================//
var logEnhancer = new logEnhancerObj();
logEnhancer.enhanceLogger($delegate);
// ensure false is being passed for production deployments
// set to true for local development
$delegate.enableLog(enableGlobally, enabledQuietly);
if ($delegate.logEnabled()) {
$delegate.log("CONFIG: LOGGING ENABLED GLOBALLY");
}
return logEnhancer.exposeSafeLog($delegate);
}]);
// Provider functions that will be exposed to allow overriding of default $logProvider functionality
/**
* Used externally to enable/disable logging globally
* @param {boolean} flag - flag that sets whether logging is enabled/disabled
* @param {boolean} verbose - flag that sets whether logging should be enabled quietly
*/
var enableLogging = function(flag, verbose) {
enableGlobally = isBoolean(flag) ? flag : false;
enabledQuietly = isBoolean(verbose) ? verbose : false;
};
/**
* Configure which log functions can be exposed at runtime
* @param {*[]} arrMethods - list of methods that can be used
*/
var restrictLogMethods = function(arrMethods) {
if (angular.isArray(arrMethods)) {
// TODO: should do validation on this to ensure valid properties are passed in
allowedMethods = arrMethods;
}
};
/**
* Modify the default log prefix
* @param {Function} logPrefix - function that defines the rule for a custom log prefix
*/
var overrideLogPrefix = function(logPrefix) {
if (angular.isFunction(logPrefix)) {
// TODO : Validation of the function to ensure it's of the correct format etc
customLogPrefixFn = logPrefix;
logPrefixOverride = true;
}
};
/**
* Turns off default coloring of logs
* @param {boolean} flag - flag that configures disabling default log colors
*/
var disableDefaultColors = function(flag) {
useDefaultColors = (!(isBoolean(flag) && flag));
};
/**
* Used to set a custom color to a specific $log method
* @param {String} methodName - method name of the log method to assign a custom color
* @param {String} colorCss - css string that defines what colour to be set for the specified log method
*/
var setLogMethodColor = function(methodName, colorCss) {
if (itypeof(methodName) === 'string' &&
defaultLogMethodColors.hasOwnProperty(methodName) &&
validateColorCssString(colorCss)) {
defaultLogMethodColors[methodName] = colorCss;
}
};
/**
* Used to set custom colors to multiple $log method
* @param {object} overrides - object that defines log method color overrides
*/
var overrideLogMethodColors = function(overrides) {
if (itypeof(overrides) === 'object') {
angular.forEach(overrides, function(colorCss, method) {
setLogMethodColor(method, colorCss);
});
}
};
/**
* Used to force default log prefix functionality
* @param {boolean} flag - when passed true or flag is not set, it forces log-ex to use the default log prefix
*/
var useDefaultLogPrefix = function(flag) {
if (angular.isUndefined(flag)) {
useDefaultPrefix = true;
} else if (isBoolean(flag)) {
useDefaultPrefix = flag;
}
};
/**
* Used to configure the filter feature configuration when logging out objects
* This will merge provided configs with the default and also validate
* that the fields are usable by the feature
* @param {Object} customConfig - config object to override/merge with default config
*/
var configureLogFilters = function(customConfig) {
if (itypeof(customConfig) === 'object' &&
itypeof(customConfig.logFilters) === 'array' &&
customConfig.logFilters.length > 0) {
angular.forEach(customConfig.logFilters, function(value) {
if (itypeof(value) === 'string' && filterConfig.logFilters.indexOf(value) < 0) {
filterConfig.logFilters.push(value);
}
});
filterConfig.filterString = (itypeof(customConfig.filterString) !== 'string') ? defaultFilterString : customConfig.filterString;
}
};
/**
* Default $get method necessary for provider to work
* @type {Function}
*/
this.$get = function() {
return {
name: 'Log Unobtrusive Extension',
version: '0.0.12-0+sha.afc3898',
enableLogging: enableLogging,
restrictLogMethods: restrictLogMethods,
overrideLogPrefix: overrideLogPrefix,
disableDefaultColors: disableDefaultColors,
setLogMethodColor: setLogMethodColor,
overrideLogMethodColors: overrideLogMethodColors,
useDefaultLogPrefix: useDefaultLogPrefix,
configureLogFilters: configureLogFilters
};
};
// add methods to $provider
this.enableLogging = enableLogging;
this.overrideLogPrefix = overrideLogPrefix;
this.restrictLogMethods = restrictLogMethods;
this.disableDefaultColors = disableDefaultColors;
this.setLogMethodColor = setLogMethodColor;
this.overrideLogMethodColors = overrideLogMethodColors;
this.useDefaultLogPrefix = useDefaultLogPrefix;
this.configureLogFilters = configureLogFilters;
}]);
The MIT License
Copyright (c) 2014-2016 Google, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
var app = angular.module('ABApp', ['log.ex.uo']);
app.config(['logExProvider', function(logExProvider) {
var enabledLogging=true, enableQuietly=true;
logExProvider.enableLogging(enabledLogging, enableQuietly);
logExProvider.restrictLogMethods(['error', 'warn', 'info', 'debug', 'log']);
logExProvider.overrideLogMethodColors({log: 'color:#000000;', warn: 'color: #FF9900; background: #FFFFCC;', debug: 'color: #990099;'});
logExProvider.overrideLogPrefix(function test1 (className)
{
var $injector = angular.injector([ 'ng' ]);
var $filter = $injector.get( '$filter' );
var separator = " >> ";
var format = "MMMM-dd-yyyy-h:mm:ssa";
var now = $filter('date')(new Date(), format);
var lineNumber = -1;
var callerFile = "<<NA>>";
var newErr = new Error();
//Start - Calculate line number
if (typeof newErr.stack !== 'undefined') {
var info = getLogInfo(newErr,1,2);
lineNumber = info[0];
callerFile = info[1];
}
else {
try {
throw newErr;
}
catch (e) {
if(!e.stack) {
lineNumber = -1;
}
else {
var info = getLogInfo(newErr,2,3);
lineNumber = info[0];
callerFile = info[1];
}
}
}
//End - Calculate line number
//When $log service's function is called from html file,
//the getLogInfo function does not get the line number and
//caller file right. You can capture that condition and set
//the values to desired values. Also, another method printStackTrace()
//is able to identify that the call was made from HTML button element,
//however, it only works in Chrome.
if(callerFile=="<anonymous>" || callerFile.indexOf("Function")>0)
{
//You can modify to reset callerFile to <<NA>> and lineNumber to -1
//Because if caller file is anonymous, it was not called from javascript file.
//callerFile="<<NA>>";
//lineNumber = -1;
//This function is able to print the stack and identify that the call
//was made from a button click. But, it does not work in IE and Firefox.
//printStackTrace();
}
return "" + (callerFile=="<<NA>>" ? "" : callerFile + "::") + (lineNumber<=0 ? "" : lineNumber + "::") + now + (!angular.isString(className) ? "" : "::" + className) + separator;
});
}]);
app.run( function($rootScope, $location, $http, $log) {
$log = $log.getInstance("RUN");
$log.info("I am RUNning");
});
app.controller('MyController', function ($scope, $log) {
$scope.$log = $log.getInstance("index-html");
$scope.message1 = 'Hello World!';
$log = $log.getInstance("MyController");
$log.log("Hello Log from Log Extender.");
$log.info("Hello Info from Log Extender.");
$log.debug("Hello Debug from Log Extender.");
$log.warn("Hello Warn from Log Extender.");
$log.error("Hello Error from Log Extender.");
});
//Anonymous function
function getLogInfo(newErr, sliceIndex1, sliceIndex2)
{
var lineNumber = -1;
var callerFile = "<<NA>>";
var lineLocation;
var stack = newErr.stack.split('\n').slice(2);
if (navigator.userAgent.indexOf("Chrome") > -1) {
stack.shift();
}
stack = stack.slice(sliceIndex1, sliceIndex2);
var stackInString = stack + '';
var splitStack;
if (navigator.userAgent.indexOf("Chrome") > -1) {
splitStack = stackInString.split(" ");
}
else {
splitStack = stackInString.split("@");
}
//console.log(splitStack);
//get the last value in stack
lineLocation = splitStack[splitStack.length - 1];
lineLocation = lineLocation.substring(lineLocation.lastIndexOf("/") + 1);
lineLocation = reverse(lineLocation);
//console.log(lineLocation);
//get line number
lineNumber = lineLocation.split(":")[1];
lineNumber = reverse(lineNumber);
//get File name
callerFile = lineLocation.split(":")[2];
callerFile=reverse(callerFile);
//console.log (callerFile);
return [lineNumber, callerFile];
}
function reverse(s) {
return s.split('').reverse().join('');
}
function printStackTrace() {
var callstack = [];
var isCallstackPopulated = false;
try {
i.dont.exist+=0; //doesn't exist- that's the point
} catch(e) {
if (e.stack) { //Firefox
console.log("Its firefox Or Chrome");
var lines = e.stack.split('\n');
for (var i=0, len=lines.length; i<len; i++) {
console.log(lines[i]);
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
callstack.push(lines[i]);
}
}
//Remove call to printStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
else if (window.opera && e.message) { //Opera
var lines = e.message.split('\n');
for (var i=0, len=lines.length; i<len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
var entry = lines[i];
//Append next line also since it has the file info
if (lines[i+1]) {
entry += ' at ' + lines[i+1];
i++;
}
callstack.push(entry);
}
}
//Remove call to printStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
}
if (!isCallstackPopulated) { //IE and Safari
console.log("Its IE or Safari");
var currentFunction = arguments.callee.caller;
while (currentFunction) {
var fn = currentFunction.toString();
var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf('')) || 'anonymous';
callstack.push(fname);
currentFunction = currentFunction.caller;
}
}
//output(callstack);
}
<div>
<p>Reload this page with open console, enter text and hit the log button...</p>
<p>Original Log Service (from google angular example of $log)</p>
<label>Message:
<input type="text" ng-model="message" /></label>
<button ng-click="$log.log('original ' + message)">log</button>
<button ng-click="$log.warn('original ' + message)">warn</button>
<button ng-click="$log.info('original ' + message)">info</button>
<button ng-click="$log.error('original ' + message)">error</button>
<button ng-click="$log.debug('original ' + message)">debug</button>
</div>
<!--
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
-->
<div>
<h4>What does this plunker demonstrate</h4>
This plunker demonstrates the following:
<ol>
<li>Using multiple ng-apps on same page.</li>
<li>Angular's official sample of $log service usage.</li>
<li>Sample of Extending $log service using decorator concept (Angular Log Extender Module) - to prefix information before logging to console.
Following information is prefixed: Caller File Name, Line Number In Caller File From where call was made, Date Time Stamp, $log instance name
and actual message to be logged.
</li>
<li>Custom Function to get the line number and caller file name, from where the $log service's function is called. This works well when $log service's
function is called from a javascript file. But, not when called from ".html" files. In that case, I recommend using instance name so as to easily
identify which html file made the call, as demonstrated in the example as well.
</li>
</ol>
</div>
<div>
<h4>Thanks to the following resources:</h4>
<ul>
<li><a href="http://solutionoptimist.com/2013/10/07/enhance-angularjs-logging-using-decorators/#comment-166287">
$log Service and Decorators</a></li>
<li><a href="https://github.com/lwhiteley/AngularLogExtender/">
Angular Log Extender</a></li>
<li><a href="http://stackoverflow.com/a/32539274/6385674">
Ploughing the Net's solution for getting the line number.</a></li>
</ul>
</div>
<div>
<h4>Source Code</h4>
<p>
Original Log service sample code is provided by Angular (Google) and is used as is.
It consists of the following files
</p>
<ul>
<li>script.js</li>
<li>OriginalLogExample.html</li>
</ul>
<p>
Extended Log Service example uses <a href="https://github.com/lwhiteley/AngularLogExtender/wiki/05.-Set-Component-Class-Name"> Angular Log Extender.</a>,
which helps to control and decorate $log console output. It allows the users to customize the prefix for log messages.
I have used that concept and added the js file name, line number etc. to log prefix. The line number is obtained using a function
and line number is of the line from where $log is actually called (when called from the Javascript).
It consists of the following files
</p>
<ul>
<li>ABApp.js</li>
<li>ExtendedLogExample.html</li>
<li>LogExtender.js - Please note as the source for Angular Log Extender does not have a CDN location for its js file, I have used it locally. Thanks to Angular LogExtender team for this open source code.
Please read their wiki for extensive information on configuring this wonderful module.</li>
<li>index.html - Main html file. Includes other html files, javascripts and bootstraps modules.</li>
<li>ReadMe.html - Information about the plunker.</li>
</ul>
</div>
<div>
<h4>Output</h4>
<p>
In the console ouput, you will see 5 tokens for each logged line from Extended Logger.
</p>
<ul>
<li>1st is the name of file from where call was made.</li>
<li>2nd is the line number of $log line.</li>
<li>3rd is the Date time stamp of when the code was executed.</li>
<li>4th is the $log instance name, if used.</li>
<li>5th is the actual message.</li>
<li>The separators used are "::" and ">>".</li>
</ul>
</div>
<p>Extended Log Service</p>
<label>Message:
<input type="text" ng-model="message1" /></label>
<button ng-click="$log.log('Extd ' + message1)">log</button>
<button ng-click="$log.warn('Extd ' + message1)">warn</button>
<button ng-click="$log.info('Extd ' + message1)">info</button>
<button ng-click="$log.error('Extd ' + message1)">error</button>
<button ng-click="$log.debug('Extd ' + message1)">debug</button>