693 lines
20 KiB
C++
693 lines
20 KiB
C++
#include "Logger.h"
|
|
#include "config.h"
|
|
|
|
#include <modbus/ModbusTCP.h>
|
|
#include <modbus/Modbus.h>
|
|
#include <modbus/ModbusTypes.h>
|
|
|
|
// This define is needed here because the library doesn't know about the app's config.h
|
|
// It must come *before* RestServer.h is included to expose the WebSocket methods.
|
|
|
|
#ifndef DISABLE_LOGGING
|
|
#include <components/RestServer.h>
|
|
#include <ArduinoJson.h>
|
|
#include <LittleFS.h>
|
|
|
|
class PrintTarget : public ILogTarget {
|
|
public:
|
|
explicit PrintTarget(Print* output) : _output(output) {}
|
|
void log(const Component* sender, LogLevel level, const char* message) override {
|
|
if (_output) {
|
|
// Re-add prefix for display
|
|
const char* level_str = "";
|
|
switch(level) {
|
|
case L_FATAL: level_str = "FATAL: "; break;
|
|
case L_ERROR: level_str = "ERROR: "; break;
|
|
case L_WARNING: level_str = "WARNING: "; break;
|
|
case L_LEVEL_INFO: level_str = "INFO: "; break;
|
|
case L_TRACE: level_str = "TRACE: "; break;
|
|
case L_VERBOSE: level_str = "VERBOSE: "; break;
|
|
default: break;
|
|
}
|
|
_output->print(level_str);
|
|
if (sender) {
|
|
_output->printf("[%s:%d] ", sender->name.c_str(), sender->id);
|
|
}
|
|
_output->println(message);
|
|
}
|
|
}
|
|
LogTargetType getType() const override { return TARGET_TYPE_PRINT; }
|
|
private:
|
|
Print* _output;
|
|
};
|
|
|
|
#ifdef ENABLE_WEBSOCKET
|
|
|
|
class WebSocketTarget : public ILogTarget {
|
|
public:
|
|
explicit WebSocketTarget(RESTServer* server) : _server(server), _lastBroadcastTime(0) {
|
|
for (int i = 0; i < LOG_QUEUE_SIZE; ++i) {
|
|
_logQueue[i].flags = 0;
|
|
}
|
|
}
|
|
|
|
void log(const Component* sender, LogLevel level, const char* message) override {
|
|
if (level > getLevel()) return;
|
|
|
|
// Find a free slot
|
|
for (int i = 0; i < LOG_QUEUE_SIZE; ++i) {
|
|
if (_logQueue[i].flags == 0) { // is free
|
|
_logQueue[i].level = level;
|
|
_logQueue[i].message = message;
|
|
_logQueue[i].componentId = sender ? (uint16_t)sender->id : (uint16_t)0;
|
|
_logQueue[i].componentName = sender ? sender->name : String("");
|
|
_logQueue[i].flags = FLAG_USED;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkAndBroadcast() {
|
|
if (_isBroadcasting || !_server) return;
|
|
|
|
if (millis() - _lastBroadcastTime < BROADCAST_INTERVAL_MS) {
|
|
return;
|
|
}
|
|
|
|
_isBroadcasting = true;
|
|
|
|
for (int i = 0; i < LOG_QUEUE_SIZE; ++i) {
|
|
if (_logQueue[i].flags == FLAG_USED) {
|
|
JsonDocument doc;
|
|
doc["level"] = _logQueue[i].level;
|
|
doc["message"] = _logQueue[i].message;
|
|
if (_logQueue[i].componentId != 0) {
|
|
doc["id"] = _logQueue[i].componentId;
|
|
doc["name"] = _logQueue[i].componentName;
|
|
}
|
|
_server->broadcast(BROADCAST_LOG_ENTRY, doc);
|
|
_logQueue[i].flags = 0; // Mark as free
|
|
_lastBroadcastTime = millis();
|
|
break; // Send one at a time
|
|
}
|
|
}
|
|
|
|
_isBroadcasting = false;
|
|
}
|
|
|
|
LogTargetType getType() const override { return TARGET_TYPE_WEBSOCKET; }
|
|
private:
|
|
RESTServer* _server;
|
|
enum LogEntryFlags {
|
|
FLAG_USED = 1
|
|
};
|
|
struct LogEntry {
|
|
LogLevel level;
|
|
String message;
|
|
uint16_t componentId;
|
|
String componentName;
|
|
uint8_t flags;
|
|
};
|
|
static const uint8_t LOG_QUEUE_SIZE = 40;
|
|
static const unsigned long BROADCAST_INTERVAL_MS = 30;
|
|
LogEntry _logQueue[LOG_QUEUE_SIZE];
|
|
unsigned long _lastBroadcastTime;
|
|
|
|
static bool _isBroadcasting;
|
|
};
|
|
bool WebSocketTarget::_isBroadcasting = false;
|
|
#endif
|
|
|
|
#ifdef ENABLE_LITTLEFS
|
|
class FileTarget : public ILogTarget {
|
|
public:
|
|
FileTarget() {
|
|
_logBuffer.reserve(BUFFER_SIZE);
|
|
}
|
|
|
|
~FileTarget() {
|
|
if (!_logBuffer.empty()) {
|
|
dumpToFile();
|
|
}
|
|
}
|
|
|
|
void log(const Component* sender, LogLevel level, const char* message) override {
|
|
if (level > getLevel() || _logBuffer.size() >= BUFFER_SIZE) return;
|
|
|
|
|
|
_logBuffer.push_back({level, String(message), millis(), sender ? (uint16_t)sender->id : (uint16_t)0, sender ? sender->name : String("")});
|
|
|
|
}
|
|
|
|
void checkAndDump() {
|
|
if (_logBuffer.size() >= BUFFER_SIZE) {
|
|
dumpToFile();
|
|
}
|
|
}
|
|
|
|
LogTargetType getType() const override { return TARGET_TYPE_FILE; }
|
|
|
|
private:
|
|
void dumpToFile() {
|
|
|
|
|
|
if (!LittleFS.begin(true)) {
|
|
|
|
return;
|
|
}
|
|
|
|
File file = LittleFS.open("/log-last.json", "w");
|
|
if (!file) {
|
|
|
|
return;
|
|
}
|
|
|
|
JsonDocument doc;
|
|
JsonArray array = doc.to<JsonArray>();
|
|
|
|
for (const auto& entry : _logBuffer) {
|
|
JsonObject obj = array.add<JsonObject>();
|
|
obj["level"] = entry.level;
|
|
obj["msg"] = entry.message;
|
|
obj["ts"] = entry.timestamp;
|
|
if (entry.componentId != 0) {
|
|
obj["id"] = entry.componentId;
|
|
obj["name"] = entry.componentName;
|
|
}
|
|
}
|
|
|
|
serializeJson(doc, file);
|
|
file.close();
|
|
_logBuffer.clear();
|
|
|
|
|
|
}
|
|
|
|
struct LogEntry {
|
|
LogLevel level;
|
|
String message;
|
|
unsigned long timestamp;
|
|
uint16_t componentId;
|
|
String componentName;
|
|
};
|
|
static const uint8_t BUFFER_SIZE = 100;
|
|
std::vector<LogEntry> _logBuffer;
|
|
|
|
};
|
|
#endif
|
|
|
|
#endif // DISABLE_LOGGING
|
|
|
|
#ifndef DISABLE_LOGGING
|
|
Logger* Logger::s_esp_log_instance = nullptr;
|
|
#endif
|
|
|
|
Logger::Logger(Component *owner, ushort componentId, ushort modbusAddress)
|
|
: Component("Logger", componentId, Component::COMPONENT_DEFAULT, owner, 0)
|
|
, modbusAddress(modbusAddress)
|
|
{
|
|
setNetCapability(OBJECT_NET_CAPS::E_NCAPS_MODBUS);
|
|
#ifndef DISABLE_LOGGING
|
|
clear();
|
|
|
|
setLevel(L_VERBOSE);
|
|
#endif
|
|
|
|
m_modbus_block[(ushort)E_Logger_MB_Offset::MB_LOG_LEVEL] = INIT_MODBUS_BLOCK_TCP(
|
|
this->modbusAddress,
|
|
(ushort)E_Logger_MB_Offset::MB_LOG_LEVEL,
|
|
E_FN_CODE::FN_WRITE_HOLD_REGISTER,
|
|
MB_ACCESS_READ_WRITE,
|
|
"Log Level (0:Silent, 1:Fatal, ... 6:Verbose)",
|
|
"Logger"
|
|
);
|
|
m_modbus_block[(ushort)E_Logger_MB_Offset::COIL_TARGET_PRINT] = INIT_MODBUS_BLOCK_TCP(
|
|
this->modbusAddress,
|
|
(ushort)E_Logger_MB_Offset::COIL_TARGET_PRINT,
|
|
E_FN_CODE::FN_WRITE_COIL,
|
|
MB_ACCESS_READ_WRITE,
|
|
"Enable Print Target",
|
|
"Logger"
|
|
);
|
|
m_modbus_block[(ushort)E_Logger_MB_Offset::COIL_TARGET_WEBSOCKET] = INIT_MODBUS_BLOCK_TCP(
|
|
this->modbusAddress,
|
|
(ushort)E_Logger_MB_Offset::COIL_TARGET_WEBSOCKET,
|
|
E_FN_CODE::FN_WRITE_COIL,
|
|
MB_ACCESS_READ_WRITE,
|
|
"Enable WebSocket Target",
|
|
"Logger"
|
|
);
|
|
m_modbus_block[(ushort)E_Logger_MB_Offset::COIL_TARGET_FILE] = INIT_MODBUS_BLOCK_TCP(
|
|
this->modbusAddress,
|
|
(ushort)E_Logger_MB_Offset::COIL_TARGET_FILE,
|
|
E_FN_CODE::FN_WRITE_COIL,
|
|
MB_ACCESS_READ_WRITE,
|
|
"Enable File Target",
|
|
"Logger"
|
|
);
|
|
|
|
m_modbus_view.data = m_modbus_block;
|
|
m_modbus_view.count = (ushort)E_Logger_MB_Offset::COUNT;
|
|
}
|
|
|
|
Logger::~Logger()
|
|
{
|
|
#ifndef DISABLE_LOGGING
|
|
if (_webSocketTarget) {
|
|
delete _webSocketTarget;
|
|
}
|
|
if (_fileTarget) {
|
|
delete _fileTarget;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
short Logger::setup()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
short Logger::loop()
|
|
{
|
|
Component::loop();
|
|
#ifndef DISABLE_LOGGING
|
|
if (_webSocketTarget) {
|
|
_webSocketTarget->checkAndBroadcast();
|
|
}
|
|
if (_fileTarget) {
|
|
_fileTarget->checkAndDump();
|
|
}
|
|
#endif
|
|
return E_OK;
|
|
}
|
|
|
|
// --- Modbus Overrides ---
|
|
void Logger::mb_tcp_register(ModbusTCP *manager)
|
|
{
|
|
if (!hasNetCapability(OBJECT_NET_CAPS::E_NCAPS_MODBUS))
|
|
return;
|
|
|
|
modbusTCP = manager;
|
|
uint16_t instanceBaseAddr = mb_tcp_base_address();
|
|
ModbusBlockView *blocksView = mb_tcp_blocks();
|
|
|
|
for (int i = 0; i < blocksView->count; ++i)
|
|
{
|
|
MB_Registers info = blocksView->data[i];
|
|
info.componentId = this->id;
|
|
manager->registerModbus(this, info);
|
|
}
|
|
}
|
|
|
|
ModbusBlockView *Logger::mb_tcp_blocks() const
|
|
{
|
|
return &m_modbus_view;
|
|
}
|
|
|
|
short Logger::mb_tcp_read(MB_Registers *reg)
|
|
{
|
|
ushort offset = reg->startAddress - modbusAddress;
|
|
switch(offset) {
|
|
case (ushort)E_Logger_MB_Offset::MB_LOG_LEVEL:
|
|
return (short)_level;
|
|
case (ushort)E_Logger_MB_Offset::COIL_TARGET_PRINT:
|
|
return TEST(_targetMask, TARGET_TYPE_PRINT);
|
|
case (ushort)E_Logger_MB_Offset::COIL_TARGET_WEBSOCKET:
|
|
return TEST(_targetMask, TARGET_TYPE_WEBSOCKET);
|
|
case (ushort)E_Logger_MB_Offset::COIL_TARGET_FILE:
|
|
return TEST(_targetMask, TARGET_TYPE_FILE);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
short Logger::mb_tcp_write(MB_Registers *reg, short networkValue)
|
|
{
|
|
ushort offset = reg->startAddress - modbusAddress;
|
|
switch(offset) {
|
|
case (ushort)E_Logger_MB_Offset::MB_LOG_LEVEL:
|
|
if (networkValue >= L_SILENT && networkValue <= L_VERBOSE) {
|
|
setLevel((LogLevel)networkValue);
|
|
return E_OK;
|
|
}
|
|
return E_INVALID_PARAMETER;
|
|
case (ushort)E_Logger_MB_Offset::COIL_TARGET_PRINT:
|
|
SET_BIT_TO(_targetMask, TARGET_TYPE_PRINT, networkValue);
|
|
return E_OK;
|
|
case (ushort)E_Logger_MB_Offset::COIL_TARGET_WEBSOCKET:
|
|
SET_BIT_TO(_targetMask, TARGET_TYPE_WEBSOCKET, networkValue);
|
|
return E_OK;
|
|
case (ushort)E_Logger_MB_Offset::COIL_TARGET_FILE:
|
|
SET_BIT_TO(_targetMask, TARGET_TYPE_FILE, networkValue);
|
|
return E_OK;
|
|
}
|
|
return E_INVALID_PARAMETER;
|
|
}
|
|
|
|
uint16_t Logger::mb_tcp_base_address() const
|
|
{
|
|
return modbusAddress;
|
|
}
|
|
|
|
// --- Logging ---
|
|
#ifndef DISABLE_LOGGING
|
|
void Logger::setLevel(LogLevel level) {
|
|
_level = level;
|
|
}
|
|
|
|
void Logger::setTargetMask(uint8_t mask) {
|
|
_targetMask = mask;
|
|
}
|
|
|
|
uint8_t Logger::getTargetMask() const {
|
|
return _targetMask;
|
|
}
|
|
|
|
bool Logger::addTarget(ILogTarget* target) {
|
|
if (_numTargets < MAX_TARGETS && target) {
|
|
_targets[_numTargets++] = target;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Logger::addPrintTarget(Print* output) {
|
|
if (output) {
|
|
PrintTarget* printTarget = new PrintTarget(output);
|
|
return addTarget(printTarget);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Logger::addWebSocketTarget(RESTServer* server) {
|
|
#ifdef ENABLE_WEBSOCKET
|
|
if (server && !_webSocketTarget) {
|
|
_webSocketTarget = new WebSocketTarget(server);
|
|
return addTarget(_webSocketTarget);
|
|
}
|
|
return _webSocketTarget != nullptr;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool Logger::addFileTarget() {
|
|
#ifdef ENABLE_LITTLEFS
|
|
if (!_fileTarget) {
|
|
_fileTarget = new FileTarget();
|
|
return addTarget(_fileTarget);
|
|
}
|
|
return true; // Already added
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void Logger::log(const Component* sender, LogLevel level, const char* format, ...) {
|
|
if (level > _level || level == L_SILENT) return;
|
|
|
|
char temp_buffer[LOG_BUFFER_LINE_LENGTH];
|
|
va_list args;
|
|
va_start(args, format);
|
|
vsnprintf(temp_buffer, LOG_BUFFER_LINE_LENGTH, format, args);
|
|
va_end(args);
|
|
|
|
dispatch(sender, level, temp_buffer);
|
|
}
|
|
|
|
void Logger::log(const Component* sender, LogLevel level, const __FlashStringHelper* format, ...) {
|
|
if (level > _level || level == L_SILENT) return;
|
|
|
|
char temp_buffer[LOG_BUFFER_LINE_LENGTH];
|
|
va_list args;
|
|
va_start(args, format);
|
|
vsnprintf_P(temp_buffer, LOG_BUFFER_LINE_LENGTH, (PGM_P)format, args);
|
|
va_end(args);
|
|
|
|
dispatch(sender, level, temp_buffer);
|
|
}
|
|
|
|
void Logger::log(const Component* sender, LogLevel level, const String& format, ...) {
|
|
if (level > _level || level == L_SILENT) return;
|
|
|
|
char temp_buffer[LOG_BUFFER_LINE_LENGTH];
|
|
va_list args;
|
|
va_start(args, format);
|
|
vsnprintf(temp_buffer, LOG_BUFFER_LINE_LENGTH, format.c_str(), args);
|
|
va_end(args);
|
|
|
|
dispatch(sender, level, temp_buffer);
|
|
}
|
|
|
|
#define GEN_LOG_METHOD(name, level) \
|
|
void Logger::name(const Component* sender, const char* format, ...) { \
|
|
if (level > _level || level == L_SILENT) return; \
|
|
va_list args; \
|
|
va_start(args, format); \
|
|
char temp[LOG_BUFFER_LINE_LENGTH]; \
|
|
vsnprintf(temp, sizeof(temp), format, args); \
|
|
va_end(args); \
|
|
log(sender, level, temp); \
|
|
}
|
|
|
|
#define GEN_LOG_METHOD_F(name, level) \
|
|
void Logger::name(const Component* sender, const __FlashStringHelper* format, ...) { \
|
|
if (level > _level || level == L_SILENT) return; \
|
|
va_list args; \
|
|
va_start(args, format); \
|
|
char temp[LOG_BUFFER_LINE_LENGTH]; \
|
|
vsnprintf_P(temp, sizeof(temp), (PGM_P)format, args); \
|
|
va_end(args); \
|
|
log(sender, level, temp); \
|
|
}
|
|
|
|
#define GEN_LOG_METHOD_S(name, level) \
|
|
void Logger::name(const Component* sender, const String& format, ...) { \
|
|
if (level > _level || level == L_SILENT) return; \
|
|
va_list args; \
|
|
va_start(args, format); \
|
|
char temp[LOG_BUFFER_LINE_LENGTH]; \
|
|
vsnprintf(temp, sizeof(temp), format.c_str(), args); \
|
|
va_end(args); \
|
|
log(sender, level, temp); \
|
|
}
|
|
|
|
GEN_LOG_METHOD(fatal, L_FATAL)
|
|
GEN_LOG_METHOD_F(fatal, L_FATAL)
|
|
GEN_LOG_METHOD(error, L_ERROR)
|
|
GEN_LOG_METHOD_F(error, L_ERROR)
|
|
GEN_LOG_METHOD(warn, L_WARNING)
|
|
GEN_LOG_METHOD_F(warn, L_WARNING)
|
|
GEN_LOG_METHOD(info, L_LEVEL_INFO)
|
|
GEN_LOG_METHOD_F(info, L_LEVEL_INFO)
|
|
GEN_LOG_METHOD(trace, L_TRACE)
|
|
GEN_LOG_METHOD_F(trace, L_TRACE)
|
|
GEN_LOG_METHOD(verbose, L_VERBOSE)
|
|
GEN_LOG_METHOD_F(verbose, L_VERBOSE)
|
|
|
|
#define GEN_LOG_METHOD_NULL_SENDER(name, level) \
|
|
void Logger::name(const char* format, ...) { \
|
|
if (level > _level || level == L_SILENT) return; \
|
|
va_list args; \
|
|
va_start(args, format); \
|
|
char temp[LOG_BUFFER_LINE_LENGTH]; \
|
|
vsnprintf(temp, sizeof(temp), format, args); \
|
|
va_end(args); \
|
|
dispatch(nullptr, level, temp); \
|
|
}
|
|
|
|
#define GEN_LOG_METHOD_F_NULL_SENDER(name, level) \
|
|
void Logger::name(const __FlashStringHelper* format, ...) { \
|
|
if (level > _level || level == L_SILENT) return; \
|
|
va_list args; \
|
|
va_start(args, format); \
|
|
char temp[LOG_BUFFER_LINE_LENGTH]; \
|
|
vsnprintf_P(temp, sizeof(temp), (PGM_P)format, args); \
|
|
va_end(args); \
|
|
dispatch(nullptr, level, temp); \
|
|
}
|
|
|
|
#define GEN_LOG_METHOD_S_NULL_SENDER(name, level) \
|
|
void Logger::name(const String& format, ...) { \
|
|
if (level > _level || level == L_SILENT) return; \
|
|
va_list args; \
|
|
va_start(args, format); \
|
|
char temp[LOG_BUFFER_LINE_LENGTH]; \
|
|
vsnprintf(temp, sizeof(temp), format.c_str(), args); \
|
|
va_end(args); \
|
|
dispatch(nullptr, level, temp); \
|
|
}
|
|
|
|
GEN_LOG_METHOD_NULL_SENDER(fatal, L_FATAL)
|
|
GEN_LOG_METHOD_F_NULL_SENDER(fatal, L_FATAL)
|
|
GEN_LOG_METHOD_S_NULL_SENDER(fatal, L_FATAL)
|
|
GEN_LOG_METHOD_NULL_SENDER(error, L_ERROR)
|
|
GEN_LOG_METHOD_F_NULL_SENDER(error, L_ERROR)
|
|
GEN_LOG_METHOD_S_NULL_SENDER(error, L_ERROR)
|
|
GEN_LOG_METHOD_NULL_SENDER(warn, L_WARNING)
|
|
GEN_LOG_METHOD_F_NULL_SENDER(warn, L_WARNING)
|
|
GEN_LOG_METHOD_S_NULL_SENDER(warn, L_WARNING)
|
|
GEN_LOG_METHOD_NULL_SENDER(info, L_LEVEL_INFO)
|
|
GEN_LOG_METHOD_F_NULL_SENDER(info, L_LEVEL_INFO)
|
|
GEN_LOG_METHOD_S_NULL_SENDER(info, L_LEVEL_INFO)
|
|
GEN_LOG_METHOD_NULL_SENDER(trace, L_TRACE)
|
|
GEN_LOG_METHOD_F_NULL_SENDER(trace, L_TRACE)
|
|
GEN_LOG_METHOD_S_NULL_SENDER(trace, L_TRACE)
|
|
GEN_LOG_METHOD_NULL_SENDER(verbose, L_VERBOSE)
|
|
GEN_LOG_METHOD_F_NULL_SENDER(verbose, L_VERBOSE)
|
|
GEN_LOG_METHOD_S_NULL_SENDER(verbose, L_VERBOSE)
|
|
#endif
|
|
|
|
// --- Ring Buffer ---
|
|
#ifndef DISABLE_LOGGING
|
|
void Logger::setOutput(Print* out) {
|
|
// This is now a bit weird. Maybe add a new PrintTarget?
|
|
// For now, let's make it replace the first target if it's a PrintTarget.
|
|
// Or just ignore it. Let's ignore it for now to avoid complexity.
|
|
}
|
|
|
|
void Logger::clear() {
|
|
memset(_buf, 0, sizeof(_buf));
|
|
_head = 0;
|
|
_filled = 0;
|
|
_idx = 0;
|
|
}
|
|
|
|
const char* Logger::getLine(size_t i) const {
|
|
if (i >= lines()) return nullptr;
|
|
size_t index = (_head + LOG_BUFFER_LINES - i - 1U) % LOG_BUFFER_LINES;
|
|
return _buf[index];
|
|
}
|
|
|
|
size_t Logger::lines() const { return (_filled < LOG_BUFFER_LINES) ? _filled : LOG_BUFFER_LINES; }
|
|
|
|
void Logger::attachToEspLog()
|
|
{
|
|
s_esp_log_instance = this;
|
|
esp_log_set_vprintf(&vprintfShim);
|
|
}
|
|
|
|
size_t Logger::write(uint8_t c) {
|
|
size_t res = writeByte(static_cast<char>(c));
|
|
return res;
|
|
}
|
|
|
|
size_t Logger::write(const uint8_t* data, size_t size) {
|
|
size_t i = 0U;
|
|
while (i < size) {
|
|
if (static_cast<char>(data[i]) == '\n') {
|
|
commitLine();
|
|
++i; // skip newline
|
|
continue;
|
|
}
|
|
appendChar(static_cast<char>(data[i++]));
|
|
}
|
|
return size;
|
|
}
|
|
|
|
void Logger::appendChar(char c) {
|
|
if (_idx < LOG_BUFFER_LINE_LENGTH - 1U) {
|
|
_line[_idx++] = c;
|
|
}
|
|
}
|
|
|
|
void Logger::commitLine() {
|
|
_line[_idx] = '\0';
|
|
_idx = 0;
|
|
|
|
int prefixLen = 0;
|
|
LogLevel level = parseLevelFromPrefix(_line, prefixLen);
|
|
|
|
// The rest of the line is the message
|
|
char* message = _line + prefixLen;
|
|
|
|
// We are inside write(), which is already in a critical section
|
|
dispatch(level, message);
|
|
}
|
|
|
|
size_t Logger::writeByte(char c) {
|
|
if (c == '\r') return 1U; // ignore CR
|
|
if (c == '\n') {
|
|
commitLine();
|
|
} else {
|
|
appendChar(c);
|
|
}
|
|
return 1U;
|
|
}
|
|
|
|
int Logger::vprintfShim(const char* fmt, va_list args) {
|
|
if (!s_esp_log_instance) return 0;
|
|
|
|
char tmp[LOG_BUFFER_LINE_LENGTH];
|
|
int len = vsnprintf(tmp, sizeof(tmp), fmt, args);
|
|
if (len < 0) return len;
|
|
|
|
for (int i = 0; i < len; ++i) {
|
|
s_esp_log_instance->write(static_cast<uint8_t>(tmp[i]));
|
|
}
|
|
return len;
|
|
}
|
|
#endif
|
|
|
|
// --- New private methods ---
|
|
#ifndef DISABLE_LOGGING
|
|
void Logger::dispatch(LogLevel level, const char* message) {
|
|
dispatch(nullptr, level, message);
|
|
}
|
|
|
|
void Logger::dispatch(const Component* sender, LogLevel level, const char* message) {
|
|
if (level > _level || level == L_SILENT) return;
|
|
|
|
char final_buffer[LOG_BUFFER_LINE_LENGTH];
|
|
const char* level_str = "";
|
|
switch(level) {
|
|
case L_FATAL: level_str = "FATAL: "; break;
|
|
case L_ERROR: level_str = "ERROR: "; break;
|
|
case L_WARNING: level_str = "WARNING: "; break;
|
|
case L_LEVEL_INFO: level_str = "INFO: "; break;
|
|
case L_TRACE: level_str = "TRACE: "; break;
|
|
case L_VERBOSE: level_str = "VERBOSE: "; break;
|
|
default: break;
|
|
}
|
|
|
|
if (sender) {
|
|
snprintf(final_buffer, LOG_BUFFER_LINE_LENGTH, "%s[%s:%d] %s", level_str, sender->name.c_str(), sender->id, message);
|
|
} else {
|
|
snprintf(final_buffer, LOG_BUFFER_LINE_LENGTH, "%s%s", level_str, message);
|
|
}
|
|
|
|
// Serial.println(final_buffer);
|
|
|
|
// Store prefixed message in ring buffer
|
|
strncpy(_buf[_head], final_buffer, LOG_BUFFER_LINE_LENGTH);
|
|
_head = (_head + 1U) % LOG_BUFFER_LINES;
|
|
if (_filled < LOG_BUFFER_LINES) ++_filled;
|
|
|
|
// Rate limit target logging
|
|
if (millis() - _lastLogTime < _logInterval) {
|
|
// return;
|
|
}
|
|
_lastLogTime = millis();
|
|
|
|
// Dispatch raw message to targets
|
|
for (uint8_t i = 0; i < _numTargets; i++) {
|
|
ILogTarget* target = _targets[i];
|
|
if (target && level <= target->getLevel()) {
|
|
if (TEST(_targetMask, target->getType())) {
|
|
target->log(sender, level, message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LogLevel Logger::parseLevelFromPrefix(const char* message, int& prefixLen) {
|
|
prefixLen = 0;
|
|
if (strstr(message, "FATAL: ") == message) { prefixLen = 7; return L_FATAL; }
|
|
if (strstr(message, "ERROR: ") == message) { prefixLen = 7; return L_ERROR; }
|
|
if (strstr(message, "WARNING: ") == message) { prefixLen = 9; return L_WARNING; }
|
|
if (strstr(message, "INFO: ") == message) { prefixLen = 6; return L_LEVEL_INFO; }
|
|
if (strstr(message, "TRACE: ") == message) { prefixLen = 7; return L_TRACE; }
|
|
if (strstr(message, "VERBOSE: ") == message) { prefixLen = 9; return L_VERBOSE; }
|
|
|
|
return L_LEVEL_INFO; // Default for non-prefixed messages
|
|
}
|
|
#endif |