# 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)
