Debug logging
The stack writes its own diagnostic messages: errors, informational notes and a small amount of debug
output. Most of them explain why a call returned false. No BACnetStack_* function reports a
reason in its return value, so the debug log is where you find out that a device already exists, an
instance number is out of range, or a feature was not compiled into your build. Messages are also
written while the stack processes traffic, for example when a received message cannot be decoded.
Register a logger as the first thing your application does, and keep it registered while you develop.
Turning it on
Section titled “Turning it on”Build option
Section titled “Build option”Define STACK_OPTION_API_DEBUG_LOGGING in your build settings (it is commented out in
CASBACnetStackOptions.h). The prebuilt libraries in a release, the npm package and the stack’s
default build settings (CIBuildSettingsDefaults.h, and CMake’s default options) already define it. If you build with your own option
set, define it yourself. The configuration presets (STACK_OPTION_TARGET_FULL and
STACK_OPTION_TARGET_EMBEDDED) do not turn it on; see Configuration presets.
STACK_OPTION_STRING_LITERALS is not required, but with it some messages carry more detail, at the
cost of flash memory.
In a build without the option, the logging calls are compiled out. Registering a callback is accepted but the callback is never called, and failures are silent.
Runtime call
Section titled “Runtime call”DllExport void BACnetStack_RegisterCallbackLogDebugMessage(void(*FPCallbackLogDebugMessage) (const char* message, const uint16_t messageLength, const uint8_t messageType));The callback type is FPCallbackLogDebugMessage, declared in BACnetCallbackInterface.h:
typedef void(*FPCallbackLogDebugMessage) (const char* message, const uint16_t messageLength, const uint8_t messageType);| Situation | What happens to each message |
|---|---|
| Option defined, no callback registered | Printed to stdout with printf() |
| Option defined, callback registered | Passed to your callback only |
Option defined, NULL registered |
Printed to stdout again |
| Option not defined | Not produced |
To silence the log in a build with the option, register a callback that does nothing.
What the callback receives
Section titled “What the callback receives”| Parameter | Meaning |
|---|---|
message |
The message text. It is NUL-terminated and ends with a newline. The buffer belongs to the stack and is valid only for the duration of the call; copy it if you need it later. |
messageLength |
The number of bytes before the NUL, including the trailing newline. |
messageType |
0 error, 1 informational, 2 debug. |
Each message has this form:
::<function>() in file: <source file>(<line>) - <Error: | FYI: | DEBUG: ><text>The function name is what the compiler reports for the function that logged: a qualified name such
as CASBACnetStack::BACnetInterface::AddDevice with MSVC, or just AddDevice with GCC and Clang.
The file is the source path as the compiler saw it, which may be absolute. The message text after the prefix is
truncated at 255 characters.
Treat the text as diagnostics for a person to read. The wording, file names and line numbers change between releases, so do not parse it to drive application logic; use the function return values for that.
The callback is called synchronously from wherever the stack logs: inside BACnetStack_Tick(), or
inside any BACnetStack_* call your application makes, on the thread that made the call.
Example
Section titled “Example”The callback writes each message to stderr with its severity. The example then adds the device
twice; the second call fails, and the stack logs the reason.
#include <stdio.h>#include <stdint.h>#include "CASBACnetStackDLL.h"
void CallbackLogDebugMessage(const char* message, const uint16_t messageLength, const uint8_t messageType){ static const char* const severityNames[] = { "error", "info", "debug" }; const char* severityName = (messageType <= 2) ? severityNames[messageType] : "unknown";
/* The message already ends with a newline. */ fprintf(stderr, "[bacnet %s] %.*s", severityName, (int)messageLength, message);}
int main(void){ /* Register the logger before any other call, so their failures are explained too. */ BACnetStack_RegisterCallbackLogDebugMessage(CallbackLogDebugMessage);
if (!BACnetStack_AddDevice(389001)) { return 1; }
/* Fails: the stack holds one main device. The reason goes to CallbackLogDebugMessage. */ if (!BACnetStack_AddDevice(389001)) { fprintf(stderr, "Second AddDevice failed, as expected.\n"); }
/* ... register the other callbacks and call BACnetStack_Tick() in your loop ... */ return 0;}Built with GCC, the second call produces output like this (the path and line number depend on your build and release):
[bacnet error] ::AddDevice() in file: source/BACnetInterface.cpp(953) - Error: device already exists. If attempting to add a virtual device, use AddDeviceToVirtualNetwork insteadSecond AddDevice failed, as expected.The message names the export BACnetStack_AddDeviceToVirtualNetwork() by its internal name.
Practical notes
Section titled “Practical notes”- Register first. Configuration calls such as adding devices and objects report their failures only through this callback, so register it before them.
- Keep the callback quick. It runs inside the stack’s own processing, including during
BACnetStack_Tick(). Write to a buffer, a queue or an existing logging framework rather than doing slow I/O inline. - Filter in production. Almost every message is an error (
messageType0). Informational messages cover things such as features that were not compiled in and requests the stack adjusted, and debug messages are rare. Keeping errors only is a reasonable production setting. Because some errors are triggered by messages received from the network, a misbehaving or hostile peer can produce many of them; rate-limit or cap what you keep. - Mind what the text reveals. Messages contain the source paths the stack was compiled from and internal function names. Keep them in your own logs rather than showing them to end users.
- Code size. The option adds the message strings and formatting code to the build. On a flash-constrained target you may leave it out of production firmware, at the cost of silent failures; keep it on for development builds.
- Reset.
BACnetStack_Reset()clears every other callback but leaves this one registered. - What your build contains. If a call fails because a feature was not compiled in, the log says
so.
BACnetStack_GetInfo()reports a summary of the features your build includes.
