xref: /dpdk/lib/log/log_color.c (revision c1d145834f287aa8cf53de914618a7312f2c360e)
1 /* SPDX-License-Identifier: BSD-3-Clause */
2 
3 #include <limits.h>
4 #include <stdbool.h>
5 #include <stdio.h>
6 #include <stdint.h>
7 #include <stdarg.h>
8 #include <stdlib.h>
9 #include <string.h>
10 
11 #include <rte_common.h>
12 #include <rte_log.h>
13 
14 #ifdef RTE_EXEC_ENV_WINDOWS
15 #include <rte_os_shim.h>
16 #endif
17 
18 #include "log_internal.h"
19 #include "log_private.h"
20 
21 enum  {
22 	LOG_COLOR_AUTO = 0,
23 	LOG_COLOR_NEVER,
24 	LOG_COLOR_ALWAYS,
25 } log_color_mode = LOG_COLOR_NEVER;
26 
27 enum color {
28 	COLOR_NONE,
29 	COLOR_RED,
30 	COLOR_GREEN,
31 	COLOR_YELLOW,
32 	COLOR_BLUE,
33 	COLOR_MAGENTA,
34 	COLOR_CYAN,
35 	COLOR_WHITE,
36 	COLOR_BOLD,
37 	COLOR_CLEAR,
38 };
39 
40 enum log_field {
41 	LOG_FIELD_SUBSYS,
42 	LOG_FIELD_TIME,
43 	LOG_FIELD_ALERT,
44 	LOG_FIELD_ERROR,
45 	LOG_FIELD_INFO,
46 };
47 
48 static const enum color field_colors[] = {
49 	[LOG_FIELD_SUBSYS] = COLOR_YELLOW,
50 	[LOG_FIELD_TIME]   = COLOR_GREEN,
51 	[LOG_FIELD_ALERT]  = COLOR_RED,
52 	[LOG_FIELD_ERROR]  = COLOR_BOLD,
53 	[LOG_FIELD_INFO]   = COLOR_NONE,
54 };
55 
56 /* If set all colors are bolder */
57 static bool dark_mode;
58 
59 /* Standard terminal escape codes for colors and bold */
60 static const uint8_t color_esc_code[] = {
61 	[COLOR_RED]	= 31,
62 	[COLOR_GREEN]	= 32,
63 	[COLOR_YELLOW]	= 33,
64 	[COLOR_BLUE]	= 34,
65 	[COLOR_MAGENTA] = 35,
66 	[COLOR_CYAN]    = 36,
67 	[COLOR_WHITE]	= 37,
68 	[COLOR_BOLD]	= 1,
69 };
70 
71 __rte_format_printf(4, 5)
72 static int
73 color_snprintf(char *buf, size_t len, enum log_field field,
74 	       const char *fmt, ...)
75 {
76 	enum color color = field_colors[field];
77 	uint8_t esc = color_esc_code[color];
78 	va_list args;
79 	int ret = 0;
80 
81 	va_start(args, fmt);
82 	if (esc == 0) {
83 		ret = vsnprintf(buf, len, fmt, args);
84 	} else {
85 		ret = snprintf(buf, len,
86 			       dark_mode ? "\033[1;%um" : "\033[%um", esc);
87 		ret += vsnprintf(buf + ret, len - ret, fmt, args);
88 		ret += snprintf(buf + ret,  len - ret, "%s", "\033[0m");
89 	}
90 	va_end(args);
91 
92 	return ret;
93 }
94 
95 /*
96  * Controls whether color is enabled:
97  * modes are:
98  *   always - enable color output regardless
99  *   auto - enable if stderr is a terminal
100  *   never - color output is disabled.
101  */
102 int
103 eal_log_color(const char *mode)
104 {
105 	if (mode == NULL || strcmp(mode, "always") == 0)
106 		log_color_mode = LOG_COLOR_ALWAYS;
107 	else if (strcmp(mode, "never") == 0)
108 		log_color_mode = LOG_COLOR_NEVER;
109 	else if (strcmp(mode, "auto") == 0)
110 		log_color_mode = LOG_COLOR_AUTO;
111 	else
112 		return -1;
113 
114 	return 0;
115 }
116 
117 bool
118 log_color_enabled(bool is_terminal)
119 {
120 	char *env, *sep;
121 
122 	/* Set dark mode using the defacto heuristics used by other programs */
123 	env = getenv("COLORFGBG");
124 	if (env) {
125 		sep = strrchr(env, ';');
126 		if (sep &&
127 		    ((sep[1] >= '0' && sep[1] <= '6') || sep[1] == '8') &&
128 		    sep[2] == '\0')
129 			dark_mode = true;
130 	}
131 
132 	if (log_color_mode == LOG_COLOR_ALWAYS)
133 		return true;
134 	else if (log_color_mode == LOG_COLOR_AUTO)
135 		return is_terminal;
136 	else
137 		return false;
138 }
139 
140 /* Look ast the current message level to determine color of field */
141 static enum log_field
142 color_msg_field(void)
143 {
144 	const int level = rte_log_cur_msg_loglevel();
145 
146 	if (level <= 0 || level >= (int)RTE_LOG_INFO)
147 		return LOG_FIELD_INFO;
148 	else if (level >= (int)RTE_LOG_ERR)
149 		return LOG_FIELD_ERROR;
150 	else
151 		return LOG_FIELD_ALERT;
152 }
153 
154 __rte_format_printf(3, 0)
155 static int
156 color_fmt_msg(char *out, size_t len, const char *format, va_list ap)
157 {
158 	enum log_field field = color_msg_field();
159 	char buf[LINE_MAX];
160 	int ret = 0;
161 
162 	/* format raw message */
163 	vsnprintf(buf, sizeof(buf), format, ap);
164 	const char *msg = buf;
165 
166 	/*
167 	 * use convention that first part of message (up to the ':' character)
168 	 * is the subsystem id and should be highlighted.
169 	 */
170 	const char *cp = strchr(msg, ':');
171 	if (cp) {
172 		/* print first part in yellow */
173 		ret = color_snprintf(out, len, LOG_FIELD_SUBSYS,
174 				     "%.*s", (int)(cp - msg + 1), msg);
175 		/* skip the first part */
176 		msg = cp + 1;
177 	}
178 
179 	ret += color_snprintf(out + ret, len - ret, field, "%s", msg);
180 	return ret;
181 }
182 
183 __rte_format_printf(2, 0)
184 int
185 color_print(FILE *f, const char *format, va_list ap)
186 {
187 	char out[LINE_MAX];
188 
189 	/* format raw message */
190 	int ret = color_fmt_msg(out, sizeof(out), format, ap);
191 	if (fputs(out, f) < 0)
192 		return -1;
193 
194 	return ret;
195 }
196 
197 __rte_format_printf(2, 0)
198 int
199 color_print_with_timestamp(FILE *f, const char *format, va_list ap)
200 {
201 	char out[LINE_MAX];
202 	char tsbuf[128];
203 	int ret = 0;
204 
205 	if (log_timestamp(tsbuf, sizeof(tsbuf)) > 0)
206 		ret = color_snprintf(out, sizeof(out),
207 				     LOG_FIELD_TIME, "[%s] ", tsbuf);
208 
209 	ret += color_fmt_msg(out + ret, sizeof(out) - ret, format, ap);
210 	if (fputs(out, f) < 0)
211 		return -1;
212 
213 	return ret;
214 }
215