Send logs
Every way in takes an app's key, and the lines land in that app.
The key
An app's key is the 32 characters of its DSN before the @, under the app's Keys. Send it as Authorization: Bearer <key>, as X-Statoss-Key: <key>, or as the password of HTTP Basic. The SDKs read it from the DSN. Each app's Logs tab has these snippets with its key filled in.
Lines go to https://ingest.statoss.com. What a team may keep is under Limits and the daily cap.
JavaScript and TypeScript: @statoss/sdk
Version 0.4.0 or later.
npm install @statoss/sdkimport * as StatOSS from "@statoss/sdk";
StatOSS.init({ dsn: "https://<key>@ingest.statoss.com/<app number>", logs: true });
StatOSS.logger.info("Order paid", { orderId: 7 });
StatOSS.logger.warn(StatOSS.logger.fmt`Search took ${ms} ms`);Python: statoss
Version 0.2.0 or later.
pip install statossimport statoss
statoss.init(dsn="https://<key>@ingest.statoss.com/<app number>", enable_logs=True)
statoss.logger.info("Order {order_id} paid", order_id=7)Rust: statoss
Version 0.1.0 or later. The feature log takes the log crate.
cargo add statoss --features tracing
cargo add tracing tracing-subscriberuse tracing_subscriber::prelude::*;
let _statoss = statoss::init(statoss::Options::new().dsn("https://<key>@ingest.statoss.com/<app number>").logs(true));
tracing_subscriber::registry().with(statoss::tracing::layer()).init();
tracing::info!(order_id = 7, "Order paid");Sentry's JavaScript SDKs
Version 9.41.0 or later. Before 10.71.0, add enableLogs: true.
import * as Sentry from "@sentry/node";
Sentry.init({ dsn: "https://<key>@ingest.statoss.com/<app number>" });
Sentry.logger.info("Order paid", { orderId: 7 });Sentry's Python SDK
sentry-sdk 2.35.0 or later. Before 2.68.0, add enable_logs=True.
import sentry_sdk
sentry_sdk.init(dsn="https://<key>@ingest.statoss.com/<app number>")
sentry_sdk.logger.info("Order {order_id} paid", order_id=7)OpenTelemetry SDKs
Python also needs opentelemetry-exporter-otlp-proto-http.
OTEL_LOGS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://ingest.statoss.com/v1/logs
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_LOGS_HEADERS=Authorization=Bearer%20<key>OpenTelemetry Collector
Before Collector 0.144.0 the exporter is named otlphttp.
exporters:
otlp_http/statoss:
endpoint: https://ingest.statoss.com
headers:
Authorization: "Bearer <key>"
service:
pipelines:
logs:
receivers: [otlp]
exporters: [otlp_http/statoss]Vercel
Pro and Enterprise. Team Settings, Drains, Add Drain: Logs, Custom Endpoint.
Endpoint https://ingest.statoss.com/logs
Format NDJSON
Header Authorization: Bearer <key>Fly.io
Fly's log shipper, as an app of its own.
fly launch --no-deploy --image ghcr.io/superfly/fly-log-shipper:latest
fly secrets set ORG=<org> ACCESS_TOKEN="$(fly tokens create readonly <org>)"
fly secrets set HTTP_URL=https://ingest.statoss.com/logs HTTP_TOKEN=<key>
# Delete [http_service] from fly.toml, then:
fly deploy --no-public-ipsCloudflare Workers
Observability, Destinations, Add destination: type Logs.
Destination
OTLP endpoint https://ingest.statoss.com/v1/logs
Header Authorization: Bearer <key>wrangler.jsonc
"observability": {
"logs": {
"enabled": true,
"destinations": ["statoss"]
}
}Heroku
Cedar apps.
heroku drains:add https://statoss:<key>@ingest.statoss.com/logs/heroku -a <app>JSON over HTTPS
One object, a JSON array, or NDJSON.
curl https://ingest.statoss.com/logs \
-H "Authorization: Bearer <key>" \
-H "Content-Type: application/json" \
-d '{"level": "error", "message": "Card declined", "service": "checkout", "orderId": 7}'JSON fields
A line sent as JSON, to /logs, is read like this. The drains' own fields, such as Vercel's projectName and Fly's app name, become the service.
| Column | From |
|---|---|
| Time | timestamp, time, ts, @timestamp or date: seconds, milliseconds, nanoseconds or ISO; else when it arrived |
| Level | level, severity, log.level, lvl or levelname: a word such as warn or error, a pino or syslog number; else info |
| Body | message, msg, body, log or text; one that is not a string is kept as JSON |
| Service | service.name or service |
| Environment | deployment.environment.name, deployment.environment, environment or env |
| Release | service.version or release |
| Trace | traceId, trace.id or trace_id, and the span's likewise |
| Attributes | everything else, nested fields flattened with dots |
Answers
| Status | Means |
|---|---|
200 | Read. /logs answers {"accepted", "rejected"}; lines past the first 5,000 are rejected. |
400 | Nothing in it could be read. |
401 | No key. |
403 | Not a live key, or logs are off for the app's team. |
413 | Over 8 MiB, before or after decompression. |
415 | A Content-Type the path does not take. |
429 | Over the app's 60,000 lines a minute, or the team's daily cap; with Retry-After. |
503 | Busy; with Retry-After. |
OpenTelemetry senders get OTLP's own answers, with partial_success when some lines were dropped.