Use console.log() like a pro (2020)
markodenic.com
markodenic.com
console.log(x, y);
which contains the information you need, but lacks any useful context, try... console.log({x, y});
...which will print out like an object, including the key names.Then you quit web development and start a new career.
console.log({x, y});
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}}`.To be fair, there is also an "i" in a square, reminding me about this behaviour.
Evaluate the expression:
const x = {value: 0};
console.log(x);
console.log({x});
x.value = 1;
You get this: {value: 0}
{x: {…}}
Expand the first arrow of the `x:`: v {x: {…}}
> x: {value: 1}
Now evaluate: x.value = 3
Then expand the second arrow: v {x: {…}}
> x:
value: 3
Now if you unexpand the arrow, you get 1, but if you expand it you get 3 =)... (Well at least in chrome)Moreover, if you expand arrow next to {value: 0}, you will see literally this:
v {value: 0} [i]
value: 3By 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.
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.
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.)
Doesn't help on browser, though; the options arg to console.dir isn't part of the standard there.
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
And anyway- JSON.parse(JSON.stringify(x)) would be preferable because you'd still get the browser's rich object exploration
Of course we have to assume that x and y may be objects!
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.
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.
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:
h = {foo: "bar"}
console.log(h)
▶ Object { foo: "bar" }
h.foo = "BAR"
Then you click the triangle and you see: ▼ {..}
| foo: "BAR"
| ▶ <prototype>: Object { .. }
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.I think it would be more confusing if the console did not work like the rest of the language does.
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.
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.
I've posted about it in more detail here: https://news.ycombinator.com/item?id=26785429.
There's a more in-depth answer SO: https://stackoverflow.com/a/23392650
But you should proofread it for typos.
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.
(This probably only works in Chrome)
Though you probably want the per-tab Console there (ctrl-shift-k). ctrl-shift-j would give you the multiprocess browser console, which is very noisy.
This for example will call `console.log(myVar)` and still call `someFunction(myVar, someOtherArgument)`.
myVar = "12345"
someFunction((console.log(myVar), myVar), someOtherArgument);
Pretty handy sometimes :) console.log("some label: " + JSON.stringify(someObj))
pass it as a separate parameter: console.log("some label: ", someObj)
and you'll get interactive expansions/manipulation in the console console.log("some label: " + JSON.parse(JSON.stringify(someObj)))Technically you'd have to do
console.log("some label: ", JSON.parse(JSON.stringify(someObj)))
> In the second version, if someObj changes after it was logged, when you'll expand it you'll see the updated valueYes, 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.
I would never debug with that mindstate. I don't trust myself.
By printing out the object you get a point-in-time snapshot rather than a reference to a mutable object.
Eg: console.groupCollapsed('data$ at load'); console.groupCollapsed('GridData'); console.table(data$.GridData.toList()); console.groupEnd(); console.groupCollapsed('Loads'); console.table(data$.Loads.toList()); console.groupEnd(); console.groupCollapsed('Drv'); console.table(data$.Drv.toList()); console.groupEnd(); console.groupEnd();
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.
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.
Neat tricks though.
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:
`MY SPECIAL VAR: [object Object]`
I forgot to format, let's run it again.
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.
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.
img = $$('img')[0];
// console.log(img);
console.log("%cPlaceholder", `background:url("${img.src}") no-repeat cyan; border:1px solid black; padding:${img.naturalHeight}px ${img.naturalWidth}px 0 0; font-size:0; line-height:0;`);
If you paste this you should see the HN logo display in your console. Credit for the image trick goes to https://github.com/adriancooney/console.imageI 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.
Maybe you can skip a step with pasting online.
I don't think closing on people just starting to learn is a good way to expand the field of software engineering.
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?
https://developer.chrome.com/blog/new-in-devtools-73/
https://developer.mozilla.org/en-US/docs/Tools/Debugger/Set_...
%s, %i, %o, %f, etc.
You can use a templated string, like this: `This is a string that has been printed ${someVar} times.`But some Googling [1] shows that this has now been fixed by being able to blackbox your wrapper scripts.
console.dir(obj, { depth: null })
However, if you need to inspect some object in a browser, don't forget you can just insert: debugger
Which can be helpful at times.You can use `$("selector")`, even without jQuery, in console. (not sure if with firefox, works with safari and chrome)
And also `$x("path")` for xpath.
But note, this works only in console, it’s not available from javascript.
I'd love to see one on the step-thru debugger!
%s – string
%i or %d – integer
%o or %O – object
%f – float
Why were these ever specific types, instead of just one option that looks at the parameter type?laughs in JS
console.table(obj)
console.time('x'); console.timeEnd('x')
console.trace()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 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.
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.
[0]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/We...
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.
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.
Wow, since when it was in JS?
https://developer.mozilla.org/en-US/docs/Web/API/Console/tab...
How is that possible?
> Most frontend developers in my experience don't know about developer tools.
I still don't know how that's possible though.
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.