Server-Side Event Filtering

Server-side filtering lets you subscribe to specific event types with rulekit expressions, reducing bandwidth and eliminating client-side filtering. Instead of receiving all events and discarding most, tell Qtap exactly what you want.

circle-info

New in v0.14.0: Server-side filtering requires Qtap v0.14.0 or later. Earlier versions stream all events without filtering.


Why Server-Side Filtering?

Without Filtering
With Filtering

Receive all events, filter client-side

Receive only matching events

High bandwidth in busy environments

Minimal bandwidth usage

Client processes every event

Server handles filtering

Same data for all consumers

Different filters per connection

Use cases:

  • High-traffic environments: Subscribe only to errors instead of all HTTP transactions

  • Targeted monitoring: Watch specific containers or processes

  • Multi-consumer scenarios: Different dashboards subscribe to different event types


Quick Start

Subscribe to HTTP errors only (status >= 400):

curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "http": "res.status >= 400"
    }
  }'

The response stream starts with system.connected confirming your subscription:


API Reference

Endpoint

Subscription Payload

The JSON payload uses a topics object to specify subscriptions:

circle-exclamation

Subscription Semantics

Payload
Behavior

"*"

Subscribe to all events of that type

Filter expression

Subscribe to matching events only

Omit topic

No events of that type

Empty payload {}

Default: all topics with "*"

Examples


Filter Fields by Topic

Native properties (belonging to the event type) have no prefix. Relational properties (from associated entities) are prefixed.

circle-info

Verified Fields: Fields marked with ✓ have been tested and verified to work with server-side filtering. Fields in the "Not Available" section are present in event data but cannot be used for server-side filtering.

Process Events

Fields available for process.started and process.stopped events:

Field
Description
Example

binary

Executable name

python3.11, node, nginx

path

Full path to binary

/usr/local/bin/python3.11

hostname

Process hostname

payment-service

user.name

Username

root, www-data

user.id

User ID

0, 1000

container.name

Container name

my-app

container.id

Container ID

1781322a598f

container.image

Container image

python:3.11-slim

container.labels.<key>

Container label value

container.labels.app

pod.name

Pod name (Kubernetes only)

payment-service-7d8f9

pod.namespace

Pod namespace (Kubernetes only)

production

pod.labels.<key>

Pod label value (Kubernetes only)

pod.labels.app

Connection Events

Native connection properties + relational process.* fields:

Field
Description
Example

direction

Connection direction

egress-external, egress-internal, ingress

protocol

L7 protocol

http1, http2, other

type

Socket type

tcp, udp

src.ip

Source IP address

172.18.0.7

src.port

Source port

34438

dst.ip

Destination IP address

52.4.128.73

dst.port

Destination port

443, 80

process.binary

Process executable name

python3.11

process.path

Full executable path

/usr/local/bin/python3.11

process.hostname

Process hostname

payment-service

process.user.name

Username

root

process.user.id

User ID

0

container.name

Container name

payment-service

container.id

Container ID

1781322a598f

container.image

Container image

python:3.11-slim

container.labels.<key>

Container label value

container.labels.app

pod.name

Pod name (Kubernetes only)

payment-service-7d8f9

pod.namespace

Pod namespace (Kubernetes only)

production

pod.labels.<key>

Pod label value (Kubernetes only)

pod.labels.app

Not available for server-side filtering (present in event data only):

  • dst.domain - Use req.host on HTTP topic instead

  • tls.enabled, tls.version, tls.sni - TLS info available in event data

HTTP Events

Native HTTP properties (req.*/res.*) + relational connection/process fields:

Field
Description
Example

req.method

HTTP method

GET, POST, PUT, DELETE

req.host

Request host

api.stripe.com

res.status

Response status code

200, 404, 500

direction

Connection direction

egress-external, egress-internal, ingress

protocol

L7 protocol

http1, http2

src.ip

Source IP address

172.18.0.7

src.port

Source port

34438

dst.ip

Destination IP address

52.4.128.73

dst.port

Destination port

443, 80

process.binary

Process executable name

python3.11

process.path

Full executable path

/usr/local/bin/python3.11

process.hostname

Process hostname

payment-service

process.user.name

Username

root

container.name

Container name

payment-service

container.id

Container ID

1781322a598f

container.image

Container image

python:3.11-slim

container.labels.<key>

Container label value

container.labels.app

pod.name

Pod name (Kubernetes only)

payment-service-7d8f9

pod.namespace

Pod namespace (Kubernetes only)

production

pod.labels.<key>

Pod label value (Kubernetes only)

pod.labels.app

Not available for server-side filtering (present in event data only):

  • req.path, req.url - Full path/URL available in decoded event payload

  • dst.domain - Use req.host instead

  • tls.enabled, tls.version, tls.sni - TLS info available in event data

  • process.user.id - Use process.user.name instead

circle-info

Cross-Entity Filtering: Filters can apply across related entities. For example, filtering HTTP events by container.name == "nginx" will match HTTP requests from that container via connection cache lookup.


Operators

Comparison Operators

Operator
Description
Example

==

Equal

res.status == 200

!=

Not equal

req.method != "GET"

>

Greater than

res.status > 399

>=

Greater or equal

res.status >= 400

<

Less than

res.status < 400

<=

Less or equal

src.port <= 1024

String Operators

Operator
Description
Example

contains

Substring match

req.host contains "api."

matches

Regex match

req.host matches /api\.[a-z]+\.com/

Logical Operators

Operator
Description
Example

AND

Both conditions

res.status >= 400 AND req.method == "POST"

OR

Either condition

res.status == 500 OR res.status == 503

NOT

Negate condition

NOT req.method == "GET"

Special Operators

Operator
Description
Example

in

Value in list

req.method in ["POST", "PUT", "DELETE"]

in

IP in CIDR

dst.ip in "10.0.0.0/8"


Custom Functions

in_zone(domain, zone)

Check if a domain belongs to a DNS zone:

Matches api.stripe.com, dashboard.stripe.com, stripe.com.

zone(domain)

Extract the zone from a domain for comparison:


Examples

Subscribe to All Events (Default)

Response confirms subscription to all topics:

HTTP Errors Only

Specific Container Traffic

HTTPS Connections Only (Port 443)

Specific Process Binary

Multiple Topics with Different Filters

Cross-Entity Filtering (HTTP by Process)

Filter HTTP transactions by the originating process:

Complex Filters

Combine multiple conditions:


Best Practices

Start Broad, Narrow Down

Begin with permissive filters to verify events are flowing, then add constraints:

Verify Subscriptions

Always check the system.connected event to confirm your filters were accepted:

Expected output:

Use Specific Topics

Only subscribe to event types you need:


Troubleshooting

No Events Received

  1. Check subscription: Verify system.connected shows your topics

  2. Generate traffic: The filter might be too restrictive

  3. Test with "*": Remove filters to confirm events are flowing

  4. Check Qtap version: Server-side filtering requires v0.14.0+

Filter Syntax Errors

Invalid filters cause the connection to fail. Test expressions incrementally:

Common Mistakes

Mistake
Fix

"HTTP" instead of "http"

Use lowercase: "http", "process", "connection"

Flat payload {"http": "..."}

Use topics wrapper: {"topics": {"http": "..."}}

status instead of res.status

Use full field path: res.status >= 400

method instead of req.method

Use full field path: req.method == "POST"

Wrong port (10002)

Use port 10001

Events Don't Match Filter

  1. Verify field names match the schema (case-sensitive)

  2. Check string values are quoted correctly

  3. Use "*" first to see actual field values in events

  4. Test filter logic with simpler conditions


Last updated