I would like to extend the 'console.log' function to add additional information to its output - but I dont want to affect the script name/line number information generated by the browser in the console window. See how if I create my own implementation, I get useless trace information, should I want to locate that region of code... (they all link to the log implementation, not the actual script that caused the log message)
Basically, my application is a very pluggable infrastructure, were any log output may occur within any number of frames.
As such, I want every log message to include a special unique identifier at the beginning of the log message.
I have tried replacing the console.log method with my own, but chrome complains with
Uncaught TypeError: Illegal invocation
this is how I override it
var orig = console.log;
console.log = function( message )
{
orig( (window == top ? '[root]' : '[' + window.name + ']') + ': ' + message );
}
Any ideas?
[EDIT]
Note: After fixing the 'illegal invocation' problem, it seems the filename/linenumber is still 'polluted' by the override...
[EDIT]
It looks like the general answer is - NO - despite some confusing goose chases, the desired functionality is NOT achievable in the current versions of browsers.
Yes, it is possible to add information without messing up the originating line numbers of the log invocation. Some of the other answers here came close, but the trick is to have your custom logging method return the modified logger. Below is a simple example that was only moderately tested that uses the context variant.
log = function() {
var context = "My Descriptive Logger Prefix:";
return Function.prototype.bind.call(console.log, console, context);
}();
This can be used with:
log("A log message...");
Here is a jsfiddle: http://jsfiddle.net/qprro98v/
One could get easily get creative and pass the context variable in, and remove the auto-executing parens from the function definition. i.e. log("DEBUG:")("A debug message"), log("INFO:")("Here is some info"), etc.
The only really import part about the function (in regards to line numbers) is that it returns the logger.
If your use case can deal with a few restrictions, there is a way that this can be made to work. The restrictions are:
The extra log content has to be calculated at bind time; it cannot be time sensitive or depend on the incoming log message in any way.
The extra log content can only be place at the beginning of the log message.
With these restrictions, the following may work for you:
var context = "ALIASED LOG:"
var logalias;
if (console.log.bind === 'undefined') { // IE < 10
logalias = Function.prototype.bind.call(console.log, console, context);
}
else {
logalias = console.log.bind(console, context);
}
logalias('Hello, world!');
http://jsfiddle.net/Wk2mf/
An acceptable solution can be to make your own log-function that returns a console.log function bound with the log arguments.
log = function() {
// Put your extension code here
var args = Array.prototype.slice.call(arguments);
args.unshift(console);
return Function.prototype.bind.apply(console.log, args);
}
// Note the extra () to call the original console.log
log("Foo", {bar: 1})();
This way the console.log call will be made from the correct line, and will be displayed nicely in the console, allowing you to click on it and everything.
It is actually possible in chrome at least. Here is the most relevant. This may vary depending on setup, and how i got the splits was to just log the whole stack, and find the information I needed.
var stack = new Error().stack;
var file = stack.split("\n")[2].split("/")[4].split("?")[0]
var line = stack.split("\n")[2].split(":")[5];
Here is the whole thing, preserving the native object logging.
var orig = console.log
console.log = function(input) {
var isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
if(isChrome){
var stack = new Error().stack;
var file = stack.split("\n")[2].split("/")[4].split("?")[0]
var line = stack.split("\n")[2].split(":")[5];
var append = file + ":" + line;
}
orig.apply(console, [input, append])
}
You need to call the console.log with the correct context (console):
orig.call(console, message);
To complete your function allowing multiple arguments:
var orig = console.log;
console.log = function() {
var msgs = [],
prefix = (window== top ? '[root]' : '[' + window.name + ']');
while(arguments.length) {
msgs.push(prefix + ': ' + [].shift.call(arguments));
}
orig.apply(console, msgs);
};
Demo: http://jsfiddle.net/je2wR/
Remember that you loose the built-in object/array browser in the console when combining objects with strings using the + sign.
I just answered this on a post that helped me answer the original 'alias' question:
(http://stackoverflow.com/a/12942764/401735)
my_log_alias = console.log.bind(console)
Apparently the capacity to do this has been designed in. Tested. Works.
thereafter my_log_alias is the same as console.log and can be called in the same way; Calling this from inside the function will report the line number for that function call, including the line inside of an alias or advice function where applicable.
Specifically, the line number Chrome provides will tell you the file the line is in, so what you are doing may be unneccesary; Consider reporting this as a bug/feature request in chrome that it provide this info in console.log.
Christopher Currie provided an excellent solution. I've expanded it a bit for my needs. Here's the AMD module:
define([], function () {
var enableDebug = true;
var separator = ">";
function bind(f, thisArg, ctx) {
if (f.bind !== 'undefined') { // IE < 10
return Function.prototype.bind.call(f, thisArg, ctx);
}
else {
return f.bind(thisArg, ctx);
}
}
function newConsole(context, parentConsole) {
var log;
var debug;
var warn;
var error;
if (!parentConsole) {
parentConsole = console;
}
context = context + separator;
if (enableDebug) {
debug = bind(console.log, console, context + "DEBUG" + separator);
} else {
debug = function () {
// suppress all debug messages
};
}
log = bind(console.log, console, context);
warn = bind(console.warn, console, context);
error = bind(console.error, console, context);
return {
debug: debug,
info: log,
log: log,
warn: warn,
error: error,
/* access console context information */
context: context,
/* create a new console with nested context */
nest: function (subContext) {
return newConsole(context + subContext, this);
},
parent: parentConsole
};
}
return newConsole("");
});
By default this will output > {message}. You can also add nested context to you logging, e.g. console.nest("my").log("test") will output >my> test.
I've also added a debug function that will indent messages with >DEBUG>
Hope somebody will find it useful.
Not long ago Chrome introduced a feature that can solve your problem without code hacks. It is called "blackbox" which basically allows you to mark files which should be ignored with their tools.
https://gist.github.com/paulirish/c307a5a585ddbcc17242
Yes, this solution is browser specific, but if you are using Chrome you do want this solution.
The solutions with a huge hack around throwing an Error for each log can show the right line, but it will not be a clickable link in your console.
The solutions based on binding/aliasing only enables you to modify the printed text. You will not be able to forward the arguments to a third function for further processing.
I have looked into this several times and always found it was not possible.
My workaround if you are interested is to assign console to another variable and then wrap all my log messages in a function which lets me modify/style/whatever on the message.
It looks nice with CoffeeScript, not sure its practical with plain JS.
I just get into the habit of prefixing everything with x.
logger.debug x 'Foo'
log x 'Bar'
log x('FooBar %o'), obj
Unfrotuantly it's currenlty not possible, In the future we might be able to do it with the Proxy object in ECMAScript 6.
My use case was to auto-prefix console messages with helpful information like the arguments passed and executing method. at the moment the closest I got is using Function.prototype.apply.
A simple approach is to just write your debug statements as such:
console.info('=== LazyLoad.css(', arguments, '): css files are skipped, gives us a clean slate to style within theme\'s CSS.');
A complicated approach is to use helper function as per below, I personally now prefer the simple approach.
/* Debug prefixing function
* ===========================
*
* A helper used to provide useful prefixing information
* when calling `console.log`, `console.debug`, `console.error`.
* But the catch is that to utilize one must leverage the
* `.apply` function as shown in the below examples.
*
* ```
* console.debug.apply(console, _fDebugPrefix(arguments)
* .concat('your message'));
*
* // or if you need to pass non strings
* console.debug.apply(console, _fDebugPrefix(arguments)
* .concat('json response was:', oJson));
*
*
* // if you need to use strict mode ("use strict") one can't
* // extract the function name but following approach works very
* // well; updating the name is just a matter of search and replace
* var aDebugPrefix = ['fYourFunctionName('
* ,Array.prototype.slice.call(arguments, 0),
* ,')'];
* console.debug.apply(console,
* aDebugPrefix.concat(['json response was:', oJson]));
* ```
*/
function _fDebugPrefix(oArguments) {
try {
return [oArguments.callee.name + '('
,Array.prototype.slice.call(oArguments, 0)
, ')'];
}
catch(err) { // are we in "use strict" mode ?
return ['<callee.name unsupported in "use strict">('
,Array.prototype.slice.call(oArguments, 0)
, ')'];
}
}
Reusable class in TS/JS
// File: LogLevel.ts
enum LogLevel {
error = 0,
warn,
info,
debug,
verbose,
}
export default LogLevel;
// File: Logger.js
import LogLevel from "./LogLevel";
export default class Logger {
static id = "App";
static level = LogLevel.info;
constructor(id) {
this.id = id;
const commonPrefix = `[${Logger.id}/${this.id}]`;
const verboseContext = `[V]${commonPrefix}`;
if (console.log.bind === "undefined") {
// IE < 10
this.verbose = Function.prototype.bind.call(console.log, console, verboseContext);
} else {
this.verbose = console.log.bind(console, verboseContext);
}
if (LogLevel.verbose > Logger.level) {
this.verbose = function() {
return // Suppress
};
}
const debugContext = `[D]${commonPrefix}`;
if (console.debug.bind === "undefined") {
// IE < 10
this.debug = Function.prototype.bind.call(console.debug, console, debugContext);
} else {
this.debug = console.debug.bind(console, debugContext);
}
if (LogLevel.debug > Logger.level) {
this.debug = function() {
return // Suppress
};
}
const infoContext = `[I]${commonPrefix}`;
if (console.info.bind === "undefined") {
// IE < 10
this.info = Function.prototype.bind.call(console.info, console, infoContext);
} else {
this.info = console.info.bind(console, infoContext);
}
if (LogLevel.info > Logger.level) {
this.info = function() {
return // Suppress
};
}
const warnContext = `[W]${commonPrefix}`;
if (console.warn.bind === "undefined") {
// IE < 10
this.warn = Function.prototype.bind.call(console.warn, console, warnContext);
} else {
this.warn = console.warn.bind(console, warnContext);
}
if (LogLevel.warn > Logger.level) {
this.warn = function() {
return // Suppress
};
}
const errorContext = `[E]${commonPrefix}`;
if (console.error.bind === "undefined") {
// IE < 10
this.error = Function.prototype.bind.call(console.error, console, errorContext);
} else {
this.error = console.error.bind(console, errorContext);
}
if (LogLevel.error > Logger.level) {
this.error = function() {
return // Suppress
};
}
}
}
Usage (React):
// File: src/index.tsx
// ...
Logger.id = "MCA"
const env = new Env()
if (env.env == Environment.dev) {
Logger.level = LogLevel.verbose
const log = new Logger("Main")
log.info("Environment is 'Development'")
}
///...
// File: src/App/CookieConsent/index.tsx
import React, { useEffect } from "react";
import { useCookies } from "react-cookie";
import "./index.scss";
import Logger from "#lib/Logger" // #lib is just alias configured in webpack.
const cookieName = "mca-cookie-consent";
// const log = new Logger(CookieConsent.name) // IMPORTANT! Don't put log instance here. It is too early! Put inside function.
export default function CookieConsent(): JSX.Element {
const log = new Logger(CookieConsent.name) // IMPORTANT! Have to be inside function, not in global scope (after imports)
useEffect(() => {
log.verbose(`Consent is accepted: ${isAccepted()}`);
}, []);
const [cookie, setCookie] = useCookies([cookieName]);
function isAccepted(): boolean {
return cookie[cookieName] != undefined;
}
function containerStyle(): React.CSSProperties {
return isAccepted() ? { display: "none" } : {};
}
function handleClick() {
const expires = new Date();
expires.setFullYear(expires.getFullYear() + 1);
log.verbose(`Accepted cookie consent. Expiration: ${expires}`)
setCookie(cookieName, true, { path: "/", expires: expires, sameSite: "lax" });
}
return (
<div className="cookieContainer" style={containerStyle()}>
<div className="cookieContent">
<div>
<p className="cookieText">This website uses cookies to enhance the user experience.</p>
</div>
<div>
<button onClick={handleClick} className="cookieButton">
I understand
</button>
</div>
</div>
</div>
);
}
Output in browser console:
20:47:48.190 [I][MCA/Main] Environment is 'Development' index.tsx:19
20:47:48.286 [V][MCA/CookieConsent] Consent is accepted: false index.tsx:13
20:47:52.250 [V][MCA/CookieConsent] Accepted cookie consent. Expiration: Sun Jan 30 2022 20:47:52 GMT+0100 (Central European Standard Time) index.tsx:29
Hope this helps for some of your cases...
const log = console.log;
export default function middleWare(optionalStringExtension = '') {
console.log = (...args) => {
log(...args, optionalStringExtension);
}
}
Either run as middleware, top of file, or first line of function.
I ran into this issue as well about extending console.log() so that the application can extend, control and do fancy stuff with it in addition to logging stuff to the console. Losing the line number information was tantamount to failure, however. After wrestling with the issue, I came up with a long-winded workaround, but at least it's still a "1-liner" to use.
First, define a global class to use or add some methods to your main existing "app" class:
/**
* Log message to our in-app and possibly on-screen console, return args.
* #param {!string} aMsgLevel - one of "log", "debug", "info", "warn", or "error"
* #param {any} aArgs - the arguments to log (not used directly, just documentation helper)
* #returns args so it can be nested within a console.log.apply(console,app.log()) statement.
*/
MyGlobalClassWithLogMethods.prototype.debugLog = function(aMsgLevel, aArgs) {
var s = '';
var args = [];
for (var i=1; i<arguments.length; i++) {
args.push(arguments[i]);
if (arguments[i])
s += arguments[i].toString()+' ';
}
if (typeof this.mLog === 'undefined')
this.mLog = [];
this.mLog.push({level: aMsgLevel, msg: s});
return args;
};
MyGlobalClassWithLogMethods.prototype.log = function() {
var args = ['log'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.debug = function() {
var args = ['debug'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.info = function() {
var args = ['info'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.warn = function() {
var args = ['warn'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.error = function() {
var args = ['error'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
//not necessary, but it is used in my example code, so defining it
MyGlobalClassWithLogMethods.prototype.toString = function() {
return "app: " + JSON.stringify(this);
};
Next, we put those methods to use like so:
//JS line done as early as possible so rest of app can use logging mechanism
window.app = new MyGlobalClassWithLogMethods();
//only way to get "line info" reliably as well as log the msg for actual page display;
// ugly, but works. Any number of params accepted, and any kind of var will get
// converted to str using .toString() method.
console.log.apply(console,app.log('the log msg'));
console.debug.apply(console,app.debug('the log msg','(debug)', app));
console.info.apply(console,app.info('the log msg','(info)'));
console.warn.apply(console,app.warn('the log msg','(warn)'));
console.error.apply(console,app.error('the log msg','(error)'));
Now the console gets log messages with their appropriate line information as well as our app contains an array of log messages that can be put to use. For example, to display your in-app log using HTML, JQuery and some CSS the following simplistic example can be used.
First, the HTML:
<div id="debug_area">
<h4 class="text-center">Debug Log</h4>
<ul id="log_list">
<!-- console log/debug/info/warn/error ('msg') lines will go here -->
</ul>
</div>
some CSS:
.log_level_log {
color: black;
background-color: white;
font-size: x-small;
}
.log_level_debug {
color: #060;
background-color: #80FF80;
font-size: x-small;
}
.log_level_info {
color: #00F;
background-color: #BEF;
font-size: x-small;
}
.log_level_warn {
color: #E65C00;
background-color: #FB8;
font-size: x-small;
}
.log_level_error {
color: #F00;
background-color: #FBB;
font-size: x-small;
}
and some JQuery:
var theLog = app.mLog || [];
if (theLog.length>0) {
var theLogList = $('#log_list');
theLogList.empty();
for (var i=0; i<theLog.length; i++) {
theLogList.prepend($('<li class="log_level_'+theLog[i].level+'"></li>').text(theLog[i].msg));
}
}
This is a simplistic use, but once you have the mechanism in place, you can do whatever your imagination can come up with, including leaving the log lines in the code, but setting a threshold so that only warnings and errors get through. Hopefully this helps others with their projects.
Today you have to use args with rest operator, because as the Mozilla docs says Function.arguments has been deprecated and is not accessible in arrow functions. So simply you can extend it like below:
//#1
const myLog= (...args) =>
console.log.bind(console, ...args);
//myLog("this is my new log")();
//#2
const myNewLog= (...args) =>{
const prefix = "Prefixed: ";
return console.log.bind(console, ...[prefix,...args]);
}
//myNewLog("test")()
And you can make a beautifulLog like this:
//#3
const colorizedLog = (text, color= "#40a7e3", ...args) =>
console.log.bind(
console,
`%c ${text}`,
`font-weight:bold; color:${color}`,
...args
);
//colorizedLog("Title:", "#40a7e3", "This is a working example")();
This snippet apply a prefix to logs for all levels (console.log console.debug console.info ...) :
export const makeConsole = (context: string, cons = console): Console =>
Object.getOwnPropertyNames(cons).reduce((c, lev) => {
if (typeof cons[lev] === "function") {
c[lev] = Function.prototype.bind.call(cons[lev], cons, context);
}
return c;
}, {});
console.debug("Hello world!")
// >> Hello world!
console = makeConsole("[logging is fun]")
// >> [logging is fun] Hello world!
Bonus, for React peeps:
export function useConsole(context: string): Console {
return React.useMemo(() => makeConsole(context), [context]);
}
Try setTimeout(console.log.bind(console,'foo'));
Related
Can anyone shed some light on how to disable console.log() on production? I have seen achievements as below:
Are you using some kind of module bundler ?
In webpack you have option to drop_console in build. What drop console does is remove all console.log statements from your code.
There is also npm module for same please take a look.
https://www.npmjs.com/package/babel-plugin-transform-remove-console
This is a bagel plugin and if you have babel setup you can use it.
If all this doesn't meet your requirements. You can simply override console.log statement or use custom logger as other answers have suggested.
var DEBUG = false;
if(!DEBUG){
if(!window.console) window.console = {};
var methods = ["log", "debug", "warn", "info"];
for(var i=0;i<methods.length;i++){
console[methods[i]] = function(){};
}
}
Approach 1: You can save the console.log to your own function and remove the original.
Something like:
writeConsoleLog = console.log; //save the log function in your own function
console.log = function(){} //override the original log function
console.log("Text"); //does nothing
writeConsoleLog("Text"); //mimics the console.log() and writes to the console
Aproach 2: You can console.log whatever is required first, then remove the log function like so
console.log("The content you want to print"); //will write to the console
console.log = function(){} //override the log function
console.log("Text"); //now it does nothing
You can replace all console.log(...) in your code with some other custom function to display the log info as you want, for example in a DIV element.
But if you really want to override the console.log function, you can do it simply like as following (generally not recommended):
#myLogDiv {color: #604040; border: 1px solid gray; padding: 5pt; }
<script>
console.log = function(t) {
document.getElementById("myLogDiv").innerText += t + "\r\n";
}
</script>
<div id="myLogDiv">» My Log: <br/></div>
<p>This is normal content</p>
<script>
console.log("hello");
</script>
<p>Other content...</p>
<script>
console.log("world!");
</script>
Can enable and disable by this example!
console.log("Before disabled logs");
const showLogs = false
if( !showLogs ) {
console.log = function() {}
}
console.log("After disabled logs #1");
console.log("After disabled logs #2");
Similar to Dodo's answer, I created a simple logging scheme that lets me control the logging level based on the host (limit output in production) and a query parameter. Here's my solution:
(() => {
const pageURL = new URL(location.href);
const prodMode = pageURL.host == 'oomdraw.com';
const logLevels = ['Debug', 'Info', 'Warn', 'Error'];
const logLevel = pageURL.searchParams.get('ll') || (prodMode ? 'Error' : 'Debug');
const ml = logLevels.indexOf(logLevel);
for (let i = 0; i < logLevels.length; i++) {
window[`log${logLevels[i]}`] = i >= ml ? console[logLevels[i].toLowerCase()] : () => {};
}
logDebug('debug messages enabled');
logInfo('info messages enabled');
logWarn('warn messages enabled');
logError('error messages enabled');
})();
I have some code that exercises the “invalid values” setting on an element range index. In this case, I have configured a dateTime element range index on the onDate element in my database (which will apply to both XML elements and JSON properties). I’ve set that index to reject invalid values. This setting means if I try to set the value of an onDate element and it is not castable to a dateTime or is null (literal null in JSON or xsi:nil="true" in XML), my update will fail. (The opposite behavior is to completely ignore invalid values.)
I tried the following code in Server-Side JavaScript in MarkLogic 8.0-4:
'use strict';
declareUpdate();
var errors = [];
var inputs = {
'/37107-valid.json': (new Date()).toISOString(),
'/37107-invalid.json': 'asdf', // Should throw an error
'/37107-null.json': null
};
for(var uri in inputs) {
try {
xdmp.documentInsert(
uri,
{ 'onDate': inputs[uri] },
xdmp.defaultPermissions(),
['37107'] // Collections
);
} catch(err) {
errors.push(err);
}
}
errors.length;
I would have expected my request to succeed and to end up with 1 === errors.length, because only the second insert would have failed because 'asdf' is not castable as a dateTime and it is not null. However, instead I get an XDMP-RANGEINDEX error and my transaction fails. Why doesn’t my try/catch work here?
The issue is how MarkLogic processes update transactions. Rather than actually changing the data with each xdmp.docuentInsert(…) call, MarkLogic queues up all of the updates and applies them atomically at the end of the request. (This is also why you can’t see database updates within the same transaction.) Thus, the error isn’t being thrown until after the loop has executed and the database tries to commit the queued transactions. This behavior is the same in XQuery (slightly simplified):
let $uris := (
'/37107-valid.xml',
'/37107-invalid.xml',
'/37107-null.xml'
)
let $docs := (
<onDate>{fn:current-dateTime()}</onDate>,
<onDate>asdf</onDate>,
<onDate xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
)
return
for $uri at $i in $uris
return
try {
xdmp:document-insert($uri, $docs[$i], (), ('37107'))
} catch($err) {
xdmp:log($err)
}
In order to catch the errors synchronously, you’d need to put each update into its own transaction. In general, this approach will be much slower and resource intensive than MarkLogic’s default transaction handling. However, it’s illustrative here to demonstrate what’s happening under the covers and can come in handy for specific use cases, like this one.
In the example below, I use xdmp.invokeFunction() to “call” a function in a separate transaction from the parent request. (First-class functions for the win!) This allows the updates to be fully applied (or rolled back with an error) and the calling module to see the updates (or errors). I’ve wrapped the low-level xdmp.invokeFunction() in my own applyAs() function to provide some niceties, like correctly passing function arguments to the curried function.
'use strict';
var errors = [];
var inputs = {
'/37107-valid.json': (new Date()).toISOString(),
'/37107-invalid.json': 'asdf',
'/37107-null.json': null
};
var insert = applyAs(
function(uri, value) {
return xdmp.documentInsert(
uri,
{ 'onDate': inputs[uri] },
xdmp.defaultPermissions(),
['37107']
);
},
{ isolation: 'different-transaction', transactionMode: 'update' },
'one'
);
for(var uri in inputs) {
try {
insert(uri, inputs[uri]);
} catch(err) {
errors.push(err);
}
}
errors.length; // Correctly returns 1
// <https://gist.github.com/jmakeig/0a331823ad9a458167f6>
function applyAs(fct, options, returnType /* 'many', 'one', 'iterable' (default) */) {
options = options || {};
return function() {
var params = Array.prototype.slice.call(arguments);
// Curry the function to include the params by closure.
// xdmp.invokeFunction requires that invoked functions have
// an arity of zero.
var f = (function() {
return fct.apply(null, params);
}).bind(this);
// Allow passing in user name, rather than id
if(options.user) { options.userId = xdmp.user(options.user); delete options.user; }
// Allow the functions themselves to declare their transaction mode
if(fct.transactionMode && !(options.transactionMode)) { options.transactionMode = fct.transactionMode; }
var result = xdmp.invokeFunction(f, options); // xdmp.invokeFunction returns a ValueIterator
switch(returnType) {
case 'one':
// return fn.head(result); // 8.0-5
return result.next().value;
case 'many':
return result.toArray();
case 'iterable':
default:
return result;
}
}
}
I want to log objects using log4javascript. For example consider the following code:
function LogObject() {
var blah = {
one: 42,
two: "486"
};
logger.Info(blah);
Assuming that logger is instance of log4javascript logger that is properly set up:
var logger = log4javascript.getLogger("InternalLogger");
var ajaxAppender = new log4javascript.AjaxAppender(url),
jsonLayout = new log4javascript.JsonLayout(false, false);
ajaxAppender.setLayout(jsonLayout);
ajaxAppender.addHeader("Content-Type", "application/json");
logger.addAppender(ajaxAppender);
I am expecting the result to the following: request payload contains array of messages first of which is my object serialized into JSON. What I see is array of messages first of which has string "Object object" (like toString() method was invoked). How can I achieve that?
JsonLayout formats the logging event (which includes log level, timestamp and logger name in addition to the log message(s)) as JSON rather than the log message, which is pretty much assumed to be a string. The reason for this is to avoid a dependency on a JSON library for older browsers; generating JSON for the simple, known data that JsonLayout deals with is no problem without a JSON library but handling arbitrary objects definitely requires one.
The workaround I'd suggest is simply to format the message before you pass it to the logging call:
logger.info( JSON.stringify(blah) );
We were following #Tim Down's suggestion
logger.info( JSON.stringify(blah) );
But we had performance issues since the JSON.stringify happens before logger.info is called, therefore it will always happen even if the logging level is set to ignore this log.
In order to work around this I wrote a new lazy layout so that the stringification only happens if the log is actually output. In order to be more flexible it also alows passing a function, in which case it outputs the result of running said function.
Usage:
logger.trace("Received ", widget, " which has ", () => countFrimbles(widget), ' frimbles');
Implementation:
function LazyFormatLayout() { }
LazyFormatLayout.prototype = new log4javascript.Layout();
LazyFormatLayout.prototype.format = function (loggingEvent) {
var time = loggingEvent.timeStamp.toTimeString().split(/\s/)[0];
var head = time + ' ' + loggingEvent.logger.name + ' [' + loggingEvent.level.name + '] - ';
var body = loggingEvent.messages.map(function (arg) {
try {
switch (typeof (arg)) {
case 'function':
return arg();
case 'object':
return JSON.stringify(arg);
}
}
catch (e) {
return '<<error while logging: ' + e.stack + '>>';
}
return arg;
}).join('');
if (!loggingEvent.exception)
return head + body;
return head + body + ' ==> Exception: ' + loggingEvent.exception.stack;
}
LazyFormatLayout.prototype.ignoresThrowable = function () { return false; };
LazyFormatLayout.prototype.toString = function () { return "LazyFormatLayout"; };
Question is somewhat dated, but a simple google search turned up this question and there seems to be a build-in way to log objects:
var log = log4javascript.getDefaultLogger();
log.info("log following object",{ data:5, text:"bla" });
output
12:49:43 INFO - log following object {
data: 5,
text: bla
}
JavaScript developers who have spent time in languages like C often miss the ability to use certain types of introspection, like logging line numbers, and what method the current method was invoked from. Well if you're using V8 (Chrome, Node.js) you can employ the following.
Object.defineProperty(global, '__stack', {
get: function(){
var orig = Error.prepareStackTrace;
Error.prepareStackTrace = function(_, stack){ return stack; };
var err = new Error;
Error.captureStackTrace(err, arguments.callee);
var stack = err.stack;
Error.prepareStackTrace = orig;
return stack;
}
});
Object.defineProperty(global, '__line', {
get: function(){
return __stack[1].getLineNumber();
}
});
console.log(__line);
The above will log 19.
Combined with arguments.callee.caller you can get closer to the type of useful logging you get in C via macros.
The problem with the accepted answer, IMO, is that when you want to print something you might be using a logger, and when that is the case, using the accepted solution will always print the same line :)
Some minor changes will help avoiding such a case!
In our case, we're using Winston for logging, so the code looks like this (pay attention to the code-comments below):
/**
* Use CallSite to extract filename and number, for more info read: https://v8.dev/docs/stack-trace-api#customizing-stack-traces
* #returns {string} filename and line number separated by a colon
*/
const getFileNameAndLineNumber = () => {
const oldStackTrace = Error.prepareStackTrace;
try {
// eslint-disable-next-line handle-callback-err
Error.prepareStackTrace = (err, structuredStackTrace) => structuredStackTrace;
Error.captureStackTrace(this);
// in this example I needed to "peel" the first CallSites in order to get to the caller we're looking for
// in your code, the number of stacks depends on the levels of abstractions you're using
// in my code I'm stripping frames that come from logger module and winston (node_module)
const callSite = this.stack.find(line => line.getFileName().indexOf('/logger/') < 0 && line.getFileName().indexOf('/node_modules/') < 0);
return callSite.getFileName() + ':' + callSite.getLineNumber();
} finally {
Error.prepareStackTrace = oldStackTrace;
}
};
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 7 years ago.
Improve this question
How can i write a console log wrapper that:
Keeping the recorded line number and file name of the log statement intact
Provides access to all log severity methods (error, log, debug, ...) and shows them in the console as they where logged
does provide some fallback (for example calls the log method when the browser does not support error)
can be switched off in a central location, so I can switch off logging for production
does handle the case that no console exists, and does not throw errors
Since logging in Java Script is so inconsistent, there must be some solution. Implementing it myself is a little bit tedious, but there seems to be no good library.
I currently found this logger that provides all the features, but it does mess up the line numbers. http://benalman.com/projects/javascript-debug-console-log/
There is my own log4javascript, which has its own logging console but also provides a wrapper around console.log. It fulfils all your criteria except keeping line numbers intact, which is impossible to achieve if you're wrapping calls to console.log() etc. in another function.
var log = log4javascript.getLogger("main");
var appender = new log4javascript.BrowserConsoleAppender();
log.addAppender(appender);
log.debug("Hello world");
I would also recommend log4javascript and explain how you can still keep the information about the printed filename and line, at least in Chrome.
I am not talking about changing the filename and line printed by Chrome but you can get to the information you are interested in and append it to the log statement.
My solution has been a quick hack but I think with a little more work you can get nicely formatted log statements.
It probably has also a heavy performance-impact, but since you won't leave your logs activated in production this shouldn't be too much of a problem.
The Concept
In Chrome you can create an Error object which provides a stack property that shows you your current stack location and somewhere in the stack string you find the file and line number of your calling script.
> new Error().stack
"Error
at eval at <anonymous> (eval at evaluate (unknown source))
at eval at evaluate (unknown source)
at FrameMirror.evaluate (native)
at Object.evaluate (unknown source)
at Object._evaluateOn (unknown source)
at Object._evaluateAndWrap (unknown source)
at Object.evaluateOnCallFrame (unknown source)
at meinAjaxAufruf (http://localhost:8080/numberajax.js:21:9)
at HTMLInputElement.onkeyup (http://localhost:8080/numberajax.html:15:188)"
For a log4javascript call the stack trace might look something like this:
"Error
at Object.append (http://localhost:8080/log4javascript_uncompressed.js:1921:17)
at Object.doAppend (http://localhost:8080/log4javascript_uncompressed.js:1047:9)
at Object.callAppenders (http://localhost:8080/log4javascript_uncompressed.js:647:27)
at Object.log (http://localhost:8080/log4javascript_uncompressed.js:640:10)
at Object.debug (http://localhost:8080/log4javascript_uncompressed.js:748:9)
at meinAjaxAufruf (http://localhost:8080/numberajax.js:36:16)
at HTMLInputElement.onkeyup (http://localhost:8080/numberajax.html:16:188)"
And the file and line that made the log4javascript call and that i am interested in is
at meinAjaxAufruf (http://localhost:8080/numberajax.js:36:16)
The Solution
I am guessing that the stack depth from the script your interested in to where the actual console call happens is always the same. So now you simply have to find out where the BrowserConsoleAppender makes its window.console access and add the line you are interested in to the formatted string. I did the following changes to log4javascript_uncompressed.js (version 1.4.2 line 1913):
} else if (window.console && window.console.log) { // Safari and Firebug
var formattedMesage = getFormattedMessage();
//---my additions
var isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
if(isChrome){
var stack = new Error().stack;
var lineAccessingLogger = stack.split("\n")[6];
formattedMesage += "\n" + lineAccessingLogger;
}
//---
// Log to Firebug using its logging methods or revert to the console.log
// method in Safari
if (window.console.debug && Level.DEBUG.isGreaterOrEqual(loggingEvent.level)) {
window.console.debug(formattedMesage);
} else if (window.console.info && Level.INFO.equals(loggingEvent.level)) {
...
Now instead of
17:53:22,872 DEBUG - sending /NumberServlet?zahl=1&text=
log4javascript.js:154
I get
17:55:53,008 DEBUG - sending /NumberServlet?zahl=1&text=
at meinAjaxAufruf (http://localhost:8080/numberajax.js:36:16) log4javascript_uncompressed.js:1930
It sure isn't a nice solution :), but I get what I need.
With a little more knowledge of the framework I suppose one could change the PatternLayout in a way that you can define how to print the file name/location and line number.
edit Instead of my prior solution I made some modifications to the PatternLayout.prototype.format function, so now I can use the additional option %l to define where and how I want to output the calling file and its line. I published my changes and a usage example as a Gist.
We had this issue with our log wrapper also and it turns out there is a fantastic, simple workaround using partial function application:
if(DEBUG_ENABLED && (typeof console != 'undefined')) {
this.debug = console.log.bind(console);
}
else {
this.debug = function(message) {};
}
With this, your browser will detect the correct line number and file of the source you wanted to log.
Crossposting from related question (A proper wrapper for console.log with correct line number?) but with updated solution to address multiple methods.
I liked #fredrik's answer, so I rolled it up with another answer which splits the Webkit stacktrace, and merged it with #PaulIrish's safe console.log wrapper. "Standardizes" the filename:line to a "special object" so it stands out and looks mostly the same in FF and Chrome.
Testing in fiddle: http://jsfiddle.net/drzaus/pWe6W/9/
_log = (function (methods, undefined) {
var Log = Error; // does this do anything? proper inheritance...?
Log.prototype.write = function (args, method) {
/// <summary>
/// Paulirish-like console.log wrapper. Includes stack trace via #fredrik SO suggestion (see remarks for sources).
/// </summary>
/// <param name="args" type="Array">list of details to log, as provided by `arguments`</param>
/// <param name="method" type="string">the console method to use: debug, log, warn, info, error</param>
/// <remarks>Includes line numbers by calling Error object -- see
/// * http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
/// * https://stackoverflow.com/questions/13815640/a-proper-wrapper-for-console-log-with-correct-line-number
/// * https://stackoverflow.com/a/3806596/1037948
/// </remarks>
// via #fredrik SO trace suggestion; wrapping in special construct so it stands out
var suffix = {
"#": (this.lineNumber
? this.fileName + ':' + this.lineNumber + ":1" // add arbitrary column value for chrome linking
: extractLineNumberFromStack(this.stack)
)
};
args = args.concat([suffix]);
// via #paulirish console wrapper
if (console && console[method]) {
if (console[method].apply) { console[method].apply(console, args); } else { console[method](args); } // nicer display in some browsers
}
};
var extractLineNumberFromStack = function (stack) {
/// <summary>
/// Get the line/filename detail from a Webkit stack trace. See https://stackoverflow.com/a/3806596/1037948
/// </summary>
/// <param name="stack" type="String">the stack string</param>
// correct line number according to how Log().write implemented
var line = stack.split('\n')[3];
// fix for various display text
line = (line.indexOf(' (') >= 0
? line.split(' (')[1].substring(0, line.length - 1)
: line.split('at ')[1]
);
return line;
};
// method builder
var logMethod = function(method) {
return function (params) {
/// <summary>
/// Paulirish-like console.log wrapper
/// </summary>
/// <param name="params" type="[...]">list your logging parameters</param>
// only if explicitly true somewhere
if (typeof DEBUGMODE === typeof undefined || !DEBUGMODE) return;
// call handler extension which provides stack trace
Log().write(Array.prototype.slice.call(arguments, 0), method); // turn into proper array & declare method to use
};//-- fn logMethod
};
var result = logMethod('log'); // base for backwards compatibility, simplicity
// add some extra juice
for(var i in methods) result[methods[i]] = logMethod(methods[i]);
return result; // expose
})(['error', 'debug', 'info', 'warn']);//--- _log
To keep it simple, I've the below wrapper for console methods:
var noop = function () {};
window.consolex = {
debug : window.console && window.console.debug && console.debug.bind(console) || noop,
log : window.console && window.console.log && console.log.bind(console) || noop,
warn: window.WARN = window.console && window.console.warn && console.warn.bind(console) || noop,
error: window.ERROR = window.console && window.console.error && console.error.bind(console) || noop
};
Also, for better logs in IE and older browsers, please read: Detailed console logging
Google Chrome will soon have a feature that will be of interest to this thread.
You can enable it now by:
Enable chrome://flags/#enable-devtools-experiments
Click on cog in dev tools
Go to Experiments Tab
Check "Javascript frameworks debugging"
Go to General Tab
Under the Sources section
Check "Skip stepping through sources with particular names"
In the pattern inputbox: type in the file name that you see now (app.log.js)
Restart and enjoy :)
References:
Tests from chrom devtools
devtools Issues thread
devtools code review
I answered this question here, but in short see the codepen for full implementation. However, this does everything you want, cross browser, no errors, correct line numbers, all available console methods, global and local control:
var Debugger = function(gState, klass) {
this.debug = {}
if (!window.console) return function(){}
if (gState && klass.isDebug) {
for (var m in console)
if (typeof console[m] == 'function')
this.debug[m] = console[m].bind(window.console, klass.toString()+": ")
}else{
for (var m in console)
if (typeof console[m] == 'function')
this.debug[m] = function(){}
}
return this.debug
}
And use it like this:
isDebug = true //global debug state
debug = Debugger(isDebug, this)
debug.log('Hello Log!')
I found a solution (requires jquery) somehwere on the Web but it does not work in most browsers.
I changed it and it works in Firefox (Mac, Linux. Android), Chrome (Mac, Linux. Android) and Safari and other Android webkit browsers.
Just write the following code to a file called e.g. debug.js and include it after the inclusion of 'jquery.js' in the <head> section of your webpage and it will work after the page has loaded (document.ready). I still have to find out to allow debugging before everything is loaded (e.g. only the <head>...</head> ).
The webpage has to be called with ?d=1 in the URL and when using Safari ?d=1s as I cannot make a distinction between Safari and another Webkit browser in the user agent and Safari has a different behavior in line number and file name handling than other Webkit browsers.
The function p_r(expression) logs to the window of the id #js_debug and to the console (if opened) with the file name and line number.
var g_d = null;
function sortObj(theObj)
{
var sortable = [];
for (var i in theObj) {
sortable.push(i);
}
sortable.sort();
var copy = new Object;
for (var i in sortable) {
var ind = sortable[i];
copy[ind] = theObj[ind];
}
return copy;
}
function p_r(s, comment, level)
{
if (!g_d) return;
var res = s;
var pre = new Array(""," " , " ", " ", " ");
if (comment) comment += ' : ';
if (arguments.length<2) comment='';
if (arguments.length<3) level = 0;
// if (console) console.log(s);
if (typeof(s) == 'object') {
var copy = sortObj(s);
comment += '\n';
res = '[object]\n';
if (level < 2) {
for (var i in copy) {
if (typeof(copy[i]) != "function")
res += pre[level] + (i) + " : " + p_r(copy[i], '', level+1) + " : " + typeof(copy[i]) + "\n";
}
res += pre[level] + "[/object]\n";
}
}
else if (typeof(s) == 'function')
res = 'function';
else if (typeof(s) != 'string')
res = '' + s;
res = res.replace(/&/g, '&');
res = res.replace(/\x3C/g, '<');
res = res.replace(/>/g, '>');
if (level == 0) {
window.LOG=res;
console.log(window.LOG + comment + res);
g_d.innerHTML += (window.LOG + comment + res + '\n');
}
return res;
}
if (location.href.match(/d\=[1-9]/)) {
$(document).ready(function() {
$("body").prepend("<div id=\"js_debugclick\" onclick=\"$('#js_debug').toggle();\">JS DEBUG</div>\
<pre onclick=\"$('#js_debug').toggle();\" id='js_debug'></pre>\
");
$("head").append("<style type=\"text/css\">\
pre#js_debug {\
border: solid black 1px; background-color: #1CF; color: #000; display:none; position:absolute; top: 20px;\
font-family: Lucida Console, monospace; font-size: 9pt; height: 400px; overflow:scroll; width:100%;\
z-index:100;\
} \
#js_debugclick { \
color:red; font-weight:bold; \
} \
</style>\
");
g_d = document.getElementById('js_debug');
});
var __moredebug = location.href.match(/d\=[2-9]/);
var __issafari = /safari/.test(navigator.userAgent.toLowerCase()) && location.href.match(/d\=[1-9]s/);
var __iswebkit = /webkit/.test(navigator.userAgent.toLowerCase());
var __isopera = /opera/.test(navigator.userAgent.toLowerCase());
if (__moredebug) console.log(__issafari, __iswebkit);
/*#const*/ //for closure-compiler
//DEBUG=2 // 0=off, 1=msg:file:line:column, 2=msg:stack-trace
/*#const #constructor*/
Object.defineProperty(window,'__stack__',{get:function(){
try{i.dont.exist()}catch(e){
if (__moredebug) var x=e.stack.split(":"); for (i in x){console.log(i,x[i]);}
// console.log(e.stack.split(":")[13].match(/(\d+)/)[1]);
return e.stack.split(":")}
}})
/*#const #constructor*/
Object.defineProperty(window,'__file__',{get:function(){
var s=__stack__,l=s.length
var f= __issafari ? s[9] : (__isopera ? s[12] : (__iswebkit ? s[14] : s[9]));
return f.replace(/^.+?\/([^\/]+?)\?.+?$/, "$1");
}})
/*#const #constructor*/
Object.defineProperty(window,'__line__',{get:function(){
var s=__stack__,l=s.length
return __issafari ? s[10].match(/(\d+)/)[1] :(__isopera ? s[13].match(/(\d+)/)[1] : (__iswebkit ? s[15] : s[10].replace(/\n/, " ").replace(/(\d+).+?$/, "$1")));
}})
/*#const #constructor*/
Object.defineProperty(window,'__col__',{get:function(){
var s=__stack__,l=s.length
return (isNaN(s[l-2]))?"NA":s[l-1]
}})
/*#const #constructor*/
Object.defineProperty(window,'LOG',{
get:function(){return out},
set:function(msg){if(0)out=msg+"\t-\t"+__stack__
else out=__file__+" "+__line__+": ";
}
})
}//end if(DEBUG)