10

Correct Behavior

When I call console.log() directly from a function, the stack (function and file) from which i called is correct in dev-tools console, as expected.

main.js:

function main() {
    // Works correctly
    console.log('Hello from main()!');
}

Console:

Hello from main()!    ...    main.js:3                       

What I want

Now, when I add a second file called debug.js and call console.log from there, the file from where i called is debug.js, which is correct... but I need debug() to log as if it was called in main.js. Somehow I need to modify the caller, stack or trace to fool console.log() it was called from main.js, when it actually was from deubg.js.

Code

debug.js:

function debug(msg) {
   console.log(msg)
}

main.js

function main() {
   debug('Hello world!') // debug() in debug.js
}

Behavior

The current behavior:

Hello world!    ...    debug.js:2

The behavior I want:

Hello world!    ...    main.js:3
tscpp
  • 1,298
  • 2
  • 12
  • 35
  • If that's the case for all logs, you could maybe overwrite the console.log function with a function that is placed in main js. Something like: console.log = function () {console.log(arguments)}. We do something similar in an application with a separate Logger.js file, which overwrites console methods and adds a Date to each log entry. In the dev tools the origin file is always Logger.js. – NickG Apr 06 '21 at 14:13
  • 2
    I understand your question, what I don't understand is its purpose, why write a function that writes a parameterized console.log and call it from another file? If indeed the purpose of the 'console' is to help during a development, maintenance or fixes ... – Nicolás Alarcón Rapela Apr 06 '21 at 14:54

4 Answers4

8

The originally recommended solution below no longer appears to work:


I believe the issue here is that console log is typically executed from wherever it runs. However, if you were to pass back a function that performs the console log, this might work as you'd like it to. Can you try the below code?

Attempt #1

debug.js:

function debug(msg) {
   return (function(msg) { console.log(msg) })(msg)
}

main.js:

function main() {
   debug('Hello world!')
}

If that doesn't work, could you try this:

Attempt #2

debug.js:

function debug() {
   return function(msg) { console.log(msg) }
}

main.js:

function main() {
   debug()('Hello world!')
}

UPDATE

As the above solutions no longer work, these are some viable alternatives:

2 methods:

1. console.trace()

One workaround to this problem is to simply console.trace() rather than console.log(), which will log the entire call stack along with the logged data, so debug() would look like this:

function debug(msg) {
  console.trace(msg);
}

2. Error().stack

Alternatively, if you only want the very first item from the call stack, you can extract the entire call stack from a new Error() and then log that with your data, which might look something like this:

function debug(msg) {
  const stackTrace = Error().stack.split('\n    at ').slice(1);
  console.log(msg, `\n\ntriggered by ${stackTrace.at(-1)}`);
}

In either of these cases, main.js can simply use debug('Hello world!');.

