Introducing jjhub OBSERVE's SQL-style log search: query massive logs with WHERE, GROUP BY, and aggregate functions, with full-text search, field filtering, and time-window aggregation, plus hot/cold tiering to cut storage cost.
Traditional log search falls into two camps: grep-style keyword matching, or point-and-click UIs with hardcoded filters. The first is fast but weak — grep has no notion of fields, so a question like "errors from order-service between 10:00 and 11:00" forces you to chain brittle regular expressions together. The second is flexible but slow — every new question demands a new dashboard or a round-trip through the UI. Neither handles questions like "which ten endpoints had the highest error rate in the last five minutes" gracefully.
jjhub OBSERVE turns your logs into a queryable table. At ingest time it automatically extracts fields such as timestamp, level, service, trace_id, host, and message, then builds indexes on them. After that you can filter, sort, and aggregate logs the same way you would write SQL against a database. Ops engineers don't need to learn yet another query language — if you can write SQL, you can start immediately. Your existing query skills transfer directly, and complex investigations become declarative statements instead of a dozen clicking steps.
Basic WHERE filtering supports standard comparison and logical operators:
SELECT * FROM logs
WHERE level = 'ERROR'
AND service = 'order-service'
AND timestamp BETWEEN '2026-10-19 10:00:00' AND '2026-10-19 11:00:00'
ORDER BY timestamp DESC
LIMIT 100
The message field uses a full-text index by default and supports phrases and wildcards. Use CONTAINS for phrase matching:
SELECT * FROM logs
WHERE message CONTAINS 'connection refused'
Aggregation uses GROUP BY with COUNT, AVG, P99, and other functions. The query below computes per-service latency percentiles for the last hour — the kind of question that would otherwise need a dedicated dashboard:
SELECT service, COUNT(*) AS total, AVG(latency) AS avg_ms, P99(latency) AS p99_ms
FROM logs
WHERE timestamp > NOW() - INTERVAL 1 HOUR
GROUP BY service
ORDER BY p99_ms DESC
A few conventions worth remembering: field names come from your collector configuration and can be customized; timestamps accept relative expressions like NOW() - INTERVAL 1 HOUR; and any query can be saved as a named view or attached to an alert rule, so a good investigation doesn't have to be rebuilt next time.
Tracing a single request: use trace_id to pull every log line for one call chain, ordered by time. This is the fastest way to reconstruct exactly what happened during one user's request.
SELECT timestamp, level, service, message
FROM logs
WHERE trace_id = 'a1b2c3d4'
ORDER BY timestamp
Error-rate patrol: aggregate the error share per service to spot which service is degrading before users notice.
SELECT service,
SUM(CASE WHEN level = 'ERROR' THEN 1 ELSE 0 END) * 100.0 / COUNT(*) AS error_rate
FROM logs
WHERE timestamp > NOW() - INTERVAL 15 MINUTE
GROUP BY service
HAVING error_rate > 5
Keyword backtrace: after a business exception appears, pull the 30 seconds of surrounding context to reconstruct the full picture — what ran before the failure and what it cascaded into.
Start broad and narrow down. Run a wide time window with a loose filter first, check the result count, then tighten the predicate — this avoids writing a query that silently returns nothing because one condition was too strict. Field names are case-sensitive and come from your collector mapping, so check the field reference in the console if a query returns zero rows. String literals use single quotes and follow standard SQL escaping. Finally, save any query you run more than twice as a named view, and attach the important ones to alert rules so the same logic guards your services around the clock.
Indexing every field at full volume is expensive, and most log searches never touch most fields. OBSERVE builds an inverted index only on message, columnar indexes on high-frequency fields, and shards data by time so scans stay bounded. Queries older than seven days are routed to cold storage automatically — latency rises slightly, but per-GB cost drops by about 70%. A sensible production default: keep 30 days of logs, with the last 7 days in hot storage and the rest archived to cold automatically. When you need to dig into older data, the same SQL syntax still works; the only difference is a slightly longer response time.