OpenObserve (O2) is an open-source observability platform for logs, metrics, and traces, written in Rust. Its defining design choice is storing data in object storage (S3, GCS, MinIO) in compressed Parquet rather than on expensive local disks — the basis of its claim of dramatically lower storage cost than Elasticsearch-based stacks. It ships as a single binary with a built-in UI, speaks OpenTelemetry natively, and queries with SQL.
Installation
| Method | Command |
|---|
| Binary | download from GitHub Releases, then ./openobserve |
| Docker | docker run -d -p 5080:5080 -e ZO_ROOT_USER_EMAIL=a@b.c -e ZO_ROOT_USER_PASSWORD=pass public.ecr.aws/zinclabs/openobserve:latest |
| Kubernetes | Helm chart available |
| UI | http://localhost:5080 |
| Verify | log in with the root credentials you set |
Storage Modes
| Mode | Use |
|---|
| Local disk | Single-node, dev/small deployments |
| S3 / GCS / Azure Blob | Production, cheap long-term retention |
| MinIO | Self-hosted object storage |
# Point at S3 for production storage
export ZO_LOCAL_MODE=false
export ZO_S3_BUCKET_NAME=my-o2-bucket
export ZO_S3_REGION_NAME=us-east-1
Ingesting Data
| Source | How |
|---|
| OpenTelemetry | Send OTLP to /api/{org}/v1/traces, /v1/logs, /v1/metrics |
| HTTP JSON | POST /api/{org}/{stream}/_json |
| Fluent Bit / Vector | Output plugin to the O2 HTTP endpoint |
| Prometheus remote write | /api/{org}/prometheus/api/v1/write |
| Syslog | TCP/UDP listener |
# Bulk-ingest JSON logs
curl -u "$USER:$PASS" -X POST \
"http://localhost:5080/api/default/app_logs/_json" \
-H "Content-Type: application/json" \
-d '[{"level":"error","message":"db timeout","service":"api"}]'
Querying (SQL)
-- Find recent errors for one service
SELECT * FROM app_logs
WHERE level = 'error' AND service = 'api'
ORDER BY _timestamp DESC
LIMIT 100;
-- Count errors per service over time
SELECT service, count(*) AS errors
FROM app_logs
WHERE level = 'error'
GROUP BY service
ORDER BY errors DESC;
| Feature | Note |
|---|
| SQL queries | Familiar syntax over log streams |
| Full-text search | match_all('timeout') |
| Time range | Selected in the UI or via _timestamp filters |
| Saved searches | Reusable queries |
Core Concepts
| Term | Meaning |
|---|
| Organization | Top-level tenant |
| Stream | A named data stream (like an index) |
| Schema | Inferred from ingested data |
| Function | VRL transform applied at ingest or query |
| Alert | Scheduled query + destination |
Alerts & Dashboards
| Capability | How |
|---|
| Scheduled alert | Query + threshold + evaluation interval |
| Real-time alert | Trigger on matching ingested records |
| Destinations | Webhook, Slack, email, PagerDuty |
| Dashboards | Build panels from SQL queries |
| Templates | Reusable alert message formats |
Ingest-Time Functions (VRL)
# Redact a field and derive a new one at ingest
.email = "REDACTED"
.status_class = floor(.status / 100)
Functions let you scrub PII or enrich records before storage — cheaper than doing it at query time.
OpenObserve vs Alternatives
| Aspect | OpenObserve | Elasticsearch/ELK | Grafana Loki |
|---|
| Storage | Object storage (Parquet) | Local disk indices | Object storage (chunks) |
| Cost profile | Very low | High | Low |
| Query | SQL | Query DSL / KQL | LogQL |
| Scope | Logs + metrics + traces | Logs (+ APM) | Logs (Grafana stack for rest) |
| Setup | Single binary | Multi-component | Multi-component |
Pairs with OpenTelemetry for instrumentation; compare with Grafana Loki and Elasticsearch.
Resources