Custom Metrics
Beyond system metrics, Probestyx can collect custom metrics from your applications, APIs, and external sources. This guide shows you how to create custom metric collectors.
Overview
Custom metrics are defined in the scrapers section of your configuration:
scrapers:
- name: my_custom_metrics
source:
type: url|file|command
url: "..."
format: json|prometheus|raw|log
metrics:
- path: "..."
name: "output_name"Scraping from Applications
JSON API Metrics
Most modern applications expose metrics as JSON. Here's how to collect them:
Your application endpoint (http://localhost:8080/metrics):
{
"http": {
"requests": {
"total": 150000,
"errors": 42
},
"response_time_ms": 125.5
},
"database": {
"queries": {
"total": 200000,
"slow": 15
},
"pool": {
"active": 8,
"idle": 2,
"max": 10
}
},
"cache": {
"hits": 45000,
"misses": 5000
}
}Probestyx configuration:
scrapers:
- name: app_metrics
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_ms"
name: "http_response_time_ms"
- path: "database.pool.active"
name: "db_connections_active"
- path: "database.pool.idle"
name: "db_connections_idle"
- path: "cache.hits"
name: "cache_hits"
- path: "cache.misses"
name: "cache_misses"Calculate cache hit rate:
metrics:
- path: "cache.hits"
name: "cache_hits"
- path: "cache.misses"
name: "cache_misses"
# Note: Complex calculations must be done in post-processing
# Probestyx only supports simple value transformationsNested JSON Objects
For deeply nested JSON, use dot notation:
Source:
{
"services": {
"api": {
"v1": {
"health": "healthy",
"requests": 1000
},
"v2": {
"health": "healthy",
"requests": 5000
}
}
}
}Configuration:
metrics:
- path: "services.api.v1.requests"
name: "api_v1_requests"
- path: "services.api.v2.requests"
name: "api_v2_requests"JSON Arrays
Extract values from arrays using index notation:
Source:
{
"servers": [
{"name": "web-01", "load": 0.5},
{"name": "web-02", "load": 0.7}
]
}Configuration:
metrics:
- path: "servers.0.load"
name: "web01_load"
- path: "servers.1.load"
name: "web02_load"Scraping from Files
Local JSON Files
Read metrics from files written by your application:
File: /var/app/stats.json
{
"timestamp": 1704067200,
"metrics": {
"queue_size": 42,
"processed_items": 1500,
"error_count": 3
}
}Configuration:
scrapers:
- name: app_stats
source:
type: file
path: "/var/app/stats.json"
format: json
metrics:
- path: "metrics.queue_size"
name: "queue_size"
- path: "metrics.processed_items"
name: "processed_items"
- path: "metrics.error_count"
name: "error_count"Prometheus Format Files
Many exporters write metrics to files:
File: /var/node_exporter/metrics.prom
# TYPE node_memory_MemTotal_bytes gauge
node_memory_MemTotal_bytes 16777216000
# TYPE node_memory_MemAvailable_bytes gauge
node_memory_MemAvailable_bytes 8388608000
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 12345.67Configuration:
scrapers:
- name: node_metrics
source:
type: file
path: "/var/node_exporter/metrics.prom"
format: prometheus
metrics:
- match: "node_memory_MemTotal_bytes"
name: "memory_total_mb"
calculate: "value / 1024 / 1024"
- match: "node_memory_MemAvailable_bytes"
name: "memory_available_mb"
calculate: "value / 1024 / 1024"Plain Text Files
Parse custom text formats using regex:
File: /var/sensors/readings.txt
temperature=23.5
humidity=65.2
pressure=1013.25
battery_voltage=3.7
signal_strength=-45Configuration:
scrapers:
- name: sensor_data
source:
type: file
path: "/var/sensors/readings.txt"
format: raw
pattern: '(\w+)=([0-9.-]+)' # key=value pattern
metrics:
- match: "temperature"
name: "temperature_celsius"
- match: "humidity"
name: "humidity_percent"
- match: "pressure"
name: "pressure_hpa"
- match: "battery_voltage"
name: "battery_volts"Custom Patterns
Define custom regex patterns for specific formats:
File: /var/log/app.log (log-based metrics)
[2024-01-01 12:00:00] Requests processed: 1500
[2024-01-01 12:00:00] Average latency: 125ms
[2024-01-01 12:00:00] Active users: 42Configuration:
scrapers:
- name: log_metrics
source:
type: file
path: "/var/log/app.log"
format: raw
pattern: '(\w+\s\w+):\s+([0-9]+)' # "Label: 123" pattern
metrics:
- match: "Requests processed"
name: "requests_processed"
- match: "Active users"
name: "active_users"Scraping from External Services
Prometheus Exporters
Collect from standard Prometheus exporters:
scrapers:
# Node Exporter
- 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_.*"
- "^node_cpu_.*"
# Redis Exporter
- name: redis_exporter
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_.*"Database Metrics
PostgreSQL stats endpoint:
scrapers:
- name: postgres_stats
source:
type: url
url: "http://localhost:9187/metrics"
format: prometheus
metrics:
- match: "pg_stat_database_numbackends"
name: "pg_active_connections"
- match: "pg_stat_database_tup_fetched"
name: "pg_rows_fetched"Message Queue Metrics
RabbitMQ management API:
scrapers:
- name: rabbitmq
source:
type: url
url: "http://localhost:15672/api/overview"
format: json
metrics:
- path: "queue_totals.messages"
name: "rabbitmq_total_messages"
- path: "queue_totals.messages_ready"
name: "rabbitmq_messages_ready"
- path: "object_totals.connections"
name: "rabbitmq_connections"Container Metrics
Docker stats (requires wrapper script):
Create a script that outputs JSON:
#!/bin/bash
# /usr/local/bin/docker-stats.sh
docker stats --no-stream --format '{"container":"{{.Name}}","cpu":"{{.CPUPerc}}","mem":"{{.MemPerc}}"}' | jq -s '.'Configuration:
scrapers:
- name: container_stats
source:
type: file
path: "/tmp/docker-stats.json" # Output of script
format: json
metrics:
- path: "0.cpu"
name: "container_1_cpu"
- path: "0.mem"
name: "container_1_mem"TIP
The wrapper-script-plus-cron-plus-file dance above exists because there was no way to feed a command's output straight into probestyx. type: command (see Scrapers → Command Sources) removes that need for sources like this — probestyx runs the command itself on every scrape.
Container Logs (Docker error lines)
To list error lines out of a container's logs instead of resource stats, skip the wrapper script and point a command source directly at docker logs, using format: log:
scrapers:
- 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" is substituted with the timestamp of this scraper's last successful run, so each scrape only asks Docker for what's new — see Scrapers → Log Parser for the full tailing/ behavior, and how to give multiple independent monitoring backends their own view via server.consumers + ?callerId=.
Metric Transformations
Unit Conversions
Convert between units using calculations:
metrics:
# Bytes to Megabytes
- path: "memory.bytes"
name: "memory_mb"
calculate: "value / 1024 / 1024"
# Bytes to Gigabytes
- path: "disk.bytes"
name: "disk_gb"
calculate: "value / 1024 / 1024 / 1024"
# Seconds to Milliseconds
- path: "response.time"
name: "response_time_ms"
calculate: "value * 1000"
# Milliseconds to Seconds
- path: "duration.ms"
name: "duration_seconds"
calculate: "value / 1000"
# Celsius to Fahrenheit
- path: "temperature.celsius"
name: "temperature_fahrenheit"
calculate: "value * 1.8 + 32"
# Ratio to Percentage
- path: "cache.hit_rate"
name: "cache_hit_percent"
calculate: "value * 100"Negative Values
Handle metrics that can be negative:
metrics:
# Temperature (can be negative)
- path: "outdoor.temperature"
name: "outdoor_temp_celsius"
# Profit/Loss
- path: "financials.profit"
name: "profit_usd"Scaling
Scale values up or down:
metrics:
# Scale down large numbers
- path: "requests.total"
name: "requests_thousands"
calculate: "value / 1000"
# Normalize percentage already in 0-100 range
- path: "cpu.usage" # Already 0-100
name: "cpu_percent"Filtering Metrics
Include Patterns
Only collect specific metrics:
scrapers:
- name: selective_metrics
source:
type: url
url: "http://localhost:9100/metrics"
format: prometheus
filter:
include:
- "^node_memory_.*" # Only memory metrics
- "^node_cpu_.*" # Only CPU metrics
- "^node_network_.*" # Only network metricsExclude Patterns
Remove unwanted metrics:
scrapers:
- name: filtered_metrics
source:
type: url
url: "http://localhost:9100/metrics"
format: prometheus
filter:
exclude:
- ".*_bucket$" # Remove histogram buckets
- ".*_sum$" # Remove histogram sums
- ".*_count$" # Remove histogram counts
- ".*internal.*" # Remove internal metrics
- ".*debug.*" # Remove debug metricsCombined Filtering
Use both include and exclude:
scrapers:
- name: precise_filtering
source:
type: url
url: "http://localhost:9100/metrics"
format: prometheus
filter:
include:
- "^node_.*" # Only node_exporter metrics
exclude:
- ".*_bucket$" # But not histograms
- ".*_info$" # Or info metricsAdvanced Examples
Multi-Endpoint Application
Collect from multiple endpoints of the same app:
scrapers:
# Main API metrics
- name: api_metrics
source:
type: url
url: "http://localhost:8080/metrics"
format: json
metrics:
- path: "requests.total"
name: "api_requests_total"
# Health check endpoint
- name: api_health
source:
type: url
url: "http://localhost:8080/health"
format: json
metrics:
- path: "database.connected"
name: "db_connected"
- path: "cache.connected"
name: "cache_connected"
# Admin endpoint
- name: api_admin
source:
type: url
url: "http://localhost:8080/admin/stats"
format: json
metrics:
- path: "worker_pool.active"
name: "workers_active"Microservices
Monitor multiple services:
scrapers:
# Auth Service
- name: auth_service
source:
type: url
url: "http://auth:8080/metrics"
format: json
metrics:
- path: "auth.logins.total"
name: "auth_logins_total"
- path: "auth.tokens.active"
name: "auth_tokens_active"
# User Service
- name: user_service
source:
type: url
url: "http://user:8080/metrics"
format: json
metrics:
- path: "users.active"
name: "users_active"
- path: "users.sessions"
name: "user_sessions"
# Order Service
- name: order_service
source:
type: url
url: "http://order:8080/metrics"
format: json
metrics:
- path: "orders.pending"
name: "orders_pending"
- path: "orders.processed"
name: "orders_processed"Hybrid Sources
Mix URLs and files:
scrapers:
# Remote API
- name: remote_api
source:
type: url
url: "https://api.example.com/metrics"
format: json
metrics:
- path: "status.active_users"
name: "remote_active_users"
# Local file from cron job
- name: batch_job_stats
source:
type: file
path: "/var/batch/last_run.json"
format: json
metrics:
- path: "processed_records"
name: "batch_records_processed"
- path: "duration_seconds"
name: "batch_duration"
# Local Prometheus exporter
- name: local_exporter
source:
type: url
url: "http://localhost:9200/metrics"
format: prometheus
metrics:
- match: "custom_metric.*"Best Practices
Naming Conventions
Use clear, consistent metric names:
# Good
metrics:
- path: "requests.total"
name: "http_requests_total" # Clear and specific
- path: "memory.used"
name: "app_memory_used_bytes" # Includes unit
- path: "queue.size"
name: "rabbitmq_queue_messages" # Includes source
# Avoid
metrics:
- path: "requests.total"
name: "total" # Too vague
- path: "memory.used"
name: "mem" # AmbiguousKeep It Simple
Don't over-collect:
# Good - focused collection
metrics:
- path: "cache.hit_rate"
name: "cache_hit_rate"
- path: "cache.total_keys"
name: "cache_total_keys"
# Avoid - collecting everything
metrics:
- path: "cache.hit_rate"
name: "cache_hit_rate"
- path: "cache.miss_rate"
name: "cache_miss_rate"
- path: "cache.total_hits"
name: "cache_total_hits"
- path: "cache.total_misses"
name: "cache_total_misses"
# ... and 20 more metrics you don't needUse Filtering
Reduce payload size:
scrapers:
- name: focused_metrics
source:
type: url
url: "http://localhost:9100/metrics"
format: prometheus
filter:
include:
- "^node_cpu_.*" # Only what you need
- "^node_memory_.*"
exclude:
- ".*_bucket$" # Remove histogram overheadDocument Your Metrics
Add comments to your config:
scrapers:
# Application performance metrics
# Updated every 60 seconds by the app
- name: app_performance
source:
type: file
path: "/var/app/perf.json"
format: json
metrics:
# Request latency in milliseconds
- path: "latency.p95"
name: "app_latency_p95_ms"
# Active database connections
- path: "database.connections.active"
name: "app_db_connections_active"Troubleshooting
Metric Not Appearing
- Check the source is accessible
- Verify the path/match is correct
- Test the source manually
- Check Probestyx logs
# Test JSON endpoint
curl http://localhost:8080/metrics | jq '.your.metric.path'
# Test file exists and is readable
cat /var/app/stats.json | jq '.your.metric.path'
# Check Probestyx logs
sudo journalctl -u probestyx -fWrong Values
- Verify calculation is correct
- Check source value type (string vs number)
- Ensure units are what you expect
Performance Issues
- Reduce number of scrapers
- Use filtering to limit metrics
- Scrapers aren't cached (
cache_ttlonly applies to system metrics) — poll/metricsless often if a scraper's source is slow - Check source response times
Next Steps
- Scrapers - Advanced scraper configuration
- Integration - Send metrics to Logstyx