# The Vibe Coder’s Guide to Observability

> Source: <https://read.technically.dev/p/the-vibe-coders-guide-to-observability>
> Published: 2026-11-03 14:40:35+00:00

*Lots of you are probably building apps with AI, and we want to do our part to make sure those apps can thrive when they make contact with real users.*

*This 6-part series, **Software Engineering for Vibe Coders**, covers Art Vandelay’s sisyphean journey to ship an app to manage Vandelay Industries’ potato chip import & export business.*

*Thanks to our friends at [Railway](https://railway.com/?utm_medium=sponsor&utm_campaign=technically), who help you peacefully deploy those vibe coded apps, for sponsoring this series.*

There’s an old adage that says you can’t improve what you can’t measure. My addition to that is that it’s really hard to open the hood of your car and figure out what’s going wrong if you have a blindfold on. And it’s also hard to know you should even pop the hood if you don’t have a check engine light.

The same is true with your app – if you don’t have visibility into what’s going on, you’re going to run into a massive host of problems, and if you don’t get pinged when something is wrong, you’ll hear about it from users when your app goes down. Observability proactively lights up your check engine light, and takes off the eye mask so you can see under the hood. It tells you when something is wrong and helps you investigate, before issues hit your users.

## The TL;DR

Observability helps teams monitor when things go wrong and profile how things change over time; basically, knowing what’s going on under the hood in your app. This post will go through:

- **What observability actually is** , and the logs and[metrics](https://technically.dev/universe/metric) your app emits to tell you what the hell is going on.
- **How to use that data when something breaks** , from figuring out where the problem is to getting the app back online.
- **How to catch problems before your users do** , with monitoring, alerting, and, when you’re feeling fancy, automated action.

Let us dive in, keen observers.

## Previously, at Vandelay Industries

To recap, Art Vandelay is vibe coding the app that runs Vandelay Industries’ potato chip import/export empire, or as we know it in the Godfather, the family business. And never ask me about my business.

So far in this series, we’ve explained every piece of how Art gets to a working app:

1. In [Part 1](https://technically.dev/posts/software-eng-vibe-coders-frontends-backends) we split it into a[frontend](https://technically.dev/universe/frontend) and a[backend](https://technically.dev/universe/backend) .
2. [Part 2](https://technically.dev/posts/databases-for-vibe-coders) gave it a place to store data: a Postgres[database](https://technically.dev/universe/database) on Railway.
3. [Part 3](https://technically.dev/posts/security-for-vibe-coders) locked the doors by adding in auth (so that Newman can’t read the shipping schedule).
4. [Part 4](https://technically.dev/posts/deployment-for-vibe-coders) put it in the[cloud](https://technically.dev/universe/cloud) and gave it a real public URL so users can actually access it.
5. [Part 5](https://technically.dev/posts/cloud-scaling-for-vibe-coders) scaled it up to accommodate additional traffic after it went more or less viral.

So [Part 5](https://technically.dev/posts/cloud-scaling-for-vibe-coders) got your app back from the brink of the dreaded “500 [server](https://technically.dev/universe/server) error”, and in the process we learned about scaling: horizontal for more copies of your app, and vertical for beefier servers. We also learned how to ensure your app scales up to accommodate load, and then back down automatically, keeping things snappy without overspending. It’s nice when things work automatically, but some day, you’ll need to know what’s actually going on under the hood.

So after scaling up/out, Art was able to get his app back online; but now he’s scared. He might have fixed *this* problem, but he has no visibility into what the *next* problem is going to be, i.e. what’s happening: who is accessing it, how close his servers are to overloading, or if a giant bot farm is DDOSing him (well maybe that’s just paranoia). Last time, he only learned his site was down when Elaine helpfully notified him that she couldn’t get in.

It’s imperative, for the continued success of Vandelay Industries, that Art finds out about issues with the import/export app *before his users do*. He needs to put some damn *guardrails* in place. To understand how to do this, let us first dive into some basics about observability.

## Observability 101

A system is said to be observable if a maintainer of that system can understand its internal state without access to it, or said differently, using only data *emitted* from the system. ***yawn***

Nah but for real. A system (Art’s app in this case) needs to continuously publish data that tells him what the hell is going on. That way, when something goes wrong, he can check that data to get an understanding of what went wrong and how he might go about fixing it. It’s like a black box in an airplane, but with much much higher stakes. This data takes many forms, but a few of the most common are logs and metrics.

### Logs

Logs are a record of **things that happened** inside your system. updated user account, attempting to connect to database, compiled code into binary. That sort of thing.

For Art’s app, a useful log might look something like:

```
ERROR: Failed to create shipment: database connection refused
```

That’s much, much, more useful than Elaine calling and saying, “the import thingy doesn’t work.” Now Art knows he has a database problem and can go troubleshoot it.

A log can really take any form; it’s just a bunch of text. There are a few different kinds of logs worth knowing about:

- **Build logs** tell you what happened while your platform was turning your code into something it could run. If you referenced a software package that doesn’t exist, or your build system had trouble packaging up your app, the answer is probably here.
- **Runtime/application logs** are emitted while your app is actually running. This is where anything you choose to log in code, such as warnings, errors, database connection failures, and other application events show up.
- **Request and network logs** tell you about traffic moving into, out of, and around your application: requests arriving, status codes being returned, network connections failing, and so on.

As a developer you can infinitely customize what kinds of logs you want to capture and how they’re structured. As a vibe coder…you are probably safe with whatever your deployment platform of choice defaults to.

On Railway, Art’s platform of choice, logs are captured and surfaced in the dashboard automatically. Not only this, but they’re fully searchable and filterable by severity and type.

The important thing to know is that logs give you **specific events to investigate**. If Art knows his app stopped working at 2:17 PM, he can pull up the logs from around 2:17 and start looking for something suspicious. Something like:

```
ERROR creating shipment 1842: database connection timed out
```

That gives Art a place to start; at least a section of the haystack in which to look for the pesky needle jamming up his app.

### Metrics

If logs are broadly qualitative, metrics are their nerdy quant cousin. They are structured representations of measurements taken repeatedly over time.

Instead of telling you that one particular thing happened, they tell you how *dimensions* of your system are behaving in aggregate:

- CPU usage is at 92%.
- Memory consumption has doubled over the last hour.
- Network traffic is suddenly five times higher than usual.
- Disk usage is slowly approaching its limit.

These are metrics. Some of the most useful [infrastructure](https://technically.dev/universe/infrastructure) metrics are:

- **CPU usage:** how much work your server’s processor is doing relative to how much work it has the capacity to do.
- **Memory usage:** how much of your allotted RAM your application is consuming.
- **Disk usage:** how much persistent storage you’re using.
- **Network traffic:** how much data is moving into and out of your services.

Railway collects these resource metrics for services *automatically* and exposes them as easy to read graphs.

Metrics become particularly useful because you can look at them over time. Seeing that your app is using 700 MB of memory *right now* may not mean much. Seeing that it used 200 MB yesterday, 400 MB this morning, and 700 MB now tells you something is probably wrong.

**So commit this to memory, young vibesmith: Logs tell you what happened, thing by thing. Metrics tell you what’s happening to the system, all at once.**

The two are most powerful when used together. Suppose Art gets reports that the app became painfully slow around noon. His metrics show CPU usage shooting to 100% at 11:58 (you do not want your CPU to be at 100%). He then checks the logs from the same time and discovers thousands of requests hammering the shipment-search [endpoint](https://technically.dev/universe/endpoint).

Now he’s able to evolve from “the app is slow” to “this endpoint is overwhelming the server” and something is wrong with shipment search.

That, my friends, is observability.

## Using Observability Data

So what do we do once our app is observable? There are two times people really tap into observability data: during an incident, and to prevent an incident. The more you do the latter, the less you have to do the former. Art’s platform of choice, Railway, comes stock with a number of observability features we’ll explore throughout.

### A beginner’s guide to incident management

When something goes horribly wrong in your app – people see the wrong data, users can’t check out, the app goes down altogether, or anything prevents a user from using a substantial or critical portion of your app – congratulations, you have an incident on your hands. It’s a rite of passage, really, and you should take a moment to congratulate yourself. Then you should crack a caffeinated beverage and lock the hell in, cause your uptime is on the line.

The first job during an incident is to figure out **where the issue is actually occurring**. “The app is down” is not, unfortunately, a particularly useful diagnosis. Maybe the newest version of the app never built successfully. Maybe it built and deployed, but crashes as soon as it starts. Perhaps the app is perfectly healthy but can’t connect to Postgres, or one particular endpoint is throwing errors, or everything technically works but takes 45 seconds to load. We have a bunch of possible culprits, so the point of all that observability data we just talked about is to narrow the field.

A good place to start is with **whatever changed most recently**. If Art deployed a new version of the app at 2:03 and Vandelay Industries ceased to function at 2:04, this is not a Sherlock level mystery; something in the new version is borked. Railway keeps the history of your deployments, so Art can see pretty quickly whether the timing lines up, and rollback (revert to a previous version) if necessary.

If the deployment itself failed, the **build logs** are the next place to look. Applications have to be turned from source code into something that can actually run, and the build logs record that process. Maybe a dependency wouldn’t install, some code wouldn’t [compile](https://technically.dev/universe/compile), or the AI hallucinated a potato-chip-sdk package with the sort of confidence normally reserved for packages that ya know, actually exist. If the app never finished building, there’s no point investigating what it did after it started, because it never did.

If the build worked, then we move on to the logs from the running application. This is where Art will see the app starting, connecting to the database, handling requests, and, crucially, throwing logs when one of those things goes wrong. An error like database connection timed out gives us a substantially better idea of what’s happening than “Elaine says it’s broken.”

Railway lets you search and filter logs, so rather than reading every line the app has ever emitted, start around the time the incident began and look for obvious suspects: errors, failed connections, timeouts, or messages from whatever part of the app users are having trouble with.

It can also be useful to look directly at the requests hitting the app from the internet. Every time your browser asks the backend for something, that request is resolved with an [HTTP](https://technically.dev/universe/http) status code, and those codes provide a wonderfully terse assessment of how things went.

- 200 means, broadly, everything is fine.
- A 400-something response generally means there’s a problem with the request, [authentication](https://technically.dev/universe/authentication) , permissions, or something else on the[client](https://technically.dev/universe/client) side.
- A 500-something response means the server has screwed something up.

If Art sees that /shipments is happily returning 200s while /exports suddenly starts returning 500s, we now know that the entire application isn’t down at all; something about exporting is broken.

Unfortunately for Art, the noble search for one beautiful error message telling you exactly what happened often comes up short. This is when metrics can become useful. Whereas logs tell us about individual events, metrics let us zoom out and look at the overall health of the system.

If users started complaining at 2:17 and CPU hit 100% at 2:16, we have a lead. If memory consumption has been slowly climbing for six hours and then the app crashes, that looks suspiciously like something is “leaking” memory. If network traffic suddenly went vertical, Art may be having another one of those viral moments we dealt with in [Part 5](https://technically.dev/posts/cloud-scaling-for-vibe-coders).

The trick here is less about any particular number being “bad” and more about looking for something that changed at the same time the app started misbehaving.

Once you have a decent idea of what broke, the immediate goal is not necessarily to understand every single thing about why it broke. The immediate goal is to keep those potato chips flowing (err… importing / exporting). So, if the app died immediately after a new deployment, just roll it back to the last working one. If the app is simply drowning in traffic, scale it up and buy yourself some breathing room to figure out the root cause.

There’s an important distinction between **mitigating** an incident and **fixing** it: mitigation stops the bleeding and gets the app working again, while fixing it means understanding the underlying cause and making sure the same thing can’t take you down next time. This brings us to the other time that you care about observability:

### How to prevent an incident

In 1980, about 29 Americans per million died in fires. By 2024, that number was closer to 12. Firefighters have, presumably, gotten better at fighting fires in the intervening decades, but a much larger share of this reduction is owed to improvements in fire detection and prevention. Building codes, smoke detectors, sprinklers, less building-buildings-out-of-kindling-and-illuminating-them-with-candles thing. Said differently, an ounce of prevention was worth a pound of cure.

The same principle applies to keeping an application running. After every incident, once everything is back online and the caffeine shakes have subsided, you should be asking two questions: **how could I have known about this sooner, and how could I have prevented it from happening at all?**

The first question gets us to alerting. The second gets us to automated action. This is the big-brain, real-engineer way of operating software: gradually taking all the ways your app has surprised you and putting guardrails around them so they stop being surprises.

#### **Alerting**

So far, all of our observability has required Art to go looking for something. Elaine tells him the app is down, Art opens Railway, Art looks at his metrics, Art discovers that his app has spent the last half hour fighting for its damn life at 100% CPU. This is better than having no observability at all, but it still requires Elaine.

What we actually want is for *the system* to notice that something weird is happening and tell Art about it **proactively**. We want technology to take Elaine’s job. That’s what an **alert** is: some condition that says, roughly, “this number has gotten bad enough that a human should probably know about it.”

Railway lets you monitor resources in its Observability dashboard for CPU, memory, disk usage, and network egress. Rather than Art periodically opening Railway to make sure everything looks okay, he can decide what “not okay” looks like ahead of time and set up monitors and alerts for it. Maybe the Vandelay app normally uses 20-40% of its available memory, so Art creates a monitor for when it gets above 80%. Now if memory starts climbing toward the limit at 3:00 in the morning, Art gets a notification (email, Slack, whatever) without having to wait for an eager exporter in a more favorable timezone to discover the problem first.

The exact threshold you pick is going to depend on the application, and you do not want to alert on every little spike. CPU hitting 80% for five seconds because somebody requested a particularly elaborate potato-chip report is probably fine as long as two users don’t do it at once, but CPU sitting at 95% while response times get worse for twenty minutes is probably worth investigating. The goal is to find conditions that give you useful warning that something is going wrong without turning Art’s inbox into a stream of messages he mostly ignores.

There are other events where the threshold is even simpler. If a [production](https://technically.dev/universe/production) deployment fails, for example, there is not much value in waiting for it to fail *really hard* before telling someone. Railway can send project events to any system via webhook, which means deployment failures and other events can ultimately make their way into whatever system your team actually pays attention to, whether that’s Slack, Discord, or something more elaborate.

The key thing here is that Art has now reversed the relationship between himself and his observability data. He no longer has to constantly ask the system whether everything is okay. The system tells him when it isn’t.

#### **Automated action**

There is, however, an even better outcome than getting a message saying that your application is about to fall over: **the application does something about it.**

We already saw one version of this in [Part 5](https://technically.dev/posts/cloud-scaling-for-vibe-coders) with scaling. Railway vertically autoscales services, meaning the app can consume more CPU and memory as it needs them, up to the limits available to that service. Instead of somebody getting an alert that says “CPU is high” and immediately rushing over to provision a beefier server, there is some room for the platform to absorb that increased load on its own.

Crashes are another example. By default, Railway can restart a service when it exits unexpectedly. If a potato chip magnate manages to hit some bizarre edge case that crashes the backend at 4:12 AM, the first response does not necessarily need to be Art opening his laptop at 4:13 and manually clicking Restart. The platform can bring the service back up itself. That does not mean Art should ignore the crash, but sometimes the good ol’ turn it off and turn it back on again thing really does work, even at cloud scale.

Because the webhook system discussed above can send any number of generic events to any URL, you can take the idea of automated action as far as you want by hosting your own webhook handler. When certain events are received, certain actions are taken - such as increasing the number of application copies if RAM gets too high, re-running a pipeline if a service fails in the middle of a job, or pinging your cell if a deployment outright fails.

The basic rule is that you automate responses to well-understood failure modes. If you know traffic spikes are normal and the appropriate response is more capacity, automated scaling is a good candidate. If a process occasionally crashes and is safe to restart, then restart it automatically. If something weird and hitherto unknown happens where the correct response is “what the…”, then it’s probably best to rope in a human.

That’s the gist of what Art has been building throughout this whole series. Not an app that can never break, which is folklore, but one where increasingly fewer failures become full-blown incidents. The first time something breaks, Art gets a call from Elaine. The second time, hopefully, he gets an alert. Eventually, for the boring and predictable stuff, the app just handles it itself.

## Where that leaves Art (and you)

What a journey we’ve been on, dear reader. We’ve truly seen Mr. Vandelay from zero to hero. From architect to importer / exporter, and from a bush league vibe coded app to bulletproof software running in the cloud.

If you’re vibe coding apps, and there’s truly never a better time to do so, we hope this series has provided a useful introduction to the things about real software engineering that are worth knowing if you want your mighty little app to succeed.

If you take away only six bullet points from this hallowed tome, let them be these:

**Your app is probably two apps.** The frontend is the part your users poke at; the backend is where the actual work happens. Keeping those concerns separate makes everything that comes after considerably less cursed.

**If the data matters, put it in a database.** Variables, JSON files, and whatever other little hiding places AI comes up with are fine until the process restarts and Vandelay Industries loses its entire shipping ledger. Use Postgres.

**Do not trust the user. Even if the user is you.** Authentication proves who somebody is; authorization decides what they’re allowed to touch. Both matter, unless you’re comfortable giving Newman admin access and seeing where the afternoon takes you.

**Your laptop is not a production environment.** At some point the app needs to leave localhost, get a real URL, and live somewhere that can deploy it consistently, keep it running, and generally behave like a cloud server instead of a MacBook.

**Success can break your app too.** More users means more CPU, more memory, more requests, and eventually more copies of the app. Learn the difference between scaling up and scaling out before your first viral moment turns into your first outage.

**When the app breaks, it should rat itself out.** Logs tell you what happened, metrics tell you how sick the system looks, alerts tell you when to care, and automation handles the boring stuff before Elaine has to call.

Good luck out there!
