已合并
fix: aclAppLog 超长日志改用截断而非丢弃整条日志(#555) #2850
GuoWenbo创建于 6月16日
fix: aclAppLog 超长日志改用截断而非丢弃整条日志(#555) #2850
已合并
共 5 个文件变更+123-17
| @@ -353,6 +353,10 @@ ACL_FUNC_VISIBILITY size_t aclDataTypeSize(aclDataType dataType); | |||
| 353 | * @param line [IN] Number of source lines where the log is located | 353 | * @param line [IN] Number of source lines where the log is located |
| 354 | * @param fmt [IN] the format of current log | 354 | * @param fmt [IN] the format of current log |
| 355 | * @param ... [IN] the value of current log | 355 | * @param ... [IN] the value of current log |
| 356 | + * | ||
| 357 | + * @attention The formatted log string is limited to 1024 bytes (including the terminating null | ||
| 358 | + * character). Content exceeding this limit will be truncated (with a trailing | ||
| 359 | + * "...[truncated]" mark) rather than discarded. | ||
| 356 | */ | 360 | */ |
| 357 | ACL_FUNC_VISIBILITY void aclAppLog(aclLogLevel logLevel, const char *func, const char *file, uint32_t line, | 361 | ACL_FUNC_VISIBILITY void aclAppLog(aclLogLevel logLevel, const char *func, const char *file, uint32_t line, |
| 358 | const char *fmt, ...); | 362 | const char *fmt, ...); |
| @@ -13,6 +13,18 @@ | |||
| 13 | 13 | ||
| 14 | 14 | ||
| 15 | 15 | ||
| 16 | +namespace { | ||
| 17 | +constexpr const char_t *const TRUNCATED_MARK = "...[truncated]"; | ||
| 18 | +constexpr size_t TRUNCATED_MARK_LEN = 14U; // strlen("...[truncated]") | ||
| 19 | + | ||
| 20 | +// 在缓冲区尾部覆盖写入截断标记,标记连同结尾 '\0' 仍落在 MAX_LOG_STRING 预算内,避免被下游二次截断。 | ||
| 21 | +void AppendTruncatedMark(char_t *const buf) | ||
| 22 | +{ | ||
| 23 | + (void)strcpy_s(&buf[acl::MAX_LOG_STRING - 1U - TRUNCATED_MARK_LEN], | ||
| 24 | + TRUNCATED_MARK_LEN + 1U, TRUNCATED_MARK); | ||
| 25 | +} | ||
| 26 | +} // namespace | ||
| 27 | + | ||
| 16 | 28 | ||
| 17 | extern "C" { | 29 | extern "C" { |
| 18 | 30 | ||
| @@ -25,21 +37,26 @@ void aclAppLogImpl(aclLogLevel logLevel, const char *func, const char *file, uin | |||
| 25 | if (!acl::AclLog::IsLogOutputEnable(logLevel)) { | 37 | if (!acl::AclLog::IsLogOutputEnable(logLevel)) { |
| 26 | return; | 38 | return; |
| 27 | } | 39 | } |
| 40 | + // 使用 vsnprintf_truncated_s / snprintf_truncated_s 而非 vsnprintf_s / sprintf_s: | ||
| 41 | + // 后者在内容超长时返回 -1 并丢弃整条日志,前者会截断保留内容(截断时返回 MAX_LOG_STRING - 1)。 | ||
| 28 | char_t str[acl::MAX_LOG_STRING] = {}; | 42 | char_t str[acl::MAX_LOG_STRING] = {}; |
| 29 | - int32_t printRet = vsnprintf_s(str, static_cast<size_t>(acl::MAX_LOG_STRING), | 43 | + const int32_t bodyRet = vsnprintf_truncated_s(str, static_cast<size_t>(acl::MAX_LOG_STRING), fmt, args); |
| 30 | - static_cast<size_t>(acl::MAX_LOG_STRING - 1U), fmt, args); | 44 | + if (bodyRet == -1) { |
| 31 | - if (printRet != -1) { | 45 | + // 仅在格式化字符串非法(如包含 %n)时进入此分支,给出准确提示。 |
| 32 | - char_t strLog[acl::MAX_LOG_STRING] = {}; | 46 | + acl::AclLog::ACLSaveLog(ACL_ERROR, "aclAppLog format string is invalid"); |
| 33 | - printRet = sprintf_s(strLog, static_cast<size_t>(acl::MAX_LOG_STRING), "%d %s:%s:%u: \"%s\"", | 47 | + return; |
| 34 | - acl::AclLog::GetTid(), func, file, line, str); | ||
| 35 | - if (printRet != -1) { | ||
| 36 | - acl::AclLog::ACLSaveLog(logLevel, strLog); | ||
| 37 | - } else { | ||
| 38 | - acl::AclLog::ACLSaveLog(ACL_ERROR, "aclAppLog call sprintf_s failed"); | ||
| 39 | - } | ||
| 40 | - } else { | ||
| 41 | - acl::AclLog::ACLSaveLog(ACL_ERROR, "aclAppLog call vsnprintf_s failed"); | ||
| 42 | } | 48 | } |
| 49 | + | ||
| 50 | + char_t strLog[acl::MAX_LOG_STRING] = {}; | ||
| 51 | + const int32_t headRet = snprintf_truncated_s(strLog, static_cast<size_t>(acl::MAX_LOG_STRING), | ||
| 52 | + "%d %s:%s:%u: \"%s\"", acl::AclLog::GetTid(), func, file, line, str); | ||
| 53 | + | ||
| 54 | + // 正文或头部拼接发生截断时,在最终串尾部补写截断标记,提示用户内容不完整。 | ||
| 55 | + if ((bodyRet >= static_cast<int32_t>(acl::MAX_LOG_STRING - 1U)) || | ||
| 56 | + (headRet >= static_cast<int32_t>(acl::MAX_LOG_STRING - 1U))) { | ||
| 57 | + AppendTruncatedMark(strLog); | ||
| 58 | + } | ||
| 59 | + acl::AclLog::ACLSaveLog(logLevel, strLog); | ||
| 43 | return; | 60 | return; |
| 44 | } | 61 | } |
| 45 | 62 | ||
| @@ -0,0 +1,18 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2025 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +// slog stub 捕获最近一次 DlogRecord 落盘的日志内容,供单测断言使用。 | ||
| 15 | +// 通过头文件声明的函数接口访问,避免在测试源文件中以 extern 声明引用外部变量(G.EXP.05-CPP)。 | ||
| 16 | +const char *DlogStubGetLastLogMsg(); | ||
| 17 | + | ||
| 18 | + | ||
| @@ -14,21 +14,39 @@ | |||
| 14 | 14 | ||
| 15 | 15 | ||
| 16 | 16 | ||
| 17 | + | ||
| 17 | 18 | ||
| 19 | + | ||
| 18 | 20 | ||
| 19 | void dlog_init(){} | 21 | void dlog_init(){} |
| 20 | 22 | ||
| 21 | -int aclStub::dlog_getlevel(int module_id, int *enable_event){ | 23 | +int aclStub::dlog_getlevel(int module_id, int *enable_event){ |
| 22 | return DLOG_DEBUG; | 24 | return DLOG_DEBUG; |
| 23 | } | 25 | } |
| 24 | 26 | ||
| 25 | -int dlog_getlevel(int module_id, int *enable_event){ | 27 | +int dlog_getlevel(int module_id, int *enable_event){ |
| 26 | return MockFunctionTest::aclStubInstance().dlog_getlevel(module_id, enable_event); | 28 | return MockFunctionTest::aclStubInstance().dlog_getlevel(module_id, enable_event); |
| 27 | } | 29 | } |
| 28 | 30 | ||
| 31 | +namespace { | ||
| 32 | +constexpr int LOG_MSG_MAX = 4096; | ||
| 33 | +char g_lastLogMsg[LOG_MSG_MAX] = {0}; | ||
| 34 | +} | ||
| 35 | + | ||
| 29 | int g_logLevel = DLOG_ERROR; | 36 | int g_logLevel = DLOG_ERROR; |
| 30 | void DlogRecord(int moduleId, int level, const char *fmt, ...) { | 37 | void DlogRecord(int moduleId, int level, const char *fmt, ...) { |
| 31 | g_logLevel = level; | 38 | g_logLevel = level; |
| 39 | + va_list args; | ||
| 40 | + va_start(args, fmt); | ||
| 41 | + int ret = vsnprintf_s(g_lastLogMsg, sizeof(g_lastLogMsg), sizeof(g_lastLogMsg) - 1U, fmt, args); | ||
| 42 | + va_end(args); | ||
| 43 | + if (ret < 0) { | ||
| 44 | + g_lastLogMsg[0] = '\0'; | ||
| 45 | + } | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +const char *DlogStubGetLastLogMsg() { | ||
| 49 | + return g_lastLogMsg; | ||
| 32 | } | 50 | } |
| 33 | 51 | ||
| 34 | void DlogErrorInner(int module_id, const char *fmt, ...){} | 52 | void DlogErrorInner(int module_id, const char *fmt, ...){} |
| @@ -34,6 +34,7 @@ | |||
| 34 | 34 | ||
| 35 | 35 | ||
| 36 | 36 | ||
| 37 | + | ||
| 37 | 38 | ||
| 38 | 39 | ||
| 39 | 40 | ||
| @@ -2708,7 +2709,7 @@ TEST_F(UTEST_ACL_Common, aclAppLog_succ) | |||
| 2708 | EXPECT_TRUE(g_logLevel == ACL_ERROR); | 2709 | EXPECT_TRUE(g_logLevel == ACL_ERROR); |
| 2709 | } | 2710 | } |
| 2710 | 2711 | ||
| 2711 | -TEST_F(UTEST_ACL_Common, aclAppLog_failed_1000bytes) | 2712 | +TEST_F(UTEST_ACL_Common, aclAppLog_truncate_1000bytes) |
| 2712 | { | 2713 | { |
| 2713 | aclLogLevel level = ACL_ERROR; | 2714 | aclLogLevel level = ACL_ERROR; |
| 2714 | const char *func = "UserFunc"; | 2715 | const char *func = "UserFunc"; |
| @@ -2726,10 +2727,11 @@ TEST_F(UTEST_ACL_Common, aclAppLog_failed_1000bytes) | |||
| 2726 | "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" | 2727 | "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" |
| 2727 | "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; | 2728 | "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; |
| 2728 | aclAppLog(level, func, file, line, fmt); | 2729 | aclAppLog(level, func, file, line, fmt); |
| 2730 | + // 超长日志被截断后仍按原 level 落盘,不再被强制成 ERROR 误报。 | ||
| 2729 | EXPECT_TRUE(g_logLevel == ACL_ERROR); | 2731 | EXPECT_TRUE(g_logLevel == ACL_ERROR); |
| 2730 | } | 2732 | } |
| 2731 | 2733 | ||
| 2732 | -TEST_F(UTEST_ACL_Common, aclAppLog_failed_1100bytes) | 2734 | +TEST_F(UTEST_ACL_Common, aclAppLog_truncate_1100bytes) |
| 2733 | { | 2735 | { |
| 2734 | aclLogLevel level = ACL_ERROR; | 2736 | aclLogLevel level = ACL_ERROR; |
| 2735 | const char *func = "UserFunc"; | 2737 | const char *func = "UserFunc"; |
| @@ -2748,7 +2750,54 @@ TEST_F(UTEST_ACL_Common, aclAppLog_failed_1100bytes) | |||
| 2748 | "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" | 2750 | "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" |
| 2749 | "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; | 2751 | "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; |
| 2750 | aclAppLog(level, func, file, line, fmt); | 2752 | aclAppLog(level, func, file, line, fmt); |
| 2753 | + // 超长日志被截断后仍按原 level 落盘,不再被丢弃或误报。 | ||
| 2751 | EXPECT_TRUE(g_logLevel == ACL_ERROR); | 2754 | EXPECT_TRUE(g_logLevel == ACL_ERROR); |
| 2755 | + // 超长日志被截断后尾部应追加截断标记,提示用户内容不完整。 | ||
| 2756 | + EXPECT_TRUE(strstr(DlogStubGetLastLogMsg(), "...[truncated]") != nullptr); | ||
| 2757 | +} | ||
| 2758 | + | ||
| 2759 | +TEST_F(UTEST_ACL_Common, aclAppLog_truncate_keep_level) | ||
| 2760 | +{ | ||
| 2761 | + // 验证超长日志被截断后仍按调用级别输出,不会被强制成 ACL_ERROR。 | ||
| 2762 | + aclLogLevel level = ACL_INFO; | ||
| 2763 | + const char *func = "UserFunc"; | ||
| 2764 | + const char *file = "main.cpp"; | ||
| 2765 | + uint32_t line = 88U; | ||
| 2766 | + const std::string fmt(1100U, 'a'); | ||
| 2767 | + aclAppLog(level, func, file, line, fmt.c_str()); | ||
| 2768 | + EXPECT_TRUE(g_logLevel == ACL_INFO); | ||
| 2769 | + // 截断标记应落在 MAX_LOG_STRING 预算内,最终落盘的日志总长不超过 1024 字节。 | ||
| 2770 | + EXPECT_TRUE(strstr(DlogStubGetLastLogMsg(), "...[truncated]") != nullptr); | ||
| 2771 | + EXPECT_TRUE(strlen(DlogStubGetLastLogMsg()) < 1024U); | ||
| 2772 | +} | ||
| 2773 | + | ||
| 2774 | +TEST_F(UTEST_ACL_Common, aclAppLog_short_keep_level) | ||
| 2775 | +{ | ||
| 2776 | + // 正常短日志按调用级别输出,且不追加截断标记。 | ||
| 2777 | + aclLogLevel level = ACL_INFO; | ||
| 2778 | + const char *func = "UserFunc"; | ||
| 2779 | + const char *file = "main.cpp"; | ||
| 2780 | + uint32_t line = 88U; | ||
| 2781 | + aclAppLog(level, func, file, line, "short message"); | ||
| 2782 | + EXPECT_TRUE(g_logLevel == ACL_INFO); | ||
| 2783 | + EXPECT_TRUE(strstr(DlogStubGetLastLogMsg(), "...[truncated]") == nullptr); | ||
| 2784 | +} | ||
| 2785 | + | ||
| 2786 | +TEST_F(UTEST_ACL_Common, aclAppLog_invalid_format) | ||
| 2787 | +{ | ||
| 2788 | + // 非法格式化字符串(含 %n,安全函数会拒绝并返回 -1)应输出准确的错误提示。 | ||
| 2789 | + // 运行时构造格式串,避免触发编译期 -Wformat 告警。 | ||
| 2790 | + aclLogLevel level = ACL_ERROR; | ||
| 2791 | + const char *func = "UserFunc"; | ||
| 2792 | + const char *file = "main.cpp"; | ||
| 2793 | + uint32_t line = 88U; | ||
| 2794 | + char fmt[8] = {}; | ||
| 2795 | + fmt[0] = '%'; | ||
| 2796 | + fmt[1] = 'n'; | ||
| 2797 | + int dummy = 0; | ||
| 2798 | + aclAppLog(level, func, file, line, fmt, &dummy); | ||
| 2799 | + EXPECT_TRUE(g_logLevel == ACL_ERROR); | ||
| 2800 | + EXPECT_TRUE(strstr(DlogStubGetLastLogMsg(), "format string is invalid") != nullptr); | ||
| 2752 | } | 2801 | } |
| 2753 | 2802 | ||
| 2754 | TEST_F(UTEST_ACL_Common, FormatStr_failed_1100bytes) | 2803 | TEST_F(UTEST_ACL_Common, FormatStr_failed_1100bytes) |