<div dir="ltr">The code has some mentions of `--log-color=none` but only the `never` option is parsed.<br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Oct 24, 2024 at 6:22 AM Stephen Hemminger <<a href="mailto:stephen@networkplumber.org">stephen@networkplumber.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Like dmesg, colorize the log output (unless redirected to file).<br>
Timestamp is green, the subsystem is in yellow and the message<br>
is red if urgent, boldface if an error, and normal for info and<br>
debug messages.<br>
<br>
The default is to not use color since it may disturb<br>
automatic tests and other embedded usage.<br>
<br>
Signed-off-by: Stephen Hemminger <<a href="mailto:stephen@networkplumber.org" target="_blank">stephen@networkplumber.org</a>><br>
Acked-by: Morten Brørup <<a href="mailto:mb@smartsharesystems.com" target="_blank">mb@smartsharesystems.com</a>><br>
Acked-by: Bruce Richardson <<a href="mailto:bruce.richardson@intel.com" target="_blank">bruce.richardson@intel.com</a>><br>
Acked-by: Chengwen Feng <<a href="mailto:fengchengwen@huawei.com" target="_blank">fengchengwen@huawei.com</a>><br>
---<br>
 app/test/test_eal_flags.c           |  24 ++++<br>
 doc/guides/prog_guide/log_lib.rst   |  24 ++++<br>
 lib/eal/common/eal_common_options.c |  11 ++<br>
 lib/eal/common/eal_options.h        |   2 +<br>
 lib/log/log.c                       |  20 ++-<br>
 lib/log/log_color.c                 | 216 ++++++++++++++++++++++++++++<br>
 lib/log/log_internal.h              |   5 +<br>
 lib/log/log_private.h               |   8 ++<br>
 lib/log/meson.build                 |   1 +<br>
 lib/log/version.map                 |   1 +<br>
 10 files changed, 308 insertions(+), 4 deletions(-)<br>
 create mode 100644 lib/log/log_color.c<br>
<br>
diff --git a/app/test/test_eal_flags.c b/app/test/test_eal_flags.c<br>
index e24630edde..cd73711da6 100644<br>
--- a/app/test/test_eal_flags.c<br>
+++ b/app/test/test_eal_flags.c<br>
@@ -1067,6 +1067,18 @@ test_misc_flags(void)<br>
        const char * const argv25[] = {prgname, prefix, mp_flag,<br>
                                       "--log-timestamp=invalid" };<br>
<br>
+       /* Try running with --log-color */<br>
+       const char * const argv26[] = {prgname, prefix, mp_flag,<br>
+                                      "--log-color" };<br>
+<br>
+       /* Try running with --log-color=never */<br>
+       const char * const argv27[] = {prgname, prefix, mp_flag,<br>
+                                      "--log-color=never" };<br>
+<br>
+       /* Try running with --log-color=invalid */<br>
+       const char * const argv28[] = {prgname, prefix, mp_flag,<br>
+                                      "--log-color=invalid" };<br>
+<br>
<br>
        /* run all tests also applicable to FreeBSD first */<br>
<br>
@@ -1187,6 +1199,18 @@ test_misc_flags(void)<br>
                printf("Error - process did run ok with --log-timestamp=invalid parameter\n");<br>
                goto fail;<br>
        }<br>
+       if (launch_proc(argv26) != 0) {<br>
+               printf("Error - process did not run ok with --log-color parameter\n");<br>
+               goto fail;<br>
+       }<br>
+       if (launch_proc(argv27) != 0) {<br>
+               printf("Error - process did not run ok with --log-color=none parameter\n");<br>
+               goto fail;<br>
+       }<br>
+       if (launch_proc(argv28) == 0) {<br>
+               printf("Error - process did run ok with --log-timestamp=invalid parameter\n");<br>
+               goto fail;<br>
+       }<br>
<br>
<br>
        rmdir(hugepath_dir3);<br>
diff --git a/doc/guides/prog_guide/log_lib.rst b/doc/guides/prog_guide/log_lib.rst<br>
index 82d3b7be2b..a4f880037b 100644<br>
--- a/doc/guides/prog_guide/log_lib.rst<br>
+++ b/doc/guides/prog_guide/log_lib.rst<br>
@@ -57,6 +57,7 @@ For example::<br>
<br>
 Within an application, the same result can be got using the ``rte_log_set_level_pattern()`` or ``rte_log_set_level_regex()`` APIs.<br>
