[v20,14/14] log: colorize log output

Message ID 20240330164433.50144-15-stephen@networkplumber.org (mailing list archive)
State New
Delegated to: Thomas Monjalon
Headers
Series Logging unification and improvements |

Checks

Context Check Description
ci/checkpatch warning coding style issues
ci/loongarch-compilation success Compilation OK
ci/loongarch-unit-testing success Unit Testing PASS
ci/Intel-compilation success Compilation OK
ci/intel-Testing success Testing PASS
ci/intel-Functional success Functional PASS
ci/iol-intel-Performance success Performance Testing PASS
ci/iol-mellanox-Performance success Performance Testing PASS
ci/github-robot: build success github build: passed
ci/iol-abi-testing success Testing PASS
ci/iol-sample-apps-testing success Testing PASS
ci/iol-compile-amd64-testing success Testing PASS
ci/iol-unit-amd64-testing success Testing PASS
ci/iol-unit-arm64-testing success Testing PASS
ci/iol-compile-arm64-testing success Testing PASS
ci/iol-broadcom-Performance success Performance Testing PASS
ci/iol-intel-Functional success Functional Testing PASS
ci/iol-broadcom-Functional success Functional Testing PASS

Commit Message

Stephen Hemminger March 30, 2024, 4:42 p.m. UTC
  Like dmesg, colorize the log output (unless redirected to file).
Timestamp is green, the subsystem is in yellow and the message
is red if urgent, boldface if an error, and normal for info and
debug messages.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test/test_eal_flags.c           |  24 +++++
 doc/guides/prog_guide/log_lib.rst   |  16 ++-
 lib/eal/common/eal_common_options.c |  11 +++
 lib/eal/common/eal_options.h        |   6 +-
 lib/log/log.c                       |  24 +++--
 lib/log/log_color.c                 | 148 ++++++++++++++++++++++++++++
 lib/log/log_internal.h              |   5 +
 lib/log/log_private.h               |   8 ++
 lib/log/meson.build                 |   1 +
 lib/log/version.map                 |   1 +
 10 files changed, 235 insertions(+), 9 deletions(-)
 create mode 100644 lib/log/log_color.c
  

Patch

diff --git a/app/test/test_eal_flags.c b/app/test/test_eal_flags.c
index 08f4866461..c6c05e2e1d 100644
--- a/app/test/test_eal_flags.c
+++ b/app/test/test_eal_flags.c
@@ -1067,6 +1067,18 @@  test_misc_flags(void)
 	const char * const argv25[] = {prgname, prefix, mp_flag,
 				       "--log-timestamp=invalid" };
 
+	/* Try running with --log-color */
+	const char * const argv26[] = {prgname, prefix, mp_flag,
+				       "--log-color" };
+
+	/* Try running with --log-color=never */
+	const char * const argv27[] = {prgname, prefix, mp_flag,
+				       "--log-color=never" };
+
+	/* Try running with --log-color=invalid */
+	const char * const argv28[] = {prgname, prefix, mp_flag,
+				       "--log-color=invalid" };
+
 
 	/* run all tests also applicable to FreeBSD first */
 
@@ -1187,6 +1199,18 @@  test_misc_flags(void)
 		printf("Error - process did run ok with --log-timestamp=invalid parameter\n");
 		goto fail;
 	}
+	if (launch_proc(argv26) != 0) {
+		printf("Error - process did not run ok with --log-color parameter\n");
+		goto fail;
+	}
+	if (launch_proc(argv27) != 0) {
+		printf("Error - process did not run ok with --log-color=none parameter\n");
+		goto fail;
+	}
+	if (launch_proc(argv28) == 0) {
+		printf("Error - process did run ok with --log-timestamp=invalid parameter\n");
+		goto fail;
+	}
 
 
 	rmdir(hugepath_dir3);
diff --git a/doc/guides/prog_guide/log_lib.rst b/doc/guides/prog_guide/log_lib.rst
index 476dedb097..f46720fe34 100644
--- a/doc/guides/prog_guide/log_lib.rst
+++ b/doc/guides/prog_guide/log_lib.rst
@@ -59,6 +59,21 @@  For example::
 
 Within an application, the same result can be got using the ``rte_log_set_level_pattern()`` or ``rte_log_set_level_regex()`` APIs.
 
