Skip to content
Console
Browse documentation
Guide

Live tail errors and reconnecting

Handle refused log subscriptions before streaming, then resume accepted streams safely.

On this page

This contract applies to GET /api/v1/stream/logs. See Search logs, traces, and metrics for filters and examples.

Before the stream opens

A successful subscription returns HTTP 200 with Content-Type: text/event-stream. A valid subscription opens even when no matching log exists yet; comment heartbeats do not represent log records or advance your saved cursor.

A refused subscription returns an HTTP error with a JSON body before any SSE heartbeat or log frame. Check the HTTP status before starting an SSE parser.

HTTP status JSON error Action
400 invalid_request Correct the request. A malformed, oversized, noncanonical or differently scoped resume cursor cannot be retried unchanged.
401 unauthorized Authenticate again or replace the credential.
403 forbidden or permission_denied Use the credential's project scope or obtain the required permission before trying again.
429 rate_limited Retry with bounded backoff.
503 unavailable Retry with bounded backoff; persistent failures may require a configuration change.

For example, a refused log-tail cursor produces:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8

{"error":"invalid_request","message":"invalid log stream request"}

Admission is time-bounded. If the service cannot confirm the subscription, it fails rather than opening an unvalidated stream. This opening bound does not limit how long an accepted live tail can remain connected.

Cursor and filter changes

Save a cursor only after processing its log frame. Reconnect with Last-Event-ID, or pass resume_cursor in the query string. A nonempty Last-Event-ID takes precedence when both are supplied. since applies only to a new subscription without a resume cursor.

A cursor belongs to its organization, effective project and filter set, including service, level, text query, environment, customer IDs and the field filter. Keep these unchanged when resuming. To change the filter set, intentionally start a new subscription without the old cursor. Do not repeatedly retry a 400 with the same rejected cursor, and do not silently reset your saved position after a refusal.

Treat cursors as opaque positions, not credentials. Authentication and authorization remain required for every subscription.

After the stream opens

Once the stream is accepted, a later failure cannot change the HTTP status. It is sent as an SSE error event, for example:

event: error
data: {"error":"invalid_request","terminal":true}

A terminal: true frame means the client must stop retrying the same subscription unchanged. For a repairable failure, re-establish credentials when needed and reconnect with bounded backoff from the last processed cursor. An accepted stream continues checking its credential; revocation or scope loss can end it even while no new logs are arriving.

Error messages are intentionally bounded public classifications. Do not depend on private validation details appearing in either JSON errors or SSE frames.