Dashboard Guide

The Bloop dashboard is a single-page application served at the root URL of your Bloop instance.

Main View

Bloop dashboard main view showing stats bar, trend chart, filters, and error list

Error Detail

Error detail panel showing stack trace, metadata, and action buttons

Click any error row to see the detail view:

Settings

Settings panel showing projects, alerts, and admin controls

Click the gear icon in the header to open the settings panel (admin only):

Insights Panel

When the analytics feature is enabled (--features analytics), an Insights button appears in the header. The button is hidden when analytics is not compiled in — the dashboard probes /v1/analytics/spikes on load and only shows the button if it gets a 200 response.

The Insights panel has five sub-tabs:

Results are cached for 60 seconds (configurable via cache_ttl_secs). All queries respect the active project filter.

LLM Tracing Panel

When LLM tracing is enabled (--features llm-tracing), an LLM button appears in the header. Like Insights, it auto-detects by probing /v1/llm/overview?hours=1 on load.

Bloop is provider-agnostic — it works with any LLM you use: OpenAI (GPT-4o, o1, etc.), Anthropic (Claude), Google (Gemini), Mistral, Cohere, LLaMA, or any local/self-hosted model. You pass the model name and provider as strings when creating traces, so Bloop tracks whatever your code reports.

The LLM panel has eight sub-tabs:

Overview

LLM Overview tab showing total traces, tokens, cost, and error rate

Summary cards showing total traces, total spans, tokens consumed, cost in dollars, average latency, and error rate for the selected time window.

Usage

LLM Usage tab showing hourly token and cost breakdown by model

Hourly breakdown of token consumption and cost, grouped by model. Each row shows the hour bucket, model name, request count, input/output tokens, and total cost.

Latency

Latency percentiles (p50, p90, p99) by model, plus average time-to-first-token. Useful for identifying slow models or degraded performance.

Models

Per-model comparison table: total calls, tokens consumed, cost, average latency, and error rate. Sorted by cost to quickly identify the most expensive models. Each model card also displays per-token pricing (input/output cost per million tokens) when pricing data is available, with support for custom price overrides.

Traces

LLM Traces tab showing paginated trace list with expandable span details

Paginated list of all LLM traces with status badges, model info, token counts, cost, and latency. Click any trace to expand its full span hierarchy with parent-child relationships.

Search

LLM Search tab with full-text search across trace names and content

Full-text search across trace names, input, and output content. Results show matching traces with highlighted context.

Prompts

LLM Prompts tab showing prompt version tracking with metrics

Prompt version tracking table: prompt name, latest version, total traces, average latency, average tokens, cost, and error rate. Useful for comparing prompt performance across versions.

Scores

LLM Scores tab showing quality scores with color-coded bars

Score cards showing quality metrics attached to traces. Each card displays the score name, count, average value (with color-coded bar: red < 0.3, amber 0.3–0.7, green > 0.7), min, and max values.

Enabling LLM Tracing

bash
# Build with LLM tracing enabled
docker build --build-arg FEATURES=llm-tracing -t bloop .

# Or enable both analytics and LLM tracing
docker build --build-arg FEATURES=analytics,llm-tracing -t bloop .

# Configure in config.toml
[llm_tracing]
enabled = true
default_content_storage = "metadata_only"

Content storage policies control what gets persisted: none (no prompts/completions), metadata_only (tokens and costs only), or full (everything). Set per-project via the Settings panel or API.

Auto-Instrumentation

The TypeScript and Python SDKs support zero-config auto-instrumentation for OpenAI and Anthropic clients. Wrap your LLM client once and every call is automatically traced — model, tokens, latency, time-to-first-token (streaming), and errors are all captured.

TypeScript
import { BloopClient } from "@dthink/bloop-sdk";
import OpenAI from "openai";

const bloop = new BloopClient({ endpoint: "...", projectKey: "..." });
const openai = bloop.wrapOpenAI(new OpenAI());

// Every call is automatically traced
const response = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hello" }],
});
Python
from bloop import BloopClient
from openai import OpenAI

client = BloopClient(endpoint="...", project_key="...")
openai = client.wrap_openai(OpenAI())

# Every call is automatically traced
response = openai.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)

Anthropic is also supported via wrapAnthropic() / wrap_anthropic(). Both wrappers auto-detect the provider from the client’s base URL, so custom endpoints (Azure OpenAI, AWS Bedrock, etc.) are identified correctly.

