From 0ff12d0d312fa2dc68de264232158e57eb8916db Mon Sep 17 00:00:00 2001 From: Matt Thiffault Date: Wed, 11 Mar 2026 16:03:36 -0400 Subject: [PATCH] Add logging abstractions to MeshCore This change introduces a `Logger` interface class which contains a small but useful set of logging functions that I believe cover all of the logging cases currently in the repository. There are header files for debug, packet, and bridge logging. Each header contains an extern declaration for a logger with type `Logger&` (eg. `extern Logger& debugLog;`). By default, the global `::mesh::debugLog` is set to an implementation which calls the Arduino `Serial` instance in most cases. `::mesh::packetLog` and `::mesh::bridgeLog` are set to be references to `::mesh::debugLog`. Any subset of the loggers can be overriden by defining one or more of CUSTOM_DEBUG_LOG, CUSTOM_PACKET_LOG, and CUSTOM_BRIDGE_LOG in one's build configuration and then providing an alternate definition of the corresponding global logger reference. --- src/BridgeLogger.cpp | 13 ++++++ src/BridgeLogger.h | 14 +++++++ src/DebugLogger.cpp | 81 ++++++++++++++++++++++++++++++++++++ src/DebugLogger.h | 16 ++++++++ src/Logger.h | 97 ++++++++++++++++++++++++++++++++++++++++++++ src/PacketLogger.cpp | 13 ++++++ src/PacketLogger.h | 14 +++++++ 7 files changed, 248 insertions(+) create mode 100644 src/BridgeLogger.cpp create mode 100644 src/BridgeLogger.h create mode 100644 src/DebugLogger.cpp create mode 100644 src/DebugLogger.h create mode 100644 src/Logger.h create mode 100644 src/PacketLogger.cpp create mode 100644 src/PacketLogger.h diff --git a/src/BridgeLogger.cpp b/src/BridgeLogger.cpp new file mode 100644 index 000000000..f2078560d --- /dev/null +++ b/src/BridgeLogger.cpp @@ -0,0 +1,13 @@ +#include "BridgeLogger.h" + +#ifndef CUSTOM_BRIDGE_LOG +#include "DebugLogger.h" + +namespace mesh { + +// The default logger for bridge logging is just the debug logger. +Logger& bridgeLog = debugLog; + +} // namespace mesh + +#endif diff --git a/src/BridgeLogger.h b/src/BridgeLogger.h new file mode 100644 index 000000000..ba9ec6628 --- /dev/null +++ b/src/BridgeLogger.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Logger.h" + +namespace mesh { + +/// @brief The global logging instance meant for logs from bridge implementations +/// +/// By default this is set to debugLog, but can be overriden by defining +/// CUSTOM_BRIDGE_LOG in a build configuration and then providing an +/// alternative global definition (eg. `Logger& bridgeLog = myCustomLogger;`) +extern Logger& bridgeLog; + +} // namespace mesh diff --git a/src/DebugLogger.cpp b/src/DebugLogger.cpp new file mode 100644 index 000000000..0603a6d59 --- /dev/null +++ b/src/DebugLogger.cpp @@ -0,0 +1,81 @@ +#include "DebugLogger.h" + +#ifndef CUSTOM_DEBUG_LOG +#include +#include + +namespace mesh { + +class DefaultDebugLogger : public Logger { +public: + DefaultDebugLogger() {} + + size_t write(uint8_t b) override { return Serial.write(b); } + + size_t write(const uint8_t *buffer, size_t size) override { return Serial.write(buffer, size); } + + size_t printf(const char *format, ...) override __attribute__((format(printf, 2, 3))) { + va_list args; + va_start(args, format); + size_t written = vprintf(format, args); + va_end(args); + return written; + } + + size_t print(char c) override { return Serial.print(c); } + + size_t print(const char str[]) override { return Serial.print(str); } + + size_t printlnf(const char *format, ...) override __attribute__((format(printf, 2, 3))) { + va_list args; + va_start(args, format); + size_t written = vprintf(format, args); + va_end(args); + written += Serial.println(); + return written; + } + + size_t println(void) override { return Serial.println(); } + + size_t println(char c) override { return Serial.println(c); } + + size_t println(const char str[]) override { return Serial.println(str); } + + void printHex(const uint8_t* src, size_t len) override { + mesh::Utils::printHex(Serial, src, len); + } + + void flush() override { Serial.flush(); } + +private: + size_t vprintf(const char* format, va_list arg) { + char loc_buf[64]; + char *temp = loc_buf; + va_list copy; + va_copy(copy, arg); + int len = vsnprintf(temp, sizeof(loc_buf), format, copy); + va_end(copy); + if (len < 0) { + return 0; + } + if (len >= (int)sizeof(loc_buf)) { // comparation of same sign type for the compiler + temp = (char *)malloc(len + 1); + if (temp == NULL) { + return 0; + } + len = vsnprintf(temp, len + 1, format, arg); + } + len = write((uint8_t *)temp, len); + if (temp != loc_buf) { + free(temp); + } + return len; + } +}; + +DefaultDebugLogger defaultDebugLogger; +Logger& debugLog = defaultDebugLogger; + +} // namespace mesh + +#endif diff --git a/src/DebugLogger.h b/src/DebugLogger.h new file mode 100644 index 000000000..bb873f2f4 --- /dev/null +++ b/src/DebugLogger.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Logger.h" + +namespace mesh { + +/// @brief The global logging instance meant for debug logs +/// +/// By default this is set to an implementation which forwards to the +/// equivalent methods in the Arduino core `Serial` instance. This can be +/// overridden by defining CUSTOM_DEBUG_LOG in a build configuration and then +/// providing an alternative global definition. For example: +/// `Logger& debugLog = myCustomLogger;`. +extern Logger& debugLog; + +} // namespace mesh diff --git a/src/Logger.h b/src/Logger.h new file mode 100644 index 000000000..fe80ca3bb --- /dev/null +++ b/src/Logger.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include + +namespace mesh { + +/// @brief An abstraction for text based logging in MeshCore +class Logger { +public: + virtual ~Logger() = default; + + /// @brief Writes a single byte to the log + /// + /// @param b An unsigned byte to be written to the log. + /// @return The number of bytes written to the log (always one). + virtual size_t write(uint8_t b) = 0; + + /// @brief Writes the specified number of bytes to the log + /// + /// @param buffer A pointer to an array of bytes to be written to the log. + /// @param size The number of bytes in the array to be written to the log. + /// @return The number of bytes written to the log. + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + + /// @brief Formats a string and writes it to the log + /// + /// @param format A format string used to generate what will be written to + /// the log. + /// @param args Any values required by the format string. + /// @return The number of bytes written to the log. + virtual size_t printf(const char * format, ...) + __attribute__ ((format (printf, 2, 3))) = 0; + + /// @brief Writes a single character to the log + /// + /// @param c A character to be written to the log. + /// @return The number of bytes written to the log (always one). + virtual size_t print(char c) = 0; + + /// @brief Writes a null-terminated string to the log + /// + /// NOTE: The null terminator is not written to the log. + /// + /// @param str A pointer to a null-terminated string to be written to the + /// log. + /// @return The number of bytes written to the log. + virtual size_t print(const char str[]) = 0; + + /// @brief Formats a string and writes it to the log followed by a newline + /// + /// NOTE: The type of newline (eg. LF vs. CRLF) is implementation dependent. + /// + /// @param format A format string used to generate what will be written to + /// the log. + /// @param args Any values required by the format string. + /// @return The number of bytes written to the log. + virtual size_t printlnf(const char * format, ...) + __attribute__ ((format (printf, 2, 3))) = 0; + + /// @brief Writes a newline to the log. + /// + /// NOTE: The type of newline (eg. LF vs. CRLF) is implementation dependent. + /// + /// @return The number of bytes written to the log. + virtual size_t println(void) = 0; + + /// @brief Writes a single character to the log follwed by a newline + /// + /// NOTE: The type of newline (eg. LF vs. CRLF) is implementation dependent. + /// + /// @param c A character to be written to the log. + /// @return The number of bytes written to the log. + virtual size_t println(char c) = 0; + + /// @brief Writes a null-terminated string to the log followed by a newline + /// + /// NOTE: The null terminator is not written to the log. + /// NOTE: The type of newline (eg. LF vs. CRLF) is implementation dependent. + /// + /// @param str A pointer to a null-terminated string to be written to the + /// log. + /// @return The number of bytes written to the log. + virtual size_t println(const char str[]) = 0; + + /// @brief Write a byte array to the log in ASCII hex format. + /// + /// @param src An array of bytes to convert to ASCII hex format and then + /// write to the log. + /// @param len The number of bytes in the array to convert and write. + virtual void printHex(const uint8_t* src, size_t len) = 0; + + /// @brief Flush any data buffered by the Logger implementation + virtual void flush() = 0; +}; + +} // namespace mesh diff --git a/src/PacketLogger.cpp b/src/PacketLogger.cpp new file mode 100644 index 000000000..12b6d4258 --- /dev/null +++ b/src/PacketLogger.cpp @@ -0,0 +1,13 @@ +#include "PacketLogger.h" + +#ifndef CUSTOM_PACKET_LOG +#include + +namespace mesh { + +// The default logger for packet logging is just the debug logger. +Logger& packetLog = debugLog; + +} // namespace mesh + +#endif diff --git a/src/PacketLogger.h b/src/PacketLogger.h new file mode 100644 index 000000000..14153f46d --- /dev/null +++ b/src/PacketLogger.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Logger.h" + +namespace mesh { + +/// @brief The global logging instance meant for packet logging +/// +/// By default this is set to debugLog, but can be overriden by defining +/// CUSTOM_PACKET_LOG in a build configuration and then providing an +/// alternative global definition (eg. `Logger& packetLog = myCustomLogger;`) +extern Logger& packetLog; + +} // namespace mesh