Brandon McConnell
  • 5,776
  • 1
  • 20
  • 36
  • Attempt #1 did work for me in FF 87. Thanks a lot! – tscpp Apr 16 '21 at 17:58
  • Nice! Thanks for letting me know @tscpp. I've wondered about that, myself, so it's good to see there is a working solution for that. – Brandon McConnell Apr 16 '21 at 18:56
  • @tscpp is this still working for you? And if so, in which browser version? For me neither in Firefox 105 nor in Chrome 106 do any of the two suggestions yield the desired behavior. I still get "debug.js" in the stack-output – Sebastian Dec 13 '22 at 20:33
  • @Sebastian do you have a Stackblitz link (or similar) where I can troubleshoot the behavior you're experiencing? – Brandon McConnell Dec 13 '22 at 23:04
  • @BrandonMcConnell thanks for responding. Stackbiltz as well as Code-Snippets within StackOverflow have a different behaviour in their console-output (no line-number nor origin printed). Did you test in your own browser? I simply put in an `index.html`-file within a script tag the `debug1()('Hello world1!'); debug2()('Hello world!2')` and the definition of `debug1` and `debug2` in a separate `debug.js` file. So almost literally as you suggested. The output contains always `debug.js:2` and `debug.js:5` instead of `index.html:...` – Sebastian Dec 14 '22 at 11:36
  • 1
    @Sebastian I can confirm that this… no longer works . No fear though. It appears that this problem is much easier to solve than I originally expected or described. For the full call stack, simply use `console.trace()` rather than `console.log()`, or is you only want the very top of the call stack, you can log the last item in the call stack by pulling the call stack from a new `Error()`. Adding all these notes to my revised answer – Brandon McConnell Dec 14 '22 at 17:41
  • @BrandonMcConnell thanks for the confirmation! That's a pity. `console.trace` and `new Error().stack` are both interesting options that were proposed already in the other answers. But they make too much noise for a standard-logging. So I like your suggestion of extracting the relevant line of the stack. As your answer was accepted by @tscpp and he didn't like the `trace`-solution, I would have maybe kept your original answer within your new answer with a remark that it no longer works. – Sebastian Dec 14 '22 at 22:20
  • 1
    @Sebastian Good point. Rolled back and included the new solution below the old. – Brandon McConnell Dec 14 '22 at 23:39
  • @BrandonMcConnell some more remarks: to get the Code working also in FF, I would define the stack as follows: `new Error().stack.split('\n').filter(x => x && x.trim() && x.trim().toLowerCase() != 'error');` . This still gets rid off the 'error' line in Chrome and it gets rid off an extra empty line in FF. Furthermore, instead of `at(-1)` I would pick the entry `[1]` from the top, because when the stack gets longer, we need the second entry from the top and not from the end, in order to have it equivalent to the original console.log. – Sebastian Dec 15 '22 at 10:10
  • It's also worth mentioning that one can save space by giving the reference to the stack a different style with smaller font-size like this: `console.log(msg + " %c%s", "font-size:6pt", stackTrace[1])`. See [Mozilla's documentation of `console`](https://developer.mozilla.org/en-US/docs/Web/API/console#outputting_text_to_the_console) – Sebastian Dec 15 '22 at 10:16
  • @Sebastian My understanding was that the author would want the origin of the call stack, not only the function calling `debug()` but I can see why `[1]` might make more sense. Good call on the styling; I'll make that change now. – Brandon McConnell Dec 16 '22 at 16:22
  • 1
    @BrandonMcConnell well, my understanding was that the author wanted the same beavior from the redefined log-function, as from the original log-function, which would be [1]. But I agree that (-1) might also be interesting depending on the use case, so probably the author needs to settle which of the two was wanted ... – Sebastian Dec 19 '22 at 13:02
4

As the WHATWG specification says, the output of the console for every function (error, warn, log, etc...) is implementation-specific:

The printer operation is implementation-defined

As it happens, chromium based browsers display the current frame of the callstack (not the full callstack) when printing the result of a console.log, and you won't be able to change this behavior, because this is related to the JavaScript engine (V8 for chromium based browsers), and not customizable through JavaScript code.

The only JavaScript standard that allows you to display the full callstack is console.trace whose specification is here: https://console.spec.whatwg.org/#trace

It will display something like this for your example code:

console.trace in a chromium based browser

Guerric P
  • 30,447
  • 6
  • 48
  • 86
1

This can be done by creating Error instance new Error() which will hold the traces in string form like below.

function debug(...args) {
    const error = new Error();
    console.log(...args, error.stack.replace(/.+\n.+\n/, ''))
}

Though the MDN Error stack document says it's a non standard property but seems all browser has support for that Browser compatibility

Ravikumar
  • 2,085
  • 12
  • 17
  • Of course I did the same, that `error` holds the complete stack trace in multiline string format and replacing first two lines will give you the stack trace from where this `debug` method is called and I believe that's what your expectation. You can use that string the way you want. – Ravikumar Apr 14 '21 at 16:43
  • As I said, I only want to change the call stack which from console.log logs. What your example does is it log the stack/parts of it. – tscpp Apr 15 '21 at 11:02
0

What you could do is:

const log = console.log // for future use, preserve the default function
console.log = (...args) => {
    console.trace(...args)
}

On Node:

console.log = (...args) => {
    console.trace(...args, path.basename(__filename))
}

To get something like this:

const path = require("path");
console.log = (...args) => {
  console.trace(...args, path.basename(__filename))
}

function f() {
  return function g() {
    console.log("test")
  }
}

f()()
Yash Shah
  • 131
  • 5
  • Guerric P already covered `console.trace`. That's not what I want. I want to change the current frame/stack/position from which `console.log` was called. – tscpp Apr 17 '21 at 07:40