OpenRouter & Proxy Providers

OpenRouter, LiteLLM proxy, and any OpenAI-compatible endpoint work out of the box with wrapOpenAI(). The provider is auto-detected from the base URL:

TypeScript
import { BloopClient } from "@dthink/bloop-sdk";
import OpenAI from "openai";

const bloop = new BloopClient({ endpoint: "...", projectKey: "..." });

// OpenRouter — detected as provider "openrouter"
const openrouter = bloop.wrapOpenAI(new OpenAI({
  baseURL: "https://openrouter.ai/api/v1",
  apiKey: process.env.OPENROUTER_API_KEY,
}));

// All calls are traced with provider="openrouter"
const response = await openrouter.chat.completions.create({
  model: "anthropic/claude-3.5-sonnet",
  messages: [{ role: "user", content: "Hello" }],
});

This also works with Azure OpenAI, AWS Bedrock, Together, Groq, Fireworks, and any service that exposes an OpenAI-compatible API. The provider name is extracted from the hostname automatically.

pi-ai (Unified LLM API)

The TypeScript SDK supports @mariozechner/pi-ai, a unified LLM library that supports 15+ providers with automatic model discovery. Wrap the complete() and stream() functions:

TypeScript
import { BloopClient } from "@dthink/bloop-sdk";
import { complete, stream, getModel } from "@mariozechner/pi-ai";

const bloop = new BloopClient({ endpoint: "...", projectKey: "..." });

// Wrap complete() for non-streaming calls
const tracedComplete = bloop.wrapPiAiComplete(complete);
const model = getModel("openai", "gpt-4o");
const response = await tracedComplete(model, {
  systemPrompt: "You are helpful.",
  messages: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
});

// Wrap stream() for streaming with TTFT tracking
const tracedStream = bloop.wrapPiAiStream(stream);
const eventStream = tracedStream(model, context);
for await (const event of eventStream) {
  // handle text, thinking, tool_call events
}
const result = await eventStream.result();

The wrapper automatically extracts the provider and model name from pi-ai’s Model object, and reads token usage from the AssistantMessage.usage field. All 15+ pi-ai providers are supported: OpenAI, Anthropic, Google, Mistral, Groq, xAI, Cerebras, OpenRouter, Azure, Bedrock, and more.

Server-Side Pricing

Cost is calculated automatically on the server from a database of 2,500+ model prices (sourced from LiteLLM, refreshed hourly). SDKs send cost: 0 and the server fills in the correct cost based on model and token counts. This means:

If an SDK sends an explicit cost (non-zero), it is preserved and not overridden.

LLM Proxy Mode (Zero-Instrumentation)

Bloop can act as a transparent proxy for OpenAI and other LLM providers. Instead of using SDK instrumentation, simply point your LLM client at bloop's proxy endpoint. All calls are automatically traced with prompts, completions, token usage, cost, and latency captured server-side.

How It Works
Your App
LLM Client
Proxy
bloop
Provider
OpenAI
Store
Trace + Cost
Setup

Change your OpenAI base URL to point at bloop:

bash
# Environment variable (most OpenAI SDKs)
export OPENAI_BASE_URL=http://localhost:5332/v1/proxy/openai

# Or in code (Python)
import openai
client = openai.OpenAI(
    api_key="sk-your-openai-key",
    base_url="http://localhost:5332/v1/proxy/openai"
)

# Or in code (TypeScript)
import OpenAI from "openai";
const client = new OpenAI({
    apiKey: "sk-your-openai-key",
    baseURL: "http://localhost:5332/v1/proxy/openai"
});
Supported Providers
ProviderProxy EndpointStatus
OpenAI/v1/proxy/openai/{path}Full support (streaming + non-streaming)
Anthropic/v1/proxy/anthropic/{path}Coming soon
Azure OpenAIVia OpenAI SDK with custom base_urlSupported
Authentication

The proxy passes through your provider API key from the Authorization header. bloop does not need any additional authentication for proxy requests - your existing OpenAI/Anthropic credentials work transparently.

What Gets Captured
Proxy vs SDK Instrumentation
FeatureProxy ModeSDK Instrumentation
SetupChange base URLInstall SDK + wrap client
Code changesNoneMinimal (one line)
Language supportAny HTTP client7 official SDKs
Custom tracesVia manual API callsFull trace/span API
Best forExisting apps, quick startNew development, complex flows
A/B Testing with Proxy

