# Fly.io Logs

Stream logs from Fly.io apps into Gonzo with a lightweight `jq` transform. Fly's CLI streams structured JSON over NATS, but your app's output is double-encoded inside the `message` field. The pipe below unwraps it so Gonzo sees clean, flat JSON.

#### Quick Start

```bash
fly logs -a <app-name> -j \
  | jq --unbuffered -c '
    {timestamp, region, instance}
    + (try (.message | fromjson) // empty)' \
  | gonzo
```

The `-j` flag gives you structured JSON. The `jq` transform extracts `timestamp`, `region`, and `instance` from the envelope, parses the inner message for your app's real fields (`level`, `message`, custom fields), and silently drops non-JSON lines (init logs, Firecracker boot messages).

#### Prerequisites

* Gonzo installed
* [Fly CLI](https://fly.io/docs/flyctl/) installed (`brew install flyctl`)
* `jq` installed (`brew install jq`)

#### Log Sources

Fly streams multiple sources through a single pipe. The `jq` transform with `// empty` keeps only your app's JSON output.

| Source     | Provider | What it captures                                    |
| ---------- | -------- | --------------------------------------------------- |
| **App**    | `app`    | Your app's stdout/stderr, plus Fly init messages    |
| **Runner** | `runner` | Image pulls, Firecracker config, machine start/stop |
| **Proxy**  | `proxy`  | Machine reachability, startup timing                |

To **include platform logs** (useful during deploy debugging), swap `// empty` for `// {message: .message}`:

```bash
fly logs -a my-app -j \
  | jq --unbuffered -c '
    {timestamp, region, instance, provider: .meta.Event.Provider}
    + (try (.message | fromjson) // {message: .message})' \
  | gonzo
```

#### Usage Patterns

**Real-time streaming** (default):

```bash
fly logs -a my-app -j \
  | jq --unbuffered -c '{timestamp, region, instance}
    + (try (.message | fromjson) // empty)' \
  | gonzo
```

**Filter by region:**

```bash
fly logs -a my-app -j --region lhr | ...
```

**Filter by machine:**

```bash
fly logs -a my-app -j --machine 6835d41f765168 | ...
```

**Buffer dump (no streaming):**

```bash
fly logs -a my-app -j --no-tail \
  | jq -c '{timestamp, region, instance}
    + (try (.message | fromjson) // empty)' \
  | gonzo
```

`--no-tail` dumps the current log buffer and exits. For historical queries with date ranges, use Fly's [Grafana log search](https://fly.io/docs/monitoring/search-logs/) (30-day retention).

**With local AI (logs never leave your machine):**

```bash
export OPENAI_API_KEY="ollama"
export OPENAI_API_BASE="http://localhost:11434"
fly logs -a my-app -j \
  | jq --unbuffered -c '{timestamp, region, instance}
    + (try (.message | fromjson) // empty)' \
  | gonzo
```

#### Machines and Auto-Stop

Fly Machines [auto-stop](https://fly.io/docs/launch/autostop-autostart/) when there's no inbound traffic. Log streams end when machines stop — this is expected, not a Gonzo or pipe issue. Machines restart on the next request. To keep logs flowing, send periodic requests or set `auto_stop_machines = false` in `fly.toml`.

#### Structured Logging Tips

Emit single-line JSON to stdout for the best Gonzo experience. Multi-line JSON objects are split into separate log entries by Fly's pipeline.

Fly tags all stderr output with `"level": "info"` at the envelope level, same as stdout. If you rely on stderr for error output (e.g. Python's `logging` defaults), the envelope level won't reflect real severity. Use structured JSON with an explicit `level` field instead.

#### Troubleshooting

| Symptom                                | Cause & fix                                                                                            |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| No logs appearing                      | Check `fly status -a <app>`. Machines may be stopped — hit your app's URL to wake them.                |
| Only build/boot logs, no app logs      | Firecracker VM boot takes a few seconds. Wait 10–15s after deploy.                                     |
| Envelope `level` always "info"         | Expected. Fly doesn't map app log level to envelope. The `jq` transform extracts the real level.       |
| Logs stall in the pipe                 | Add `--unbuffered` to `jq`. Without it, output buffers and logs appear in delayed bursts.              |
| Init messages mixed with app logs      | Fly's init process is tagged `provider: "app"`. The `// empty` pattern drops these (they're not JSON). |
| Trial account machines killed after 5m | Add a payment method at [fly.io/trial](https://fly.io/trial). Free allowance covers small apps.        |

***

**Time to complete:** 5 minutes **Prerequisites:** Fly CLI, jq, Gonzo installed **Full guide:** [`guides/FLY_USAGE_GUIDE.md`](https://github.com/control-theory/gonzo/blob/main/guides/FLY_USAGE_GUIDE.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.controltheory.com/controltheory-documentation/gonzo-docs/integration-examples/fly.io-logs.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
