Log Query

The Log Query feature provides a KQL-like query interface for exploring and correlating data across Praxis virtual tables (captured traffic, events, recon results, nodes, agents, operation history, etc). The syntax is inspired by Kusto Query Language but only a subset of KQL is implemented — not all features or functions from the full Kusto specification will work. Write queries in the code editor, execute them with Ctrl+Enter, and browse paginated results.

Available Tables

AgentLogs

Discovered agents across all nodes (in-memory).

ColumnDescription
timestampLast update time
node_idNode identifier
agent_short_nameAgent short name
agent_nameAgent display name
versionAgent version (if known)

EventLogs

Centralized application log entries from service, web, and nodes. Requires application_logs_enabled to be set to true in settings.

ColumnDescription
timestampWhen the log entry was recorded
sourceOrigin category: "service", "web", or "node"
source_idInstance identifier (e.g. node UUID, web client ID; empty for service)
levelLog level: error, warn, info, debug, trace
targetLog target/module (may be null)
messageLog message text

SemanticOperationChainLogs

Chain execution history, including per-element state and final outputs. The elements and outputs columns contain JSON — use contains() to search within them.

ColumnDescription
timestampWhen the chain execution was created
execution_idChain execution identifier
chain_idChain definition identifier
chain_nameChain display name
node_idNode that executed the chain
agent_short_nameAgent that executed the chain
statusExecution status: Queued, Running, Completed, Failed, Cancelled
elementsPer-element execution state (JSON)
outputsFinal outputs from termination elements (JSON)
started_atWhen execution started
ended_atWhen execution ended (null if still running)

NodeLogs

Currently connected nodes (in-memory).

ColumnDescription
timestampLast update time
node_idNode identifier
machine_nameMachine hostname
os_detailsOperating system details
intercept_activeWhether interception is active

SemanticOperationLogs

Semantic operation execution history, including results and summaries. The operation_spec column contains the full operation definition as JSON — use contains() to search within it.

ColumnDescription
timestampWhen the operation was created
operation_idOperation identifier
node_idNode that executed the operation
agent_short_nameAgent that executed the operation
statusOperation status: Queued, Running, Completed, Failed, Cancelled
operation_specFull operation specification (JSON)
start_timeWhen the operation started
end_timeWhen the operation ended (null if still running)
summaryBrief summary of actions taken
resultActual findings/data/output
chain_execution_idParent chain execution ID (null if standalone)

ReconLogs

Summary of reconnaissance results per node+agent.

ColumnDescription
timestampWhen recon was performed
node_idNode identifier
agent_short_nameAgent short name
is_semanticWhether this was a semantic recon
mcp_server_countNumber of MCP servers discovered
skill_countNumber of skills discovered
internal_tool_countNumber of internal tools discovered
config_countNumber of config items discovered
session_countNumber of sessions discovered
project_path_countNumber of project paths discovered

ReconMetadataLogs

User identities and API keys extracted from agent configurations.

ColumnDescription
timestampWhen recon was performed
node_idNode identifier
agent_short_nameAgent short name
entry_type"user_identity" or "api_key"
valueThe identity or key value

ReconSessionLogs

Sessions discovered during reconnaissance.

ColumnDescription
timestampWhen recon was performed
node_idNode identifier
agent_short_nameAgent short name
session_idSession identifier
context_pathProject/context path
last_modifiedWhen the session was last modified
message_countNumber of messages in the session

ReconToolLogs

Individual tools discovered during reconnaissance (MCP tools, skills, internal tools).

ColumnDescription
timestampWhen recon was performed
node_idNode identifier
agent_short_nameAgent short name
tool_typeType: "mcp", "skill", or "internal"
server_nameMCP server name (null for skills/internal)
tool_nameTool name
tool_descriptionTool description
transportMCP transport type (null for skills/internal)

TrafficLogs

Intercepted HTTP traffic stored in the database.

ColumnDescription
timestampWhen the traffic was captured
traffic_idTraffic entry ID (join key for TrafficMatchLogs)
node_idNode that captured the traffic
agent_short_nameAgent associated with this traffic
intercept_methodMethod used (proxy, vpn, hosts, tproxy)
directionsend or receive
methodHTTP method (GET, POST, etc.)
urlFull URL
hostHost/domain
request_headersRequest headers as JSON
request_bodyRequest body as text
response_statusHTTP response status code
response_headersResponse headers as JSON
response_bodyResponse body as text

TrafficMatchLogs

Traffic that matched intercept rules, joined with traffic details.

ColumnDescription
timestampWhen the match occurred
traffic_idID of the matched traffic entry (join key for TrafficLogs)
node_idNode that captured the traffic
agent_short_nameAgent associated with this traffic
rule_idID of the matching rule
rule_nameName of the matching rule
summaryLLM-generated summary (if rule has summarization prompt)
methodHTTP method
urlFull URL
hostHost/domain
directionsend or receive
response_statusHTTP response status code

Supported KQL Operators

