diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index 94854d5aba..83ad6b2720 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -41,31 +41,39 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t len -= 11; } - // Build syslog packet on stack - 508 is max UDP packet size + // Build syslog packet on stack (508 bytes chosen as practical limit for syslog over UDP) char packet[508]; size_t offset = 0; + size_t remaining = sizeof(packet); - // Write PRI - int ret = snprintf(packet, sizeof(packet), "<%d>", pri); - if (ret > 0) - offset = ret; + // Write PRI - abort if this fails as packet would be malformed + int ret = snprintf(packet, remaining, "<%d>", pri); + if (ret <= 0 || static_cast(ret) >= remaining) { + return; + } + offset = ret; + remaining -= ret; - // Write timestamp directly into packet (RFC 5424: use "-" if time not valid) + // Write timestamp directly into packet (RFC 5424: use "-" if time not valid or strftime fails) auto now = this->time_->now(); - if (now.is_valid()) { - offset += now.strftime(packet + offset, sizeof(packet) - offset, "%b %e %H:%M:%S"); - } else { + size_t ts_written = now.is_valid() ? now.strftime(packet + offset, remaining, "%b %e %H:%M:%S") : 0; + if (ts_written > 0) { + offset += ts_written; + remaining -= ts_written; + } else if (remaining > 0) { packet[offset++] = '-'; + remaining--; } // Write hostname, tag, and message - ret = snprintf(packet + offset, sizeof(packet) - offset, " %s %s: %.*s", App.get_name().c_str(), tag, (int) len, - message); - if (ret > 0) - offset += ret; + ret = snprintf(packet + offset, remaining, " %s %s: %.*s", App.get_name().c_str(), tag, (int) len, message); + if (ret > 0) { + // snprintf returns chars that would be written; clamp to actual buffer space + offset += std::min(static_cast(ret), remaining > 0 ? remaining - 1 : 0); + } if (offset > 0) { - this->parent_->send_packet(reinterpret_cast(packet), std::min(offset, sizeof(packet) - 1)); + this->parent_->send_packet(reinterpret_cast(packet), offset); } } diff --git a/tests/integration/fixtures/syslog.yaml b/tests/integration/fixtures/syslog.yaml index fee00eb8ff..df376087e3 100644 --- a/tests/integration/fixtures/syslog.yaml +++ b/tests/integration/fixtures/syslog.yaml @@ -16,6 +16,11 @@ api: "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + - service: log_short_message + then: + - lambda: |- + // Log a short message that should arrive complete (not truncated) + ESP_LOGI("shorttest", "BEGIN|SHORT_MESSAGE_CONTENT|FINISH"); logger: level: DEBUG diff --git a/tests/integration/test_syslog.py b/tests/integration/test_syslog.py index 552dbc610e..b31a19392c 100644 --- a/tests/integration/test_syslog.py +++ b/tests/integration/test_syslog.py @@ -33,7 +33,7 @@ class ParsedSyslogMessage(TypedDict): # Example: <134>Dec 20 14:30:45 syslog-test app: [D][app:029]: Running... SYSLOG_PATTERN = re.compile( r"<(\d+)>" # PRI (priority = facility * 8 + severity) - r"(\S+ +\d+ \d+:\d+:\d+|-)" # TIMESTAMP (BSD format or NILVALUE "-") + r"(\S+ +\d+ \d+:\d+:\d+|-)" # TIMESTAMP (BSD-style "%b %e %H:%M:%S", e.g. "Dec 20 14:30:45", or NILVALUE "-") r" (\S+)" # HOSTNAME r" (\S+):" # TAG r" (.*)" # MESSAGE @@ -258,3 +258,27 @@ async def test_syslog( assert "|END" not in trunc_msg, ( "Message should be truncated before END marker" ) + + # Test short message - should arrive complete (not truncated) + short_service = next( + (s for s in services if s.name == "log_short_message"), None + ) + assert short_service is not None, "log_short_message service not found" + + await client.execute_service(short_service, {}) + + try: + short_msg = await receiver.wait_for_pattern(r"shorttest.*BEGIN\|") + except TimeoutError: + pytest.fail( + f"Short test message not received. Got: {receiver.messages[-10:]}" + ) + + # Verify short message arrived complete with both markers + assert "BEGIN|" in short_msg, "Short message missing BEGIN marker" + assert "|FINISH" in short_msg, ( + f"Short message truncated unexpectedly: {short_msg}" + ) + assert "SHORT_MESSAGE_CONTENT" in short_msg, ( + f"Short message content missing: {short_msg}" + )