This guide walks you through setting up a complete, self-hosted observability stack where Qtap events flow to Grafana via Loki and full HTTP payloads stay in your own S3-compatible object storage. Every byte of captured data — metadata and payloads — remains inside your infrastructure.
Qtap produces two distinct outputs. Understanding the split is key to this architecture:
Events
Objects
What
Lightweight metadata — method, URL, status, duration, process
HTTP transaction objects — metadata at summary level; headers and bodies at full level
Sensitivity
Low — safe to send anywhere
Varies — summary objects contain only metadata; full objects may contain API keys, tokens, PII
Storage
Loki (via OTel Collector)
Garage S3 (your infrastructure)
Volume
Every observed request
Every captured request (content varies by capture level)
The link between them: Qtap's access_url template embeds a clickable URL into each artifact_record event. When you find an interesting event in Grafana, you click the URL to fetch the complete HTTP transaction from your S3 storage.
Configuration Files
Create a project directory and add these files:
OTel Collector
The collector listens for gRPC on port 4317 (where Qtap sends events) and forwards them to Loki's OTLP endpoint. Since the collector runs with network_mode: host, it reaches Loki at localhost:3100.
Loki
Key settings: allow_structured_metadata: true lets Loki store Qtap's structured attributes (method, status, host, etc.) as queryable fields. Retention is set to 7 days (168h).
Garage (S3-Compatible Object Storage)
Garage provides the S3 API on port 3900 (where Qtap writes payloads), a web endpoint on 3902 (for anonymous reads), and an admin API on 3903 (for bucket management).
Nginx Proxy
The nginx proxy translates requests like http://localhost:3904/qpoint/<DIGEST> into Garage web requests with the correct virtual-host Host header. This avoids needing wildcard DNS for Garage's subdomain-based routing.
Grafana Datasource
Qtap
This configuration captures all egress HTTP traffic at summary level (metadata only — no headers or bodies), and automatically escalates to full capture for any 4xx or 5xx response. Full captures store complete request/response headers in Garage S3. Both levels emit artifact_record events with clickable URLs pointing to the stored objects.
Docker Compose
The OTel Collector uses network_mode: host so that Qtap (running on the host) can reach it at localhost:4317. Because it shares the host network stack, it also reaches Loki at localhost:3100.
Running the Stack
Step 1: Start the Services
Verify all containers are running:
Step 2: Initialize Garage
Wait for Garage to be ready, then configure the cluster layout, create a bucket, and set up access credentials:
The credentials above are for local development only. For production, generate unique keys and manage them securely.
Step 3: Start Qtap
Step 4: Wait for Initialization
Qtap needs a few seconds to load eBPF programs and start capturing.
Step 5: Generate Test Traffic
Generate both a successful request and an error to see both data paths:
Navigate to Explore (compass icon in the left sidebar)
Select Loki as the datasource
Enter a LogQL query:
Click Run query
You should see Qtap events — both connection events (TCP connection metadata) and artifact_record events (HTTP transaction summaries, with full headers and bodies for requests matching capture rules).
Click on any log entry to expand it. You'll see structured attributes including:
request_method, request_host, request_scheme
response_status, duration_ms
process_exe, direction
For artifact records: digest, url, type
Object Linking — From Events to Full Payloads
This is the key capability of this stack: linking lightweight events in Grafana to complete HTTP transactions stored in your own S3.
How It Works
Qtap captures a request — all capture levels store an object in Garage S3, keyed by its SHA1 digest. In our config, errors get full capture (with headers), while other traffic gets summary (metadata only)
The HTTP transaction object is stored in Garage S3 as JSON
Qtap emits an artifact_record event to the OTel Collector, which includes a url field pointing to the stored object
The event flows through to Loki and appears in Grafana
You click the URL to view the complete HTTP transaction
Walkthrough
1. Find an error event in Grafana
In Explore, query for artifact records:
2. Expand the log entry
Click on an artifact record event. Look for these fields:
3. Click the URL
The url field is a direct link to the stored object. Click it (or open it in a new tab) to see the complete HTTP transaction:
This is the full HTTP transaction — headers and metadata — stored entirely in your infrastructure.
All capture levels generate artifact_record events and store objects in S3. The difference is content: summary objects contain only metadata (method, URL, status, duration), while headers and full objects include the complete request/response headers and bodies. The object linking walkthrough above is most useful for full captures where you can inspect the actual HTTP payload.
The access_url Template
The link between events and objects is configured in the Qtap object_stores section:
{{DIGEST}} is replaced with the SHA1 hash of the stored object. The resulting URL is embedded in every artifact_record event.
For production, replace localhost with the hostname or IP address that Grafana users can reach: