So the entire internet seems to have settled on LangSmith or maybe Arize as the default choice for LLM observability. Naturally, I decided to build our own. Because after the third time LangSmith's UI froze while trying to filter a 10k trace dataset, I figured how hard could it be?
Spoiler: It's harder. But we ended up with something lean, surprisingly functional, and entirely under our control. We call it "OpenClaw" because it's a grabby little thing for snatching traces out of the ether. It's basically FastAPI + SQLite (yes, SQLite in production, fight me) + a React frontend.
The core of it is a Python decorator that wraps any LLM call. Instead of piping everything to a third-party service, it logs the good, the bad, and the painfully slow locally first. Here's the heart of the tracing client:
```python
class OpenClawTracer:
def __init__(self, storage_path="traces.db"):
self.conn = sqlite3.connect(storage_path, check_same_thread=False)
# ... setup tables
def trace(self, operation_name):
def decorator(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
trace_id = str(uuid.uuid4())
start_time = time.perf_counter()
try:
result = await func(*args, **kwargs)
latency = time.perf_counter() - start_time
self._record_success(trace_id, operation_name, kwargs, result, latency)
return result
except Exception as e:
latency = time.perf_counter() - start_time
self._record_failure(trace_id, operation_name, kwargs, str(e), latency)
raise
return wrapper
return decorator
```
The main win isn't avoiding vendor lock-in. It's the ability to add custom metadata fields on the fly without waiting for a SaaS roadmap. When we needed to track cache hit rates for our specific Redis setup, we added a `cache_layer` tag in an afternoon. Try getting that prioritized with a hosted provider.
The frontend is a mess of React hooks I'm not proud of, but it renders latency percentiles and token counts per model without requiring a PhD in their query DSL. Is it as pretty? No. Does it handle our 50k daily traces without falling over? Yes, because we can index exactly what we need and ignore the bloat.
I'll probably regret this when we need to scale beyond a single node, but for now, it's refreshing to debug our own code instead of waiting for someone else's "status page" to turn green.
prove it to me