Combine the proxy with prompt versioning to run A/B tests:

bash
# 1. Create an experiment via API
curl -X POST http://localhost:5332/v1/llm/experiments \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "Summarizer Prompt Test",
    "prompt_name": "summarizer",
    "variants": [
      {"name": "Baseline", "version": "v1", "prompt_template": "Summarize:", "traffic_percentage": 50},
      {"name": "Detailed", "version": "v2", "prompt_template": "Provide a detailed summary:", "traffic_percentage": 50}
    ]
  }'

# 2. Tag your traces with prompt_version
# In your app, include the prompt version in request metadata:
# X-Prompt-Version: v1  # or v2

# 3. Compare results
curl "http://localhost:5332/v1/llm/experiments/{id}/compare?baseline=v1&treatment=v2" \
  -H "Authorization: Bearer $TOKEN"
Limitations

Error Lifecycle

Every error in Bloop has a status that tracks its lifecycle:

StatusMeaningAlerts fire?
unresolvedActive issue that needs attentionYes
resolvedFixed and deployedYes (regression)
ignoredKnown issue, not worth fixingNo
mutedTemporarily silencedNo

Status Transitions

All status changes are recorded in an audit trail visible in the error detail view.

Regression Detection

If a resolved error receives a new occurrence, Bloop can detect the regression. The error appears again in the unresolved list with its full history preserved.

API

bash
# Resolve
curl -X POST http://localhost:5332/v1/errors/FINGERPRINT/resolve \
  -H "Cookie: session=YOUR_SESSION_TOKEN"

# Mute
curl -X POST http://localhost:5332/v1/errors/FINGERPRINT/mute \
  -H "Cookie: session=YOUR_SESSION_TOKEN"

# Unresolve
curl -X POST http://localhost:5332/v1/errors/FINGERPRINT/unresolve \
  -H "Cookie: session=YOUR_SESSION_TOKEN"

# View status history
curl http://localhost:5332/v1/errors/FINGERPRINT/history \
  -H "Cookie: session=YOUR_SESSION_TOKEN"

Troubleshooting

Common Issues

ProblemCauseSolution
401 Unauthorized on ingest HMAC signature doesn't match Ensure you're signing the exact request body with the correct API key. The signature must be a hex-encoded HMAC-SHA256 of the raw JSON body.
401 invalid project key API key not recognized Check the X-Project-Key header matches a project's API key in Settings → Projects. Keys are case-sensitive.
Events accepted but not appearing Buffer full or processing delay Check /health endpoint — if buffer_usage is near 1.0, the pipeline is backed up. Events are batched every 2 seconds.
Passkey registration fails rp_id / rp_origin mismatch The rp_id must match your domain (e.g., errors.myapp.com) and rp_origin must match the full URL including protocol.
Source maps not deobfuscating Release version mismatch The release in the source map upload must exactly match the release field sent with the error event.
Slack notifications not arriving Webhook URL expired or channel archived Test the alert via Settings → Alerts → Test. Check that the Slack app is still installed and the channel exists.
Dashboard shows stale data Browser caching Hard refresh (Cmd+Shift+R / Ctrl+Shift+R). Stats auto-refresh every 30 seconds.
High memory usage Large moka cache Source maps and aggregates are cached in memory. Restart the server to clear caches, or reduce the number of uploaded source maps.
500 Unable To Extract Key! Rate limiter can't determine client IP This happens when running behind a reverse proxy (Traefik, Nginx) that doesn't forward client IP headers. Upgrade to the latest Bloop release, which uses SmartIpKeyExtractor to read X-Forwarded-For and X-Real-IP headers automatically.
403 Forbidden with bearer token Token lacks required scope Check the token's scopes. Read operations require *:read scopes, write operations require *:write scopes. Create a new token with the needed scopes.

Checking Server Health

bash
curl http://localhost:5332/health | jq .

# Response:
# {
#   "status": "ok",
#   "db_ok": true,
#   "buffer_usage": 0.002
# }

buffer_usage shows the MPSC channel fill ratio (0.0 = empty, 1.0 = full). If consistently above 0.5, consider increasing ingest.channel_capacity or pipeline.flush_batch_size.

Debug Logging

bash
# Enable detailed logging
RUST_LOG=bloop=debug cargo run

# Or in Docker
docker run -e RUST_LOG=bloop=debug ...

Debug logging shows every ingested event, fingerprint computation, batch write, and alert evaluation.