You know that moment when your code just… doesn't work, and you have absolutely no idea why?
You've read the same fifteen lines four times. It looks right. The logic makes sense in your head. But the app keeps crashing, or the button does nothing, or the number that should say 10 stubbornly says undefined. So you sit there, squinting, quietly asking your screen what you did to deserve this.
I've been in that exact spot more times than I'd like to admit. And the thing that pulled me out, almost every single time, wasn't some fancy debugger or a genius insight. It was a log.
That's really what "coding with logs" means. It's the habit of getting your code to tell you what it's actually doing while it runs — instead of you guessing from the outside and hoping you're right.
Let me walk you through it the way I wish someone had explained it to me early on.
The core problem logs solve
Here's the uncomfortable truth about code: it doesn't do what you meant. It does what you wrote. And those two things drift apart constantly.
When you read your code, you're reading your intentions. You see the story you were trying to tell. But the computer doesn't care about your story — it just executes instructions, and somewhere in there, one of those instructions is quietly betraying you.
A log is how you catch it in the act.
Instead of assuming a variable holds the right value, you make the code say it out loud. Instead of trusting that a function ran, you make it announce that it ran. Suddenly you're not guessing anymore. You're watching.
That shift — from guessing to watching — is the whole game.
The humble console.log (a.k.a. everyone's first debugger)
Almost nobody starts their logging journey with a proper tool. They start with the equivalent of yelling into the void.
In JavaScript, that's console.log(). In Python, it's print(). Same idea in every language — a little line that spits out whatever you hand it.
console.log("got here!");
console.log("user value is:", user);
I still remember littering my code with console.log("here"), console.log("here 2"), console.log("HEEEERE") like breadcrumbs, just trying to figure out how far the code got before it fell over. It's not elegant. It's not "best practice." But it works, and honestly, every experienced dev I know still does some version of this when they're in a hurry.
So if that's your current level — congratulations, you're already coding with logs. The rest of this is just doing it more deliberately.
What actually makes a log useful
Here's the mistake I made for way too long: my logs were vague.
console.log("here") tells you the code reached a spot. Cool. But it doesn't tell you what the data looked like when it got there, which is usually the part that matters.
A good log answers three questions:
- Where am I? (which function, which step)
- What have I got? (the actual values, not just "it ran")
- What did I decide? (which branch, why it went left instead of right)
Compare these two:
// weak
console.log("checking user");
// actually helpful
console.log("checking user — id:", userId, "| isLoggedIn:", isLoggedIn);
The second one, when it prints id: undefined | isLoggedIn: false, basically hands you the bug on a plate. You instantly know the user ID never arrived, which means the problem is upstream — before this function even ran. That's minutes saved instead of an hour of poking around.
Log levels: not everything is an emergency
Once you outgrow scattering print statements everywhere, you bump into something called log levels. It sounds formal, but the idea is dead simple — not all messages are equally important.
The common ones:
- debug — tiny details you only care about while actively hunting a bug
- info — normal "this happened" events ("server started", "email sent")
- warn — something's off but the app survived ("retrying connection")
- error — something actually broke and needs attention
Why bother? Because in a real app you can filter by level. During development you crank it up and see everything. In production you turn the noise down and only keep warnings and errors, so your logs don't turn into an unreadable firehose.
This is where you graduate from console.log to real tools.
The tools worth knowing about
When your project gets serious, raw console.log starts to hurt. Here's what people actually reach for, roughly in order of "how deep are you in":
- Browser DevTools console (Chrome/Firefox/Edge) — press F12, click Console. This is where your frontend logs live, and the Network tab right next to it shows every request your code makes. Learn these two tabs and you've solved half your bugs already.
- Winston and Pino — proper logging libraries for Node.js. They give you levels, timestamps, and nicely formatted output without you hand-rolling any of it.
-
Python's built-in
loggingmodule — comes free with Python, does levels and formatting properly. Worth switching to the moment yourprint()habit gets out of hand. - Sentry — instead of logs vanishing into a terminal, it catches errors from real users and emails you the full stack trace. The first time you get an alert about a bug before a user complains, you feel like a wizard.
- LogRocket — replays what the user actually did in the browser, logs and all. Wild the first time you see it.
- Datadog, Grafana Loki, and the ELK stack (Elasticsearch, Logstash, Kibana) — the heavy machinery for when you've got servers spread across the cloud and need all the logs in one searchable place. You don't need these on day one. You'll know when you do.
Don't feel like you have to learn all of these. Most days, DevTools plus one logging library covers it.
A simple way to actually debug with logs
When something's broken and I don't know why, this is the loop I run, more or less on autopilot:
- Find the two ends. Where does the code start doing the thing, and where does it end? Drop a log at each. Confirm it even reaches both.
- Log your inputs. Right where the data enters the function, print it. Nine times out of ten the bug is "the data wasn't what I assumed."
-
Log at every fork. Every
if, every branch — log which way it went and why. This is where hidden bugs love to hide. - Move inward. Once you know the problem lives between log A and log B, add a log halfway between them. Repeat. You're basically playing a guessing game that halves the search area each time.
- Read the values, not your intentions. Believe what the log says, even when it contradicts what you "know" is true. The log is right. You are wrong. (Painful, but usually accurate.)
That's it. It's not clever. It's just relentless. And it beats staring at the code hoping for divine inspiration.
Mistakes I've watched people make (myself included)
Leaving debug logs in production. We've all shipped a console.log(user) to a live site. Best case, it's messy. Worst case, keep reading.
Logging secrets. This is the scary one. Never — never — log passwords, API keys, auth tokens, or full credit card numbers. Logs get stored, shared, and sometimes leaked. A password sitting in a log file is a genuine security hole, not a "clean up later" thing. Strip that stuff out as you write the log, not afterward.
Logging inside tight loops. Put a log inside a loop that runs 50,000 times and you'll bury the one line you actually needed under a wall of garbage — and possibly slow everything to a crawl. Log the summary, not every iteration.
Vague messages. console.log("error") with no context is almost as useless as no log at all. Give it something to say.
Logging nothing at all. The flip side. Some people are so allergic to "messy" code that they refuse to add logs and instead spend an hour re-reading the same function. Add the log. Delete it later. It's free.
So, what is "code with logs," really?
It's a mindset more than a technique.
It's choosing to make your code observable — to build it so that when things go sideways (and they will), the code can explain itself instead of leaving you to guess. It's treating logs not as an afterthought, but as the running commentary that turns a black box into something you can actually see inside.
The best part? You don't need permission or fancy setup to start. Open your project, find the spot that's misbehaving, and make it talk. Print the variable. Log the branch. Watch what it says.
Do that a few dozen times and something clicks. You stop being scared of bugs, because you've got a reliable way to corner them every single time. That confidence — being able to walk up to any broken thing and go "alright, let's see what you're actually doing" — is honestly one of the most freeing feelings in coding.
Your code is always trying to tell you something. Logs are just how you finally start listening.
Top comments (0)