+Color output
+~~~~~~~~~~~~
+
+The log library will highlight important messages.
+This is controlled by the ``--log-color`` option.
+he optional argument ``when`` can be ``auto``, ``never``, or ``always``.
+The default setting is ``auto`` which enables color when the output to
+``stderr`` is a terminal.
+If the ``when`` argument is omitted, it defaults to ``always``.
+
+For example to turn off all coloring::
+
+	/path/to/app --log-color=none
+
+
 Log timestamp
 ~~~~~~~~~~~~~
 
@@ -115,7 +130,6 @@  There are three possible settings for this option:
 *always*
     Always try to direct messages to journal socket.
 
-
 Using Logging APIs to Generate Log Messages
 -------------------------------------------
 
diff --git a/lib/eal/common/eal_common_options.c b/lib/eal/common/eal_common_options.c
index 9a82118184..70fdf3f5a1 100644
--- a/lib/eal/common/eal_common_options.c
+++ b/lib/eal/common/eal_common_options.c
@@ -73,6 +73,7 @@  eal_long_options[] = {
 	{OPT_HUGE_UNLINK,       2, NULL, OPT_HUGE_UNLINK_NUM      },
 	{OPT_IOVA_MODE,	        1, NULL, OPT_IOVA_MODE_NUM        },
 	{OPT_LCORES,            1, NULL, OPT_LCORES_NUM           },
+	{OPT_LOG_COLOR,		2, NULL, OPT_LOG_COLOR_NUM	  },
 	{OPT_LOG_LEVEL,         1, NULL, OPT_LOG_LEVEL_NUM        },
 	{OPT_LOG_JOURNAL,	2, NULL, OPT_LOG_JOURNAL_NUM	  },
 	{OPT_LOG_TIMESTAMP,     2, NULL, OPT_LOG_TIMESTAMP_NUM    },
@@ -1620,6 +1621,7 @@  eal_log_level_parse(int argc, char * const argv[])
 		case OPT_SYSLOG_NUM:
 		case OPT_LOG_JOURNAL_NUM:
 		case OPT_LOG_TIMESTAMP_NUM:
+		case OPT_LOG_COLOR_NUM:
 			if (eal_parse_common_option(opt, optarg, internal_conf) < 0)
 				return -1;
 			break;
@@ -1872,6 +1874,14 @@  eal_parse_common_option(int opt, const char *optarg,
 		}
 		break;
 
+	case OPT_LOG_COLOR_NUM:
+		if (eal_log_color(optarg) < 0) {
+			EAL_LOG(ERR, "invalid parameters for --"
+				OPT_LOG_COLOR);
+			return -1;
+		}
+		break;
+
 #ifndef RTE_EXEC_ENV_WINDOWS
 	case OPT_TRACE_NUM: {
 		if (eal_trace_args_save(optarg) < 0) {
@@ -2240,6 +2250,7 @@  eal_common_usage(void)
 	       "                      Set specific log level\n"
 	       "  --"OPT_LOG_LEVEL"=help    Show log types and levels\n"
 	       "  --"OPT_LOG_TIMESTAMP"[=<format>]  Timestamp log output\n"
+	       "  --"OPT_LOG_COLOR"[=<when>] Colorize log messages\n"
 #ifndef RTE_EXEC_ENV_WINDOWS
 	       "  --"OPT_TRACE"=<regex-match>\n"
 	       "                      Enable trace based on regular expression trace name.\n"
diff --git a/lib/eal/common/eal_options.h b/lib/eal/common/eal_options.h
index c5a1c70288..c0686c2544 100644
--- a/lib/eal/common/eal_options.h
+++ b/lib/eal/common/eal_options.h
@@ -33,10 +33,12 @@  enum {
 	OPT_HUGE_UNLINK_NUM,
 #define OPT_LCORES            "lcores"
 	OPT_LCORES_NUM,
-#define OPT_LOG_LEVEL         "log-level"
-	OPT_LOG_LEVEL_NUM,
+#define OPT_LOG_COLOR	      "log-color"
+	OPT_LOG_COLOR_NUM,
 #define OPT_LOG_JOURNAL       "log-journal"
 	OPT_LOG_JOURNAL_NUM,
+#define OPT_LOG_LEVEL         "log-level"
+	OPT_LOG_LEVEL_NUM,
 #define OPT_LOG_TIMESTAMP     "log-timestamp"
 	OPT_LOG_TIMESTAMP_NUM,
 #define OPT_TRACE             "trace"
diff --git a/lib/log/log.c b/lib/log/log.c
index 13d68402d9..856c0a0a29 100644
--- a/lib/log/log.c
+++ b/lib/log/log.c
@@ -503,10 +503,14 @@  rte_log(uint32_t level, uint32_t logtype, const char *format, ...)
 void
 eal_log_init(const char *id)
 {
+	bool is_terminal;
+
 #ifdef RTE_EXEC_ENV_WINDOWS
 	RTE_SET_USED(id);
+
+	is_terminal = _isatty(_fileno(stderr));
 #else
-	bool is_terminal = isatty(STDERR_FILENO);
+	is_terminal = isatty(STDERR_FILENO);
 
 #ifdef RTE_EXEC_ENV_LINUX
 	if (log_journal_enabled())
@@ -516,11 +520,19 @@  eal_log_init(const char *id)
 	if (log_syslog_enabled(is_terminal))
 		log_syslog_open(id, is_terminal);
 	else
-#endif
-	if (log_timestamp_enabled())
-		rte_logs.print_func = log_print_with_timestamp;
-	else
-		rte_logs.print_func = vfprintf;
+#endif /* RTE_EXEC_ENV_WINDOWS */
+
+	if (log_color_enabled(is_terminal)) {
+		if (log_timestamp_enabled())
+			rte_logs.print_func = color_print_with_timestamp;
+		else
+			rte_logs.print_func = color_print;
+	} else {
+		if (log_timestamp_enabled())
+			rte_logs.print_func = log_print_with_timestamp;
+		else
+			rte_logs.print_func = vfprintf;
+	}
 
 #if RTE_LOG_DP_LEVEL >= RTE_LOG_DEBUG
 	RTE_LOG(NOTICE, EAL,
diff --git a/lib/log/log_color.c b/lib/log/log_color.c
new file mode 100644
index 0000000000..897a10a342
--- /dev/null
+++ b/lib/log/log_color.c
@@ -0,0 +1,148 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_log.h>
+
+#ifdef RTE_EXEC_ENV_WINDOWS
+#include <rte_os_shim.h>
+#endif
+
+#include "log_internal.h"
+#include "log_private.h"
+
+enum  {
+	LOG_COLOR_AUTO = 0,	/* default */
+	LOG_COLOR_NEVER,
+	LOG_COLOR_ALWAYS,
+} log_color_mode;
+
+enum color {
+	COLOR_NONE,
+	COLOR_RED,
+	COLOR_GREEN,
+	COLOR_YELLOW,
+	COLOR_BLUE,
+	COLOR_MAGENTA,
+	COLOR_CYAN,
+	COLOR_WHITE,
+	COLOR_BOLD,
+	COLOR_CLEAR,
+};
+
+/* Standard terminal escape codes for colors and bold */
+static const char * const color_code[] = {
+	[COLOR_NONE]	= "",
+	[COLOR_RED]	= "\033[31m",
+	[COLOR_GREEN]	= "\033[32m",
+	[COLOR_YELLOW]	= "\033[33m",
+	[COLOR_BLUE]	= "\033[34m",
+	[COLOR_MAGENTA] = "\033[35m",
+	[COLOR_CYAN]    = "\033[36m",
+	[COLOR_WHITE]	= "\033[37m",
+	[COLOR_BOLD]	= "\033[1m",
+	[COLOR_CLEAR]	= "\033[0m",
+};
+
+__rte_format_printf(3, 4)
+static int color_fprintf(FILE *out, enum color color, const char *fmt, ...)
+{
+	va_list args;
+	int ret = 0;
+
+	va_start(args, fmt);
+	ret = fprintf(out, "%s", color_code[color]);
+	ret += vfprintf(out, fmt, args);
+	ret += fprintf(out, "%s", color_code[COLOR_CLEAR]);
+	va_end(args);
+
+	return ret;
+}
+
+/*
+ * Controls whether color is enabled:
+ * modes are:
+ *   always - enable color output regardless
+ *   auto - enable if stderr is a terminal
+ *   never - color output is disabled.
+ */
+int
+eal_log_color(const char *mode)
+{
+	if (mode == NULL || strcmp(mode, "always") == 0)
+		log_color_mode = LOG_COLOR_ALWAYS;
+	else if (strcmp(mode, "never") == 0)
+		log_color_mode = LOG_COLOR_NEVER;
+	else if (strcmp(mode, "auto") == 0)
+		log_color_mode = LOG_COLOR_AUTO;
+	else
+		return -1;
+
+	return 0;
+}
+
+bool
+log_color_enabled(bool is_terminal)
+{
+	switch (log_color_mode) {
+	default:
+	case LOG_COLOR_NEVER:
+		return false;
+	case LOG_COLOR_ALWAYS:
+		return true;
+	case LOG_COLOR_AUTO:
+		return is_terminal;
+	}
+}
+
+int
+color_print(FILE *f, const char *format, va_list ap)
+{
+	const int level = rte_log_cur_msg_loglevel();
+	char *cp, *msg;
+	char buf[LINE_MAX];
+	int ret = 0;
+
+	vsnprintf(buf, LINE_MAX, format, ap);
+	msg = buf;
+
+	/*
+	 * use convention that first part of message (up to the ':' character)
+	 * is the subsystem id and should be highlighted.
+	 */
+	cp = strchr(msg, ':');
+	if (cp) {
+		/* print first part in yellow */
+		ret = color_fprintf(f, COLOR_YELLOW, "%.*s",
+				    (int)(cp - msg + 1), msg);
+		msg = cp + 1;
+	}
+
+	if (level <= 0 || level >= (int)RTE_LOG_INFO)
+		ret += fprintf(f, "%s", msg);
+	else if (level >= (int)RTE_LOG_ERR)
+		ret += color_fprintf(f, COLOR_BOLD, "%s", msg);
+	else
+		ret += color_fprintf(f, COLOR_RED, "%s", msg);
+
+	return ret;
+}
+
+int
+color_print_with_timestamp(FILE *f,
+			   const char *format, va_list ap)
+{
+	char tsbuf[128];
+
+	if (log_timestamp(tsbuf, sizeof(tsbuf)) > 0)
+		color_fprintf(f, COLOR_GREEN, "[%s] ", tsbuf);
+
+	return color_print(f, format, ap);
+}
diff --git a/lib/log/log_internal.h b/lib/log/log_internal.h
index 82fdc21ac2..bba7041ea3 100644
--- a/lib/log/log_internal.h
+++ b/lib/log/log_internal.h
@@ -50,5 +50,10 @@  void rte_eal_log_cleanup(void);
 __rte_internal
 int eal_log_timestamp(const char *fmt);
 
+/*
+ * Enable or disable color in log messages
+ */
+__rte_internal
+int eal_log_color(const char *mode);
 
 #endif /* LOG_INTERNAL_H */
diff --git a/lib/log/log_private.h b/lib/log/log_private.h
index 91976ab313..5952bfb453 100644
--- a/lib/log/log_private.h
+++ b/lib/log/log_private.h
@@ -23,4 +23,12 @@  void log_syslog_open(const char *id, bool is_terminal);
 bool log_journal_enabled(void);
 void log_journal_open(const char *id);
 
+bool log_color_enabled(bool is_tty);
+
+__rte_format_printf(2, 0)
+int color_print(FILE *f, const char *format, va_list ap);
+
+__rte_format_printf(2, 0)
+int color_print_with_timestamp(FILE *f, const char *format, va_list ap);
+
 #endif /* LOG_PRIVATE_H */
diff --git a/lib/log/meson.build b/lib/log/meson.build
index 0a80027326..4041feed13 100644
--- a/lib/log/meson.build
+++ b/lib/log/meson.build
@@ -4,6 +4,7 @@ 
 includes += global_inc
 sources = files(
         'log.c',
+        'log_color.c',
         'log_timestamp.c',
 )
 
diff --git a/lib/log/version.map b/lib/log/version.map
index 7af97ece43..eb3200dcde 100644
--- a/lib/log/version.map
+++ b/lib/log/version.map
@@ -25,6 +25,7 @@  DPDK_24 {
 INTERNAL {
 	global:
 
+	eal_log_color;
 	eal_log_init;
 	eal_log_journal;
 	eal_log_level2str;