Skip to content
Version 6.1.0
Contact Us

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.

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.

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.

Your application calls BACnetStack_AddDevice(389001) a second time. The stack returns false, and calls the debug message callback with “Error: device already exists”. Messages are typed 0 error, 1 informational or 2 debug. The return value says that a call failed; the debug message says why.

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.

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 instead
Second AddDevice failed, as expected.

The message names the export BACnetStack_AddDeviceToVirtualNetwork() by its internal name.

  • 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 (messageType 0). 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.