Configuration
Probestyx is configured using a YAML file (config.yaml). This guide covers all configuration options with examples.
Configuration File Location
By default, Probestyx looks for config.yaml in the current working directory:
- Linux/macOS:
/opt/probestyx/config.yaml - Windows:
C:\probestyx\config.yaml
Basic Configuration Structure
server:
port: 9100
secret: ""
# consumers: [] # optional, only needed for per-consumer log tailing - see Scrapers
system:
enabled: true
cache_ttl: 60 # optional, caches system metrics only (see below)
metrics:
- cpu_usage_percent
- ram_usage_percent
scrapers:
- name: scraper_name
source:
type: url
url: "http://example.com/metrics"
format: json
metrics:
- path: "metric.path"
name: "output_name"Server Configuration
The server section configures the HTTP server that exposes metrics.
server:
port: 9100 # HTTP server port (default: 9100)
secret: "your-secret" # Optional HMAC authentication secret
consumers: [] # Optional allowlist for ?callerId= (see Scrapers → Per-Consumer Tailing)Options
| Option | Type | Default | Description |
|---|---|---|---|
port | integer | 9100 | Port to listen on |
secret | string | "" | HMAC secret for authentication (empty = no auth) |
consumers | list of strings | (none) | Allowlist of ?callerId= values for per-consumer log tailing |
TIP
Looking for cache_ttl? It lives under system:, not server: — see System Metrics Configuration below. It only caches system metrics; scrapers are never cached and re-run on every request.
Authentication
If secret is provided, clients must sign requests:
server:
secret: "my-secure-secret-key"If secret is empty or omitted, no authentication is required:
server:
port: 9100
# No secret = no authenticationSee Authentication for client implementation.
System Metrics Configuration
The system section enables collection of built-in system metrics.
system:
enabled: true # Enable/disable system metrics
cache_ttl: 60 # Optional: cache duration in seconds (default: 60)
metrics: # List of metrics to collect
- cpu_usage_percent
- ram_usage_percent
- disk_usage_percentCaching
cache_ttl (under system:) applies only to system metrics (CPU/RAM/disk/network/etc.) — it avoids re-querying the OS on every rapid /metrics request. It has no effect on the scrapers: section: every scraper (URL, file, or command) runs fresh on every request, with no caching layer of its own.
The one exception is format: log scrapers, which have their own separate mechanism — they track what's already been read (a file offset, or a timestamp) rather than caching a result. See Scrapers → Log Parser.
Available Metrics
See System Metrics for a complete list of available system metrics.
Examples
Minimal system metrics:
system:
enabled: true
metrics:
- cpu_usage_percent
- ram_usage_percent
- disk_usage_percentComprehensive monitoring:
system:
enabled: true
metrics:
# CPU
- cpu_usage_percent
- cpu_usage_per_core
- cpu_count
- cpu_load_1min
- cpu_load_5min
# Memory
- ram_usage_percent
- available_ram_mb
- total_ram_mb
- swap_usage_percent
# Disk
- disk_usage_percent
- disk_read_bytes_per_sec
- disk_write_bytes_per_sec
# Network
- network_bytes_sent_per_sec
- network_bytes_recv_per_sec
- active_connections
# System
- system_uptime_seconds
- process_countDisable system metrics:
system:
enabled: falseScrapers Configuration
The scrapers section defines external metric sources to collect from.
Basic Structure
scrapers:
- name: scraper_name # Unique identifier
source:
type: url|file|command # Source type
url: "..." # For type: url
path: "..." # For type: file
command: "..." # For type: command
format: json|prometheus|raw|log
pattern: "..." # For format: raw/log (optional)
max_lines: 100 # For format: log (optional, default 100)
metrics:
- path: "..." # For JSON format
match: "..." # For Prometheus/raw/log format
name: "output_name"
calculate: "..." # Optional transformation
filter: # Optional filtering
include:
- "pattern.*"
exclude:
- ".*exclude.*"
push: false # Optional; format: log only - see Push ModeSource Types
URL Source
Fetch metrics from an HTTP endpoint.
scrapers:
- name: api_metrics
source:
type: url
url: "https://api.example.com/metrics"
format: jsonCommand Source
Run a shell command and capture its combined stdout+stderr as the source text — useful for things with no file to point at, like docker logs.
scrapers:
- name: docker_errors
source:
type: command
command: "docker logs --since {{since}} --tail 1000 my-container 2>&1"
format: logRuns via sh -c on Linux/macOS or cmd /C on Windows. Only put trusted, operator-authored commands here — this isn't meant to run untrusted or request-derived input. See Log Parser below for what does.
File Source
Read metrics from a local file.
scrapers:
- name: local_metrics
source:
type: file
path: "/var/app/metrics.json"
format: jsonFormat Types
JSON Format
Extract values from JSON using dot notation paths.
scrapers:
- name: json_example
source:
type: url
url: "http://localhost:8080/stats"
format: json
metrics:
- path: "database.connections"
name: "db_connections"
- path: "cache.hit_rate"
name: "cache_hit_percent"
calculate: "value * 100"Source JSON:
{
"database": {
"connections": 42
},
"cache": {
"hit_rate": 0.95
}
}Output:
{
"db_connections": 42,
"cache_hit_percent": 95
}Prometheus Format
Parse Prometheus exposition format.
scrapers:
- name: node_exporter
source:
type: url
url: "http://localhost:9100/metrics"
format: prometheus
metrics:
- match: "node_cpu_seconds_total"
name: "cpu_seconds"
- match: "node_memory_MemAvailable_bytes"
name: "memory_mb"
calculate: "value / 1024 / 1024"Source Prometheus:
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 12345.67
# TYPE node_memory_MemAvailable_bytes gauge
node_memory_MemAvailable_bytes 4294967296Output:
{
"cpu_seconds": 12345.67,
"memory_mb": 4096
}Raw Format
Use regex patterns to extract key-value pairs from text.
scrapers:
- name: sensors
source:
type: file
path: "/data/sensors.txt"
format: raw
pattern: 'sensor_(\w+)=([0-9.]+)' # Optional, default: (\w+)=(\S+)
metrics:
- match: "temperature"
name: "temp_celsius"
- match: "humidity"
name: "humidity_percent"Source text:
sensor_temperature=23.5
sensor_humidity=65.2
sensor_pressure=1013.25Output:
{
"temp_celsius": 23.5,
"humidity_percent": 65.2
}Log Format
Scans a text source line-by-line and returns the lines that look like errors, plus a count — built for grepping error lines out of log files or command output (an nginx error.log, docker logs, etc). Unlike raw, which collapses matches into single key/value pairs, log is built to return a list of matching lines.
scrapers:
- name: nginx_errors
source:
type: file
path: "/var/log/nginx/error.log"
format: log
max_lines: 50
# pattern: '\[error\]' # optional regex; default matches lines containing "error" (case-insensitive)
metrics:
- match: "count"
name: "nginx_error_count"
- match: "lines"
name: "nginx_error_lines"Output:
{
"nginx_error_count": 1,
"nginx_error_lines": [
"2026/07/18 10:00:05 [error] 123#0: *2 open() \"/var/www/missing.html\" failed (2: No such file or directory)"
]
}Only new lines are reported each scrape — not everything currently sitting in the log:
type: filesources are tailed automatically: probestyx remembers how far it has already read. The first scrape after startup seeds that position at the current end of the file (so it doesn't dump the whole historical log on boot) and reports nothing; every scrape after that only sees lines appended since the previous one. If the file shrinks (log rotation/truncation), it detects that and starts over from the beginning of the new file.type: commandsources (e.g.docker logs) have no seekable position, so this is opt-in via aplaceholder in the command — it's substituted with the timestamp of that scraper's last successful run:docker logs --since --tail 1000 my-container 2>&1. Withoutin the command, it re-runs and re-scans the full output every time.- This tailing state lives in memory only, per scraper: a probestyx restart resets it to "start from now."
Per-Consumer Tailing
If more than one independent monitoring backend polls the same probestyx instance, they'd race over a single shared tailing position by default — whichever polls first after a new error line appears "consumes" it, and the other never sees it.
Declare the expected consumers in server.consumers, then have each pass its identity as ?callerId=:
server:
consumers:
- logstyx-prod
- staging-pollercurl "http://localhost:9100/metrics?callerId=logstyx-prod"
curl "http://localhost:9100/metrics?callerId=staging-poller"Each declared callerId gets its own independent tailing position per scraper. A request with no callerId (or when server.consumers isn't set) falls back to a single shared "default" bucket — today's behavior if you never touch this. A callerId that isn't in server.consumers is rejected with 400 Bad Request rather than silently tracked, since /metrics is often unauthenticated and an open-ended ID namespace would let anyone grow the in-memory state without bound.
Most setups don't need this at all — see Integration, where one Logstyx probe polls one probestyx instance, which is exactly the single-consumer default case.
Under pull mode, a new error only reaches whoever's polling /metrics on their next scrape — up to a full poll interval of latency. If that's too slow, see Push Mode below: probestyx can send new lines to Logstyx itself, as they appear.
Metric Transformations
Use the calculate field to transform values:
metrics:
# Convert bytes to megabytes
- path: "memory.bytes"
name: "memory_mb"
calculate: "value / 1024 / 1024"
# Convert ratio to percentage
- path: "cache.hit_rate"
name: "cache_hit_percent"
calculate: "value * 100"
# Convert seconds to milliseconds
- path: "response.time"
name: "response_ms"
calculate: "value * 1000"
# Add offset
- path: "temperature.celsius"
name: "temperature_kelvin"
calculate: "value + 273.15"Supported operators:
+(addition)-(subtraction)*(multiplication)/(division)
Note: Only simple expressions with value and one operator are supported.
Filtering
Filter which metrics to include or exclude using regex patterns.
scrapers:
- name: filtered_metrics
source:
type: url
url: "http://localhost:9100/metrics"
format: prometheus
filter:
include:
- "^node_.*" # Only metrics starting with "node_"
- ".*memory.*" # Metrics containing "memory"
exclude:
- ".*_bucket$" # Exclude histogram buckets
- ".*internal.*" # Exclude internal metricsHow filtering works:
- If
includepatterns are specified, only metrics matching at least one pattern are kept - Then, metrics matching any
excludepattern are removed - If no
includepatterns are specified, all metrics pass (before exclusion)
Examples:
Include only network metrics:
filter:
include:
- "^network_.*"Exclude debug and internal metrics:
filter:
exclude:
- ".*_debug$"
- ".*internal.*"Include node metrics but exclude histograms:
filter:
include:
- "^node_.*"
exclude:
- ".*_bucket$"
- ".*_sum$"
- ".*_count$"Push Mode
Probestyx is normally pull-only: it exposes /metrics and waits to be polled. Push mode is an opt-in add-on for format: log scrapers specifically — instead of waiting for the next poll, probestyx detects new error lines itself and POSTs them straight to Logstyx's ingestion API, cutting latency from "up to one poll interval" down to about the length of a short debounce window.
How it decides when to push
type: filescrapers are event-driven: probestyx watches the log file's directory (not the file itself — this is what lets it survive log rotation, including the rename-then-recreate scheme, since a directory watch isn't tied to the old file's inode) and pushes shortly after new lines are written, debounced by ~200ms so a burst of many lines in one write collapses into a single push instead of one per line.type: commandscrapers (e.g.docker logs) have no file to watch, so they're re-run on a ticker instead —push.command_poll_interval_seconds(default 15).- Either way, one push is one scraper's new lines since the last trigger (matching the
{count, lines}shapeformat: logalready produces) — never one push per line, since there's no batch endpoint on the ingestion side and this keeps well under its rate limit during a burst. - Push maintains its own tailing cursor per scraper, completely independent of whatever polls
/metrics— enabling push for a scraper doesn't change what/metricsreports for it, and vice versa. If a scraper is both pulled and pushed, the same lines appear through both channels independently.
Configuration
server:
port: 9100
push:
enabled: true
endpoint: "https://api.logstyx.com/api/v1/logs"
project_id: "your-project-id"
secret: "your-project-secret" # from the Logstyx dashboard - NOT the same as server.secret
level: "error" # optional, default "error"
context: # optional static tags attached to every push
environment: "production"
region: "us-east-1"
scrapers:
- name: nginx_errors
source:
type: file
path: "/var/log/nginx/error.log"
format: log
max_lines: 50
push: true # opt this scraper into push modepush.secret is deliberately a separate field from server.secret: server.secret authenticates callers of probestyx's own /metrics endpoint, while push.secret is the credential probestyx uses to authenticate itself to Logstyx. They're different keys for different directions and must never be the same value. push: true on a scraper only takes effect when its format is log and the global push.enabled is true — anything else is ignored with a startup warning, not a hard failure.
Reliability characteristics, plainly
- In-memory retry queue only — failed pushes are retried with exponential backoff (1s up to 30s), but nothing is persisted to disk. A probestyx restart during a Logstyx outage loses whatever was queued, and (since tailing state is also in-memory) resets to "start from now" — it will not go back and re-send what it missed while it was down.
- A
401(badsecret/project_id) is treated as fatal, not retried: push logs one prominent warning, drops everything queued, and stops trying for the rest of that process's life, rather than silently retrying into a bounded queue forever. Restart probestyx after fixing the config. - A
200response does not guarantee the log was actually stored — an unknown or mistypedproject_idcan be silently accepted as a fake success server-side and the log discarded, with no way to detect this from the response. Double-checkproject_idagainst the dashboard rather than trusting a lack of errors. - The queue is bounded (
push.max_queue_size, default 256); if it fills up (e.g. a prolonged outage), the oldest pending item is dropped to make room, and this is logged.
Complete Examples
Minimal Configuration
Only collect basic system metrics:
server:
port: 9100
system:
enabled: true
metrics:
- cpu_usage_percent
- ram_usage_percent
- disk_usage_percentWith Authentication
Secure Probestyx with HMAC authentication:
server:
port: 9100
secret: "change-this-to-a-secure-secret"
system:
enabled: true
metrics:
- cpu_usage_percent
- ram_usage_percentMultiple Scrapers
Collect from multiple sources:
server:
port: 9100
system:
enabled: true
cache_ttl: 30
metrics:
- cpu_usage_percent
- ram_usage_percent
- disk_usage_percent
scrapers:
# Application metrics from JSON API
- name: app_metrics
source:
type: url
url: "http://localhost:8080/metrics"
format: json
metrics:
- path: "requests.total"
name: "total_requests"
- path: "requests.errors"
name: "error_count"
- path: "database.connections"
name: "db_connections"
# Node exporter metrics
- name: node_exporter
source:
type: url
url: "http://localhost:9100/metrics"
format: prometheus
metrics:
- match: "node_memory_MemAvailable_bytes"
name: "memory_available_mb"
calculate: "value / 1024 / 1024"
filter:
include:
- "^node_memory_.*"
# Custom sensor data
- name: sensors
source:
type: file
path: "/var/sensors/data.txt"
format: raw
pattern: 'sensor_(\w+)=([0-9.]+)'
metrics:
- match: "temperature"
name: "temperature_celsius"High-Frequency Monitoring
Fast metrics collection with short cache:
server:
port: 9100
system:
enabled: true
cache_ttl: 5 # Cache for only 5 seconds
metrics:
- cpu_usage_percent
- cpu_usage_per_core
- ram_usage_percent
- disk_read_bytes_per_sec
- disk_write_bytes_per_sec
- network_bytes_sent_per_sec
- network_bytes_recv_per_secProduction Configuration
A complete production setup:
server:
port: 9100
secret: "use-a-strong-secret-here"
system:
enabled: true
cache_ttl: 60
metrics:
# CPU Metrics
- cpu_usage_percent
- cpu_count
- cpu_load_1min
- cpu_load_5min
- cpu_load_15min
# Memory Metrics
- ram_usage_percent
- available_ram_mb
- total_ram_mb
- swap_usage_percent
# Disk Metrics
- disk_usage_percent
- available_disk_gb
- total_disk_gb
- disk_read_bytes_per_sec
- disk_write_bytes_per_sec
# Network Metrics
- network_bytes_sent_per_sec
- network_bytes_recv_per_sec
- active_connections
# System Info
- system_uptime_seconds
- hostname
- process_count
scrapers:
# Application metrics
- name: application
source:
type: url
url: "http://localhost:8080/metrics"
format: json
metrics:
- path: "http.requests.total"
name: "http_requests_total"
- path: "http.requests.errors"
name: "http_errors_total"
- path: "http.response_time.avg"
name: "http_response_time_ms"
calculate: "value * 1000"
- path: "database.pool.active"
name: "db_connections_active"
- path: "database.pool.idle"
name: "db_connections_idle"
- path: "cache.hit_rate"
name: "cache_hit_percent"
calculate: "value * 100"
# Redis metrics
- name: redis
source:
type: url
url: "http://localhost:9121/metrics"
format: prometheus
metrics:
- match: "redis_connected_clients"
name: "redis_clients"
- match: "redis_memory_used_bytes"
name: "redis_memory_mb"
calculate: "value / 1024 / 1024"
filter:
include:
- "^redis_.*"
exclude:
- ".*_total$"Error Log Monitoring
Grep nginx and Docker container logs for error lines, with each declared consumer tailing independently:
server:
port: 9100
consumers:
- logstyx-prod
system:
enabled: true
metrics:
- cpu_usage_percent
- ram_usage_percent
scrapers:
- name: nginx_errors
source:
type: file
path: "/var/log/nginx/error.log"
format: log
max_lines: 50
metrics:
- match: "count"
name: "nginx_error_count"
- match: "lines"
name: "nginx_error_lines"
- name: docker_errors
source:
type: command
command: "docker logs --since {{since}} --tail 1000 my-container 2>&1"
format: log
max_lines: 50
metrics:
- match: "count"
name: "docker_error_count"
- match: "lines"
name: "docker_error_lines"curl "http://localhost:9100/metrics?callerId=logstyx-prod"Authentication
Server Side
Enable authentication by setting a secret:
server:
secret: "your-secure-secret-key"Client Side
Clients must include authentication headers:
Bash example:
#!/bin/bash
SECRET="your-secure-secret-key"
TIMESTAMP=$(date +%s)
SIGNATURE=$(echo -n "$TIMESTAMP" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
curl -H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE" \
http://localhost:9100/metricsPython example:
import time
import hmac
import hashlib
import requests
SECRET = "your-secure-secret-key"
timestamp = str(int(time.time()))
signature = hmac.new(
SECRET.encode(),
timestamp.encode(),
hashlib.sha256
).hexdigest()
response = requests.get(
"http://localhost:9100/metrics",
headers={
"X-Timestamp": timestamp,
"X-Signature": signature
}
)Logstyx Integration:
When creating a probe in Logstyx, select Probestyx as the authentication type and enter your secret.
Validation
After creating or modifying config.yaml, validate the syntax:
Check YAML syntax:
python3 -c "import yaml; yaml.safe_load(open('config.yaml'))"Test Probestyx:
# Start Probestyx manually to check for errors
/opt/probestyx/probestyx
# Expected: Server starts without errors
# Server listening on :9100Troubleshooting
Invalid YAML Syntax
Error: yaml: unmarshal errors
- Check indentation (use spaces, not tabs)
- Ensure colons have spaces after them:
key: value - Validate with a YAML linter
Metrics Not Appearing
- Verify metric names in
metricslist are correct - Check scraper
sourceURL is accessible - Review logs for parsing errors
- Test source URL manually:
curl <url>
Authentication Failing
- Ensure secret matches on both server and client
- Check for extra spaces in secret value
- Verify timestamp is current (within 5 minutes)
Next Steps
- System Metrics - Reference of all system metrics
- Custom Metrics - Create custom metric scrapers
- Scrapers - Advanced scraper configuration
- Integration - Connect to Logstyx