← Back to blog
Product 4 min read 炬鲸团队

Query Logs with SQL Syntax: From Paging to Second-Level Root Cause Location

Logs grow, search slows down. This article introduces Jujing OBSERVE's SQL-like log search: field filtering, aggregation, and context correlation that turn slow manual paging into second-level root cause location with a single query.

Query Logs with SQL Syntax: From Paging to Second-Level Root Cause Location

Production logs grow faster than the business itself. A mid-size microservice cluster can emit tens of thousands of log lines per second at peak, which adds up to billions a day. Traditional log search — really just full-text keyword matching plus manual paging — collapses at this scale. Search "error" and you get hundreds of thousands of hits; the next page takes ten seconds to load; and by the time you finally land on the line that matters, the incident has already spread to more services and more customers.

The problem isn't the search engine's raw speed. It's that plain text gives the engine nothing to work with. A keyword match tells you a line contains "error" — it can't tell you which service errored, how many times, or how the errors cluster over time. Jujing OBSERVE addresses this by turning log search into a SQL-like query language built on structured fields. The workflow is simple: filter first, aggregate second, drill into details last.

From keywords to structured queries

When logs are ingested, we automatically parse common formats — JSON, Nginx access logs, Java stack traces, Kubernetes container logs — and extract timestamp, level, service name, traceId, and status code into first-class fields. For non-standard formats you can define your own parsing rules with a grok-style pattern, and the extracted fields become queryable just like the built-ins. Structured fields are indexed, so a query filters them directly instead of scanning raw text.

A typical query looks like this:

service = "order-svc" AND level = "ERROR"
  AND status_code >= 500
  AND ts >= now() - 15m
ORDER BY ts DESC
LIMIT 200

It reads like SQL, so both ops engineers and developers can pick it up in minutes. Supported operators include =, !=, >, <, >=, <=, IN, LIKE, regex ~, and AND/OR/NOT, plus aggregations such as COUNT, GROUP BY, and histogram. Field extraction is what makes all of this possible — without it, you're back to substring matching.

Aggregate before you page

The most common mistake is paging before aggregating. Instead of scrolling through raw matches, let a query summarize the problem for you. A single GROUP BY answers "which service is erroring most in the last 15 minutes":

SELECT service, COUNT(*) AS err_cnt
FROM logs
WHERE level = "ERROR" AND ts >= now() - 15m
GROUP BY service
ORDER BY err_cnt DESC

Add histogram(ts, interval=1m) and you get the error count per minute, which tells you whether you're looking at a spike (a bad deploy, a traffic burst) or steady degradation (a leak, a slow dependency). This "aggregate to locate, then drill down" sequence shrinks the search space from hundreds of thousands of lines to a few dozen — the scale at which a human can actually read.

Context correlation: from one line to the whole trace

A single log line is only one point in a request's life. Jujing links the traceId and spanId found in logs to distributed tracing: click a traceId on a log detail page and you see the full call chain for that request — every hop, its latency, and the upstream/downstream logs around it. Conversely, click a span in the trace view and you drill straight into the logs that span emitted. Troubleshooting stops being a game of tab-switching between a logging system and a tracing system.

An incident walkthrough

Here is how the pieces fit together in practice. A payment service starts failing at 14:03. Instead of eyeballing logs, you run one aggregate query and see the error rate for pay-svc jumped 40x, concentrated on status_code = 504 and a single downstream host. A histogram shows the spike began exactly at a config deploy. You click the offending traceId and see the gateway timed out waiting on a cache lookup that suddenly added 900ms. Two queries and one click later you know the deploy changed the cache endpoint; rolling it back fixes the incident. The whole thing took under two minutes.

Practical recommendations

  1. Agree on common fields before launch. Keep service, traceId, and businessId as structured fields rather than burying them in the message body — everything downstream depends on them.
  2. Save your high-frequency troubleshooting queries as saved searches and reuse them as alert rules or dashboard panels, so the whole team benefits from one person's debugging.
  3. Mask sensitive fields — phone numbers, ID numbers, card numbers — at the collection layer so they never enter the index and can't appear in results.
  4. Use hot/cold tiering. Hot data lives in in-memory indexes for sub-second returns; cold data ages out to object storage so cost stays flat as volume grows.

The value of log search is not "can you find it" but "how fast and how precisely." Turning paging into queries improves troubleshooting efficiency by an order of magnitude — and that is the difference between a ten-minute incident and a two-hour outage.