Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild
Back to Blog Operations

Heroku Logs: How to Read Them, How to Drain Them, and What the Migration Off Them Actually Looks Like

Sean

Platform Writer

Jun 17, 2026
7 min read

heroku logs --tail is the answer, but the answer is only the start. Production log analysis needs drains, structured logs, a destination, and a way to keep the destination when the team decides to leave Heroku. The heroku logs CLI is a real-time view; the heroku drains system is the production path; the structured-logs add-on is the modern answer for log shipping. The reason “heroku logs” is still a top search is that the answer has three layers, and most blog posts only cover the first.

This post covers all three. The first half is the CLI, the real-time view, and the cases where it is enough. The second half is the drains, the destinations, the structured-logs add-on, and the migration story when the team eventually moves off Heroku.

The interesting thing about Heroku logs is that the system has not changed much in a decade. The CLI is the same. The drain protocol is the same. The destination options are more numerous, but the contract is the same. The trap is that the contract works the same way on every modern PaaS, and the team that learns it once can apply it everywhere.

Heroku Logs: How to Read Them, How to Drain Them, and What the Migration Off Them Actually Looks Like

Table of contents

The direct answer

For a quick look:

heroku logs --tail --app=my-app
# or with the short flag
heroku logs -t -a my-app

For the last 200 lines:

heroku logs -n 200 --app=my-app

For everything in the last hour, by source:

heroku logs --since=1h --source app --app=my-app
heroku logs --since=1h --source heroku --app=my-app

For production analysis, the answer is the drain system plus a log destination (Papertrail, Logtail, Datadog, a self-hosted Loki, etc.). The CLI is for live debugging. The drain is for everything else.

The rest of the post is the layers below.

The CLI: heroku logs —tail and friends

The heroku logs command has three modes:

Tail mode (--tail or -t). Streams new log lines to the terminal in real-time. Press Ctrl+C to stop. This is the “live” view. The output is whatever the app has logged plus the Heroku platform events (deploys, restarts, dyno changes).

Snapshot mode (no flag). Returns the last 1,500 log lines, then exits. Useful for a quick check without live tailing.

Filtered mode (--since, --until, --source, --dyno). Returns log lines within a time range, from a specific source (app, heroku, or add-on), or from a specific dyno. Useful for narrowing the output to the relevant lines.

The CLI connects to Logplex, Heroku’s log routing system. Logplex buffers logs, routes them to the drains, and exposes them to the CLI. The buffer is small (1,500 lines is the visible limit), so the CLI is not a substitute for a log destination. It is a debugging tool.

The --source flag is the underused one. The default view mixes app logs and Heroku platform events. The --source app view shows only the application’s stdout and stderr. The --source heroku view shows only the platform events (deploys, restarts, HTTP routing, database state). Most debugging sessions want the first; most postmortems want both.

The --dyno flag is the other underused one. A Heroku app runs multiple dynos (web, worker, etc.). The --dyno web.1 view shows only one dyno’s logs. Useful when one dyno is misbehaving and the others are fine.

What Heroku logs contain by default

By default, Heroku captures whatever the application writes to stdout and stderr. The Heroku platform adds:

  • A timestamp.
  • A source (app, heroku, or an add-on name).
  • A dyno identifier (web.1, worker.2).
  • The log line itself.

The format is roughly:

2026-06-17T14:32:01.234567+00:00 app[web.1]: INFO: Request received GET /api/users
2026-06-17T14:32:01.345678+00:00 heroku[router]: at=info method=GET path="/api/users" host=my-app.herokuapp.com request_id=abc123 fwd="1.2.3.4" dyno=web.1 connect=0ms service=12ms status=200 bytes=1234

The first line is the application’s log (a Python logging call or a Node console.log). The second is the Heroku router’s access log, which is added automatically for every HTTP request.

The default format is plain text with a known prefix. The plain text is human-readable. The plain text is also parseable: the timestamp, source, dyno, and message are easy to extract. Most log destinations can ingest the Heroku format directly.

