From patchwork Mon Jan 19 20:41:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1643 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1768855376; bh=oMg7UCZfbPXbLb+t5IWlsvuPTCWcfGUJUX1VscGxVsE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=CLiCwUQZwv6CyeMHhglozhoz3EWbRZCF1H2d0VSzglSxsuB/ljCGaFNaz/Y7rrwKV ayRbtRgzzCGJVBTLCWTXH9YrYXefKGVlssSimJuWCXRzFryBI0gTEbKkiY7l8TSb7k J9eB4aNJw+ayUAUFddpZttyayqrUvgNf1gxQ53A8jKMz0R0kqlApXNVaeSct6yFo0/ bU4gCARRf+x+aPwjlHIxpM6EfCpJd0RHemqS4YNpYvbFbUeRiRfzUCUYA3uscO3lts bAWYoC8GGKvGhHmY06RKOuQUuXbUPSgjG5ePu6b7k4/LLf7u7lRFqMa5SvOC6emx4m eNpLca8wyHgCQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C44E569519 for ; Mon, 19 Jan 2026 13:42:56 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id vgTSv1eu_5_G for ; Mon, 19 Jan 2026 13:42:56 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1768855376; bh=oMg7UCZfbPXbLb+t5IWlsvuPTCWcfGUJUX1VscGxVsE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=CLiCwUQZwv6CyeMHhglozhoz3EWbRZCF1H2d0VSzglSxsuB/ljCGaFNaz/Y7rrwKV ayRbtRgzzCGJVBTLCWTXH9YrYXefKGVlssSimJuWCXRzFryBI0gTEbKkiY7l8TSb7k J9eB4aNJw+ayUAUFddpZttyayqrUvgNf1gxQ53A8jKMz0R0kqlApXNVaeSct6yFo0/ bU4gCARRf+x+aPwjlHIxpM6EfCpJd0RHemqS4YNpYvbFbUeRiRfzUCUYA3uscO3lts bAWYoC8GGKvGhHmY06RKOuQUuXbUPSgjG5ePu6b7k4/LLf7u7lRFqMa5SvOC6emx4m eNpLca8wyHgCQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B2965694CE for ; Mon, 19 Jan 2026 13:42:56 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1768855374; bh=JJrIcFFqqXxVvfJ8d+AUA+cpnDuipoe5m6S2PaWjXgQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=wok++HfFPmrWR535tVVMb7ylyAhas9fvIs6urcSj3cEy4V3TuyFtu4hcUhzhtV0/D +YsWhBeW3dM39BSnuYxLZQcO91QF/dnXJ1qERmObt4FXPudsP2PogdGftBinKzYJPv pmenPG8Wr0Wo+v4jPdY1bILVMCabzzHwFgJ7zm7+GQ/E8sv/1KWqTI7tOoCNPIlLCE emZVbJXhVyfZ3tycXtYAmMGvLZpQC+XJIMI1XEn7mFYZauoyCmXBWgTggXOcdFC0Mj vlAaqFiPS3bKW5dnwv2/a0NLGvRfZMDK76MGDe+pwey5rbaOiH+Nlj6xeyyRpwbJBv vjoQEqNHC2EWQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9DADF693E3; Mon, 19 Jan 2026 13:42:54 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id FDzjUrMwHXV2; Mon, 19 Jan 2026 13:42:54 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1768855370; bh=paYXL/BE+jwOA8P9bOZKlRdr7DrwTZePnB81nB2djBc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PKEzunS09s0UqJXc5zWVMBUS33oMegZfB0Uz+FPQigz4G4OZbOxXB1QfuEMcUKxPK +6pIPbt705mcOYMlAQ9ASEQ1SrbXxeerdDVP67BIwbpb2c/+gqOm7Oe2K9OJzfNgiY eDM/UCGCwkvzj6uxh7xb+ZIED5Qpeefrsj4XyY967ZgKCO6EKGZipOUaZmvmuZBVKE LgXPxZILPUpiIZnz8jPHWxnM5S4nN3h8roRkJFdAROZtFnLdlBAbS+52CPQmpS6lVJ DNrhRzJ8Evj9IRetFqXZlolb8QNgRooX75gevxDI+1oD0O4W5E/sgbSLVuzopamdf+ V2fS3clc3yL8Q== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 412C0693BA; Mon, 19 Jan 2026 13:42:50 -0700 (MST) From: Simon Glass To: U-Boot Concept Date: Mon, 19 Jan 2026 13:41:06 -0700 Message-ID: <20260119204130.3972647-14-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260119204130.3972647-1-sjg@u-boot.org> References: <20260119204130.3972647-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: MOA7PSVVEUVWPTEBRKSVMJSTOR4PNMOQ X-Message-ID-Hash: MOA7PSVVEUVWPTEBRKSVMJSTOR4PNMOQ X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 13/27] sandbox: log: Add file log driver List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Add a new log driver that writes log records to a file. This is useful for capturing log output during testing or debugging in sandbox. The filename can be set via the 'log_file' environment variable, or programmatically using log_file_set_fname(). Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- common/Kconfig | 8 +++ common/Makefile | 1 + common/log_file.c | 103 ++++++++++++++++++++++++++++++++++++++ configs/sandbox_defconfig | 2 + doc/develop/logging.rst | 5 ++ include/log.h | 11 ++++ lib/getopt.c | 4 +- test/log/Makefile | 1 + test/log/log_file_test.c | 43 ++++++++++++++++ test/log/log_filter.c | 9 ++-- 10 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 common/log_file.c create mode 100644 test/log/log_file_test.c diff --git a/common/Kconfig b/common/Kconfig index 597bea70b9b..8d3bda6f588 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -453,6 +453,14 @@ config LOG_SYSLOG Enables a log driver which broadcasts log records via UDP port 514 to syslog servers. +config LOG_FILE + bool "Log output to a file (sandbox only)" + depends on SANDBOX + help + Enables a log driver which writes log records to a file. Set the + 'log_file' environment variable to the filename to use, or call + log_file_set_fname() to set it programmatically. + config SPL_LOG bool "Enable logging support in SPL" depends on LOG && SPL diff --git a/common/Makefile b/common/Makefile index fdf4cff94f4..125f768ef53 100644 --- a/common/Makefile +++ b/common/Makefile @@ -97,6 +97,7 @@ obj-$(CONFIG_DFU_OVER_USB) += dfu.o obj-y += command.o obj-$(CONFIG_$(PHASE_)LOG) += log.o obj-$(CONFIG_$(PHASE_)LOG_CONSOLE) += log_console.o +obj-$(CONFIG_LOG_FILE) += log_file.o obj-$(CONFIG_$(PHASE_)LOG_SYSLOG) += log_syslog.o obj-y += s_record.o obj-$(CONFIG_CMD_LOADB) += xyzModem.o diff --git a/common/log_file.c b/common/log_file.c new file mode 100644 index 00000000000..6f445a9d561 --- /dev/null +++ b/common/log_file.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Log driver to write to a file (sandbox only) + * + * Copyright 2026 Canonical Ltd + * Written by Simon Glass + */ + +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +static int log_fd = -1; + +static void append(char **buf, char *buf_end, const char *fmt, ...) +{ + va_list args; + size_t size = buf_end - *buf; + + va_start(args, fmt); + vsnprintf(*buf, size, fmt, args); + va_end(args); + *buf += strlen(*buf); +} + +int log_file_set_fname(const char *fname) +{ + if (log_fd != -1) { + os_close(log_fd); + log_fd = -1; + } + + if (!fname) + return 0; + + log_fd = os_open(fname, OS_O_WRONLY | OS_O_CREAT | OS_O_TRUNC); + if (log_fd < 0) + return log_fd; + + return 0; +} + +static int log_file_emit(struct log_device *ldev, struct log_rec *rec) +{ + int fmt = gd->log_fmt; + char buf[512]; + char *buf_end = buf + sizeof(buf); + char *ptr = buf; + const char *fname; + int len; + + /* If no file open, try to open one from the environment */ + if (log_fd == -1) { + fname = env_get("log_file"); + if (!fname) + return 0; + + log_fd = os_open(fname, OS_O_WRONLY | OS_O_CREAT | OS_O_TRUNC); + if (log_fd < 0) + return 0; + } + + /* + * The output format is designed to give someone a fighting chance of + * figuring out which field is which: + * - level is in CAPS + * - cat is lower case and ends with comma + * - file normally has a .c extension and ends with a colon + * - line is integer and ends with a - + * - function is an identifier and ends with () + * - message has a space before it unless it is on its own + */ + if (!(rec->flags & LOGRECF_CONT) && fmt != BIT(LOGF_MSG)) { + if (fmt & BIT(LOGF_LEVEL)) + append(&ptr, buf_end, "%s.", + log_get_level_name(rec->level)); + if (fmt & BIT(LOGF_CAT)) + append(&ptr, buf_end, "%s,", + log_get_cat_name(rec->cat)); + if (fmt & BIT(LOGF_FILE)) + append(&ptr, buf_end, "%s:", rec->file); + if (fmt & BIT(LOGF_LINE)) + append(&ptr, buf_end, "%d-", rec->line); + if (fmt & BIT(LOGF_FUNC)) + append(&ptr, buf_end, "%s() ", rec->func ?: "?"); + } + if (fmt & BIT(LOGF_MSG)) + append(&ptr, buf_end, "%s", rec->msg); + + len = ptr - buf; + os_write(log_fd, buf, len); + + return 0; +} + +LOG_DRIVER(file) = { + .name = "file", + .emit = log_file_emit, + .flags = LOGDF_ENABLE, +}; diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 4dbb77abcff..93ef88d597e 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -57,6 +57,7 @@ CONFIG_LOG=y CONFIG_LOG_MAX_LEVEL=9 CONFIG_LOG_DEFAULT_LEVEL=6 CONFIG_LOGF_FUNC=y +CONFIG_LOG_FILE=y CONFIG_DISPLAY_BOARDINFO_LATE=y CONFIG_STACKPROTECTOR=y CONFIG_ANDROID_AB=y @@ -149,6 +150,7 @@ CONFIG_CMD_EROFS=y CONFIG_CMD_EXT4_WRITE=y CONFIG_CMD_SQUASHFS=y CONFIG_CMD_MTDPARTS=y +CONFIG_CMD_LOG=y CONFIG_CMD_STACKPROTECTOR_TEST=y CONFIG_MAC_PARTITION=y CONFIG_OF_CONTROL=y diff --git a/doc/develop/logging.rst b/doc/develop/logging.rst index d7a40c94bf0..6314cae380c 100644 --- a/doc/develop/logging.rst +++ b/doc/develop/logging.rst @@ -172,10 +172,15 @@ enabled or disabled independently: * console - goes to stdout * syslog - broadcast RFC 3164 messages to syslog servers on UDP port 514 +* file - writes to a file (sandbox only) The syslog driver sends the value of environmental variable 'log_hostname' as HOSTNAME if available. +The file driver sends log records to a file and is only available in sandbox. +Set the 'log_file' environment variable to specify the filename, or call +log_file_set_fname() to set it programmatically. + Filters ------- diff --git a/include/log.h b/include/log.h index 8e933071cf1..c79042f1a5c 100644 --- a/include/log.h +++ b/include/log.h @@ -675,6 +675,17 @@ int log_remove_filter(const char *drv_name, int filter_num); */ int log_device_set_enable(struct log_driver *drv, bool enable); +/** + * log_file_set_fname() - Set the filename for the file log driver + * + * This sets or changes the file used by the file log driver. If a file is + * already open it is closed first. + * + * @fname: Filename to use, or NULL to close any existing file + * Return: 0 if OK, -ve on error + */ +int log_file_set_fname(const char *fname); + #if CONFIG_IS_ENABLED(LOG) /** * log_init() - Set up the log system ready for use diff --git a/lib/getopt.c b/lib/getopt.c index e9175e2fff4..c7bb6d3671a 100644 --- a/lib/getopt.c +++ b/lib/getopt.c @@ -25,8 +25,8 @@ int __getopt(struct getopt_state *gs, int argc, char *const argv[], const char *curoptp; /* pointer to the current option in optstring */ while (1) { - log_debug("arg_index: %d index: %d\n", gs->arg_index, - gs->index); + log_content("arg_index: %d index: %d\n", gs->arg_index, + gs->index); /* `--` indicates the end of options */ if (gs->arg_index == 1 && argv[gs->index] && diff --git a/test/log/Makefile b/test/log/Makefile index 24b7c46786d..6fc45d59a16 100644 --- a/test/log/Makefile +++ b/test/log/Makefile @@ -10,6 +10,7 @@ ifdef CONFIG_UT_LOG ifdef CONFIG_SANDBOX obj-$(CONFIG_LOG_SYSLOG) += syslog_test.o obj-$(CONFIG_LOG_SYSLOG) += syslog_test_ndebug.o +obj-$(CONFIG_LOG_FILE) += log_file_test.o endif ifdef CONFIG_LOG diff --git a/test/log/log_file_test.c b/test/log/log_file_test.c new file mode 100644 index 00000000000..e1d6b7dedf1 --- /dev/null +++ b/test/log/log_file_test.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2026 Canonical Ltd + * Written by Simon Glass + * + * Test for log file driver + */ + +#include +#include +#include +#include +#include + +/* Test that the log_file driver can write to a file */ +static int log_test_file_driver(struct unit_test_state *uts) +{ + const char *fname = "/tmp/log_test.log"; + void *buf; + int size; + + ut_assertok(log_file_set_fname(fname)); + + /* Generate some log messages using log rec command */ + run_command("log format Lfm", 0); + run_command("log rec none warning test.c 123 my_func 'Test message'", 0); + run_command("log rec none err error.c 456 err_func 'Error occurred'", 0); + + /* Close the file so we can read it */ + ut_assertok(log_file_set_fname(NULL)); + + /* Read the file contents */ + ut_assertok(os_read_file(fname, &buf, &size)); + + /* Check the contents */ + ut_asserteq_strn("123-my_func() Test message\n", buf); + ut_assertnonnull(strstr(buf, "456-err_func() Error occurred\n")); + + os_free(buf); + + return 0; +} +LOG_TEST(log_test_file_driver); diff --git a/test/log/log_filter.c b/test/log/log_filter.c index 8622dcf2913..05ff4e94bd2 100644 --- a/test/log/log_filter.c +++ b/test/log/log_filter.c @@ -39,11 +39,12 @@ static int log_test_filter(struct unit_test_state *uts) #define create_filter(args, filter_num) do {\ ut_assertok(run_command("log filter-add -p " args, 0)); \ + console_record_readline(uts->actual_str, sizeof(uts->actual_str)); \ ut_assertok(strict_strtoul(uts->actual_str, 10, &(filter_num))); \ ut_assert_console_end(); \ } while (0) - create_filter("", filt1); + create_filter("-l info", filt1); create_filter("-DL warning -cmmc -cspi -ffile", filt2); ldev = log_device_find_by_name("console"); @@ -52,7 +53,7 @@ static int log_test_filter(struct unit_test_state *uts) if (filt->filter_num == filt1) { filt1_found = true; ut_asserteq(0, filt->flags); - ut_asserteq(LOGL_MAX, filt->level); + ut_asserteq(LOGL_INFO, filt->level); ut_assertnull(filt->file_list); } else if (filt->filter_num == filt2) { filt2_found = true; @@ -89,8 +90,8 @@ static int log_test_filter(struct unit_test_state *uts) ut_asserteq(false, filt1_found); ut_asserteq(false, filt2_found); - create_filter("", filt1); - create_filter("", filt2); + create_filter("-l info", filt1); + create_filter("-l info", filt2); ut_assertok(run_command("log filter-remove -a", 0)); ut_assert_console_end();