Copy page
OpenTelemetry ingestion
Send error logs to Bicho over OpenTelemetry OTLP/HTTP using JSON or protobuf. Configure exception fields, batching, request limits, and retries for your app.
Bicho accepts exception and error logs at POST /v1/logs over OTLP/HTTP JSON or binary protobuf. It is not a general log archive. It does not accept gRPC, traces, or metrics, including /v1/traces and /v1/metrics. An OpenTelemetry Collector is optional and is not bundled with Bicho.
Configure a logs exporter
First register a project and save its generated otlp.endpoint and otlp.headers. For exporters that honor the standard environment variables:
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://your-bicho-worker.example/v1/logs
export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_LOGS_HEADERS=X-Bicho-Key=YOUR_PUBLIC_KEY
The logs-specific endpoint is the complete URL and is used as supplied. A generic OTEL_EXPORTER_OTLP_ENDPOINT is a base URL: HTTP exporters normally append /v1/logs. Do not configure the full logs path as a generic base endpoint or assume a gRPC exporter will work. http/json is also supported where your exporter implements it.
The headers variable uses comma-separated name=value pairs, not JSON or colon-separated HTTP header lines. A secret-enabled backend project requires both generated headers:
export OTEL_EXPORTER_OTLP_LOGS_HEADERS="X-Bicho-Key=YOUR_PUBLIC_KEY,X-Bicho-Secret=YOUR_INGEST_SECRET"
X-Bicho-Key selects the project. Resource attributes and service names never select or authenticate it. Never put the ingest secret in a browser. Neither ingest credential grants API reads, issue updates, source-map uploads, or MCP access. See privacy and credentials.
Environment variables configure transport, not capture. Install and initialize your language’s OpenTelemetry logs SDK and exporter, or a logging bridge that emits exception records. Some instrumentation records exceptions only as trace events; those do not reach this logs endpoint. Flush the exporter during orderly shutdown according to its SDK’s instructions.
The JavaScript SDK provides browser capture and explicit Node.js reporting. The PHP SDK and Python SDK provide explicit synchronous reporting, and the Rust SDK provides explicit awaited reporting on Tokio. The Astro integration uses the JavaScript SDK for opt-in browser capture and SSR request-exception reporting. All send OTLP/HTTP JSON. Direct SDK backend capture is manual; Python exception capture includes traceback locations. Other languages can send compatible OTLP logs through their own instrumentation and exporters. The core includes interoperability tests for the official OpenTelemetry JavaScript JSON and protobuf log exporters; that does not establish compatibility with every language or exporter version. See the OpenTelemetry exporter configuration reference and your language’s logs documentation.
Send a JSON record
Use a dedicated test project and replace the endpoint and key with its generated values. For a secret-enabled project, also supply X-Bicho-Secret from a secure backend environment.
curl --fail-with-body -sS https://your-bicho-worker.example/v1/logs \
-H 'Content-Type: application/json' \
-H 'X-Bicho-Key: YOUR_PUBLIC_KEY' \
--data-binary '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "checkout"}},
{"key": "service.version", "value": {"stringValue": "checkout@1.0.0"}},
{"key": "deployment.environment.name", "value": {"stringValue": "test"}}
]
},
"scopeLogs": [{
"scope": {"name": "checkout-errors", "version": "1.0.0"},
"logRecords": [{
"severityNumber": 17,
"severityText": "ERROR",
"body": {"stringValue": "Checkout failed"},
"attributes": [
{"key": "exception.type", "value": {"stringValue": "TestError"}},
{"key": "exception.message", "value": {"stringValue": "Checkout failed"}},
{"key": "exception.stacktrace", "value": {"stringValue": "TestError: Checkout failed\n at checkout (app.js:20:3)"}},
{"key": "log.record.uid", "value": {"stringValue": "bicho-docs-checkout-occurrence-1"}}
]
}]
}]
}]
}'
The fixed UID lets you resend this exact occurrence without incrementing its count. Use a new UID for a genuinely new occurrence. Verify the stored event through authenticated event reads or MCP; HTTP 200 alone can include rejected records.
Accepted records and severity
A record is an exception when at least one of exception.type, exception.message, or exception.stacktrace is a nonempty string attribute. Such records are accepted regardless of severity. Otherwise, only records with numeric severityNumber of 17 through 24, the ERROR and FATAL ranges, are accepted. Plain ERROR/FATAL messages do not need a stack trace. Ordinary lower-severity logs are rejected through partial success.
severityText is preserved as evidence but does not control acceptance or the indexed level:
| Numeric severity | Indexed level |
|---|---|
| Missing or 0 | error for an accepted exception |
| 1 through 8 | debug |
| 9 through 12 | info |
| 13 through 16 | warning |
| 17 through 20 | error |
| 21 through 24 | fatal |
The indexed message uses the string exception.message when present, otherwise the log body. Structured bodies become JSON text; absent bodies use Unknown error. The exception type defaults to Error. Indexed messages stop at 16,384 characters, while the sanitized OTLP evidence is stored separately within the record size limit.
Metadata and captured evidence
Bicho preserves the sanitized resource, instrumentation scope, schema URLs, and log record under the stored event’s otlp field. Resource service.version becomes the release. deployment.environment.name becomes the environment, with deployment.environment as a fallback. service.name remains metadata rather than a routing key.
Up to 32 scalar resource/log attributes become indexed tags. Keys must fit 128 characters and values 512 characters; log attributes override resource attributes of the same name. Other supported structured values remain in the sanitized evidence rather than becoming searchable tags.
OTLP JSON uses lowerCamelCase field names and typed values such as stringValue. Supply nanosecond timestamps as decimal strings to avoid JavaScript number precision loss. Bicho uses nonzero timeUnixNano, then nonzero observedTimeUnixNano, then receipt time for the searchable timestamp. The index has millisecond precision; the stored log retains its nanosecond timestamp.
JSON traceId and spanId use hexadecimal strings of 32 and 16 characters respectively. Invalid or all-zero correlation IDs are omitted. Valid IDs remain correlation evidence, not deduplication keys. Protobuf uses the standard binary fields.
Bicho retains sanitized raw exception.stacktrace text even when it cannot parse frames. It recognizes common JavaScript, Python, Ruby, PHP, and Rust stack lines, deriving at most 100 frames from the first 200 input lines. Python traceback ordering is reversed for grouping. Recognizing a stack format does not mean Bicho installs that language’s instrumentation or supports native crash symbolication.
Source-map enrichment requires matching debug IDs in captured evidence and uploaded maps. Ordinary OTLP stack attributes and a release label do not supply them. The JavaScript SDK does not attach debug IDs or upload maps. See source maps before building an enrichment workflow.
Deduplication and grouping
Give each occurrence a nonempty string log.record.uid of at most 512 characters and preserve it across retries. Bicho hashes the original UID with the project identity to derive a 32-character event ID. A retry of an already stored occurrence neither adds an event nor consumes quota, even when the quota is full. The UID returned by a client is not the server’s event lookup ID.
Without a UID, Bicho generates an ID. Resending that record can add another occurrence. Reusing one UID for unrelated errors suppresses later records rather than updating the first event.
Grouping is separate: exception type, message, and the most recent recognized stack location determine the issue fingerprint. Different occurrence UIDs can add events to the same issue. Bicho does not normalize changing IDs or URLs in messages and does not infer a root cause.
Encoding and limits
| Input | Requirement or limit |
|---|---|
| JSON | Content-Type: application/json, OTLP protobuf JSON mapping |
| Binary protobuf | Content-Type: application/x-protobuf, OTLP logs export request schema |
| Compression | No encoding, identity, gzip, or deflate in Content-Encoding |
| Request bytes | 2 MiB on the wire and 2 MiB after decompression |
| Batch | At most 100 log records across all resources and scopes |
| Structure | At most 32 nesting levels and 20,000 structural values |
| Normalized event | At most 1 MiB before persistence |
Compression does not increase the decoded allowance. Unsupported content types or encodings return 415; oversized input returns 413. JSON severity values must be integers from 0 through 24, not enum names. Unknown JSON fields are ignored, so use supported OTLP fields for evidence you need to retain.
Validation, partial success, and retries
Bicho decodes and normalizes the batch before storing any events. Malformed known fields, invalid accepted-record UIDs, or record limits reject the request before persistence. This differs from a valid record outside the error tracker’s scope, which contributes to partial rejection.
Full success returns HTTP 200 with an empty OTLP export response ({} for JSON). A valid batch with rejected records returns HTTP 200 with a response such as:
{
"partialSuccess": {
"rejectedLogRecords": "1",
"errorMessage": "Ordinary logs below ERROR are outside the error-tracker scope"
}
}
Do not retry a populated partial-success response. It can mean some or all records were outside scope, or that quota/storage failure stopped persistence after an accepted prefix. Replaying the accepted batch is not the recovery protocol.
If quota or storage failure occurs before any record is accepted, the whole request can return 429 or 503. Retry transient whole-request failures with backoff, keeping occurrence UIDs unchanged, and honor Retry-After when supplied. Quota responses specify the wait until the next UTC hour. Fix validation and authentication failures instead of retrying them unchanged.
Whole-request errors use OTLP’s google.rpc.Status shape, not the REST API’s { "error": "..." } shape. Responses use the request encoding: JSON for JSON requests and binary protobuf for protobuf requests. See the OTLP specification, exception log conventions, and operations troubleshooting.