<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>