mirror of
https://github.com/esphome/esphome.git
synced 2026-10-08 03:54:03 +00:00
[core] Keep log tags in flash on ESP8266 with ESPHOME_LOG_TAG (#20290)
This commit is contained in:
@@ -3,11 +3,14 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin)
|
||||
static constexpr uint16_t MAX_HEADER_SIZE = 128;
|
||||
// Longest tag the header budget allows
|
||||
static constexpr size_t MAX_TAG_LENGTH = 32;
|
||||
|
||||
// ANSI color code last digit (30-38 range, store only last digit to save RAM on ESP8266)
|
||||
static const char LOG_LEVEL_COLOR_DIGIT[] PROGMEM = {
|
||||
@@ -189,13 +192,23 @@ struct LogBuffer {
|
||||
*p++ = 'm';
|
||||
}
|
||||
// Copy string without null terminator, updates pointer in place
|
||||
// Caller is responsible for ensuring buffer has sufficient space
|
||||
// Caller is responsible for ensuring buffer has sufficient space. Tags may be in PROGMEM on ESP8266.
|
||||
void copy_string_(char *&p, const char *str) {
|
||||
#ifdef USE_ESP8266
|
||||
// Only ESP8266 caps the length; tags may be in PROGMEM, so read each byte once
|
||||
for (size_t i = 0; i < MAX_TAG_LENGTH; i++) {
|
||||
const char c = static_cast<char>(progmem_read_byte(reinterpret_cast<const uint8_t *>(str + i)));
|
||||
if (c == '\0')
|
||||
break;
|
||||
*p++ = c;
|
||||
}
|
||||
#else
|
||||
const size_t len = strlen(str);
|
||||
// NOLINTNEXTLINE(bugprone-not-null-terminated-result) - intentionally no null terminator, building string piece by
|
||||
// piece
|
||||
memcpy(p, str, len);
|
||||
p += len;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -144,7 +144,10 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
|
||||
inline uint8_t Logger::level_for(const char *tag) {
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
auto it = this->log_levels_.find(tag);
|
||||
// Skip the lookup when no per-tag levels are set
|
||||
if (this->log_levels_.empty())
|
||||
return this->current_level_;
|
||||
auto it = this->log_levels_.find(FlashTag{tag});
|
||||
if (it != this->log_levels_.end())
|
||||
return it->second;
|
||||
#endif
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include "log_buffer.h"
|
||||
#include "task_log_buffer_host.h"
|
||||
@@ -85,12 +86,16 @@ class LoggerLevelListener {
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
// Comparison function for const char* keys in log_levels_ map
|
||||
// RAM map keys compared with a tag that may be in PROGMEM, without copying it
|
||||
struct FlashTag {
|
||||
const char *tag;
|
||||
};
|
||||
struct CStrCompare {
|
||||
using is_transparent = void;
|
||||
bool operator()(const char *a, const char *b) const { return strcmp(a, b) < 0; }
|
||||
bool operator()(const char *key, FlashTag t) const { return ESPHOME_strcmp_P(key, t.tag) < 0; }
|
||||
bool operator()(FlashTag t, const char *key) const { return ESPHOME_strcmp_P(key, t.tag) > 0; }
|
||||
};
|
||||
#endif
|
||||
|
||||
// Stack buffer size for retrieving thread/task names from the OS
|
||||
// macOS allows up to 64 bytes, Linux up to 16
|
||||
@@ -488,6 +493,12 @@ class LoggerMessageTrigger final : public Trigger<uint8_t, const char *, const c
|
||||
[](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
auto *trigger = static_cast<LoggerMessageTrigger *>(self);
|
||||
if (level <= trigger->level_) {
|
||||
#ifdef USE_ESP8266
|
||||
// User lambdas may strcmp the tag, which may be in PROGMEM. The copy lives in
|
||||
// the trigger so an automation that suspends (delay) still sees a valid tag.
|
||||
ESPHOME_strncpy_P(trigger->ram_tag_, tag, MAX_TAG_LENGTH);
|
||||
tag = trigger->ram_tag_;
|
||||
#endif
|
||||
trigger->trigger(level, tag, message);
|
||||
}
|
||||
});
|
||||
@@ -495,6 +506,9 @@ class LoggerMessageTrigger final : public Trigger<uint8_t, const char *, const c
|
||||
|
||||
protected:
|
||||
uint8_t level_;
|
||||
#ifdef USE_ESP8266
|
||||
char ram_tag_[MAX_TAG_LENGTH + 1]{};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
namespace esphome::touchscreen {
|
||||
|
||||
static const char *const TAG = "touchscreen";
|
||||
static constexpr uint32_t TOUCH_TIMEOUT_ID = 0;
|
||||
|
||||
void TouchscreenInterrupt::gpio_intr(TouchscreenInterrupt *store) { store->touched = true; }
|
||||
|
||||
@@ -63,9 +64,9 @@ void Touchscreen::loop() {
|
||||
// Simulate a touch after <this->touch_timeout_> ms. This will reset any existing timeout operation.
|
||||
// This is to detect touch release.
|
||||
if (this->is_touched_) {
|
||||
this->set_timeout(TAG, this->touch_timeout_, [this]() { this->store_.touched = true; });
|
||||
this->set_timeout(TOUCH_TIMEOUT_ID, this->touch_timeout_, [this]() { this->store_.touched = true; });
|
||||
} else {
|
||||
this->cancel_timeout(TAG);
|
||||
this->cancel_timeout(TOUCH_TIMEOUT_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
static const char *const TAG = "app";
|
||||
ESPHOME_LOG_TAG(TAG, "app");
|
||||
|
||||
// Delay after setup() finishes before trimming the scheduler freelist of its post-boot peak.
|
||||
// 10 s is well past the bulk of post-setup async work (Wi-Fi/MQTT connects, first-read latency).
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
static const char *const TAG = "component";
|
||||
ESPHOME_LOG_TAG(TAG, "component");
|
||||
|
||||
// Global vectors for component data that doesn't belong in every instance.
|
||||
// Using vector instead of unordered_map for both because:
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
static const char *const TAG = "entity_base";
|
||||
ESPHOME_LOG_TAG(TAG, "entity_base");
|
||||
|
||||
void EntityBase::configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_fields) {
|
||||
this->name_ = StringRef(name);
|
||||
|
||||
@@ -28,7 +28,7 @@ std::string progmem_string(ProgmemStr str) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char *const TAG = "helpers";
|
||||
ESPHOME_LOG_TAG(TAG, "helpers");
|
||||
|
||||
__attribute__((noinline, cold)) void *callback_manager_grow(void *data, uint16_t size, uint16_t &capacity,
|
||||
size_t elem_size) {
|
||||
|
||||
@@ -190,4 +190,12 @@ struct LogString;
|
||||
|
||||
#endif
|
||||
|
||||
// Declares a static log tag at namespace scope in a .cpp file; on ESP8266 it lives in flash, so read it with
|
||||
// the _P functions, never strcmp/strlen.
|
||||
#ifdef USE_ESP8266
|
||||
#define ESPHOME_LOG_TAG(name, tag) static const char name[] PROGMEM = tag
|
||||
#else
|
||||
#define ESPHOME_LOG_TAG(name, tag) static constexpr const char *const name = tag
|
||||
#endif
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
static const char *const TAG = "scheduler";
|
||||
ESPHOME_LOG_TAG(TAG, "scheduler");
|
||||
|
||||
// Maximum number of logically deleted (cancelled) items before forcing cleanup.
|
||||
// Empirically chosen to balance cleanup overhead against tombstone accumulation in items_.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
namespace esphome {
|
||||
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
static const char *const TAG = "time_64";
|
||||
ESPHOME_LOG_TAG(TAG, "time_64");
|
||||
#endif
|
||||
|
||||
#ifdef ESPHOME_THREAD_SINGLE
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/components/logger/log_buffer.h"
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::logger::testing {
|
||||
|
||||
ESPHOME_LOG_TAG(TEST_TAG, "test.tag");
|
||||
|
||||
TEST(LogTagTest, HeaderContainsTagAndLine) {
|
||||
char data[256];
|
||||
LogBuffer buf{data, sizeof(data)};
|
||||
buf.write_header(5, TEST_TAG, 42, nullptr);
|
||||
const std::string header(data, buf.pos);
|
||||
EXPECT_NE(header.find("[D][test.tag:042]"), std::string::npos) << header;
|
||||
}
|
||||
|
||||
TEST(LogTagTest, PerTagLevelLookupMatchesByContent) {
|
||||
std::map<const char *, uint8_t, CStrCompare> levels{{"api", 1}, {"sensor", 2}, {"wifi", 3}};
|
||||
// Copies, so a match must compare content rather than pointers
|
||||
char api[] = "api", sensor[] = "sensor", wifi[] = "wifi", missing[] = "switch";
|
||||
EXPECT_EQ(levels.find(FlashTag{api})->second, 1);
|
||||
EXPECT_EQ(levels.find(FlashTag{sensor})->second, 2);
|
||||
EXPECT_EQ(levels.find(FlashTag{wifi})->second, 3);
|
||||
EXPECT_EQ(levels.find(FlashTag{missing}), levels.end());
|
||||
}
|
||||
|
||||
} // namespace esphome::logger::testing
|
||||
Reference in New Issue
Block a user