The trap: the plain text format is not structured. The team that wants JSON logs has to add a logging library that emits JSON to stdout, and the team that wants to query by field has to make sure the log destination understands the JSON.

The drain system: from app to destination

The drain system is the production path. A drain is an HTTPS endpoint that Logplex POSTs log batches to. The team configures a drain to point at a log destination, and the destination ingests the logs.

The configuration:

heroku drains:add https://logs.example.com/heroku --app my-app

The drain URL is the destination’s HTTPS endpoint. The destination handles authentication, batching, parsing, and storage. The team does not write any code; they configure the drain, and Logplex starts shipping logs.

Drains can be added to specific apps or to all apps in a team. The platform supports up to 5 drains per app (enough for a destination, a backup, and a couple of debug tools). The team can also remove drains:

heroku drains:remove https://logs.example.com/heroku --app my-app

The drain protocol is plain HTTPS with a psvm+msgpack or psvm+json content type. The body is a batch of log lines with the same Heroku format. The destination must accept the format, parse it, and store the lines. Most managed log destinations (Papertrail, Logtail, Datadog, Sumo Logic) understand the Heroku format out of the box.

The trap: drains are at-most-once. If the destination is down, Logplex retries for a while, then drops the logs. The team that needs durable log shipping needs a destination that writes to disk and survives a destination outage. The Papertrail and Datadog destinations are durable. The “I’ll run a quick HTTP server” destination is not.

The structured-logs add-on: the modern answer

Heroku’s structured-logs add-on is a newer option for teams that want JSON logs by default. The add-on:

  • Captures whatever the application writes to stdout or stderr.
  • Parses each line as JSON if possible, as plain text otherwise.
  • Adds a timestamp, source, dyno, and request ID automatically.
  • Sends the parsed logs to a log destination (typically the add-on’s own destination).

The application does not have to emit JSON. Plain text logs are still captured, but the JSON-shaped fields are extracted. A console.log("User signed up: " + userId) is captured as plain text. A console.log(JSON.stringify({event: "signup", userId})) is captured as a structured event with event and userId fields.

The benefit: the log destination can query the JSON fields. “Show me all signup events for user 12345” is a query. “Show me all 500 errors” is a query. “Show me the average response time by endpoint” is a query. The plain-text equivalent is a regex against the log line, which is fragile and slow.

The trade: the add-on costs money. The free tier covers most small apps; the paid tier covers larger apps. The add-on is the right answer for production. The free tier is the right answer for prototyping.

The setup:

heroku addons:create heroku-structured-logs --app my-app

The add-on sets the drain automatically. The team configures the application to emit JSON (or not), and the destination handles the rest.

The destinations: Papertrail, Logtail, Datadog, and the team that runs their own

The destination is where the logs go. The options:

Papertrail. The classic Heroku log destination. Plain-text search, alerting, and a long retention window. The right answer for small teams that want a simple, reliable destination. The Heroku docs recommend it; the Heroku team uses it internally.

Logtail (Better Stack). A modern log destination with structured-log support, full-text search, and a generous free tier. The right answer for teams that want JSON-native ingestion and a fast query experience.

Datadog. The full observability platform. Logs, metrics, traces, and APM in one tool. The right answer for teams that already use Datadog for metrics and want logs in the same place.

Sumo Logic. The enterprise log destination. The right answer for large organizations with compliance requirements.

Self-hosted (Loki, Elastic, Splunk). The team that runs their own log destination. The right answer for teams with strict data-residency requirements or a large enough log volume that managed destinations are too expensive.

For most teams, Papertrail is the right default. It is the simplest, the cheapest, and the one the Heroku docs recommend. The team that outgrows Papertrail moves to Logtail, Datadog, or a self-hosted destination.

For a self-hosted destination, the most common open-source choices are Grafana Loki (lightweight, designed for Kubernetes and cloud-native workloads) and the Elastic Stack (the classic, more features, more operational overhead). The right answer depends on the team’s existing observability stack.

The migration off Heroku logs: the contract to preserve

