What Is Observability and How Do You Log Properly?
Learn what observability means, why it matters for debugging your apps, and how to write logs that actually help you.
Seeing Inside Your App From the Outside
Imagine you send a letter through the mail. You put it in the box, and a few days later you either get a reply or you don't. That's a bit like using an app with no observability — you send in a request, and either something comes back or it doesn't. You have no idea what happened inside.
Now imagine every post office along the way stamped your letter with notes: "Received here. Moved to next city. Delivered." You'd know exactly where your letter was, how long each step took, and where it got stuck if it failed. That's observability. It's the practice of recording what's happening inside your app so you can understand it from the outside.
Observability has three main pillars. Logs are the stories your app tells you. Metrics are numbers — like how many people used your app in an hour. Traces follow a single request through every step it takes, like following one letter's entire journey.
Logs
Text messages your app writes as it runs — like notes scribbled in the margins of a process.
Metrics
Numbers that measure things over time — page views, error rates, response speeds.
Traces
Following one request step-by-step through every part of your system.
Without Logs, You're Guessing
Picture this: your website suddenly stops working for your users. No error message shows up for them — just a blank screen. You have no idea what went wrong. You start clicking around your code trying to guess where the problem is. That's not fun, and it wastes a lot of time.
With good logging, your app tells you what happened. It might say "The database connection timed out at 2:03 PM when loading the homepage." Now you know exactly where to look. You've turned a mystery into a to-do list.
Good observability also helps you notice problems before they become disasters. If you see your error rate slowly climbing over the past hour, you can fix it before it takes down your whole site.
💡 Key Insight
The best debugging happens when you don't have to ask users what went wrong. Your app should be telling you — automatically, through logs — exactly what happened the moment it happened.
The Four Levels of Logging
Not all log messages are equally important. Developers use log levels to organize messages from "everything is fine" to "something is seriously wrong." Think of it like a fire alarm system:
Here's how to write good logs. A great log message has three parts: what happened, where it happened, and when it happened. Vague logs like "something went wrong" are almost useless. Specific logs like "checkout.js — Stripe payment failed for user ID 4821, error code 403" give you a starting point to fix the problem.
Structured logging means writing logs in a machine-readable format, like JSON. Instead of plain text, you'd log: {"level": "ERROR", "message": "Payment failed", "user_id": 4821, "error_code": 403}. This makes it easy for tools to search, filter, and alert on your logs automatically.
Logging a User Login in JavaScript
Here's a simple example of how to add logging to a user login function. This code uses a logging library to record what the app is doing — and captures details if something goes wrong.
// Using a logger (winston or pino in real projects) const logger = { info: (msg, data) => console.log(JSON.stringify({ level: 'info', msg, ...data })), warn: (msg, data) => console.log(JSON.stringify({ level: 'warn', msg, ...data })), error: (msg, data) => console.log(JSON.stringify({ level: 'error', msg, ...data })), }; async function loginUser(username, password) { logger.info('Login attempt started', { username }); try { const user = await db.findUser(username); if (!user) { logger.warn('Login failed — user not found', { username }); return { success: false, reason: 'Invalid credentials' }; } const match = await bcrypt.compare(password, user.passwordHash); if (!match) { logger.warn('Login failed — wrong password', { username, userId: user.id }); return { success: false, reason: 'Invalid credentials' }; } logger.info('Login successful', { username, userId: user.id }); return { success: true, user }; } catch (err) { logger.error('Login crashed unexpectedly', { username, error: err.message }); return { success: false, reason: 'Server error' }; } }
Notice every log entry tells you what happened, who it involved (username, userId), and uses a log level that matches the severity. When something breaks in production, you can search your logs for "Login failed" or "Login crashed" and see exactly what went wrong for exactly which user.
Knowledge Check
Test what you learned with this quick quiz.