OperatorDescriptionExample
whereFilter rowsTrafficLogs | where host contains "openai"
projectSelect columnsTrafficLogs | project timestamp, url, host
project-awayRemove columnsTrafficLogs | project-away request_body, response_body
sort / orderSort rowsTrafficLogs | sort timestamp
take / limitLimit rowsTrafficLogs | take 50
topTop N by columnTrafficLogs | top 10 by timestamp
extendAdd computed columnsTrafficLogs | extend url_length = strlen(url)
countCount rowsTrafficLogs | count
distinctUnique valuesTrafficLogs | distinct host
summarizeAggregateTrafficLogs | summarize count() by host
joinJoin two tablesTrafficLogs | join (TrafficMatchLogs) on traffic_id

Join supports qualified keys when column names differ between tables:

LeftTable | join (RightTable) on $left.col_a == $right.col_b

Supported Expressions

  • Comparisons: ==, !=, <, >, <=, >=
  • Logical: and, or, not
  • String functions: contains, startswith, endswith, has, strlen, tolower, toupper
  • Null checks: isnotempty(), isnull(), isempty()
  • Aggregations (in summarize): count(), sum(), avg(), min(), max(), dcount()
  • Type conversion: tostring(), toint(), tolong()

Example Queries

// List recent traffic
TrafficLogs | take 20

// Find traffic to a specific host
TrafficLogs | where host contains "api.openai.com" | project timestamp, method, url, response_status

// Count traffic by host
TrafficLogs | summarize count() by host

// List all connected nodes
NodeLogs

// Find available agents
AgentLogs | where available == true

// Find all MCP tools across agents
ReconToolLogs | where tool_type == "mcp" | project agent_short_name, server_name, tool_name

// List API keys found in recon
ReconMetadataLogs | where entry_type == "api_key"

// Correlate traffic matches with rules
TrafficMatchLogs | project timestamp, rule_name, url, summary | take 50

// Join traffic with matches to see matched URLs with rule names
TrafficLogs | join (TrafficMatchLogs) on traffic_id | project timestamp, url, rule_name, summary

// Find traffic with large responses
TrafficLogs | where response_status == 200 | project timestamp, url, host | take 100

// View recent error logs
EventLogs | where level == "error" | take 50

// Count log entries by source
EventLogs | summarize count() by source

// List completed operations with results
SemanticOperationLogs | where status == "Completed" | project timestamp, agent_short_name, summary, result | take 50

// Find failed operations
SemanticOperationLogs | where status == "Failed" | project timestamp, operation_id, agent_short_name, result

// Count operations by status
SemanticOperationLogs | summarize count() by status

// Find operations that are part of a chain
SemanticOperationLogs | where isnotempty(chain_execution_id) | project timestamp, operation_id, chain_execution_id, summary

// List chain executions
SemanticOperationChainLogs | project timestamp, chain_name, status, outputs | take 20

// Find completed chains with their outputs
SemanticOperationChainLogs | where status == "Completed" | project timestamp, chain_name, outputs

Query Execution

SQL Pushdown

Tables backed by the database (EventLogs, TrafficLogs, TrafficMatchLogs, SemanticOperationLogs, SemanticOperationChainLogs) benefit from automatic SQL pushdown. When the executor encounters leading where and take/limit operators in a query pipeline, it translates KQL expressions directly into SQL WHERE clauses with parameterized queries. This means the database handles filtering before rows are loaded into memory, enabling efficient queries over large datasets.

The following KQL constructs are translated to SQL:

  • Comparisons: ==, !=, <, >, <=, >= become SQL comparison operators
  • Logical: and, or become SQL AND/OR
  • String functions: contains/has become LOWER(col) LIKE '%value%', startswith becomes LIKE 'value%', endswith becomes LIKE '%value'
  • Null checks: isnull()/isempty() become IS NULL OR = '', isnotnull()/isnotempty() become IS NOT NULL AND != ''
  • Case functions: tolower(), toupper() become SQL LOWER(), UPPER()
  • Utility: strlen() becomes LENGTH(), tostring() becomes CAST(... AS TEXT), toint()/tolong() become CAST(... AS INTEGER), now() binds the current UTC timestamp

User-provided string values in LIKE patterns are escaped to prevent SQL wildcard injection (% and _ are matched literally).

If any expression in the leading where clauses cannot be translated to SQL (e.g. an unsupported function), the executor falls back to fetching all rows with just a LIMIT and applies all filtering in memory. Operators that appear after a non-pushable operator (like project, extend, summarize) always run in memory.

In-memory tables (NodeLogs, AgentLogs) and JSON-expanded tables (ReconLogs, ReconToolLogs, etc.) are always materialized fully and filtered in memory.

Result Limits

Results are capped by the log_query_row_limit setting, which defaults to 10,000,000 rows. This limit can be configured in Settings > Service > Event Logging. The total_count field reflects the actual count before capping. Use take or limit to reduce result size for large tables.

KQL Parser

The Log Query feature uses a vendored fork of the kqlparser crate (v0.0.4, Apache-2.0) for parsing KQL syntax. The vendored copy lives in service/src/log_query/parser/ and includes fixes for multiline join expressions and native $left/$right join key syntax. Only the subset of KQL operators and functions listed above are supported; unsupported constructs will return an error.