| name | apoxy-query |
| description | Use when querying Apoxy proxy logs via the apoxy MCP server tools (describe_schema, query). Provides PRQL syntax, field reference, query patterns, and engine behavior details. |
Querying Apoxy Proxy Logs
You have access to the apoxy MCP server with two tools for querying Envoy proxy access logs.
Tools
describe_schema
Returns all available log fields with their types, sources, and descriptions. Call this first if you are unsure what fields exist.
query
Executes a PRQL query against your proxy access logs.
Parameters:
prql (required) — PRQL query string. The table source is implicit — start directly with transforms like filter, select, derive, group, sort, or take.
start_time (optional) — RFC 3339 timestamp. Defaults to 24 hours ago.
end_time (optional) — RFC 3339 timestamp. Defaults to now.
cursor (optional) — Pagination cursor from a previous response's next_cursor.
There is no separate limit parameter in the MCP tool. Use take N in your PRQL query to control the number of rows returned.
PRQL Syntax
Queries are pipelines of transforms separated by newlines. Backtick-quote any field name containing dots.
filter `http.response.status_code` >= 500
select {Timestamp, `url.path`, `http.response.status_code`}
sort {-Timestamp}
take 10
Key transforms:
filter <condition> — keep rows matching a condition.
select {col1, col2} — choose columns.
derive {name = expression} — add computed columns.
group {col} (aggregate {name = func this}) — group and aggregate.
sort {-col} — sort (prefix - for descending).
take N — limit to N rows.
How the Engine Works
Understanding the engine's behavior helps you write correct queries and interpret compiled_sql output.
Field rewriting
Dotted field names in backticks are rewritten to the appropriate storage-level access expressions and aliased back to the clean field name:
`url.path` → accessed from log attributes, aliased as "url.path" in results
`cluster_name` → accessed from resource attributes, aliased as "cluster_name" in results
The engine uses a field registry to determine which attribute group each field belongs to. Top-level columns (like Timestamp, Body) are not rewritten.
Type casting
Attribute fields are stored as strings internally but integer and float fields are automatically cast to their numeric types:
- Integer fields (e.g.,
http.response.status_code): cast to 64-bit integer
- Float fields (e.g.,
http.request.duration_ms): cast to 64-bit float
This means you can use numeric comparisons directly: filter http.response.status_code >= 400 works without manual casting.
String literal skipping
The rewriter does not touch string literals in your PRQL. A filter like filter url.path == "/api/users" correctly rewrites url.path to LogAttributes['url.path'] but leaves "/api/users" untouched.
LIMIT behavior
- Queries without
take get a default limit of 1000 rows.
- The maximum limit is 10000 rows. Use cursor pagination for larger result sets.
- When you write
take N, the engine detects it and uses your limit directly (compiled SQL shows LIMIT N). When you omit take, the engine appends its own limit placeholder.
WHERE injection
The engine automatically injects WHERE conditions for project filtering and time range before any GROUP BY clause. These appear in compiled_sql but do not need to be written by the user.
Field Reference
Fields come from three sources. Call describe_schema for the full list including dynamically discovered fields.
Top-level columns (use unquoted names):
| Field | Type | Description |
|---|
| Timestamp | timestamp | Event timestamp (DateTime64) |
| TimestampTime | timestamp | Truncated timestamp (DateTime, useful for grouping) |
| Body | string | Log message body |
| ServiceName | string | Service name |
| SeverityText | string | Log severity text |
| SeverityNumber | integer | Log severity number (UInt8) |
| TraceId | string | Trace correlation ID |
| SpanId | string | Span correlation ID |
| TraceFlags | integer | Trace flags (UInt8) |
| EventName | string | Event name |
LogAttributes (backtick-quote names, sourced from Envoy access logs):
| Field | Type | Description |
|---|
http.request.method | string | HTTP request method |
http.response.status_code | integer | HTTP response status code |
url.path | string | Request URL path |
client.address | string | Client IP:port |
upstream.address | string | Upstream host address |
upstream.cluster | string | Envoy upstream cluster name |
http.request.duration_ms | float | Request duration in milliseconds |
http.request.body.size | integer | Request body bytes |
http.response.body.size | integer | Response body bytes |
http.request.id | uuid | Request UUID |
user_agent.original | string | User-Agent header |
url.host | string | Request host header |
network.protocol.name | string | Protocol (HTTP/1.1, HTTP/2) |
envoy.response_flags | string | Envoy response flags |
ResourceAttributes (backtick-quote names, infrastructure metadata):
| Field | Type | Description |
|---|
cluster_name | string | Cluster identifier |
log_name | string | Log source name |
project_id | uuid | Project UUID |
node_name | string | Node UUID |
service.name | string | Service name |
zone_name | string | Zone identifier |
Additional fields may be discovered dynamically from the data. Use describe_schema to see the current full list.
Common Patterns
Filter by status code:
filter `http.response.status_code` >= 500
select {Timestamp, `url.path`, `http.response.status_code`}
sort {-Timestamp}
Aggregate by field:
group {`http.response.status_code`} (aggregate {count = count this})
Derived/computed columns:
derive {is_error = `http.response.status_code` >= 400}
select {Timestamp, `url.path`, is_error}
Filter with string equality:
filter `url.path` == "/api/users"
filter `http.request.method` == "POST"
select {Timestamp, `http.response.status_code`, `http.request.duration_ms`}
Mixed top-level and attribute fields:
select {Timestamp, Body, `http.request.method`, `client.address`, ServiceName}
sort {-Timestamp}
take 20
ResourceAttributes fields:
select {Timestamp, `cluster_name`, `node_name`, `service.name`}
sort {-Timestamp}
Best Practices
- Call
describe_schema first if you are unsure about available fields or their exact names.
- Use explicit time ranges for large datasets — pass
start_time and end_time to narrow the window.
- Preview with
take 10 before fetching large result sets. Without take, you get up to 1000 rows.
- Cursor pagination requires
start_time and end_time — both are mandatory when using a cursor. The server rejects cursor requests that omit either timestamp. Use the same values across pages.
- Check
compiled_sql in the response if results look unexpected — it shows the SQL that was executed, including field rewriting, type casting, and injected WHERE conditions.
Documentation