The team that migrates off Heroku loses Logplex, the CLI, and the default drains. The team that wants to keep the log workflow needs to preserve the contract: the app writes to stdout and stderr, the platform collects, the destination stores, the team queries.

The migration path:

  1. The app. Already writes to stdout and stderr. No code change needed.
  2. The platform. A new PaaS with a log collection system that reads the container’s stdout and stderr. Most modern PaaS platforms (Render, Fly, Railway, the RunxBuild platform, Kubernetes with a log collector) read container logs by default.
  3. The destination. A log destination that ingests the platform’s log format. The migration usually changes the format from “Heroku prefix + plain text” to “JSON or plain text” depending on the platform.
  4. The team. A query interface and a retention policy. The team can use the destination’s UI, a CLI, or an API.

The discipline: the team should not couple the application to the log platform. The application writes to stdout. The platform collects. The destination stores. The team queries. Each layer is independent, and the team can swap any layer without rewriting the application.

A team that ships logs as JSON, with consistent field names (timestamp, level, message, request_id), can move to any destination without code changes. A team that ships logs as a custom format tied to a specific platform is locked in.

For a sanity check on the cost of the new platform, the hosting cost calculator gives a real number to compare against. The cost of the platform is one number; the cost of the log destination is another; the total is the migration budget.

The opinion this post is built on

Heroku logs are a real-time view, a drain system, a structured-logs add-on, and a destination choice. The CLI is for debugging. The drain is for shipping. The add-on is for structure. The destination is for storage and query. The four pieces are independent, and the team that learns the contract can apply it to any modern PaaS.

The contract is: the app writes to stdout and stderr. The platform collects. The destination stores. The team queries. The contract is the same on Heroku, on Render, on Fly, on Railway, on a Kubernetes cluster, and on the RunxBuild platform. The team that learns the contract once can ship logs anywhere.

The deeper discipline: the log format is the team’s choice. JSON with consistent field names is the modern default. The team that ships JSON is the team that can query by field, alert by value, and move destinations without rewriting. The team that ships plain text is the team that greps log lines and hopes the regex is right. The format choice is a one-time decision that pays for itself every time the team needs to debug.

The destination is a tool. The query is the work. The team that picks the boring destination (Papertrail, Logtail) and the boring format (JSON with a timestamp, level, message) is the team that does not have a “where is the log line” problem. The team that picks the fancy destination and the custom format is the team that does.

FAQ

How do I tail Heroku logs in real time?

heroku logs --tail --app=my-app (or -t -a my-app). The CLI streams new log lines as they are produced. Press Ctrl+C to stop. The CLI connects to Logplex, Heroku’s log routing system.

How long does Heroku retain logs?

The default retention in the CLI is 1,500 lines. Heroku’s longer-term retention requires a log destination (Papertrail, Logtail, Datadog, etc.). The destination’s retention is a function of the plan — typically 7 to 30 days for the standard plans, longer for enterprise.

What is the difference between heroku logs and heroku drains?

heroku logs is the CLI for reading logs in real time. heroku drains is the configuration for shipping logs to an external destination. The CLI is for debugging. The drains are for production. The team uses both.

Can I ship Heroku logs to my own log server?

Yes. Configure a drain to point at your server’s HTTPS endpoint. The server must accept the Heroku log format (psvm+msgpack or psvm+json), parse the batches, and store the lines. Most open-source log servers (Grafana Loki, Elastic, Fluentd) can ingest the Heroku format with a small adapter.

What is the Heroku structured-logs add-on?

A paid add-on that parses the application’s stdout and stderr as JSON when possible, extracts the fields, and ships the structured events to a log destination. The add-on is the right answer for teams that want JSON-native log queries. The free tier covers most small apps; the paid tier covers larger apps.

How do I migrate off Heroku logs?

The app writes to stdout and stderr. The new platform collects. The new destination stores. The team queries. Each layer is independent. The team that ships logs as JSON with consistent field names can move to any destination without code changes. The destination choice is the only piece that changes; the contract is the same on every modern PaaS.

#heroku logs#heroku logs command#heroku log drain#heroku logplex#heroku structured logs#heroku papertrail