Logs and metrics
Read your project's deploy and build log, follow it live, and see its traffic, failures and latency in the console or over the API.
Everything about what your project is doing is in one place in the console: open the project and choose Observability. It has three tabs: Overview for traffic and latency, Logs for what the platform and your builds have written, and Activity for the history of changes to the project. Each one is a view over an API route you can call yourself.
Logs
The log is an ordered list of events. Each event has a sequence number (seq), a time (ts, milliseconds since the epoch), a source, a stream (out or err) and its text.
| Source | Written by |
|---|---|
system | Fortly, narrating what it did to your project: a deploy arriving, what it found, each step, and the outcome. |
build | Your install and build, during a deploy that needs building. |
app | A project running as its own process. |
Releases served as Workers — server-rendered frameworks and Node servers — do not write what they print to this log. For those, the deploy and build history is here, and how the app is answering is in Metrics.
Read logs in the console
The Logs tab streams new events as they arrive and keeps the view pinned to the newest line. Scroll up to read and it stops following without stopping the stream; it tells you how much arrived while you were reading.
- Search output filters by text, across every line of a multi-line entry.
- Level filters on the level a line declares about itself — errors, warnings, info, debug — or on
stderr. Fortly only marks a level when the line contains one near its start; it does not guess. - Source filters to
system,buildorapp. - Pause holds new lines until you resume, Load older pages back through history, and the copy and download buttons take the lines currently shown.
Indented lines that follow a line on the same stream, and Python tracebacks, are grouped into one entry you can expand.
Read a deploy in the log
A deploy reads top to bottom as an account of what happened. A build's own output arrives in one piece when the build finishes — its last 200 lines — between Building with the build service and Built:
Deploying an upload · 1.2 MB
Unpacked 214 files · 3.4 MB
Node.js · Next.js · npm
Building with the build service (small)
fortly-build: manager=npm adapter=next
...
Built · 87 files · 5.1 MB
Uploading runtime bundle...
Validating deployment...
Deployment ready.A failed deploy ends with an err line under system, such as Deploy failed. The release that was already live is still live. The reason is also kept on the release itself, in its failure field. How builds work lists the common failures.
Environment values 8 characters or longer are replaced with [redacted] wherever they would appear.
Read logs over the API
curl "https://api.fortly.com/projects/my-app-ada/logs?lines=100" \
-H "Authorization: Bearer $FORTLY_TOKEN"linesintegerbeforeintegerseq lower than this. Pass the oldest value from a previous reply to page backwards.The reply has events, plus oldest and newest (the sequence numbers at each end) and more (whether there is older history to page back to).
To follow the log live, open the stream. It uses server-sent events, and each log event's id is its sequence number:
curl -N https://api.fortly.com/projects/my-app-ada/logs/stream \
-H "Authorization: Bearer $FORTLY_TOKEN"event: gap
data: false
id: 1043
event: log
data: {"seq":1043,"ts":1760263200000,"source":"system","stream":"out","text":"Deployment ready."}To resume after a dropped connection, send the last id you saw as Last-Event-ID, or as ?after=. You receive what you missed, then the live feed. Fortly holds the most recent 2,000 events per project for this. If you were away longer than that, the stream starts with a gap event whose data is true: fetch history with GET /logs instead of assuming nothing happened. A keep-alive comment is sent every 20 seconds while nothing is happening.
Logs are for watching and debugging your project now, not for keeping. They are not a long-term archive.
Read logs from an agent
An agent connected over MCP with the logs:read scope can call query_logs with a project name. It returns a page of recent lines, oldest first — 100 by default, at most 500 — and takes before to page back. It does not follow the log; call it again for newer lines.
Metrics
The Overview tab shows how your project has been answering requests, and refreshes itself every 15 seconds.
| Panel | What it shows |
|---|---|
| Traffic | Requests over the last 15 minutes, hour, 24 hours or 7 days, split into 2xx–4xx and 5xx. Beside the chart: Requests, Success rate (answers that were not 5xx), Failed (5xx, your project's fault) and Refused (4xx, the caller's). |
| Latency | Median, 95th percentile and Slowest answer time. |
| Routes | Your busiest routes, each with its calls, failures and latency. |
A few things to know when you read them:
- Failed and refused are kept apart. A wall of
404s is usually a broken link somewhere else, not your project being down. - Routes are grouped by shape.
/users/1and/users/2are counted as one route, because a segment that looks like an id is treated as one. Up to 120 routes are tracked per project; past that, new ones are counted together asother. - Percentiles are from a histogram, so each reads as "at or under this". There is no average, because one very slow answer barely moves an average.
- Windows up to 24 hours are counted since Fortly last restarted the machine serving your project. The 7-day view is read from hourly records and survives restarts; hours from before those records began are missing rather than shown as zero.
Scheduled runs reach your project through the same front door as visitors, so they are counted here too.
Metrics over the API
curl "https://api.fortly.com/projects/my-app-ada/traffic?span=1h" \
-H "Authorization: Bearer $FORTLY_TOKEN"span is 15m, 1h, 24h (the default) or 7d; anything else is refused with span is 15m, 1h, 24h or 7d, not 2h. The reply has step_s, the width of each point in seconds, and points, each with at, requests, failed and refused.
GET /health summarises every project you can reach: requests_24h, failed_24h, refused_24h, p50_ms, p95_ms and slowest_ms for each, with "since_restart": true to remind you where the count began. GET /projects/{name}/routes returns the busiest routes with their own latency, and limit, the most routes tracked.
Activity
The Activity tab is the history of changes to the project, newest first: projects created, releases deployed, environment values set, and domains added and verified. Each entry links to where the change was made. Things that leave no record — a removed environment value, a deleted project — do not appear.
GET /activity returns the same history for every project you can reach, as events with at, kind (project.created, deploy, secret.set, domain.added or domain.verified), project and detail (the release id, value name or hostname). It returns the 200 most recent.
API routes
/ projects/ {name}/ logsA page of recent log events; lines and before/ projects/ {name}/ logs/ streamThe log as server-sent events, resumable from a sequence number/ projects/ {name}/ trafficRequests over a span: 15m, 1h, 24h or 7d/ projects/ {name}/ routesThe busiest routes, with calls, failures and latency/ healthTraffic and latency for every project you can reach/ activityRecent changes across your projectsUse the project's full API name, such as my-app-ada, in these routes — see Deploying your code.