[log] Detect early log calls before logger init and optimize hot path (#14538)

This commit is contained in:
J. Nick Koston
2026-03-10 09:12:10 -10:00
committed by GitHub
parent 89bb5d9e42
commit 4d2ef09a29
10 changed files with 134 additions and 19 deletions
@@ -0,0 +1,50 @@
"""Tests for the logger component."""
import re
def test_logger_pre_setup_before_other_components(generate_main):
"""Logger::pre_setup() must be called before any other component is created.
Log functions call global_logger->log_vprintf_() without a null check,
so global_logger must be set before anything can log.
"""
main_cpp = generate_main("tests/component_tests/logger/test_logger.yaml")
# Find the logger's pre_setup() call specifically
logger_pre_setup = re.search(r"logger_logger->pre_setup\(\)", main_cpp)
if logger_pre_setup is None:
# Fall back to finding any logger-related pre_setup
logger_pre_setup = re.search(r"logger\w*->pre_setup\(\)", main_cpp)
assert logger_pre_setup is not None, (
"Logger pre_setup() not found in generated code"
)
# Find all "new " allocations (component creation)
new_allocations = list(re.finditer(r"\bnew [\w:]+", main_cpp))
assert len(new_allocations) > 0, "No component allocations found"
# Separate logger and non-logger allocations
logger_allocs = [a for a in new_allocations if "logger" in a.group().lower()]
non_logger_allocs = [
a
for a in new_allocations
if "logger" not in a.group().lower()
# Skip placement new for App
and "(&App)" not in main_cpp[max(0, a.start() - 5) : a.start()]
]
assert len(logger_allocs) > 0, (
f"Logger allocation not found in: {[a.group() for a in new_allocations]}"
)
assert len(non_logger_allocs) > 0, (
"No non-logger component allocations found — "
"add a component to test_logger.yaml so the ordering check is meaningful"
)
# All non-logger allocations must appear after logger pre_setup()
for alloc in non_logger_allocs:
assert alloc.start() > logger_pre_setup.start(), (
f"Component allocation '{alloc.group()}' at position {alloc.start()} "
f"appears before logger pre_setup() at position {logger_pre_setup.start()}"
)
@@ -0,0 +1,14 @@
---
esphome:
name: test
esp8266:
board: d1_mini_lite
logger:
level: DEBUG
# Need at least one non-logger component so the ordering test
# can verify that logger pre_setup() comes before other allocations.
preferences:
flash_write_interval: 1min
+8
View File
@@ -1,5 +1,7 @@
#include <gtest/gtest.h>
#include "esphome/components/logger/logger.h"
/*
This special main.cpp replaces the default one.
It will run all the Google Tests found in all compiled cpp files and then exit with the result
@@ -18,6 +20,12 @@ void original_setup() {
}
void setup() {
// Log functions call global_logger->log_vprintf_() without a null check,
// so we must set up a Logger before any test that triggers logging.
static esphome::logger::Logger test_logger(0);
test_logger.set_log_level(ESPHOME_LOG_LEVEL);
test_logger.pre_setup();
::testing::InitGoogleTest();
int exit_code = RUN_ALL_TESTS();
exit(exit_code);
+1
View File
@@ -193,6 +193,7 @@ async def yaml_config(request: pytest.FixtureRequest, unused_tcp_port: int) -> s
" platformio_options:\n"
" build_flags:\n"
' - "-DDEBUG" # Enable assert() statements\n'
' - "-DESPHOME_DEBUG" # Enable ESPHOME_DEBUG_ASSERT checks\n'
' - "-DESPHOME_DEBUG_API" # Enable API protocol asserts\n'
' - "-g" # Add debug symbols',
)