Skip to content
Noite
Esc
↑↓navigate↵open⌘Jpreview
On this page

Observe

Logs, hourly metrics, errors, product-analytics events, and idle sleep for one app.

Logs

The Deployments tab shows the app’s runtime logs, live over SSE, and follows the tail while pinned; scroll up to hold still. It holds the last 24 hours of logs ingested from celld’s telemetry (up to 500 lines, about 40 s behind live traffic) followed by the fleet’s live stdout/stderr, which lives in memory and resets when the runner restarts. Each deploy’s build log is on its own row in the same tab. A deploy row inside a preview environment’s group shows that environment’s own fleet instead: its ingested telemetry plus its live stdout/stderr, never production’s lines.

Metrics

The Metrics tab covers the last 24 hours, 7 days or 30 days (the range picker; ?r= keeps it across reloads): stat tiles for requests, error rate, p50/p95 latency, peak memory and CPU, then one chart per hour for requests, errors, p50 and p95 latency, peak memory and CPU. The Overview tab shows the same numbers in a smaller card. The environment picker scopes every chart to one environment; without it the tab is production only.

The numbers are the edge’s own record of the hour, not an estimate from traces: every request a fleet’s own proxy answers is one access-log line, which gives the hour’s request count, its 5xx errors (error rate), and its latency percentiles (p50 and p95, from a fixed log-scale histogram of every served line’s duration). The edge’s own answers — the :80 redirect, a gate refusal, a rate limit, the starting page — are not requests and do not count. Memory is the fleet cgroup’s charge, sampled every few seconds, so an hour carries its peak and its exact mean; CPU is the celld process tree’s own time. Retention follows RUNNER_TELEMETRY_RETENTION_DAYS.

Ingestion is idempotent and resumable: each pass rewrites the hours it reads, so a failed pass is retried instead of skipped, and after the runner has been down the gap is ingested hour by hour on restart (bounded by telemetry retention). An operator can replay a window by hand with noite-runner telemetry reingest.

Errors

The Errors tab collects exceptions without an SDK. Noite reads them from the same telemetry as Metrics, so they show up about a minute after they happen. The environment picker scopes the tab to one environment’s issues; without it the tab is production only.

  • What counts: anything a handler throws or rejects (fetch, Durable Objects, alarms, queues, cron), a rejected ctx.waitUntil(), and console.error(err) when err carries a stack. A console.error("text") without a stack stays a log line.
  • Grouping: occurrences group into one error by type plus the functions in your code on the stack. Line numbers are ignored, so a redeploy doesn’t split an error in two. Errors without a stack group by message, with numbers and ids masked.
  • Each occurrence keeps the stack trace (runtime frames dimmed), the request’s method, path (never the query string), status and browser, the log lines from the same request, and the commit that was live. The last 20 occurrences are kept per error.
  • Triage: anyone with push access can resolve, ignore or reopen an error. A resolved error that happens again reopens and is marked Regressed. An ignored one keeps counting but never reopens.
  • Retention follows telemetry retention (RUNNER_TELEMETRY_RETENTION_DAYS, 30 days by default).

Browser errors via Sentry

Server-side errors need no SDK, but anything thrown in the browser needs the stock Sentry SDK pointed at Noite. Install @sentry/browser and initialize it with the DSN from Settings → Error reporting:

import * as Sentry from "@sentry/browser";

Sentry.init({
  dsn: "https://<key>@<control-host>/<n>",
  release: "[email protected]",
  environment: "production",
});

Browser events land in the same Errors tab with the same grouping, labelled Browser, and each occurrence keeps the page URL plus the release and environment rows. Envelopes are gzipped by the SDK, capped at 200 KB, and non-error items (transactions, sessions, replays, attachments, client reports) are accepted and dropped with a 200 so the SDK never retries them.

Source maps

Each successful deploy stores its source maps under maps/<deploy-sha>/ in its own bucket prefix (production or the environment’s), and the Errors tab symbolicates every occurrence against the maps of the commit it ran on: the stack shows the original file, line and function, with the minified frame kept on a dimmer line below.

  • Worker maps need no config. Each app deploy passes --sourcemap to celld deploy, which writes the bundled Worker’s map with project-root-relative sources — the deployed bytes stay byte-identical — chained through the build’s own map when the entry carries one. For a no_bundle entry, celld collects the entry’s sibling map instead, so those stacks resolve too. Asset-only and Python projects get no worker map (celld has nothing to map), as does a no_bundle entry without a sibling map.
  • Browser maps come from your build. Only maps the build emitted are collected, never generated. With Vite, set build.sourcemap: "hidden" in vite.config.ts: the hidden setting emits *.js.map next to the bundles without the sourceMappingURL comment, so browsers fetch no map file while the runner can still symbolicate. Like any hidden-map setup, the map files stay publicly fetchable by exact URL — the same tradeoff as Cloudflare.
  • Grouping still reads the raw frames, so a minified rename can split one bug into an issue per deploy; each occurrence still resolves to its original source. Maps are kept for the last ten successful deploys plus the current one (and deleted with the app or its environment); older occurrences render their raw stacks.

Events and insights

The Events tab is tenant-facing product analytics, separate from metrics. The app emits events into named channels; the UI offers channel filter, text search, paging, per-event tags and user properties, and insight widgets over the feed.

Before the first event lands, the tab shows an ingest snippet for that app. Identified users carry properties that appear with their events.

The control plane’s own app

_control is a reserved pseudo app in your app list — the Noite control plane itself, labelled “admin”. It has no runner app row, no fleet to deploy and no source to browse, so its detail carries the control plane’s telemetry tabs (Overview with metrics, an errors summary and the control D1, Metrics, Errors, Logs) next to the instance administration tabs (Users, Apps, Invites) and Settings (the instance-wide switches: anonymous telemetry and the release check). The control D1 opens on its own /storage page, like any app’s storage. This page is the single admin home. The metrics are the edge’s record of the control node’s own traffic — including this dashboard’s polling and event streams, real load rather than noise to subtract — and the errors and logs come from its celld telemetry, ingested exactly like a tenant’s (Parquet under control/telemetry/).

It is visible to a real instance admin only, never an impersonated session, and both the page and every stream behind it re-check that rule server-side.

Sleep

An app with no requests for a day stops costing anything; the next request wakes it.

  • Asleep is not stopped. Desired state stays running; status reads sleeping. A stopped app never sleeps or wakes.
  • Idle window: 24 h of no requests by default. A fresh deploy or manual start gets a full window before it can sleep.
  • Wake is invisible. The edge holds the request — body included — while the fleet cold-starts, typically a few seconds, then proxies it as if the app had never slept. Concurrent requests wait on the same wake, and only a few apps start at once host-wide (RUNNER_WAKE_CONCURRENCY); only a failed wake answers 503.
  • Deploy wakes. A push, rollback, or web commit to a sleeping app starts the fleet first.

Was this page helpful?