Observability
The Modbus library provides comprehensive observability through OpenTelemetry metrics, distributed tracing, and structured logging.
Metrics
Setup
Register Modbus instrumentation with your OpenTelemetry configuration:
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddModbusInstrumentation()
.AddPrometheusExporter());All metrics are exported via the Voltimax.Edge.Modbus meter.
Available Metrics
Connection Metrics
| Metric | Type | Description | Tags |
|---|---|---|---|
modbus_connection_attempts_total | Counter | Total connection attempts | target |
modbus_connection_failures_total | Counter | Failed connection attempts | target |
modbus_connection_duration_seconds | Histogram | Time to establish connection | target, port, status |
modbus_clients_created_total | Counter | Total clients created | target |
modbus_clients_active | UpDownCounter | Currently active clients | target |
modbus_reconnection_attempts_total | Counter | Reconnection attempts | target |
Operation Metrics
| Metric | Type | Description | Tags |
|---|---|---|---|
modbus_operation_duration_seconds | Histogram | Operation execution time | target, operation, slave_address |
modbus_operation_failures_total | Counter | Failed operations | target, operation, slave_address |
modbus_registers_read_count | Histogram | Registers read per operation | target, slave_address |
modbus_registers_written_count | Histogram | Registers written per operation | target, slave_address |
Example Queries
Prometheus queries:
# Average operation duration by device
rate(modbus_operation_duration_seconds_sum[5m])
/ rate(modbus_operation_duration_seconds_count[5m])
# Operation failure rate per device
rate(modbus_operation_failures_total[5m])
# 95th percentile operation latency
histogram_quantile(0.95,
rate(modbus_operation_duration_seconds_bucket[5m]))
# Active client count
modbus_clients_active
# Connection success rate
rate(modbus_connection_attempts_total[5m]) - rate(modbus_connection_failures_total[5m])
/ rate(modbus_connection_attempts_total[5m])Grafana dashboard panels:
{
"title": "Modbus Operation Duration",
"targets": [
{
"expr": "rate(modbus_operation_duration_seconds_sum[5m]) / rate(modbus_operation_duration_seconds_count[5m])",
"legendFormat": "{{target}} - {{operation}}"
}
]
}Distributed Tracing
Setup
Register Modbus tracing with OpenTelemetry:
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddModbusInstrumentation()
.AddOtlpExporter());All traces use the Voltimax.Edge.Modbus activity source.
Span Structure
Connect operation:
Connect [2.3s]
└─ TCP Connection [2.2s]Read operation:
ReadHoldingRegisters [45ms]
├─ Modbus Request [40ms]
└─ NumericConversion [<1ms]Write operation:
WriteMultipleRegisters [52ms]
└─ Modbus Request [52ms]Span Attributes
Connection Spans
| Attribute | Description | Example |
|---|---|---|
modbus.connection.target | Host or IP address | 192.168.1.100 |
modbus.connection.port | TCP port | 502 |
modbus.connection.method | Connection type | hostname, ip_address |
Operation Spans
| Attribute | Description | Example |
|---|---|---|
modbus.operation | Operation type | read_holding, write_coil |
modbus.slave_address | Device unit ID | 1 |
modbus.start_address | First register/coil | 0 |
modbus.register_count | Number of registers | 2 |
modbus.function_code | Modbus function code | 03, 16 |
Example Trace
[Span: Gateway Polling] 12.5s
├─ [Span: ReadHoldingRegisters] 45ms
│ Attributes:
│ modbus.operation: read_holding
│ modbus.slave_address: 1
│ modbus.start_address: 0
│ modbus.register_count: 2
│ modbus.connection.target: 192.168.1.100
├─ [Span: ReadHoldingRegisters] 42ms
└─ [Span: WriteMultipleCoils] 38msStructured Logging
Setup
builder.Services.AddLogging(logging =>
{
logging.AddConsole();
logging.AddJsonConsole(); // For production
logging.SetMinimumLevel(LogLevel.Information);
});Log Levels
| Level | Events |
|---|---|
| Trace | Register-level data (verbose) |
| Debug | Connection details, endpoints |
| Information | Connection lifecycle, successful operations |
| Warning | Reconnection attempts, retries, circuit breaker events |
| Error | Operation failures, connection errors |
| Critical | Unrecoverable errors |
Log Scopes
All log entries include scopes with contextual information:
{
"Timestamp": "2026-02-16T10:30:45.123Z",
"Level": "Information",
"Message": "Successfully connected to Modbus device",
"Target": "192.168.1.100",
"Port": 502,
"Operation": "Connect",
"DurationMs": 245
}Key Log Events
Connection Events
// Connection established
[Information] Successfully connected to target={Target} port={Port} in {DurationMs}ms
// Connection failed
[Error] Failed to connect to target={Target} port={Port} after {DurationMs}ms
Exception: System.Net.Sockets.SocketException: Connection refused
// Connection timeout
[Warning] Connection to target={Target} port={Port} timed out after {TimeoutMs}msReconnection Events
// Reconnection started
[Warning] Connection lost to target={Target} port={Port}, attempting reconnect
// Reconnection attempt
[Information] Starting reconnection attempt {Attempt}/{MaxAttempts} for target={Target}
// Reconnection succeeded
[Information] Reconnection successful for target={Target} after {Attempts} attemptsCircuit Breaker Events
// Circuit opened
[Warning] Circuit breaker opened for target={Target}, break_duration={Duration}s
// Circuit half-open
[Information] Circuit breaker half-open for target={Target}, testing recovery
// Circuit closed
[Information] Circuit breaker closed for target={Target}, normal operation resumedOperation Events
// Operation started (Debug)
[Debug] Starting {Operation} at address={StartAddress} count={RegisterCount} slave={SlaveAddress}
// Operation completed (Trace)
[Trace] Completed {Operation} in {DurationMs}ms
// Operation failed
[Error] {Operation} failed for target={Target} slave={SlaveAddress}
Exception: System.InvalidOperationException: Illegal data addressFiltering Logs
Reduce verbosity in production:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Voltimax.Edge.Modbus": "Warning"
}
}
}Debug connection issues:
{
"Logging": {
"LogLevel": {
"Voltimax.Edge.Modbus": "Debug"
}
}
}Trace register data:
{
"Logging": {
"LogLevel": {
"Voltimax.Edge.Modbus": "Trace"
}
}
}Dashboard Examples
Grafana Dashboard
Sample dashboard configuration for monitoring Modbus clients:
Panels:
- Active Connections -
modbus_clients_active - Operation Latency (p95) -
histogram_quantile(0.95, modbus_operation_duration_seconds_bucket) - Failure Rate -
rate(modbus_operation_failures_total[5m]) - Reconnection Events -
rate(modbus_reconnection_attempts_total[5m]) - Registers per Second -
rate(modbus_registers_read_count_sum[5m])
Application Insights
Query successful operations:
traces
| where customDimensions.CategoryName == "Voltimax.Edge.Modbus"
| where message contains "Successfully connected"
| summarize count() by bin(timestamp, 5m), tostring(customDimensions.Target)Query operation failures:
traces
| where customDimensions.CategoryName == "Voltimax.Edge.Modbus"
| where severityLevel >= 3 // Error and Critical
| project timestamp, message, customDimensions.Target, customDimensions.OperationAlerting
Sample Alerts
High failure rate:
alert: ModbusHighFailureRate
expr: rate(modbus_operation_failures_total[5m]) > 0.1
for: 5m
annotations:
summary: "High Modbus operation failure rate for {{ $labels.target }}"Circuit breaker open:
alert: ModbusCircuitBreakerOpen
expr: modbus_circuit_breaker_state == 1
for: 1m
annotations:
summary: "Modbus circuit breaker open for {{ $labels.target }}"Operation latency:
alert: ModbusHighLatency
expr: histogram_quantile(0.95, rate(modbus_operation_duration_seconds_bucket[5m])) > 1.0
for: 5m
annotations:
summary: "High Modbus operation latency for {{ $labels.target }}"