
Overview
When you run a platform for users to execute their own code on Fly.ioāthink per-user development environments or AI/LLM appsāitās a good idea to have real-time observability for those user apps. You might also want to stream logs from those apps back to the original developer (your end-user), so they can see what their code is doing. Fly.io offers built-in telemetry based on NATS that you can tap into to achieve this. At a high level, Fly.io captures everything your app writes to stdout and funnels it into an internal NATS log stream. A proxy in front of this NATS cluster ensures that each organization can only access its own applicationsā logs. Both theflyctl logs command and the our web dashboardās āLive Logsā use this pipeline under the hood. We can use the same system! By connecting a NATS client within your Fly organizationās private network, we can subscribe to log subjects and stream logs out to wherever we need, in real-time.
Deploying the Fly Telemetry Forwarder
If you want a birdāsāeye view for your own operations team, Fly provides an open source reference app called fly-telemetry that you can deploy to quickly get an observability stack running in your Fly organization. This app acts as a telemetry forwarder: it subscribes to the platformās NATS logs (and metrics) streams and stores the data for you. It includes a lightweight time-series database (VictoriaMetrics for metrics, VictoriaLogs for logs) and a Grafana instance with pre-configured dashboards, so you can explore your appsā logs and metrics visually. In an empty directory, run:fly proxy and you have instant Grafana dashboards.
This is handy for SREs, but not necessary for your endāusers.
Streaming Fly app logs to your end users
Streaming live logs straight to your users gives them insight into what their code is doingādevelopers see exactly what the runtime sees, in real time, without leaving your product or juggling extra tooling.Connecting to the NATS Log Stream
Every Fly organization has access to NATS which carries the logs for its apps. The server is available at the well-known address[fdaa::3]:4223. To connect to this endpoint, you must do so from within the Fly networkāfor example, from code running in a Fly app (like your telemetry forwarder or router) or from your local machine over a WireGuard connection.
Using the NATS JavaScript client, you could connect as follows:
Authentication
When connecting from a Fly app in your org, youāll need to supply an access token. To generate one manually, runfly tokens create readonly <org> and store it as an app secrets. The NATS proxy will accept the token as the password and your org name as the username.
Log Subjects and Filtering
Logs in the NATS stream are organized by subject. The subject format for Fly logs is:panini-786 and itās running in region dfw, you might see subjects like logs.panini-786.dfw.d896d7c609dd68 (where d896d7c609dd68 is the Machine ID). You can subscribe at varying levels of granularity using NATS wildcards:
In practice, to stream all logs for a given userās app, youāll subscribe to the subject pattern
logs.<user-app-name>.> ā this will capture any log line from any Machine of that app, in any region.
A Real-World Example
NATstream is a reference app that streams logs from your Fly apps right back to the browser. It connects to NATS and turns those events into a live feed at a simple HTTP/logs endpoint using Server-Sent Events (SSE). Developers could log in to your product and watch their appās logs flow in real time, without needing separate logging tools or the Fly CLI. The app keeps things high-level and lightweightāthereās no deep framework complexity here, just a straightforward example of hooking into Flyās log pipeline and broadcasting events to the browser.

Building a Central Log Router Service
Create a Fly ālog routerā app that sits between Flyās NATS telemetry stream, your product and your developers. Because each customer already has their own Fly app, every userās logs appear on a unique subject (logs.<app>.>), so multi-tenancy is solved by designāsubscribe only to the subject that matches the requesting developerās app and youāll never mix data between users.
Note: Your router service should enforce that a user can only request the logs for the app(s) they own. This means your routing logic needs an access control check (e.g. based on the authenticated userās ID matching the app name or an internal mapping). Fortunately, if youāve structured your platform as one Fly app per user, itās straightforward to maintain a mapping from user account to Fly app name and use that for authorization.
- On startup, connect to the NATS log stream using the org-wide credentials (as shown earlier).
- Wait for developers to request or connect for log streaming (e.g., a developer opens a web UI to view their appās live logs, or runs a command to view logs).
- When a request for a specific appās logs is received, subscribe to that appās log subject (if not already subscribed).
- Stream the incoming log messages to the requesting developer in real-time.
- Ensure that each developer only receives their own appās logs. The router should use the mapping of app ā ā user to publish the right data to the correct output channel (and not mix different usersā data).
Related reading
- Logging overview How logs flow from your Fly apps into the platformās NATS pipeline.
- Logs API options Guide with approaches for tapping directly into structured logs programmatically.
- fly-telemetry Lightweight reference implementation for quick, out-of-the-box observability for deployments on Fly.io.