Logging
The library uses the tracing crate for contextual logging. Rust users should refer to the tracing documentation for details.
The bindings provide a single callback method for logging messages. Configurable options include:
LogLevelthat controls which messages are generated- How and if to print the time as part of the message
- Line or JSON based output
The LogLevel defaults to Info, recording Info, Warn, and Error messages. Use Debug and Trace levels only when debugging runtime issues.
Protocol decoding is always logged at the Info level and is configured separately for each client channel and server.
Configuration
- Rust
- C
- C++
- Java
- C#
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.init();
// callback which will receive log messages: each one already ends in a newline
void on_log_message(rodbus_log_level_t level, const char *message, void *ctx)
{
printf("%s", message);
}
rodbus_logger_t get_logger(void)
{
return (rodbus_logger_t){
.on_message = &on_log_message,
.on_destroy = NULL, // no context to free
.ctx = NULL,
};
}
// initialize logging with the default configuration
rodbus_param_error_t logging_err = rodbus_configure_logging(rodbus_logging_config_init(), get_logger());
if (logging_err) {
printf("unable to configure logging: %s\n", rodbus_param_error_to_string(logging_err));
return EXIT_FAILURE;
}
// receives the log messages: each one already ends in a newline
class Logger : public rodbus::Logger {
void on_message(rodbus::LogLevel level, const char *message) override
{
std::cout << message;
}
};
std::unique_ptr<rodbus::Logger> create_logger()
{
return std::make_unique<Logger>();
}
// initialize logging with the default configuration
rodbus::Logging::configure(rodbus::LoggingConfig(), create_logger());
private static class ConsoleLogger implements Logger {
@Override
public void onMessage(LogLevel level, String message) {
// the message already carries the level and ends in a newline
System.out.print(message);
}
}
// initialize logging with the default configuration
Logging.configure(new LoggingConfig(), new ConsoleLogger());
class ConsoleLogger : ILogger
{
public void OnMessage(LogLevel level, string message)
{
// the message already carries the level and ends in a newline
Console.Write(message);
}
}
// initialize logging with the default configuration
Logging.Configure(
new LoggingConfig(),
new ConsoleLogger()
);
The bindings use the tracing_subscriber crate internally. If you use Rust, you can pick which tracing backend to use.
Example output
Logs include contextual metadata so you can:
- Determine which communication session produced the message
- Understand what state the software was in when the event occurred
Here is the example server, with data values, MBAP headers and physical-layer lengths decoded, answering one read from a client that then disconnects:
2026-10-23T23:03:53.225466Z INFO Modbus-Server-TCP{listen=127.0.0.1:502}: accepted connection from: 127.0.0.1:34736 - assigned session id: 0
2026-10-23T23:03:54.222005Z INFO Modbus-Server-TCP{listen=127.0.0.1:502}:Session{id=0 remote=127.0.0.1:34736}: PHYS RX - 12 bytes
2026-10-23T23:03:54.222256Z INFO Modbus-Server-TCP{listen=127.0.0.1:502}:Session{id=0 remote=127.0.0.1:34736}: MBAP RX - tx_id: 0x00 unit: 0x01 len: 6
2026-10-23T23:03:54.222336Z INFO Modbus-Server-TCP{listen=127.0.0.1:502}:Session{id=0 remote=127.0.0.1:34736}: PDU RX - READ DISCRETE INPUTS (0x02) start: 0x0000 qty: 5
2026-10-23T23:03:54.222379Z INFO Modbus-Server-TCP{listen=127.0.0.1:502}:Session{id=0 remote=127.0.0.1:34736}: PDU TX - READ DISCRETE INPUTS (0x02) start: 0x0000 qty: 5
idx: 0x0000 value: 0
idx: 0x0001 value: 0
idx: 0x0002 value: 0
idx: 0x0003 value: 0
idx: 0x0004 value: 0
2026-10-23T23:03:54.222406Z INFO Modbus-Server-TCP{listen=127.0.0.1:502}:Session{id=0 remote=127.0.0.1:34736}: MBAP TX - tx_id: 0x00 unit: 0x01 len: 4
2026-10-23T23:03:54.222425Z INFO Modbus-Server-TCP{listen=127.0.0.1:502}:Session{id=0 remote=127.0.0.1:34736}: PHYS TX - 10 bytes
2026-10-23T23:03:55.224061Z INFO Modbus-Server-TCP{listen=127.0.0.1:502}:Session{id=0 remote=127.0.0.1:34736}: PHYS RX - 0 bytes
2026-10-23T23:03:55.224151Z WARN Modbus-Server-TCP{listen=127.0.0.1:502}:Session{id=0 remote=127.0.0.1:34736}: session error: unexpected end of file
2026-10-23T23:03:55.224340Z INFO Modbus-Server-TCP{listen=127.0.0.1:502}:Session{id=0 remote=127.0.0.1:34736}: session shutdown
Protocol decoding
Set the DecodeLevel when you create a client channel or a server, and change it at runtime with set_decode_level. It
controls decoding for each layer of the protocol stack:
- Application layer (PDU): the function code, the request or response header, and the data values
- Frame (ADU): the MBAP header on TCP and TLS, or the serial frame on RTU
- Physical layer: the length and bytes of data sent and received
See the language-specific API documentation for the meaning of each enumeration value.
Protocol decoding is always output at the Info log level and can be too verbose for a production system. When you're debugging a communication issue, try application-layer decoding first.