Files
project-vesper/vesper/src/Logging/Logging.hpp
bonamin fe6b1d871a feat: Add per-subsystem log tags to all firmware modules
Refactored logging system to require a TAG as first argument on all
LOG_* macros, enabling per-subsystem log filtering and cleaner output.
Each subsystem now defines its own TAG (e.g. "BellEngine", "Player").
Also overhauled Logging.hpp/cpp with improved level control and output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 17:31:28 +02:00

146 lines
9.7 KiB
C++

/*
* ═══════════════════════════════════════════════════════════════════════════════════
* LOGGING.HPP - Subsystem-Aware Centralized Logging System
* ═══════════════════════════════════════════════════════════════════════════════════
*
* 📝 THE INFORMATION CHRONICLER OF VESPER 📝
*
* Three independent output channels, each with their own level:
* • Serial — USB debugging, local connection
* • MQTT — Remote troubleshooting via web dashboard
* • SD — Persistent log storage for post-mortem analysis
*
* Per-subsystem filtering: each subsystem tag can have its own level
* overrides per channel. If no override is set, the global channel
* level applies. Set a tag's level to NONE on a specific channel to
* silence it entirely on that channel (e.g. MQTT internals on MQTT).
*
* Usage in each .cpp file:
* #define TAG "BellEngine" // one line at the top
* LOG_INFO(TAG, "Ring scheduled"); // all calls include the tag
*
* The JSON payload sent over MQTT includes the subsystem field:
* {"level":"WARNING","subsystem":"BellEngine","message":"...","timestamp":12345}
*
* 📋 VERSION: 3.0 (Subsystem-aware logging)
* 📅 DATE: 2025
* 👨‍💻 AUTHOR: Advanced Bell Systems
* ═══════════════════════════════════════════════════════════════════════════════════
*/
#ifndef LOGGING_HPP
#define LOGGING_HPP
#include <Arduino.h>
#include <map>
#include <functional>
class Logging {
public:
// ═══════════════════════════════════════════════════════════════════════════════
// LOG LEVELS
// ═══════════════════════════════════════════════════════════════════════════════
enum LogLevel {
NONE = 0, // No output
ERROR = 1, // Errors only
WARNING = 2, // Warnings and errors
INFO = 3, // Info, warnings, errors
DEBUG = 4, // Debug detail
VERBOSE = 5 // Everything
};
// ═══════════════════════════════════════════════════════════════════════════════
// CALLBACK TYPES
// ═══════════════════════════════════════════════════════════════════════════════
using MqttPublishCallback = std::function<void(const String& topic, const String& payload, int qos)>;
using SdWriteCallback = std::function<void(const String& line)>;
// ═══════════════════════════════════════════════════════════════════════════════
// GLOBAL CHANNEL LEVELS
// Set the baseline level for each output channel.
// Per-subsystem overrides take precedence when set.
// ═══════════════════════════════════════════════════════════════════════════════
static void setSerialLevel(LogLevel level);
static void setMqttLevel(LogLevel level);
static void setSdLevel(LogLevel level);
static LogLevel getSerialLevel();
static LogLevel getMqttLevel();
static LogLevel getSdLevel();
// Legacy compatibility (maps to serial level)
static void setLevel(LogLevel level) { setSerialLevel(level); }
static LogLevel getLevel() { return getSerialLevel(); }
static void setMqttLogLevel(LogLevel level) { setMqttLevel(level); }
static LogLevel getMqttLogLevel() { return getMqttLevel(); }
// ═══════════════════════════════════════════════════════════════════════════════
// PER-SUBSYSTEM LEVEL OVERRIDES
// Call these at startup to silence or focus specific subsystems per channel.
// Pass NONE to completely silence a subsystem on a channel.
// Pass a level to cap that subsystem at that level on that channel.
// ═══════════════════════════════════════════════════════════════════════════════
static void setSubsystemSerialLevel(const char* tag, LogLevel level);
static void setSubsystemMqttLevel(const char* tag, LogLevel level);
static void setSubsystemSdLevel(const char* tag, LogLevel level);
// ═══════════════════════════════════════════════════════════════════════════════
// OUTPUT CHANNEL REGISTRATION
// ═══════════════════════════════════════════════════════════════════════════════
static void setMqttPublishCallback(MqttPublishCallback callback, const String& logTopic);
static void setSdWriteCallback(SdWriteCallback callback);
// ═══════════════════════════════════════════════════════════════════════════════
// LOGGING FUNCTIONS (tag = subsystem name, e.g. "BellEngine")
// ═══════════════════════════════════════════════════════════════════════════════
static void error(const char* tag, const char* format, ...);
static void warning(const char* tag, const char* format, ...);
static void info(const char* tag, const char* format, ...);
static void debug(const char* tag, const char* format, ...);
static void verbose(const char* tag, const char* format, ...);
// ═══════════════════════════════════════════════════════════════════════════════
// UTILITIES
// ═══════════════════════════════════════════════════════════════════════════════
static bool isLevelEnabled(LogLevel level);
static String levelToString(LogLevel level);
private:
// Global channel levels
static LogLevel _serialLevel;
static LogLevel _mqttLevel;
static LogLevel _sdLevel;
// Per-subsystem overrides per channel (tag -> level)
// A value of NONE means "suppress this subsystem on this channel entirely"
static std::map<String, LogLevel> _serialOverrides;
static std::map<String, LogLevel> _mqttOverrides;
static std::map<String, LogLevel> _sdOverrides;
// Output channel callbacks
static MqttPublishCallback _mqttCallback;
static SdWriteCallback _sdCallback;
static String _mqttLogTopic;
// Core internal methods
static void log(LogLevel level, const char* levelStr, const char* tag, const char* format, va_list args);
static void publishToMqtt(LogLevel level, const char* levelStr, const char* tag, const char* message);
static void writeToSd(LogLevel level, const char* levelStr, const char* tag, const char* message);
// Resolve effective level for a tag on a channel
static LogLevel resolveLevel(const char* tag, LogLevel globalLevel, const std::map<String, LogLevel>& overrides);
};
// ═══════════════════════════════════════════════════════════════════════════════════
// MACROS
// Each .cpp file defines: #define TAG "SubsystemName"
// Then uses: LOG_INFO(TAG, "message %d", value)
// ═══════════════════════════════════════════════════════════════════════════════════
#define LOG_ERROR(tag, ...) Logging::error(tag, __VA_ARGS__)
#define LOG_WARNING(tag, ...) Logging::warning(tag, __VA_ARGS__)
#define LOG_INFO(tag, ...) Logging::info(tag, __VA_ARGS__)
#define LOG_DEBUG(tag, ...) Logging::debug(tag, __VA_ARGS__)
#define LOG_VERBOSE(tag, ...) Logging::verbose(tag, __VA_ARGS__)
#endif