<br>
+<br>
 Using Logging APIs to Generate Log Messages<br>
 -------------------------------------------<br>
<br>
@@ -137,3 +138,26 @@ To prefix all console messages with ISO format time the syntax is::<br>
<br>
    Timestamp option has no effect if using syslog because the ``syslog()``<br>
    service already does timestamping internally.<br>
+<br>
+<br>
+Color output<br>
+~~~~~~~~~~~~<br>
+<br>
+The log library will highlight important messages.<br>
+This is controlled by the ``--log-color`` option.<br>
+The optional argument describes when color is enabled:<br>
+<br>
+:never: Do not enable color. This is the default behavior.<br>
+<br>
+:auto: Enable color only when printing to a terminal.<br>
+       This is the same as ``--log-color`` with no argument<br>
+<br>
+:always: Always print color<br>
+<br>
+For example to enable color in logs if using terminal::<br>
+<br>
+       /path/to/app --log-color<br>
+<br>
+.. note::<br>
+<br>
+   Color output is never used for syslog or systemd journal logging.<br>
diff --git a/lib/eal/common/eal_common_options.c b/lib/eal/common/eal_common_options.c<br>
index a70d63479a..6965666a7c 100644<br>
--- a/lib/eal/common/eal_common_options.c<br>
+++ b/lib/eal/common/eal_common_options.c<br>
@@ -73,6 +73,7 @@ eal_long_options[] = {<br>
        {OPT_HUGE_UNLINK,       2, NULL, OPT_HUGE_UNLINK_NUM      },<br>
        {OPT_IOVA_MODE,         1, NULL, OPT_IOVA_MODE_NUM        },<br>
        {OPT_LCORES,            1, NULL, OPT_LCORES_NUM           },<br>
+       {OPT_LOG_COLOR,         2, NULL, OPT_LOG_COLOR_NUM        },<br>
        {OPT_LOG_LEVEL,         1, NULL, OPT_LOG_LEVEL_NUM        },<br>
        {OPT_LOG_TIMESTAMP,     2, NULL, OPT_LOG_TIMESTAMP_NUM    },<br>
        {OPT_TRACE,             1, NULL, OPT_TRACE_NUM            },<br>
@@ -1620,6 +1621,7 @@ eal_log_level_parse(int argc, char * const argv[])<br>
                case OPT_LOG_LEVEL_NUM:<br>
                case OPT_SYSLOG_NUM:<br>
                case OPT_LOG_TIMESTAMP_NUM:<br>
+               case OPT_LOG_COLOR_NUM:<br>
                        if (eal_parse_common_option(opt, optarg, internal_conf) < 0)<br>
                                return -1;<br>
                        break;<br>
@@ -1859,6 +1861,14 @@ eal_parse_common_option(int opt, const char *optarg,<br>
                }<br>
                break;<br>
<br>
+       case OPT_LOG_COLOR_NUM:<br>
+               if (eal_log_color(optarg) < 0) {<br>
+                       EAL_LOG(ERR, "invalid parameters for --"<br>
+                               OPT_LOG_COLOR);<br>
+                       return -1;<br>
+               }<br>
+               break;<br>
+<br>
 #ifndef RTE_EXEC_ENV_WINDOWS<br>
        case OPT_TRACE_NUM: {<br>
                if (eal_trace_args_save(optarg) < 0) {<br>
@@ -2225,6 +2235,7 @@ eal_common_usage(void)<br>
               "                      Set specific log level\n"<br>
               "  --"OPT_LOG_LEVEL"=help    Show log types and levels\n"<br>
               "  --"OPT_LOG_TIMESTAMP"[=<format>]  Timestamp log output\n"<br>
+              "  --"OPT_LOG_COLOR"[=<when>] Colorize log messages\n"<br>
 #ifndef RTE_EXEC_ENV_WINDOWS<br>
               "  --"OPT_TRACE"=<regex-match>\n"<br>
               "                      Enable trace based on regular expression trace name.\n"<br>
diff --git a/lib/eal/common/eal_options.h b/lib/eal/common/eal_options.h<br>
index e24c9eca53..87c3c32f86 100644<br>
--- a/lib/eal/common/eal_options.h<br>
+++ b/lib/eal/common/eal_options.h<br>
@@ -33,6 +33,8 @@ enum {<br>
        OPT_HUGE_UNLINK_NUM,<br>
 #define OPT_LCORES            "lcores"<br>
        OPT_LCORES_NUM,<br>
+#define OPT_LOG_COLOR        "log-color"<br>
+       OPT_LOG_COLOR_NUM,<br>
 #define OPT_LOG_LEVEL         "log-level"<br>
        OPT_LOG_LEVEL_NUM,<br>
 #define OPT_LOG_TIMESTAMP     "log-timestamp"<br>
diff --git a/lib/log/log.c b/lib/log/log.c<br>
index 343f9d77b7..f1bd408bee 100644<br>
--- a/lib/log/log.c<br>
+++ b/lib/log/log.c<br>
@@ -515,10 +515,22 @@ eal_log_init(const char *id)<br>
<br>
        if (logf)<br>
                rte_openlog_stream(logf);<br>
-       else if (log_timestamp_enabled())<br>
-               rte_logs.print_func = log_print_with_timestamp;<br>
-       else<br>
-               rte_logs.print_func = vfprintf;<br>
+       else {<br>
+               bool is_terminal = isatty(fileno(stderr));<br>
+               bool use_color = log_color_enabled(is_terminal);<br>
+<br>
+               if (log_timestamp_enabled()) {<br>
+                       if (use_color)<br>
+                               rte_logs.print_func = color_print_with_timestamp;<br>
+                       else<br>
+                               rte_logs.print_func = log_print_with_timestamp;<br>
+               } else {<br>
+                       if (use_color)<br>
+                               rte_logs.print_func = color_print;<br>
+                       else<br>
+                               rte_logs.print_func = vfprintf;<br>
+               }<br>
+       }<br>
<br>
 #if RTE_LOG_DP_LEVEL >= RTE_LOG_DEBUG<br>
        RTE_LOG(NOTICE, EAL,<br>
diff --git a/lib/log/log_color.c b/lib/log/log_color.c<br>
new file mode 100644<br>
index 0000000000..82a9ac8022<br>
--- /dev/null<br>
+++ b/lib/log/log_color.c<br>
@@ -0,0 +1,216 @@<br>
+/* SPDX-License-Identifier: BSD-3-Clause */<br>
+<br>
+#include <limits.h><br>
+#include <stdbool.h><br>
+#include <stdio.h><br>
+#include <stdint.h><br>
+#include <stdarg.h><br>
+#include <stdlib.h><br>
+#include <string.h><br>
+<br>
+#include <rte_common.h><br>
+#include <rte_log.h><br>
+<br>
+#ifdef RTE_EXEC_ENV_WINDOWS<br>
+#include <rte_os_shim.h><br>
+#endif<br>
+<br>
+#include "log_internal.h"<br>
+#include "log_private.h"<br>
+<br>
+enum  {<br>
+       LOG_COLOR_AUTO = 0,<br>
+       LOG_COLOR_NEVER,<br>
+       LOG_COLOR_ALWAYS,<br>
+} log_color_mode = LOG_COLOR_NEVER;<br>
+<br>
+enum color {<br>
+       COLOR_NONE,<br>
+       COLOR_RED,<br>
+       COLOR_GREEN,<br>
+       COLOR_YELLOW,<br>
+       COLOR_BLUE,<br>
+       COLOR_MAGENTA,<br>
+       COLOR_CYAN,<br>
+       COLOR_WHITE,<br>
+       COLOR_BOLD,<br>
+       COLOR_CLEAR,<br>
+};<br>
+<br>
+enum log_field {<br>
+       LOG_FIELD_SUBSYS,<br>
+       LOG_FIELD_TIME,<br>
+       LOG_FIELD_ALERT,<br>
+       LOG_FIELD_ERROR,<br>
+       LOG_FIELD_INFO,<br>
+};<br>
+<br>
+static const enum color field_colors[] = {<br>
+       [LOG_FIELD_SUBSYS] = COLOR_YELLOW,<br>
+       [LOG_FIELD_TIME]   = COLOR_GREEN,<br>
+       [LOG_FIELD_ALERT]  = COLOR_RED,<br>
+       [LOG_FIELD_ERROR]  = COLOR_BOLD,<br>
+       [LOG_FIELD_INFO]   = COLOR_NONE,<br>
+};<br>
+<br>
+/* If set all colors are bolder */<br>
+static bool dark_mode;<br>
+<br>
+/* Standard terminal escape codes for colors and bold */<br>
+static const uint8_t color_esc_code[] = {<br>
+       [COLOR_RED]     = 31,<br>
+       [COLOR_GREEN]   = 32,<br>
+       [COLOR_YELLOW]  = 33,<br>
+       [COLOR_BLUE]    = 34,<br>
+       [COLOR_MAGENTA] = 35,<br>
+       [COLOR_CYAN]    = 36,<br>
+       [COLOR_WHITE]   = 37,<br>
+       [COLOR_BOLD]    = 1,<br>
+};<br>
+<br>
+__rte_format_printf(4, 5)<br>
+static int<br>
+color_snprintf(char *buf, size_t len, enum log_field field,<br>
+              const char *fmt, ...)<br>
+{<br>
+       enum color color = field_colors[field];<br>
+       uint8_t esc = color_esc_code[color];<br>
+       va_list args;<br>
+       int ret = 0;<br>
+<br>
+       va_start(args, fmt);<br>
+       if (esc == 0) {<br>
+               ret = vsnprintf(buf, len, fmt, args);<br>
+       } else {<br>
+               ret = snprintf(buf, len,<br>
+                              dark_mode ? "\e[1;%um" : "\e[%um", esc);<br>
+               ret += vsnprintf(buf + ret, len - ret, fmt, args);<br>
+               ret += snprintf(buf + ret,  len - ret, "%s", "\e[0m");<br>
+       }<br>
+       va_end(args);<br>
+<br>
+       return ret;<br>
+}<br>
+<br>
+/*<br>
+ * Controls whether color is enabled:<br>
+ * modes are:<br>
+ *   always - enable color output regardless<br>
+ *   auto - enable if stderr is a terminal<br>
+ *   never - color output is disabled.<br>
+ */<br>
+int<br>
+eal_log_color(const char *mode)<br>
+{<br>
+       if (mode == NULL || strcmp(mode, "always") == 0)<br>
+               log_color_mode = LOG_COLOR_ALWAYS;<br>
+       else if (strcmp(mode, "never") == 0)<br>
+               log_color_mode = LOG_COLOR_NEVER;<br>
+       else if (strcmp(mode, "auto") == 0)<br>
+               log_color_mode = LOG_COLOR_AUTO;<br>
+       else<br>
+               return -1;<br>
+<br>
+       return 0;<br>
+}<br>
+<br>
+bool<br>
+log_color_enabled(bool is_terminal)<br>
+{<br>
+       char *env, *sep;<br>
+<br>
+       /* Set dark mode using the defacto heuristics used by other programs */<br>
+       env = getenv("COLORFGBG");<br>
+       if (env) {<br>
+               sep = strrchr(env, ';');<br>
+               if (sep &&<br>
+                   ((sep[1] >= '0' && sep[1] <= '6') || sep[1] == '8') &&<br>
+                   sep[2] == '\0')<br>
+                       dark_mode = true;<br>
+       }<br>
+<br>
+       switch (log_color_mode) {<br>
+       case LOG_COLOR_ALWAYS:<br>
+               return true;<br>
+       case LOG_COLOR_AUTO:<br>
+               return is_terminal;<br>
+       default:<br>
+               return false;<br>
+<br>
+       }<br>
+}<br>
+<br>
+/* Look ast the current message level to determine color of field */<br>
+static enum log_field<br>
+color_msg_field(void)<br>
+{<br>
+       const int level = rte_log_cur_msg_loglevel();<br>
+<br>
+       if (level <= 0 || level >= (int)RTE_LOG_INFO)<br>
+               return LOG_FIELD_INFO;<br>
+       else if (level >= (int)RTE_LOG_ERR)<br>
+               return LOG_FIELD_ERROR;<br>
+       else<br>
+               return LOG_FIELD_ALERT;<br>
+}<br>
+<br>
+__rte_format_printf(3, 0)<br>
+static int<br>
+color_fmt_msg(char *out, size_t len, const char *format, va_list ap)<br>
+{<br>
+       enum log_field field = color_msg_field();<br>
+       char buf[LINE_MAX];<br>
+       int ret = 0;<br>
+<br>
+       /* format raw message */<br>
+       vsnprintf(buf, sizeof(buf), format, ap);<br>
+       const char *msg = buf;<br>
+<br>
+       /*<br>
+        * use convention that first part of message (up to the ':' character)<br>
+        * is the subsystem id and should be highlighted.<br>
+        */<br>
+       const char *cp = strchr(msg, ':');<br>
+       if (cp) {<br>
+               /* print first part in yellow */<br>
+               ret = color_snprintf(out, len, LOG_FIELD_SUBSYS,<br>
+                                    "%.*s", (int)(cp - msg + 1), msg);<br>
+               msg = cp + 1;<br>
+       }<br>
+<br>
+       ret += color_snprintf(out + ret, len - ret, field, "%s", msg);<br>
+       return ret;<br>
+}<br>
+<br>
+__rte_format_printf(2, 0)<br>
+int<br>
+color_print(FILE *f, const char *format, va_list ap)<br>
+{<br>
+       char out[LINE_MAX];<br>
+<br>
+       /* format raw message */<br>
+       int ret = color_fmt_msg(out, sizeof(out), format, ap);<br>
+       if (fputs(out, f) < 0)<br>
+               return -1;<br>
+<br>
+       return ret;<br>
+}<br>
+<br>
+__rte_format_printf(2, 0)<br>
+int<br>
+color_print_with_timestamp(FILE *f, const char *format, va_list ap)<br>
+{<br>
+       char out[LINE_MAX];<br>
+       char tsbuf[128];<br>
+       int ret = 0;<br>
+<br>
+       if (log_timestamp(tsbuf, sizeof(tsbuf)) > 0)<br>
+               ret = color_snprintf(out, sizeof(out),<br>
+                                    LOG_FIELD_TIME, "[%s] ", tsbuf);<br>
+<br>
+       ret += color_fmt_msg(out + ret, sizeof(out) - ret, format, ap);<br>
+       if (fputs(out, f) < 0)<br>
+               return -1;<br>
+<br>
+       return ret;<br>
+}<br>
diff --git a/lib/log/log_internal.h b/lib/log/log_internal.h<br>
index 82fdc21ac2..bba7041ea3 100644<br>
--- a/lib/log/log_internal.h<br>
+++ b/lib/log/log_internal.h<br>
@@ -50,5 +50,10 @@ void rte_eal_log_cleanup(void);<br>
 __rte_internal<br>
 int eal_log_timestamp(const char *fmt);<br>
<br>
+/*<br>
+ * Enable or disable color in log messages<br>
+ */<br>
+__rte_internal<br>
+int eal_log_color(const char *mode);<br>
<br>
 #endif /* LOG_INTERNAL_H */<br>
diff --git a/lib/log/log_private.h b/lib/log/log_private.h<br>
index 625f57c0cc..42babbcac1 100644<br>
--- a/lib/log/log_private.h<br>
+++ b/lib/log/log_private.h<br>
@@ -25,4 +25,12 @@ ssize_t log_timestamp(char *tsbuf, size_t tsbuflen);<br>
 __rte_format_printf(2, 0)<br>
 int log_print_with_timestamp(FILE *f, const char *format, va_list ap);<br>
<br>
+bool log_color_enabled(bool is_tty);<br>
+<br>
+__rte_format_printf(2, 0)<br>
+int color_print(FILE *f, const char *format, va_list ap);<br>
+<br>
+__rte_format_printf(2, 0)<br>
+int color_print_with_timestamp(FILE *f, const char *format, va_list ap);<br>
+<br>
 #endif /* LOG_PRIVATE_H */<br>
diff --git a/lib/log/meson.build b/lib/log/meson.build<br>
index 86e4452b19..b3de57b9c7 100644<br>
--- a/lib/log/meson.build<br>
+++ b/lib/log/meson.build<br>
@@ -4,6 +4,7 @@<br>
 includes += global_inc<br>
 sources = files(<br>
         'log.c',<br>
+        'log_color.c',<br>
         'log_timestamp.c',<br>
 )<br>
<br>
diff --git a/lib/log/version.map b/lib/log/version.map<br>
index 800d3943bc..09d8a4289b 100644<br>
--- a/lib/log/version.map<br>
+++ b/lib/log/version.map<br>
@@ -25,6 +25,7 @@ DPDK_25 {<br>
 INTERNAL {<br>
        global:<br>
<br>
+       eal_log_color;<br>
        eal_log_init;<br>
        eal_log_journal; # WINDOWS_NO_EXPORT<br>
        eal_log_level2str;<br>
-- <br>
2.45.2<br>
<br>
</blockquote></div><br clear="all"><br><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature"><div dir="ltr"><table style="direction:ltr;border-collapse:collapse"><tbody><tr><td style="font-size:0px;height:24px;line-height:0"></td></tr><tr><td><table cellpadding="0" cellspacing="0" border="0" style="width:100%" width="100%"><tbody><tr><td><div><div><table border="0" cellspacing="0" cellpadding="0" width="510" style="width:510px"><tbody><tr><td valign="top" style="vertical-align:top;padding-right:12px;width:120px" width="120"><table border="0" cellspacing="0" cellpadding="0"><tbody><tr><td><img src="https://cdn.gifo.wisestamp.com/im/sh/aHR0cHM6Ly9kNGQ4eGQyMGVyOGxnLmNsb3VkZnJvbnQubmV0LzBjOTUwMTdkLTAzZjMtNGIwZC1iZDM0LTVkMzkzOGEzNjM4ZS9qNXJvQXhodUVRMGRqVmlZcmhzQ2p6VDVmaWhDZTRLUi5wbmcjbG9nbw==/rounded.png" width="120" height="92" style="width: 120px; height: 92px; border-radius: 4px;"></td></tr></tbody></table></td><td><table border="0" cellspacing="0" cellpadding="0" width="100%" style="width:100%"><tbody><tr><td><table border="0" cellpadding="0" cellspacing="0" width="100%" style="color:rgb(78,75,76);padding-left:2px;font-weight:normal;width:100%;padding-bottom:5px;border-bottom:5px solid rgb(212,212,212);font-size:16px"><tbody><tr><td><span style="color:rgb(119,61,190);line-height:200%;font-weight:bold;font-family:Arial">Baruch Even</span><br><span style="font-family:Arial;color:rgb(51,51,51);line-height:200%">Platform Technical Lead</span> <span style="font-family:Arial;line-height:200%;color:rgb(51,51,51)">at </span><span style="color:rgb(119,61,190);line-height:200%;font-family:Arial">WEKA</span></td></tr></tbody></table></td></tr><tr><td><table border="0" cellpadding="0" cellspacing="0" align="left" width="100%" style="width:100%"><tbody><tr><td style="font-size:16px"><span style="color:rgb(119,61,190);line-height:150%;font-weight:bold;font-family:Arial">E </span><a href="mailto:baruch@weka.io" style="text-decoration:none;line-height:150%;color:rgb(78,75,76);font-family:Arial" target="_blank">baruch@weka.io</a><i style="color:rgb(255,255,255)"> ­</i><span style="color:rgb(119,61,190);line-height:150%;font-weight:bold;font-family:Arial">W </span><a href="https://www.weka.io" style="text-decoration:none;line-height:150%;color:rgb(78,75,76);font-family:Arial" target="_blank">https://www.weka.io</a><i style="color:rgb(255,255,255)"> ­</i></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table></div></div></td></tr><tr><td style="line-height:1%;padding-top:8px;font-size:1px"></td></tr><tr><td><table cellpadding="0" cellspacing="0" style="line-height:normal;width:100%" width="100%"><tbody><tr><td style="text-align:left"><p style="margin:1px"><a href="https://www.weka.io/trends-in-AI-emsig?utm_campaign=signature&utm_source=WiseStamp&utm_medium=email" rel="nofollow noreferrer" target="_blank"><img src="https://d36urhup7zbd7q.cloudfront.net/u/MnDyROw8y77/324e8ddb-ebe2-4864-a177-a690bc3babbc__760x286__.png" style="width: 299px; height: 112px;" width="299" height="112" alt="App Banner Image"></a></p></td></tr></tbody></table></td></tr><tr><td style="line-height:1%;padding-top:8px;font-size:1px"></td></tr></tbody></table></td></tr><tr><td style="font-family:"ws-id 9qgYxGOb9J9R";font-size:0.01px;line-height:0"> </td></tr></tbody></table></div></div>