get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/138995/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 138995,
    "url": "http://patches.dpdk.org/api/patches/138995/?format=api",
    "web_url": "http://patches.dpdk.org/project/dpdk/patch/20240330030429.4630-16-stephen@networkplumber.org/",
    "project": {
        "id": 1,
        "url": "http://patches.dpdk.org/api/projects/1/?format=api",
        "name": "DPDK",
        "link_name": "dpdk",
        "list_id": "dev.dpdk.org",
        "list_email": "dev@dpdk.org",
        "web_url": "http://core.dpdk.org",
        "scm_url": "git://dpdk.org/dpdk",
        "webscm_url": "http://git.dpdk.org/dpdk",
        "list_archive_url": "https://inbox.dpdk.org/dev",
        "list_archive_url_format": "https://inbox.dpdk.org/dev/{}",
        "commit_url_format": ""
    },
    "msgid": "<20240330030429.4630-16-stephen@networkplumber.org>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20240330030429.4630-16-stephen@networkplumber.org",
    "date": "2024-03-30T03:00:58",
    "name": "[v19,15/15] log: colorize log output",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": true,
    "hash": "04814d10621b308ee294ea511b37e3df349cd6ce",
    "submitter": {
        "id": 27,
        "url": "http://patches.dpdk.org/api/people/27/?format=api",
        "name": "Stephen Hemminger",
        "email": "stephen@networkplumber.org"
    },
    "delegate": {
        "id": 1,
        "url": "http://patches.dpdk.org/api/users/1/?format=api",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": "thomas@monjalon.net"
    },
    "mbox": "http://patches.dpdk.org/project/dpdk/patch/20240330030429.4630-16-stephen@networkplumber.org/mbox/",
    "series": [
        {
            "id": 31647,
            "url": "http://patches.dpdk.org/api/series/31647/?format=api",
            "web_url": "http://patches.dpdk.org/project/dpdk/list/?series=31647",
            "date": "2024-03-30T03:00:44",
            "name": "Logging unification and improvements",
            "version": 19,
            "mbox": "http://patches.dpdk.org/series/31647/mbox/"
        }
    ],
    "comments": "http://patches.dpdk.org/api/patches/138995/comments/",
    "check": "fail",
    "checks": "http://patches.dpdk.org/api/patches/138995/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<dev-bounces@dpdk.org>",
        "X-Original-To": "patchwork@inbox.dpdk.org",
        "Delivered-To": "patchwork@inbox.dpdk.org",
        "Received": [
            "from mails.dpdk.org (mails.dpdk.org [217.70.189.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id ACEC343D8B;\n\tSat, 30 Mar 2024 04:06:25 +0100 (CET)",
            "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id B3F6342D99;\n\tSat, 30 Mar 2024 04:05:06 +0100 (CET)",
            "from mail-pf1-f182.google.com (mail-pf1-f182.google.com\n [209.85.210.182])\n by mails.dpdk.org (Postfix) with ESMTP id BD36641104\n for <dev@dpdk.org>; Sat, 30 Mar 2024 04:04:53 +0100 (CET)",
            "by mail-pf1-f182.google.com with SMTP id\n d2e1a72fcca58-6ea80de0e97so1682125b3a.2\n for <dev@dpdk.org>; Fri, 29 Mar 2024 20:04:53 -0700 (PDT)",
            "from hermes.local (204-195-123-203.wavecable.com. [204.195.123.203])\n by smtp.gmail.com with ESMTPSA id\n o3-20020a056a00214300b006e6288ef4besm3655486pfk.54.2024.03.29.20.04.52\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Fri, 29 Mar 2024 20:04:52 -0700 (PDT)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=networkplumber-org.20230601.gappssmtp.com; s=20230601; t=1711767893;\n x=1712372693; darn=dpdk.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=VkK4F13YG5mZA4a0cEYatTcU1uoLlLFo0sOonnBvIt4=;\n b=kD2e/eo3Q/eUvVHV5V0EOoMM1MSHDqS8dSWxgzZclbNToPGqeYVO4aDeIhZDAy8lVk\n dxNr1goKLinzYdT3Ggfn1GAX1SZgXs0Gw/SmMrAyZS/X44KxRY6+8KGc8lH5tEjLbpy4\n AIG2HM7mjroS2T3HJZn9GnkSw5cLCapNxoV49WNpCBHc5g0WwMlgIWpfZ7CQoMvJG5ES\n +nqsJ4+1ktHcly+HYv0/g5w72YOnDYox8WDFR0LPZ+rZTs2XKEsnf1OThtbbDpW7ZnM/\n QrmUhNNqrGlYnB2sYrd3JFSwNV39MFtoMFRd5Izc2jlL5a0rsDq0Ge4XDuRsWWeXcqAF\n By8A==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1711767893; x=1712372693;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n :subject:date:message-id:reply-to;\n bh=VkK4F13YG5mZA4a0cEYatTcU1uoLlLFo0sOonnBvIt4=;\n b=wigtSLkSIP/KTeltL0W7cwkuzjB/OzcpruPlHrNFXnlmYsKcqYWDtnpy4O+vCsyraA\n JAQYnss1J1TAnav8xEGo8F7D0UNiKdJN1xyWODK776wO/uafjHhiEL2NHmgsUjHF8yFU\n /WkdfAFueB3zVZt99eCPlBs00o9QleQdiiiH6rSbXt9osDSIGNXtD+2D/sPcJxkMhFBo\n TKSngVWGyNZn4634W2Csk0NruvcYmeG43NoBVmYPgG9sZ7puSxT4/iZq3Ivz/Wg6Ztd9\n tSL2cGugwlrO4a25vLbMaMWgU1nuVT+BXcCHcrIjpf/dqbvXYEhU8r1GmjO6kupRa1P2\n f00Q==",
        "X-Gm-Message-State": "AOJu0YyATxNZsNtnUz9Z8mX04gjSOEFSc8zKzxKF4U7tMkVxmHsrjzI7\n AiV17aY23hfSN6OPTqAeV1Pwh9yT1giamT3pTl62E0k5833Lk/nl8qTq9KCM15j1BYz1O27pt0A\n w",
        "X-Google-Smtp-Source": "\n AGHT+IEdR+QJMWcyX8xmWzXLItlC8Xvth9faPxomT0H0cIF19R3Eh+7eJwArAmNFIgmxYOcSZUQUIQ==",
        "X-Received": "by 2002:a05:6a20:3d90:b0:1a3:81d2:29f with SMTP id\n s16-20020a056a203d9000b001a381d2029fmr4189815pzi.17.1711767892830;\n Fri, 29 Mar 2024 20:04:52 -0700 (PDT)",
        "From": "Stephen Hemminger <stephen@networkplumber.org>",
        "To": "dev@dpdk.org",
        "Cc": "Stephen Hemminger <stephen@networkplumber.org>",
        "Subject": "[PATCH v19 15/15] log: colorize log output",
        "Date": "Fri, 29 Mar 2024 20:00:58 -0700",
        "Message-ID": "<20240330030429.4630-16-stephen@networkplumber.org>",
        "X-Mailer": "git-send-email 2.43.0",
        "In-Reply-To": "<20240330030429.4630-1-stephen@networkplumber.org>",
        "References": "<20200814173441.23086-1-stephen@networkplumber.org>\n <20240330030429.4630-1-stephen@networkplumber.org>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "X-BeenThere": "dev@dpdk.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "DPDK patches and discussions <dev.dpdk.org>",
        "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>",
        "List-Archive": "<http://mails.dpdk.org/archives/dev/>",
        "List-Post": "<mailto:dev@dpdk.org>",
        "List-Help": "<mailto:dev-request@dpdk.org?subject=help>",
        "List-Subscribe": "<https://mails.dpdk.org/listinfo/dev>,\n <mailto:dev-request@dpdk.org?subject=subscribe>",
        "Errors-To": "dev-bounces@dpdk.org"
    },
    "content": "Like dmesg, colorize the log output (unless redirected to file).\nTimestamp is green, the subsystem is in yellow and the message\nis red if urgent, boldface if an error, and normal for info and\ndebug messages.\n\nSigned-off-by: Stephen Hemminger <stephen@networkplumber.org>\n---\n app/test/test_eal_flags.c           |  24 +++++\n doc/guides/prog_guide/log_lib.rst   |  16 ++-\n lib/eal/common/eal_common_options.c |  11 ++\n lib/eal/common/eal_options.h        |   6 +-\n lib/log/log.c                       |  24 +++--\n lib/log/log_color.c                 | 160 ++++++++++++++++++++++++++++\n lib/log/log_internal.h              |   5 +\n lib/log/log_private.h               |   8 ++\n lib/log/meson.build                 |   2 +-\n lib/log/version.map                 |   1 +\n 10 files changed, 247 insertions(+), 10 deletions(-)\n create mode 100644 lib/log/log_color.c",
    "diff": "diff --git a/app/test/test_eal_flags.c b/app/test/test_eal_flags.c\nindex 08f4866461..c6c05e2e1d 100644\n--- a/app/test/test_eal_flags.c\n+++ b/app/test/test_eal_flags.c\n@@ -1067,6 +1067,18 @@ test_misc_flags(void)\n \tconst char * const argv25[] = {prgname, prefix, mp_flag,\n \t\t\t\t       \"--log-timestamp=invalid\" };\n \n+\t/* Try running with --log-color */\n+\tconst char * const argv26[] = {prgname, prefix, mp_flag,\n+\t\t\t\t       \"--log-color\" };\n+\n+\t/* Try running with --log-color=never */\n+\tconst char * const argv27[] = {prgname, prefix, mp_flag,\n+\t\t\t\t       \"--log-color=never\" };\n+\n+\t/* Try running with --log-color=invalid */\n+\tconst char * const argv28[] = {prgname, prefix, mp_flag,\n+\t\t\t\t       \"--log-color=invalid\" };\n+\n \n \t/* run all tests also applicable to FreeBSD first */\n \n@@ -1187,6 +1199,18 @@ test_misc_flags(void)\n \t\tprintf(\"Error - process did run ok with --log-timestamp=invalid parameter\\n\");\n \t\tgoto fail;\n \t}\n+\tif (launch_proc(argv26) != 0) {\n+\t\tprintf(\"Error - process did not run ok with --log-color parameter\\n\");\n+\t\tgoto fail;\n+\t}\n+\tif (launch_proc(argv27) != 0) {\n+\t\tprintf(\"Error - process did not run ok with --log-color=none parameter\\n\");\n+\t\tgoto fail;\n+\t}\n+\tif (launch_proc(argv28) == 0) {\n+\t\tprintf(\"Error - process did run ok with --log-timestamp=invalid parameter\\n\");\n+\t\tgoto fail;\n+\t}\n \n \n \trmdir(hugepath_dir3);\ndiff --git a/doc/guides/prog_guide/log_lib.rst b/doc/guides/prog_guide/log_lib.rst\nindex 476dedb097..f46720fe34 100644\n--- a/doc/guides/prog_guide/log_lib.rst\n+++ b/doc/guides/prog_guide/log_lib.rst\n@@ -59,6 +59,21 @@ For example::\n \n Within an application, the same result can be got using the ``rte_log_set_level_pattern()`` or ``rte_log_set_level_regex()`` APIs.\n \n+Color output\n+~~~~~~~~~~~~\n+\n+The log library will highlight important messages.\n+This is controlled by the ``--log-color`` option.\n+he optional argument ``when`` can be ``auto``, ``never``, or ``always``.\n+The default setting is ``auto`` which enables color when the output to\n+``stderr`` is a terminal.\n+If the ``when`` argument is omitted, it defaults to ``always``.\n+\n+For example to turn off all coloring::\n+\n+\t/path/to/app --log-color=none\n+\n+\n Log timestamp\n ~~~~~~~~~~~~~\n \n@@ -115,7 +130,6 @@ There are three possible settings for this option:\n *always*\n     Always try to direct messages to journal socket.\n \n-\n Using Logging APIs to Generate Log Messages\n -------------------------------------------\n \ndiff --git a/lib/eal/common/eal_common_options.c b/lib/eal/common/eal_common_options.c\nindex 9a82118184..70fdf3f5a1 100644\n--- a/lib/eal/common/eal_common_options.c\n+++ b/lib/eal/common/eal_common_options.c\n@@ -73,6 +73,7 @@ eal_long_options[] = {\n \t{OPT_HUGE_UNLINK,       2, NULL, OPT_HUGE_UNLINK_NUM      },\n \t{OPT_IOVA_MODE,\t        1, NULL, OPT_IOVA_MODE_NUM        },\n \t{OPT_LCORES,            1, NULL, OPT_LCORES_NUM           },\n+\t{OPT_LOG_COLOR,\t\t2, NULL, OPT_LOG_COLOR_NUM\t  },\n \t{OPT_LOG_LEVEL,         1, NULL, OPT_LOG_LEVEL_NUM        },\n \t{OPT_LOG_JOURNAL,\t2, NULL, OPT_LOG_JOURNAL_NUM\t  },\n \t{OPT_LOG_TIMESTAMP,     2, NULL, OPT_LOG_TIMESTAMP_NUM    },\n@@ -1620,6 +1621,7 @@ eal_log_level_parse(int argc, char * const argv[])\n \t\tcase OPT_SYSLOG_NUM:\n \t\tcase OPT_LOG_JOURNAL_NUM:\n \t\tcase OPT_LOG_TIMESTAMP_NUM:\n+\t\tcase OPT_LOG_COLOR_NUM:\n \t\t\tif (eal_parse_common_option(opt, optarg, internal_conf) < 0)\n \t\t\t\treturn -1;\n \t\t\tbreak;\n@@ -1872,6 +1874,14 @@ eal_parse_common_option(int opt, const char *optarg,\n \t\t}\n \t\tbreak;\n \n+\tcase OPT_LOG_COLOR_NUM:\n+\t\tif (eal_log_color(optarg) < 0) {\n+\t\t\tEAL_LOG(ERR, \"invalid parameters for --\"\n+\t\t\t\tOPT_LOG_COLOR);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tbreak;\n+\n #ifndef RTE_EXEC_ENV_WINDOWS\n \tcase OPT_TRACE_NUM: {\n \t\tif (eal_trace_args_save(optarg) < 0) {\n@@ -2240,6 +2250,7 @@ eal_common_usage(void)\n \t       \"                      Set specific log level\\n\"\n \t       \"  --\"OPT_LOG_LEVEL\"=help    Show log types and levels\\n\"\n \t       \"  --\"OPT_LOG_TIMESTAMP\"[=<format>]  Timestamp log output\\n\"\n+\t       \"  --\"OPT_LOG_COLOR\"[=<when>] Colorize log messages\\n\"\n #ifndef RTE_EXEC_ENV_WINDOWS\n \t       \"  --\"OPT_TRACE\"=<regex-match>\\n\"\n \t       \"                      Enable trace based on regular expression trace name.\\n\"\ndiff --git a/lib/eal/common/eal_options.h b/lib/eal/common/eal_options.h\nindex c5a1c70288..c0686c2544 100644\n--- a/lib/eal/common/eal_options.h\n+++ b/lib/eal/common/eal_options.h\n@@ -33,10 +33,12 @@ enum {\n \tOPT_HUGE_UNLINK_NUM,\n #define OPT_LCORES            \"lcores\"\n \tOPT_LCORES_NUM,\n-#define OPT_LOG_LEVEL         \"log-level\"\n-\tOPT_LOG_LEVEL_NUM,\n+#define OPT_LOG_COLOR\t      \"log-color\"\n+\tOPT_LOG_COLOR_NUM,\n #define OPT_LOG_JOURNAL       \"log-journal\"\n \tOPT_LOG_JOURNAL_NUM,\n+#define OPT_LOG_LEVEL         \"log-level\"\n+\tOPT_LOG_LEVEL_NUM,\n #define OPT_LOG_TIMESTAMP     \"log-timestamp\"\n \tOPT_LOG_TIMESTAMP_NUM,\n #define OPT_TRACE             \"trace\"\ndiff --git a/lib/log/log.c b/lib/log/log.c\nindex d97bde984e..a2254b461b 100644\n--- a/lib/log/log.c\n+++ b/lib/log/log.c\n@@ -510,10 +510,14 @@ log_print(FILE *f, uint32_t level __rte_unused,  const char *format, va_list ap)\n void\n eal_log_init(const char *id)\n {\n+\tbool is_terminal;\n+\n #ifdef RTE_EXEC_ENV_WINDOWS\n \tRTE_SET_USED(id);\n+\n+\tis_terminal = _isatty(_fileno(stderr));\n #else\n-\tbool is_terminal = isatty(STDERR_FILENO);\n+\tis_terminal = isatty(STDERR_FILENO);\n \n #ifdef RTE_EXEC_ENV_LINUX\n \tif (log_journal_enabled(id))\n@@ -523,11 +527,19 @@ eal_log_init(const char *id)\n \tif (log_syslog_enabled(is_terminal))\n \t\tlog_syslog_open(id, is_terminal);\n \telse\n-#endif\n-\tif (log_timestamp_enabled())\n-\t\trte_logs.print_func = log_print_with_timestamp;\n-\telse\n-\t\trte_logs.print_func = log_print;\n+#endif /* RTE_EXEC_ENV_WINDOWS */\n+\n+\tif (log_color_enabled(is_terminal)) {\n+\t\tif (log_timestamp_enabled())\n+\t\t\trte_logs.print_func = color_print_with_timestamp;\n+\t\telse\n+\t\t\trte_logs.print_func = color_print;\n+\t} else {\n+\t\tif (log_timestamp_enabled())\n+\t\t\trte_logs.print_func = log_print_with_timestamp;\n+\t\telse\n+\t\t\trte_logs.print_func = log_print;\n+\t}\n \n #if RTE_LOG_DP_LEVEL >= RTE_LOG_DEBUG\n \tRTE_LOG(NOTICE, EAL,\ndiff --git a/lib/log/log_color.c b/lib/log/log_color.c\nnew file mode 100644\nindex 0000000000..2bed521bc0\n--- /dev/null\n+++ b/lib/log/log_color.c\n@@ -0,0 +1,160 @@\n+/* SPDX-License-Identifier: BSD-3-Clause */\n+\n+#include <stdbool.h>\n+#include <stdio.h>\n+#include <stdint.h>\n+#include <stdarg.h>\n+#include <stdlib.h>\n+#include <string.h>\n+\n+#include <rte_common.h>\n+#include <rte_log.h>\n+\n+#ifdef RTE_EXEC_ENV_WINDOWS\n+#include <rte_os_shim.h>\n+#endif\n+\n+#include \"log_internal.h\"\n+#include \"log_private.h\"\n+\n+enum  {\n+\tLOG_COLOR_AUTO = 0,\t/* default */\n+\tLOG_COLOR_NEVER,\n+\tLOG_COLOR_ALWAYS,\n+} log_color_mode;\n+\n+enum color {\n+\tCOLOR_NONE,\n+\tCOLOR_RED,\n+\tCOLOR_GREEN,\n+\tCOLOR_YELLOW,\n+\tCOLOR_BLUE,\n+\tCOLOR_MAGENTA,\n+\tCOLOR_CYAN,\n+\tCOLOR_WHITE,\n+\tCOLOR_BOLD,\n+\tCOLOR_CLEAR,\n+};\n+\n+/* Standard terminal escape codes for colors and bold */\n+static const char * const color_code[] = {\n+\t[COLOR_NONE]\t= \"\",\n+\t[COLOR_RED]\t= \"\\033[31m\",\n+\t[COLOR_GREEN]\t= \"\\033[32m\",\n+\t[COLOR_YELLOW]\t= \"\\033[33m\",\n+\t[COLOR_BLUE]\t= \"\\033[34m\",\n+\t[COLOR_MAGENTA] = \"\\033[35m\",\n+\t[COLOR_CYAN]    = \"\\033[36m\",\n+\t[COLOR_WHITE]\t= \"\\033[37m\",\n+\t[COLOR_BOLD]\t= \"\\033[1m\",\n+\t[COLOR_CLEAR]\t= \"\\033[0m\",\n+};\n+\n+__rte_format_printf(3, 4)\n+static int color_fprintf(FILE *out, enum color color, const char *fmt, ...)\n+{\n+\tva_list args;\n+\tint ret = 0;\n+\n+\tva_start(args, fmt);\n+\tret = fprintf(out, \"%s\", color_code[color]);\n+\tret += vfprintf(out, fmt, args);\n+\tret += fprintf(out, \"%s\", color_code[COLOR_CLEAR]);\n+\tva_end(args);\n+\n+\treturn ret;\n+}\n+\n+static ssize_t\n+color_log_write(FILE *f, int level, char *msg)\n+{\n+\tchar *cp;\n+\tssize_t ret = 0;\n+\n+\t/*\n+\t * use convention that first part of message (up to the ':' character)\n+\t * is the subsystem id and should be highlighted.\n+\t */\n+\tcp = strchr(msg, ':');\n+\tif (cp) {\n+\t\t/* print first part in yellow */\n+\t\tret = color_fprintf(stderr, COLOR_YELLOW, \"%.*s\",\n+\t\t\t\t    (int)(cp - msg + 1), msg);\n+\t\tmsg = cp + 1;\n+\t}\n+\n+\tif (level <= 0 || level >= (int)RTE_LOG_INFO)\n+\t\tret += fprintf(f, \"%s\", msg);\n+\telse if (level >= (int)RTE_LOG_ERR)\n+\t\tret += color_fprintf(f, COLOR_BOLD, \"%s\", msg);\n+\telse\n+\t\tret += color_fprintf(f, COLOR_RED, \"%s\", msg);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * Controls whether color is enabled:\n+ * modes are:\n+ *   always - enable color output regardless\n+ *   auto - enable if stderr is a terminal\n+ *   never - color output is disabled.\n+ */\n+int\n+eal_log_color(const char *mode)\n+{\n+\tif (mode == NULL || strcmp(mode, \"always\") == 0)\n+\t\tlog_color_mode = LOG_COLOR_ALWAYS;\n+\telse if (strcmp(mode, \"never\") == 0)\n+\t\tlog_color_mode = LOG_COLOR_NEVER;\n+\telse if (strcmp(mode, \"auto\") == 0)\n+\t\tlog_color_mode = LOG_COLOR_AUTO;\n+\telse\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+bool\n+log_color_enabled(bool is_terminal)\n+{\n+\tswitch (log_color_mode) {\n+\tdefault:\n+\tcase LOG_COLOR_NEVER:\n+\t\treturn false;\n+\tcase LOG_COLOR_ALWAYS:\n+\t\treturn true;\n+\tcase LOG_COLOR_AUTO:\n+\t\treturn is_terminal;\n+\t}\n+}\n+\n+int\n+color_print(FILE *f, uint32_t level, const char *format, va_list ap)\n+{\n+\tchar *buf = NULL;\n+\tint ret;\n+\n+\t/* need to make temporary buffer for color scan */\n+\tret = vasprintf(&buf, format, ap);\n+\tif (ret > 0) {\n+\t\tret = color_log_write(f, level, buf);\n+\t\tfree(buf);\n+\t\treturn ret;\n+\t}\n+\n+\t/* if vasprintf fails, print without color */\n+\treturn log_print(f, level, format, ap);\n+}\n+\n+int\n+color_print_with_timestamp(FILE *f, uint32_t level,\n+\t\t\t   const char *format, va_list ap)\n+{\n+\tchar tsbuf[128];\n+\n+\tif (log_timestamp(tsbuf, sizeof(tsbuf)) > 0)\n+\t\tcolor_fprintf(f, COLOR_GREEN, \"[%s] \", tsbuf);\n+\n+\treturn color_print(f, level, format, ap);\n+}\ndiff --git a/lib/log/log_internal.h b/lib/log/log_internal.h\nindex 82fdc21ac2..bba7041ea3 100644\n--- a/lib/log/log_internal.h\n+++ b/lib/log/log_internal.h\n@@ -50,5 +50,10 @@ void rte_eal_log_cleanup(void);\n __rte_internal\n int eal_log_timestamp(const char *fmt);\n \n+/*\n+ * Enable or disable color in log messages\n+ */\n+__rte_internal\n+int eal_log_color(const char *mode);\n \n #endif /* LOG_INTERNAL_H */\ndiff --git a/lib/log/log_private.h b/lib/log/log_private.h\nindex 988579fc08..bb93584f29 100644\n--- a/lib/log/log_private.h\n+++ b/lib/log/log_private.h\n@@ -23,4 +23,12 @@ bool log_journal_enabled(const char *id);\n __rte_format_printf(3, 0)\n int journal_print(FILE *f, uint32_t level, const char *format, va_list ap);\n \n+bool log_color_enabled(bool is_tty);\n+\n+__rte_format_printf(3, 0)\n+int color_print(FILE *f, uint32_t level, const char *format, va_list ap);\n+\n+__rte_format_printf(3, 0)\n+int color_print_with_timestamp(FILE *f, uint32_t level, const char *format, va_list ap);\n+\n #endif /* LOG_PRIVATE_H */\ndiff --git a/lib/log/meson.build b/lib/log/meson.build\nindex 5b9be7f6f1..3467cb5e9d 100644\n--- a/lib/log/meson.build\n+++ b/lib/log/meson.build\n@@ -4,6 +4,7 @@\n includes += global_inc\n sources = files(\n         'log.c',\n+        'log_color.c',\n         'log_timestamp.c',\n )\n \n@@ -17,5 +18,4 @@ else\n \tsources += files('log_syslog.c')\n endif\n \n-\n headers = files('rte_log.h')\ndiff --git a/lib/log/version.map b/lib/log/version.map\nindex 7af97ece43..eb3200dcde 100644\n--- a/lib/log/version.map\n+++ b/lib/log/version.map\n@@ -25,6 +25,7 @@ DPDK_24 {\n INTERNAL {\n \tglobal:\n \n+\teal_log_color;\n \teal_log_init;\n \teal_log_journal;\n \teal_log_level2str;\n",
    "prefixes": [
        "v19",
        "15/15"
    ]
}