← Open in the full interactive course (progress tracking, search & more)

There is no server to SSH into and no log file to tail. Writing a log line in a container means something different than it did on a single machine you owned.

Lesson 269 gave you the three pillars of observability — logs, metrics, and traces. Lesson 270 gave you OpenTelemetry, the vendor-neutral way to instrument all three so where the data goes stays a configuration decision, not a rewrite. Both lessons were deliberately written to be true regardless of where your app runs. This lesson is about the part that's specific to where a cloud-native, containerized deployment actually runs: what happens to those logs the instant the container they were written in no longer exists.

In this lesson, you'll learn why cloud-native applications write logs to stdout/stderr instead of to a file, why that choice is a direct consequence of a container's filesystem being ephemeral, why structured logging (from Intermediate lesson 128) matters even more once dozens of instances are producing logs simultaneously, and precisely why OpenTelemetry becomes more essential — not less — the moment "SSH into the server" stops being a coherent idea at all.

What Is It?

The Simple Explanation

On a traditional server you owned, "logging" often meant writing lines of text to a file on that machine's disk, and reading them later by opening that same file on that same machine. In a cloud-native, containerized deployment, that entire mental model breaks — not because logging itself changed, but because the machine and the disk underneath your app are no longer stable, permanent things you can count on being there tomorrow.

The Technical Definition

Cloud-native logging is the practice of writing log output to standard output (stdout) and standard error (stderr) — the same streams a console application always had — rather than to a file on the container's own filesystem, so that the container platform itself can capture that output and forward it to a centralized, durable, queryable logging system, independent of the application's own knowledge of where it ultimately ends up.

Log to a local file (traditional)

Log to stdout/stderr (cloud-native)

Why Does It Exist?

The Problem — a Container's Own Filesystem Is Ephemeral

A container's filesystem is, by default, tied directly to the container's own lifetime. The moment that container is destroyed — and in a cloud-native environment, this happens routinely and expectedly, not as some rare disaster: a rolling deployment replaces it, a health probe failure restarts it (lesson 302), an autoscaler scales it in as load drops, the underlying node itself is retired — whatever was written to its local disk is gone with it. There is no guarantee the replacement container even lands on the same physical node.

If your app writes its logs to a file inside that container, every one of those routine, expected events silently destroys the exact evidence you'd need to diagnose a problem that happened right before the container disappeared — often the single most important moment to have logs for.

The Solution — Let the Platform Own Log Durability, Not the App

The fix isn't to make the app more careful about where it writes files — it's to stop making the app responsible for log durability at all. By writing to stdout/stderr instead, the application hands its log output to the one thing in this picture that is designed to survive the container's destruction: the container platform itself. Every mainstream container runtime and orchestrator captures a container's stdout/stderr streams as a first-class feature, and forwards that captured output onward — to a centralized system built specifically to store and index log data durably, outside any single container's lifetime.

The genuinely elegant part: your application code needs zero knowledge of where its logs end up. It doesn't configure a destination, doesn't hold a connection to a log server, doesn't handle that connection failing. It just writes lines to the console, exactly as it always could — the platform does the rest, entirely outside the app's own responsibility.

Big Picture

FROM YOUR CODE TO A CENTRALIZED LOG STORE
1. YOUR .NET CODE
2. CONSOLE / stdout / stderr
3. CONTAINER RUNTIME CAPTURES THE STREAM
4. A LOG-FORWARDING AGENT SHIPS IT ONWARD
5. CENTRALIZED LOG AGGREGATION PLATFORM

How It Works — Structured Logs, at Scale, Across Instances

Lesson 128 already taught you why structured logging beats free-text logging for a single app: a structured log entry carries its data as separate, named fields rather than an interpolated sentence, so it can be filtered and queried precisely instead of pattern-matched with fragile string searches. That benefit was real even for one instance. In a cloud-native deployment, it stops being a nice-to-have and becomes close to mandatory.

Free-text log line

Structured (JSON) log line

A JSON-formatted structured log line can be parsed, indexed, and searched efficiently by the centralized platform the moment it arrives — it doesn't need to guess where the order ID starts and ends inside a sentence. Multiply that by dozens of container instances, all logging simultaneously, all being scaled in and out (lesson 304 covers exactly this horizontal scaling), and free-text logs stop being merely inconvenient — they become genuinely difficult to search at all, because there's no single machine's log file to grep, and no shared, predictable structure across the combined output of every instance to search against precisely.

