OpenTelemetry
Zharp takes OpenTelemetry traces and logs over OTLP/HTTP. Any language with an OpenTelemetry SDK, and any Collector, can send to it without a Zharp SDK.
The Zharp SDKs are themselves OpenTelemetry underneath, so this page is also the reference for what they send. To set up an app, add it in App Telemetry with the OpenTelemetry platform: the setup page fills in the key and service name for you.
Endpoint and headers
| Value | |
|---|---|
| Base endpoint | https://api.zharp.io/api/v1/otlp |
| Traces | POST /v1/traces under it |
| Logs | POST /v1/logs under it |
| Protocol | OTLP/HTTP, as protobuf (application/x-protobuf) or JSON (application/json). gRPC isn't accepted directly — send gRPC to the agent or a Collector. |
| Compression | gzip or deflate, as Content-Encoding |
| Size | Up to 10 MB per request, after decompression |
| Key | Authorization: Bearer <key> — or an x-zharp-key or x-api-key header |
Metrics aren't taken here. The key is an app key (zpk_…) or a workspace API key that can ingest; see Keys for the difference.
Any OpenTelemetry SDK
Every OpenTelemetry SDK reads these environment variables. Traces and logs both go over OTLP/HTTP:
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.zharp.io/api/v1/otlp OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer zpk_… OTEL_SERVICE_NAME=checkout-api OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production
The exporter adds /v1/traces and /v1/logs to the endpoint itself. Some SDKs default to gRPC; set the protocol:
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf # or http/json — not grpc
Then turn on your language's instrumentations for your framework, HTTP client and database. To link the app to a Zharp front end, keep the default propagators — tracecontext and baggage — and copy enduser.id and session.id from baggage onto your spans and logs (OpenTelemetry's baggage span processor does this where your language has one).
OpenTelemetry Collector
Add an exporter, then add it to your traces and logs pipelines:
exporters:
otlphttp/zharp:
endpoint: https://api.zharp.io/api/v1/otlp
headers:
Authorization: Bearer zpk_…
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp/zharp]
logs:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp/zharp]A Collector usually sends for several services. Use a workspace API key with Ingest only ticked, and each service's service.name picks its app — new ones appear as discovered apps. With an app key, everything goes to that one app.
Attributes Zharp reads
Attributes are read from the record first, then its resource, and follow the OpenTelemetry semantic conventions. Everything else you send is kept and shown with the span or log.
| Attribute | Used for |
|---|---|
service.name | Which app it belongs to, when the key is a workspace key. OpenTelemetry's unknown_service default counts as none. |
service.version | The release. |
deployment.environment.name (or deployment.environment) | The environment. |
enduser.id (or user.id) | The user: the Users tab and who an error happened to. |
session.id | The session the record belongs to. |
browser.name, browser.version | The browser. |
os.name (or os.type), os.version | The operating system. |
device.id, device.model.name (or device.model.identifier) | The device. |
telemetry.sdk.language | The platform a discovered app is listed under. |
http.request.method, http.route, http.response.status_code | Requests, routes and failures on the Overview and Traces tabs. The older http.method, http.target and http.status_code work too. |
db.system.name | Database spans. |
Requests are the spans of kind SERVER and the spans with no parent (a page load, a screen, a job); one with status ERROR is a failed request. Log levels come from severityNumber (9 info, 13 warn, 17 error, 21 fatal), or from severityText when the number is missing.
How errors are detected
An error event — the thing grouped into issues — is any of:
- A span event named
exceptionwithexception.type,exception.messageandexception.stacktrace— whatspan.recordException()writes in every OpenTelemetry SDK. - A log record with any of those
exception.*attributes.
{
"name": "exception",
"attributes": [
{ "key": "exception.type", "value": { "stringValue": "PaymentError" } },
{ "key": "exception.message", "value": { "stringValue": "Card declined" } },
{ "key": "exception.stacktrace", "value": { "stringValue": "PaymentError: Card declined\n at charge (src/pay.js:42:11)\n…" } },
{ "key": "zharp.handled", "value": { "stringValue": "true" } }
]
}zharp.handled"true"marks one your code caught; anything else is shown as unhandled. OpenTelemetry'sexception.escapedfalsemeans handled too.- Send the stack trace as the language prints it, with
Type: messageon the first line. Zharp recognises the frame formats of JavaScript, Python, Go, Java and Kotlin, PHP, Dart and Apple crash reports, and groups by your own frames. - Record the exception on the span it happened in, so the issue links to its trace.
Responses
| Status | Meaning | What an exporter does |
|---|---|---|
| 200 | Stored | — |
| 400 | Not valid OTLP, or too large | Drops the batch |
| 401 | Missing, invalid or revoked key | Drops the batch |
| 403 | The key doesn't allow this origin or IP address | Drops the batch |
| 503 | Zharp couldn't store it just now | Retries with backoff |
Try it with curl
curl https://api.zharp.io/api/v1/otlp/v1/logs \
-H "Authorization: Bearer zpk_…" \
-H "Content-Type: application/json" \
-d '{"resourceLogs":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"checkout-api"}}]},
"scopeLogs":[{"logRecords":[{"severityNumber":9,"body":{"stringValue":"Hello from curl"}}]}]}]}'This is what Send a test event on the setup page does. If it arrives and your app's data doesn't, the key is fine and the problem is in the exporter's configuration.