The best trick for actual debugging is to leverage your browser console.
In a local build you basically have a 1:1 mapping of source code to deployed code and the debugger here basically becomes your IDE. You can hit Ctrl+Shift+P in the inspector and use the same kind of fuzzy file matcher you have in Sublime Text or VS Code, and from there you can set breakpoints, modify the code in memory, and so on. The console will reflect on the entire scope and annotate the code with their runtime values, the same as you get when working inside a JetBrains IDE.
But it's JS, so you can tweak it without committing it to disk and so you get some form of REPL driven development. I can't remember the last time I've used a console.log over setting a breakpoint in the debugger and fucking with the application state at that point to understand an issue.
You can get quite close to that after you've bundled your code and deployed it to a server, so long as you've got good source maps going on.
Print debugging is invaluable, but I can't help but think there's something of Smalltalk or Lisp in how you can mess with your app within a sandbox through the inspector, at runtime. The only thing that breaks the model is the transpilation and minification, without sourcemaps.
FYI VS Code's JS debugger speaks Chrome Debugger Protocol and allows you to use the built in Debug Console just as you would the browser's debug console, also it's possible to set browser breakpoints/etc., and you get inline value hovers.
Then, one day, after 12 hours chasing a race condition bug, you learn that when you expand objects from console.log, it shows you the current value of properties and not the values at the time of the console log. Because the values are evaluated when you click on the arrow.
Then you quit web development and start a new career.
Rite of passage for every web developer. The moment when you realize this is the moment when the universe makes sense again... after long hours of total confusion.
FWIW, it's not like it could work in any other way.
When you console.log() an object, it just stores a pointer to the object, and decorates it with an interactive label containing some text, so that it looks nice. This is fast to do, and most of the time, it's exactly what you want.
To log a static snapshot with equivalent interactive expansion capability, console.log() would have to do a general deep copy of your object - it would have to walk every pointer in the object and replace the pointee with its own recursive full deep copy, making sure to detect and handle cycles well, and keeping track of every replacement to ensure referential integrity (e.g. if two random objects A and B both have a pointer to the same object C, the copied A' and B' better both point at the same C'). An unlucky console.log() could easily copy half of your heap into the console, and god help you if you logged a DOM element.
(Also, all these copies would not be garbage-collected until you cleared the console.)
An universal deep copy is impractical to implement (notice how nobody seems to ever implement it, at all, in any programming language), and having console.log() do one would be an incredibly powerful and unpredictable footgun. Meanwhile, if you want to log a static snapshot of an object, all you need to do is to write console.log(cloneForLog(object)), where cloneForLog() is a function you wrote that does whatever copying is appropriate in your situation.
I think the only bad thing about console.log() is that this behavior is not taught to people as a core and important aspect of the function. I guess maybe if console.log() was restricted to strings, and something like console.logPresentation() was a separate function for printing objects, people would check the docs first and wouldn't be surprised.
> To log a static snapshot with equivalent interactive expansion capability, console.log() would have to do a general deep copy of your object
Not necessarily, and if it did I don't think it would address the actual problem.
console.log could do all the presentation work upfront, right then and there when you log, and provide a collapsed view of that. This would have to include detecting and cycling handles, as it would have to do for a deep copy as you mentioned. The cost would be wasting cycles on this work even if nobody looks at its result.
But where it gets dangerous is if there's any side effects in following the object tree. If visiting for logging e.g. creates nodes, or changes them, or whatever. Arguably "you get what you ask for", but this and the performance hit for generating log output before anyone really looks at it deeply, are probably the main reason things are as they are.
In the current status quo, devtools requests values through CDP as you expand the nodes.
Based on experiences with using Expand recursively on surprisingly short JSON network responses, I get the impression either the CDP I/O or the JS driving it is quite slow. Or the implementation's just accidentally quadratic.
In any case, I get the impression the right solution would be to make V8 execute and retain the deep-copy internally, then forward bits of it over CDP as requested.
Hrm, now I'm curious if the underlying mechanics that power the HeapProfiler could be readily repurposed for this.
The only fundamental issue, which is likely been the central blocker all along, is representing objects that are cyclic; the implementation would be closer to "object snapshot" than "literal deep copy".
(CDP = chrome devtools protocol, ie what gets exposed over --remote-debugging-port.)
And then you can't repro the bug anymore because it was related to Mobx observables or whatever and that JSON.stringify suddenly made it work so you just... leave it there...
the wrapping object is being created at log time, so its values will never be changed after the fact. That could still happen with the contents of x or y themselves, but then it's no different from the original way (console.log(x, y);)
const x = {value: 0};
console.log(x);
console.log({x});
x.value = 1;
Running that in the latest Chrome javascript console, we see that the first version prints `{value: 0}` and the second prints `{x: {...}}`. When you expand the second one, it will show `{x: {value: 1}}`.
By this I mean, the x and y in the output will never themselves change value. They may be mutated, but they cannot be reassigned in the printed object. The printed object is exclusively referenced by the console itself, even if the nested objects within it may be referenced elsewhere.
> That could still happen with the contents of x or y themselves, but then it's no different from the original way (console.log(x, y);)
By this I mean exactly what you demonstrated, the point being that it had nothing to do with the original suggestion made by jchw.
On Node, console.dir(x, {depth: null}) will print the object fully expanded (even in cases where console.log by itself won't). Pass something other than `null` to `depth` and it'll recurse to that depth, as well.
Doesn't help on browser, though; the options arg to console.dir isn't part of the standard there.
You can console.log(JSON.stringify(x, null, 2)) to pretty-print the object as JSON. It doesn’t look as good, but it’ll expand and show you the value as it was the moment you logged it. (As opposed to the value when you open it in dev tools)
I really like this method but unfortunately most debuggers print objects with keys in alphabetical order so there’s no way to get the keys you care about most at the top. Is there a way to rectify this?
This is going to make it significantly more janky but why not just put it into a list inside the curly brackets? That should preserve order while still triggering the object debugging feature
But this way is easier to reconcile the output, because the values logged shown are what they where at the time of the console.log(), not at the time of expansion (later).
Try this in a browser console: x={a:1,b:{c:1}};console.log(x);x.b.c=2;
then 'expand' the object, 'c' will be logged as 2, not 1
The original commenter was creating the object at log-time, though, which means it wouldn't be shared by anything else. Unless x or y is an object, but in that case the issue is completely tangential to the original suggestion
And anyway- JSON.parse(JSON.stringify(x)) would be preferable because you'd still get the browser's rich object exploration
Each tip has a textual explanation, and an animated gif if you're a visual learner (I know, I need to scrap gifs and move to regular videos).
There's a lot of tricks there which can hopefully improve your development and debugging workflows. Let me know if there are specific things you'd like to see. A few people have asked for how to find memory leaks.
Speaking of memory leaks, I once had to debug a memory leak caused by console log entries. Objects that are logged to the console are prevented from being garbage collected, even if the console was never opened. That includes DOM nodes or I think also handles to WebGL textures.
In Firefox any objects you pass to console.log are expandable, so you can say console.log("my hash", h). It seems to behave the same when you say console.log("my hash %o", h).
But there is a tricky thing that has really confused me in some debugging efforts: when expanded, the object display is "live", so it always shows the current properties of the object, not the properties as they were when you printed them. But the unexpanded view shows them as they were. So for example:
I don't know if that's a bug or desired behavior, but watch out for it! In the past I've used console.log(JSON.stringify(h)) to capture the full object as-is. I guess turning it back into an object would be even nicer, so you could have a deep copy to navigate.
It's perhaps badly named, but it's also the only way it could work in practice. A general facility for expanding any logged object to arbitrary depths would be prohibitively expensive to implement with static snapshots.
It's more that the way that the console reflects how the language works, and the console is not a log, it's a REPL (so console.output would have been a nicer name than console.log).
I think it would be more confusing if the console did not work like the rest of the language does.
> It's more that the way that the console reflects how the language works, and the console is not a log, it's a REPL (so console.output would have been a nicer name than console.log).
That is entirely irrelevant. The primary purpose of `console.log` is and has always be to generate output from normal, non-interactive programs.
And it's completely wrong, `console.log` was absolutely intended as a logging method, as evidenced by its siblings `debug`, `info`, `warn` and `error`, pretty much like every logging API out there.
It's also ahistorical revisionism "the console" was added very late into the history of the language, it and the entire console API were added by Firebug in the mid aughts. The language had been a thing for a decade at that point.
> I think it would be more confusing if the console did not work like the rest of the language does.
It would be the exact opposite. When I try to output something, my intent is to show the state of that thing at that point. That JS consoles are lazy (and even deferred) has systematically been a pain point and a pain in the ass leading to eager deep cloning to ensure I can see what I actually have on hand at that point, especially in mutation-heavy code.
I'm absolutely certain the number of times I've considered the behaviour a feature rather than an annoyance is 0.
What I expect to happen when I do `console.log(obj)` is that it called `obj.toString()` which means I'd expect it to print `[object Object]` and that if I wanted to see all the values I'd have to either serialize the object `console.log(JSON.stringify(obj))` or manually generate a string `console.log(`field1: ${obj.field1}, field2: ${obj.field2}`);
The fact that the browser provides me this convenience of a link to an expandable live object is a bonus feature. I'm glad it doesn't try to deep copy the object. If it did it would make console.log useless because of the performance overhead.
If you want to capture all the fields then `console.log({...obj})` would work. But of course any of those fields that are references to objects will be live. I wouldn't expect any thing else. A print function shouldn't be required to figure out if your deep references are circular which would be required if you wanted deep copies.
oh really? Interesting, why wouldn't Safari support the console APIs? Huh, well, I'm going to take the high road and say Safari intentionally not supported for such-and-such high-minded reasons.
Hey thanks! I love the sense of discovery the dev console provides. When you go to someone's site and hit `F12` to see what's going on and BOOM, your confronted with a message, like they were waiting for you personally. It's those little bits of hidden magic the internet can provide.
If you're using console.log to do debugging then it's worthwhile giving yourself a little more data to point at where a problem might lie - timings.
Open up devtools (cmd+option+j), then open the command palette (cmd+shift+P), and then search for "console", and then select "Console - Show Timestamps". Now every console output will have the high definition timestamp prepended to it. That can be really helpful if you don't want to go down the whole perf chart rabbit hole, or if you think things might be running in the wrong order due to some async weirdness.
Let me save someone a few minutes of confusion: generally I use console.table instead of console.dir ever since I discovered console.dir is basically unpredictable. Try using it on an Error or anything that inherits from Error and you'll see it puts out what looks like an expandable stack trace. I have no idea how or why it's implemented to do that, but basically it just varies from one object to the next and I dislike that.
Since I don’t see it mentioned yet: my favorite thing to do if I’m console slumming is to use it as a comma-separated expression. You can use console.log(), foo as a single expression (eg as an arrow function return) the log is executed but its undefined return value discarded. This saves a lot of keystrokes where you’d otherwise have to wrap the function body in braces with an explicit return statement.
My (newly discovered) favorite trick is using the comma operator. Using it, you can (admittedly horribly) log anything in the middle of any expression.
This for example will call `console.log(myVar)` and still call `someFunction(myVar, someOtherArgument)`.
No, this is totally different. In the second version, if someObj changes after it was logged, when you'll expand it you'll see the updated value. JSON.stringify freezes the value. To get the same as the first example, but interactive, you have to do:
> In the second version, if someObj changes after it was logged, when you'll expand it you'll see the updated value
Yes, this is something to be aware of (and is getting beaten to death throughout this comments section), but if like me you mostly use plain objects in an immutable way, you generally don't have to bother with cloning. Just keep this in the back of your head and know when it won't do what you want in a particular context.
This is great trick because the console will keep a reference to your object, not a copy, so if it changes between the time it was logged and now then the log expando shows the new value, not the old value.
By printing out the object you get a point-in-time snapshot rather than a reference to a mutable object.
This will present `> data$ at load` and clicking on the chevron will open the data showing the list of entries and clicking on their chevrons will show the table for each.
> Using console.log() for JavaScript debugging is the most common practice among developers. But, there is more…
Cut your debugging time, knowledge of console.log, and mental churn in half and set up your tooling to use a `debugger` statement. The console.log method may be used heavily but it’s actually a bad practice and often leaves code littered with log statements. Even for the purpose of logging itself you should use a logging library for serious development.
You should use a debugger in every language you can for development.
If you want to quickly log stuff without adding a log statement, you can right click the line gutter in sources and add a logpoint. Similar to a breakpoint, but logs every time it hits that line. Not modifying the source code so you don't have to remember about removing it.
Browser debuggers are amazing. I've been using them since Venkman. There's no substitute for setting a breakpoint, via the UI or a `debugger` statement, and watching your code execute a line at a time, in context. But that doesn't make console.log() a bad practice! The console is a great tool I am glad to have, especially when I remember the alternative, which was calling alert() and getting [object Object] and wanting to throw your desktop tower out the nearest window.
Use console.log for it's intended use, formatting string output, but for debugging purposes, it is a bad practice because the reason it's used usually do not usually justify the sloppy unfocused mess it leaves behind. A poorly written log statement is tech debt, as well. Debugger provides all the benefits one would expect with console.log usage:
1. You can see where you are at that moment of execution in code
2. You can see variables and arguments within scope
3. You can forget about it and a linter will pick it up or it will be ignored unless run in debugging mode
All of this allows you to focus on the actual bug and not looking for something like:
I find use cases for both. Sometimes I want to run code and review the generated debug log, other times I want to step through interactively.
I find interactive debugging takes more concentration than a workflow of: form hypothesis, add the console.logs to prove/disprove it, run the code, and analyze the result.
Console.group is one of my favorite features but Chrome does not handle filtering it very well. Basically, if you want to filter on a certain term, all the groups will remain, even if nothing from those groups (title, subfields, otherwise) matches. There has been a Chromium bug open since 2014: https://bugs.chromium.org/p/chromium/issues/detail?id=363796
I use the console.dir method occasionally to get the public methods and properties of HTML elements, for instance console.dir(document.body) would output all the methods and properties
Sometimes you wanna see the value of a variable but it's inside a hot function and ends up spamming/freezing your console if you log it.
Not quite a console.log statement, but Live Expression in DevTools is pretty useful for that. It's the little "eye" next to filter at the top, and it'll constantly watch an expression and show the latest value. Worst case you can assign your value to `window.myValue` and put a watch on that.
I use this a lot for working with animated canvases. Appending the current frame into the page is not the same since you lose the context you get from being interleaved with your other log messages.
Very important for backend is %j (e.g.: console.log('%j', variable); which can output complex json objects to the terminal. I often write it to the terminal and then copy it to an online json viewer so it is formatted in a viewable format.
Is there any way to add logging that is stripped at build time? Like in C# if I have a “Debug.WriteLine” it doesn’t exist if built in release mode. I now build ts to js with a “dev” config, could that not strip out all console.debug for me?
Not everybody have already learned everything. Sure, documentation is a really good source of truth, but for some people simpler introduction than a whole whitepaper's worth of information, which you get in MDN, might be more digestible.
I don't think closing on people just starting to learn is a good way to expand the field of software engineering.
This brings to light an issue I have encountered recently which has caused me to rely on console.log more than I would like.
In a recent project I have started using async/await, and seem to have lost the ability to use the debugger effectively. Breakpoints no longer seem to work properly. Its a huge negative, and im thinking of rewriting a lot of the code to remove async/await if I cant fix this issue.
Has anyone experienced this? If so, is there a way to fix it, or is this what I can expect when using async/await?
I've experienced this when the async/await is not handled natively (for example when targeting ES5/6) and chrome or the sourcemap have it difficult to map the original code to the executed js.
I didn't know about .memory, .trace() and .assert(), all of which are very helpful. Up till now, I've had to add a try-throw-catch to get a stack trace and be able to follow synchronous execution flow leading up to a given function call, but console.trace will do that for me, so yay.
Please don't add colours to your console.log statements. It might look nice in the Chrome console, but as soon as your messages end up elsewhere, in a terminal or text file for instance, it makes the statements look very messy.
Looks like Firefox gives you `document.getElementById` with `$`. You need $$("selector") for `document.querySelectorAll`. Or rather, it looks like it does something like `[...document.querySelectorAll(arg)]` (it returns an `Array` not a `NodeList`.)
I could see both, but things like integer vs float couldn't be determined from the type, since you just have the single `number` type. That being said, you could just convert to an int on the value you're passing in rather than having the interpolation do it for you. Same thing with object vs string, you could pass in `obj.toString()` instead of just `obj` with an `%s`
Didn't know about the string interpolation. Don't see a reason to use that over built in JS string interpolation (i.e. `var is ${var]`), but still interesting.
Somethings don’t log correctly if you use the built in interpolation without using JSON.stringify. And if you use stringify it’ll actually “freeze” it and it will not be interactive.
I think the very fact that you have to rely on things like that when debugging speaks volumes about the language deficiencies. Or maybe it's just the tooling or environments where JS is executed?
In any case, the debugging experience is probably the biggest reason why I dislike modern web dev and tend to steer my career towards back-end.
I don’t understand your comment. Firefox and Chrome both have actual debuggers as well, but many times console.log is a faster way. What are the deficiencies you speak of?
I just think there are easier ways to debug your code than including logging statements between your lines. And in some environments I have never felt the need to do so.
I use the browser debuggers as well but I never saw a JS stack trace approaching readability of a C# stack trace, and there are also other things that make the entire debugging experience more troublesome. Maybe that's just because I have very little front-end experience (and no workflows and IDEs properly set up to work with particular frameworks). Or maybe it's because of the JS ecosystem. Threads like this one make me lean towards the latter.
If there’s an unhandled exception (or even a log message) in your JavaScript, you can click on it in the console and go straight to the source line, set a breakpoint and reload or re-trigger the code, and now you’ve got a fully interactive “stack trace” where you can inspect the values of local variables at all levels of the stack. Or you can set the debugger to automatically break on exceptions, or even to break on events or DOM modifications.
I also can’t stress enough how nice it is to have the debugger integrated into the runtime environment as opposed to an auxiliary debugger like GDB. No need to launch the debugger after my code breaks because it’s already there.
I think web technology has come a long way in the past few years, but a lot of people still seem to hold a grudge against it that I can only imagine they developed in the days of “jQuery all the things”. Are there still problems? Massively yes, but what ecosystem is perfect? I think if you don’t have much recent experience, it can’t hurt to try these things again with an open mind and see if your opinions still hold. I personally don’t have any experience with C# to be able to compare the debugging experience, but compared to Go or C/C++ I think it is massively better.
I think with a good setup including a LSP server for your editor, a good linter, and a type checker, frontend development is actually one of the more enjoyable ways to write code.
Tangential, but the only feature I really miss from browser debuggers is time travel debugging. Mozilla was working on implementing this in the Firefox DevTools as “WebReplay” [0] but it was spun out as a separate project called Replay.io [1] and I haven’t heard much about it since then.
You don't have to "rely" on it just like you don't have the rely on `fmt.Println()` in golang for debugging.
A language/runtime/framework gives you options and you pick your poison, browsers offer you step by step debuggers and REPL out of the box and I actually think it's awesome in terms of debugging, you can of course only use console.log(), and it's a good place to start. When you're ready to move on there are many more useful tabs in the browser devtools to debug with.
What worries me is that, apparently, there are many people with a lot of web dev experience who haven't moved on. Why haven't they moved on? I don't understand it.
I have yet to see a mid-level engineer using the same method when debugging C# code.
Despite all that, I try to refrain from strong statements about technologies. Just throwing around ideas.
99% of Frontend developers don't know about the debugger/developer tools. You can even log stuff without ever writing/compiling directly from Chrome Developer Tools
OK. I would argue that if you spend that much time making your console look pretty (beyond useful), then you're either not spending enough time on things that matter (everything else, that the user may see) or you're grossly over budgeted.
Btw none of this is as useful as a breakpoint. Type `debugger;` in your code, refresh chromium or what have you with the dev panel open and inspect everything, jump over etc. ad nauseam. Pro tips use IntelliJ or webstorm for a really nice experience debugging.