GQL Query Language Reference
Overview
GQL (GoodLogs Query Language) is a concise, pipeline-based query language for searching logs, aggregating metrics, and defining alert conditions. It compiles directly to optimized SQL.
A GQL query has three parts: filters, a pipeline, and modifiers.
severity:error message:~timeout | count by endpoint | top 10 | last:24h
Filters narrow down results. The pipeline transforms them (aggregate, group, sort). Modifiers control time range, limit, and ordering.
Filters
Filters use field:value syntax. Multiple filters are AND'd together.
Exact Match
severity:error
service:billing
status_code:200
Not Equal
severity:!=debug
status_code:!=200
Numeric Comparisons
duration_ms:>500
status_code:>=400
response_time:<100
memory_mb:<=512
Pattern Matching (LIKE)
Use ~ for case-insensitive pattern matching. * is a wildcard.
message:~timeout
message:~"connection refused"
endpoint:~*/api/users*
Negated Pattern
message:!~healthcheck
Regex
message:=~"payment.*failed"
path:=~"^/api/v[23]/"
IN (multiple values)
severity:(error,fatal,warning)
region:(us,eu,ap)
NOT IN
severity:!(debug,info)
Exists / Not Null
has:user_id
field:*
Null Check
error_message:null
All filter operators:
| Operator | Syntax | Example | Description |
|---|---|---|---|
| Equals | field:value | severity:error | Exact match |
| Not equals | field:!=value | severity:!=debug | Not equal |
| Greater than | field:>N | duration_ms:>500 | Numeric > |
| Greater or equal | field:>=N | status_code:>=400 | Numeric >= |
| Less than | field:<N | response_time:<100 | Numeric < |
| Less or equal | field:<=N | memory_mb:<=512 | Numeric <= |
| LIKE (pattern) | field:~pattern | message:~timeout | Case-insensitive pattern (* = wildcard) |
| NOT LIKE | field:!~pattern | message:!~healthcheck | Negated pattern |
| Regex | field:=~"regex" | message:=~"pay.*fail" | Regex match |
| IN | field:(a,b,c) | severity:(error,fatal) | Match any in list |
| NOT IN | field:!(a,b) | severity:!(debug,info) | Not in list |
| Exists | has:field | has:user_id | Field is NOT NULL |
| Wildcard | field:* | error_code:* | Field exists |
| Null | field:null | error:null | Field IS NULL |
| BETWEEN | field:lo..hi | duration_ms:100..500 | Range (BETWEEN lo AND hi) |
OR Logic
By default all filters are AND'd. Use OR for alternative conditions:
severity:error OR status_code:>=500
severity:error service:billing OR severity:fatal OR message:~panic
severity:error OR message:~timeout | count | last:24h
Each OR group is AND'd internally: severity:error service:billing = severity=error AND service=billing.
Nested Field Access
Query nested JSONB properties using dot notation:
user.address.city:London
request.headers.content_type:application/json
payment.card.brand:visa | count | last:24h
Compiles to: properties->'user'->'address'->>'city' = 'London'
Source Selection
Use from: to specify the data source. If omitted, GQL auto-detects based on filters (e.g., severity: → logs, event_name: → events).
from:logs severity:error
from:events event_name:signup
from:logs+events last:1h
from:logs+alerts last:24h
from:* last:1h
| Source | Description |
|---|---|
| from:logs | Application logs (severity, message, properties) |
| from:events | Product analytics events (event_name, distinct_id, properties) |
| from:errors | Individual captured exceptions (one row per event) |
| from:issues | Grouped exceptions (one row per fingerprint, with event_count + status) |
| from:spans | Distributed tracing spans (op, status, duration_ms, trace_id, attributes) |
| from:transactions | Alias for from:spans |
| from:web_vitals | Real-User Monitoring samples (lcp / inp / cls / fcp / ttfb / …) |
| from:alerts | Alert rules (name, metric, status, severity, threshold, enabled) |
| from:alert_events | Alert timeline (event_type, metric, actual_value, alert_name, duration_seconds) |
| from:X+Y | Union of any sources (e.g. logs+events, logs+alerts, events+alert_events) |
| from:* or from:all | Everything (all 4 logs/events/alerts/alert_events sources combined) |
| (auto) | Inferred from filters — severity → logs, event_name → events, event_type → alert_events |
Nested attribute access
For sources backed by a JSONB column (spans.attributes, web_vitals_raw.attributes, logs.properties, events.properties), use dotted paths:
from:spans attributes.http.route:/checkout
from:spans attributes.db.system:postgres | count by op
from:web_vitals attributes.tenant:acme metric:lcp
from:logs properties.orderId:ord_123
You can also use properties.x or attributes.x interchangeably — both prefixes are stripped.
Pipeline Stages
count
Count matching rows. Optionally group by a field.
severity:error | count
severity:error | count by endpoint
from:events | count by event_name
count_distinct
Count unique values of a field.
from:events event:signup | count_distinct(distinct_id) | last:7d
sum / avg / min / max
Numeric aggregations. JSONB fields auto-cast to numeric.
| sum(amount) by product | top 10
| avg(duration_ms) by endpoint | last:24h
| min(response_time) | last:1h
| max(memory_mb) by service
Percentiles
p25, p50 (median), p75, p90, p95, p99, or arbitrary pN:
| p50(duration_ms) by endpoint | last:24h
| p90(duration_ms) | timeseries 1h | last:7d
| p95(duration_ms) by service | top 10 | last:24h
| percentile(duration_ms, 42) | last:24h
top N
Limit aggregate results to top N groups (by value descending).
severity:error | count by message | top 10
timeseries
Bucket results by time interval for trend visualization.
severity:error | count | timeseries 1h | last:7d
from:events | count by event_name | timeseries 1d | last:30d
select
Project specific fields in search mode.
severity:error | select severity, message, endpoint, timestamp
distinct
List unique values of a field (SELECT DISTINCT).
| distinct endpoint | last:24h
from:events | distinct event_name | last:7d
as (alias)
Rename the output column of an aggregation.
severity:error | count as error_count
| avg(duration_ms) as avg_latency
Conditional Aggregation
Count only rows matching an inline filter. Compiles to SQL FILTER (WHERE ...).
| count(severity:error) as errors
| count(status_code:>=500) as server_errors
| count(message:~timeout) as timeouts
Multi-Aggregate
Multiple aggregations in one query with comma separation:
from:events event:api_call | count(success:true) as successful, count as total | last:30d
| count(severity:error) as errors, count as total by endpoint | top 10 | last:24h
| count(status_code:>=500) as server_errors, count(status_code:>=400) as client_errors, count as total
Tip: Multi-aggregate is more efficient than running separate queries — it scans the data once.
Modifiers
last (time range)
| Duration | Meaning |
|---|---|
| last:5m | Last 5 minutes |
| last:1h | Last 1 hour |
| last:24h | Last 24 hours |
| last:7d | Last 7 days |
| last:30d | Last 30 days |
limit
severity:error limit:100
order
severity:error order:asc
| count by endpoint order:asc
order:endpoint:asc — sort by specific field
having (post-aggregation filter)
Filter groups after aggregation — only keep groups exceeding a threshold.
| count by endpoint having:>10 | last:24h
| p95(duration_ms) by service having:>500 | last:24h
| count by message having:>=5 | last:24h
Multiple GROUP BY
Group by multiple fields with comma separation.
| count by severity, endpoint | last:24h
| count by endpoint | timeseries 1h | last:24h — multi-dimensional timeseries
Let Bindings (Computed Metrics)
Define named sub-queries, combine with math formulas. Compiles to SQL CTEs (WITH ... AS).
let a = severity:error | count | last:24h;
let b = | count | last:24h;
a * 100 / b as error_rate
Supports: +, -, *, /, parentheses. Division auto-wraps with NULLIF(x, 0) for safety.
Warning: Let bindings are best for cross-table or complex ratios. For simple conditional counts, prefer multi-aggregate — it's faster (single scan).
Each let binding is a full GQL query with its own filters, time range, and source:
let success = from:events event:api_call success:true | count | last:30d;
let total = from:events event:api_call | count | last:30d;
success * 100 / total as success_rate
Joins
Join two aggregate results on a shared field. Compiles to SQL JOIN.
let errors = severity:error | count by endpoint | last:24h;
let latency = | avg(duration_ms) by endpoint | last:24h;
errors join latency on endpoint | top 10
| Join Type | Syntax | Behavior |
|---|---|---|
| Inner | a join b on field | Only rows in both |
| Left | a left join b on field | All from a, matching from b |
| Right | a right join b on field | All from b, matching from a |
| Full | a full join b on field | All from both, nulls where no match |
Tip: Joins work across tables — join log aggregates with event aggregates on a shared field like endpoint, service, or region.
Alert Conditions
severity:error | count | over:30m > 100
severity:fatal | count | over:5m > 0
message:~"payment failed" | count | over:1h > 10
| avg(duration_ms) | over:10m > 2000
from:events | count | over:10m < 1
status_code:>=500 | count | over:5m > 50
Operators: >, >=, <, <=, =, !=
Schema-Aware Autocomplete
The GQL bar provides real-time autocomplete from your project's schema. As you ingest data, GoodLogs discovers property names, types, and example values. Tab to accept, ↑↓ to navigate, Esc to dismiss.
Examples
Log Investigation
severity:error last:1h
severity:error service:billing message:~timeout last:24h
severity:!=debug last:1h
severity:error status_code:(500,502,503) last:6h
Event Analytics
from:events event:signup | count | last:7d
from:events event:page_view | count_distinct(distinct_id) | timeseries 1d | last:30d
from:events event:purchase | count by product_name | top 10 | last:30d
from:events event:subscription | sum(amount) by plan | last:30d
Performance Monitoring
| avg(duration_ms) by endpoint | top 10 | last:24h
| p95(duration_ms) | timeseries 1h | last:7d
duration_ms:>1000 | count by endpoint | top 10 | last:24h
Computed Metrics
| count(severity:error) as errors, count as total | last:24h
| count(severity:error) as errors, count as total by endpoint | top 10 | last:24h
let success = from:events event:api_call success:true | count | last:30d;
let total = from:events event:api_call | count | last:30d;
success * 100 / total as success_rate
Alert Rules
severity:error | count | over:30m > 100
from:events | count | over:10m < 1
| avg(duration_ms) | over:10m > 2000
from:events event:login_failed | count | over:10m > 20