Simple Example

Nothing about writing the log call itself changes for a container — this is exactly the ILogger<T> code from lesson 128:

public class PaymentService(ILogger<PaymentService> logger) { public async Task ChargeAsync(int orderId, decimal amount) { logger.LogInformation( "Charging order {OrderId} for {Amount:C}", orderId, amount); // ... } }

What differs in a containerized deployment is only the destination configuration — by default, ASP.NET Core's console logging provider already writes to stdout, which is exactly what a cloud-native deployment wants. No code change is required to "become cloud-native" here; the trap is adding a file-based logging provider on top of it and pointing it at the container's local disk, which quietly reintroduces the ephemeral-filesystem problem this lesson is about.

Real-World Example — Diagnosing a Failed Payment Across a Scaled-Out API

A payment API is running as 12 horizontally-scaled container instances (lesson 304) behind a load balancer. A customer reports a failed charge. The instance that actually handled that specific request could have been any one of the 12 — and by the time someone investigates, that particular pod might already have been rescheduled or scaled in, its local filesystem gone entirely.

Because every instance logs structured JSON to stdout, and the platform forwards all of it into one centralized log aggregation system, none of that matters. A single query — event:"PaymentFailed" AND orderId:4821 — finds the relevant log entry regardless of which of the 12 instances originally wrote it, and regardless of whether that instance still exists. There is no "which server was it on" question to even ask.

Analogy

A Diary vs. a Mailbox That's Emptied Every Night

Writing logs to a local file inside a container is like keeping a personal diary in a hotel room you know gets fully cleared out and possibly demolished at unpredictable times — sometimes tonight, sometimes next week. Whatever you wrote in that diary is only safe for as long as that specific room happens to still exist. That's a terrible way to keep anything you actually care about.

Writing logs to stdout is like dropping a note in an outgoing mailbox that someone else — the postal service, standing in for the container platform — reliably collects and delivers to a permanent archive before the room is ever cleared. You never had to trust the room's lifetime at all. The archive doesn't care which room, or how many rooms, the notes came from — it just has all of them, filed and searchable.

Under the Hood — Where the Logs Actually End Up

The centralized aggregation platform on the receiving end is intentionally not something this lesson teaches as a deep tutorial — the point is that it's a real, separate, durable system decoupled from any one container, and that OpenTelemetry (270) is what lets your app stay agnostic about which one it happens to be. Some of the platforms you'll encounter by name in real cloud-native .NET deployments:

PlatformWhere it's typically used
Azure Monitor / Application InsightsThe native choice for .NET apps running on Azure
AWS CloudWatchThe native choice for .NET apps running on AWS
Grafana / Loki stackA popular open-source combination for log aggregation plus dashboards, cloud-agnostic
ELK stack (Elasticsearch, Logstash, Kibana)A long-established open-source log search and visualization combination

Which one a given team uses is an infrastructure decision, made largely independent of the application code itself — precisely because the app only ever wrote to stdout/stderr, and OpenTelemetry's exporter layer (270) is what routes that data to whichever backend the platform team has chosen, without your business logic needing to know or care which one it is.

Common Confusion

1. "stdout is a less serious place to log than a file" — it's the opposite in this context

On a developer's own machine, writing to the console can feel like the throwaway option, and a log file can feel like the "real" durable one. In a containerized deployment, that intuition inverts completely: the file is the one that vanishes, and the console output is the one the platform actually captures and preserves.

2. "Structured logging is only about making logs prettier" — it's about making them queryable at all

Structured logging's real payoff isn't cosmetic formatting — it's that a centralized platform can index and query specific fields precisely, across the combined output of every running instance, instead of relying on fragile text pattern-matching over free-form sentences.

Common Mistakes

Mistake 1 — Adding a file-based logging provider "just to be safe"

Registering a file logging provider inside a containerized app, pointed at a path on the container's own local disk, on the assumption that a file feels more durable than console output.

Rely on stdout/stderr and let the platform's log-capture mechanism handle durability — that's the entire point of the cloud-native pattern.

Mistake 2 — Writing free-text logs and expecting to search them the same way across many instances

Continuing to write interpolated free-text log messages, then being surprised when a centralized platform can't cleanly filter "just the failed payments" out of the combined output of a dozen instances.

Use structured logging (lesson 128) consistently, so every instance's output shares the same queryable field names.

When Should I Use It?

Why OpenTelemetry matters more here, not less: Lesson 270 introduced OpenTelemetry as the vendor-neutral way to correlate logs, metrics, and traces into one connected view of a single request's journey. In a cloud-native world, that correlation isn't a convenience — it's often the only way to understand what happened. A single user request can flow across several different container instances, each one starting, stopping, and being rescheduled independently, with no one stable server anywhere in the picture to "SSH in and look at the log file" on. OpenTelemetry's shared trace and span identifiers are what stitch that request's fragments — spread across however many ephemeral instances actually touched it — back into one coherent, followable story, in the centralized platform this lesson has been describing. Without that correlation, you'd have a pile of individually-correct log lines from a dozen different, possibly-already-gone containers, with no thread connecting them back into the one request you actually care about.

Mental Model

Container filesystem = temporary, gone the moment the container is
stdout/stderr = the one output the platform is watching and will save for you
Structured logging = what makes that saved output actually searchable at scale
OpenTelemetry = what stitches one request's fragments back together across every instance it touched

Never trust the container's own disk with anything you need after the container is gone. Trust the platform's stdout capture instead — that's the whole shift.

Key Takeaway


Check Your Understanding

You've seen why cloud-native logging looks different from logging on a machine you own. Let's confirm the reasoning behind it stuck.

1. Why do cloud-native applications typically write logs to stdout/stderr rather than to a file on the container's own disk?

Show answer

Correct: B

Why B is correct: This is the core architectural reason — a container's own disk doesn't survive the container's own destruction, and destruction/rescheduling happens routinely in a cloud-native environment. stdout/stderr is the channel the platform is set up to capture and forward durably.

Why A is incorrect: Raw throughput isn't the reason; durability across the container's lifecycle is.

Why C is incorrect: Containers absolutely can write files; the problem is those files don't survive the container being destroyed, not that writing them is disallowed.

Why D is incorrect: Structured logging (JSON-formatted entries) can be written to a file too — the two concepts (structure vs. destination) are independent.

Reinforcement: The filesystem is temporary; stdout, captured by the platform, is what actually survives.

2. Why does structured (JSON) logging matter more in a cloud-native deployment than it did for a single-instance app?

Show answer

Correct: B

Why B is correct: With dozens of instances logging simultaneously and no single log file, precise, field-based querying across all of that combined output is what makes the logs actually usable at scale — that's exactly what structured fields enable.

Why A is incorrect: Terminal coloring is a cosmetic, unrelated detail, not the reason structured logging matters here.

Why C is incorrect: Log format has no bearing on whether a container starts.

Why D is incorrect: Structured logging is about queryability, not raw write performance.

Reinforcement: At scale, across many instances, structured fields are what make targeted searching possible at all.

3. A request flows through three different container instances before completing, and one of those instances has already been scaled in and destroyed by the time an engineer investigates a slow response. What makes it still possible to understand that request's full journey?

Show answer

Correct: B

Why B is correct: This is precisely why OpenTelemetry matters more in a cloud-native world — its correlated telemetry, already shipped off to the centralized platform via stdout capture and exporters, survives the container's own destruction and lets you follow the request across every instance it touched.

Why A is incorrect: This is exactly the outcome cloud-native logging/tracing is designed to prevent — the point of forwarding telemetry off-container is that it outlives the container.

Why C is incorrect: Kubernetes does not preserve a destroyed container's local filesystem; that data is genuinely gone, which is the whole motivation for not relying on it.

Why D is incorrect: Structured logging alone doesn't inherently link entries across separate instances — that correlation is specifically what shared trace/span identifiers (part of OpenTelemetry) provide.

Reinforcement: Correlated, centrally-stored telemetry is what makes a request's story reconstructable even after every container involved is gone.

You now understand why cloud-native logging looks the way it does — and why the observability foundation from lessons 269 and 270 becomes even more essential once your app runs across many ephemeral, disposable instances.


dotnetmadeeasy.com — Learn C# and .NET, the right way.