Ingestion
Sentry-compatible ingest
Bilis speaks the Sentry SDKs' ingest protocol, so an SDK you already have can ship exceptions here as searchable error logs.
Sentry's SDKs are good at one thing Bilis does not do itself: catching an exception in your application and packaging it with the context around it. Bilis implements the ingest protocol those SDKs speak, so you can point one at Bilis and have its exceptions land in your log stream.
This is protocol compatibility for log ingestion, and nothing more. Bilis is a
log store: exceptions arrive as ERROR records in the same table, searchable
over the same time range as everything else you ship. There is no issue list,
no grouping, no assignment and no resolve button, and none is planned. If you
want error tracking, you want Sentry — this is for putting the exceptions your
app already reports next to the logs that explain them.
Sentry is a trademark of Functional Software, Inc. Bilis is not affiliated with, endorsed by, or a replacement for it; the SDKs remain theirs, and this page only describes an endpoint that accepts what they send.
Pointing an SDK at Bilis
An SDK is configured with a DSN, which carries the public half of an API key. Copy it from Projects → your project, under the key you want to use:
https://bilis_pk_YOUR_PUBLIC_KEY@bilis.example.com/1
Set it wherever the SDK reads its DSN:
SENTRY_DSN="https://bilis_pk_YOUR_PUBLIC_KEY@bilis.example.com/1"
That is the whole setup. The SDK builds its own endpoint from the DSN and posts
envelopes to POST /api/{id}/envelope/, which is why the path is Sentry's shape
rather than /api/v1.
The project id at the end of the DSN is ignored. The project is always the one the public key belongs to, exactly as it is for the other ingest endpoints.
What is stored
One envelope event becomes one log record:
| Log field | Comes from |
|---|---|
| Body | Type: message of the thrown exception, or the event's message |
| Severity | The SDK's level (warning becomes WARN) |
| Service | The service tag, falling back to server_name |
| Trace / span id | contexts.trace, which is already OpenTelemetry's hex |
| Timestamp | The event timestamp |
Everything else is flattened into attributes, under the OpenTelemetry names the rest of your logs already use rather than the ones on the wire:
| Attribute | Was |
|---|---|
exception.type, exception.message |
the thrown exception |
exception.stacktrace |
the frames, most recent first |
exception.origin |
the innermost frame in your own code |
exception.handled, exception.mechanism |
how it was caught |
service.version, service.dist |
release, dist |
deployment.environment |
environment |
transaction.name, logger.name, event.id |
transaction, logger, event id |
host.name |
server name |
telemetry.sdk.name, .version, .language |
which client reported it |
process.runtime.name, .version |
the runtime context |
tag.*, extra.*, user.*, http.* |
tags, extras, user, request |
breadcrumbs |
breadcrumbs, newest first |
Search them the way you search any attribute — exception.type is a good place
to start when you want one class of failure.
What is not stored
An SDK sends more than events. Performance transactions, sessions, attachments, minidumps and client reports all belong to features Bilis does not have, so they are counted and dropped. The SDK is never told they failed — it would only retry them.
transaction items are among what is dropped: this endpoint stores errors, and
Bilis takes traces over OTLP instead. Leave
traces_sample_rate at zero, or a transaction is sent only to be discarded.
Errors still join the traces you send over OTLP. When the SDK puts a
contexts.trace on an event — which it does whenever it is running inside a
propagated trace — its trace_id and span_id are stored in the log record's
TraceId / SpanId columns, so the error links to that trace's waterfall and
the span links back to the error. Both must be OpenTelemetry's hex spelling
(32 and 16 characters); an id in any other shape is left empty rather than
stored as something no trace will ever match.
From the browser
A page can only post to Bilis from an origin you have listed. Add it under Projects → your project → Browser origins, one per line:
https://shop.example.com
https://*.staging.example.com
Scheme and host, plus a port if it is not the default. A leading *. stands
for exactly one subdomain label, so https://*.example.com covers
https://app.example.com and not https://a.b.example.com. A lone * allows
any origin, which is worth doing only while you are testing.
An empty list means no browser may post at all, which is the right setting for a project that only ships from servers. Nothing is rejected outright — the request may still reach Bilis — but without a header naming its origin the browser discards the response, and the SDK is told its request went nowhere.
This is per project, not per key, because it describes where your application is served from. It is also why the public key being readable costs you little: the key alone is not enough to post from a page you do not control.
Compatibility
- Server-side SDKs work as they are.
- Browser SDKs work once their origin is listed above.
- The older
POST /api/{id}/store/endpoint is accepted too. - Gzipped bodies, which most SDKs send by default, are inflated.
Everything else follows the usual ingest contract: a payload Bilis cannot read is counted and dropped, never answered with a client error. See Limits and behavior.