Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <assert.h> |
19 | | #include <stdarg.h> |
20 | | #include <stdatomic.h> |
21 | | #include <stdint.h> |
22 | | #include <stdio.h> |
23 | | #include <stdlib.h> |
24 | | #include <string.h> |
25 | | |
26 | | #include "mpv_talloc.h" |
27 | | |
28 | | #include "common/common.h" |
29 | | #include "common/global.h" |
30 | | #include "misc/codepoint_width.h" |
31 | | #include "options/options.h" |
32 | | #include "options/path.h" |
33 | | #include "osdep/io.h" |
34 | | #include "osdep/terminal.h" |
35 | | #include "osdep/threads.h" |
36 | | #include "osdep/timer.h" |
37 | | |
38 | | #include "mpv/client.h" |
39 | | |
40 | | #include "msg.h" |
41 | | #include "msg_control.h" |
42 | | |
43 | | // log buffer size (lines) logfile level |
44 | 0 | #define FILE_BUF 100 |
45 | | |
46 | | // lines to accumulate before any client requests the terminal loglevel |
47 | 303k | #define EARLY_TERM_BUF 100 |
48 | | |
49 | | // logfile lines to accumulate during init before we know the log file name. |
50 | | // thousands of logfile lines during init can happen (especially with many |
51 | | // scripts, big config, etc), so we set 5000. If it cycles and messages are |
52 | | // overwritten, then the first (virtual) log line indicates how many were lost. |
53 | 303k | #define EARLY_FILE_BUF 5000 |
54 | | |
55 | | struct mp_log_root { |
56 | | struct mpv_global *global; |
57 | | mp_mutex lock; |
58 | | mp_mutex log_file_lock; |
59 | | mp_cond log_file_wakeup; |
60 | | // --- protected by lock |
61 | | char **msg_levels; |
62 | | bool use_terminal; // make accesses to stderr/stdout |
63 | | bool module; |
64 | | bool show_time; |
65 | | int blank_lines; // number of lines usable by status |
66 | | int status_lines; // number of current status lines |
67 | | bool color[STDERR_FILENO + 1]; |
68 | | bool isatty[STDERR_FILENO + 1]; |
69 | | int verbose; |
70 | | bool really_quiet; |
71 | | bool force_stderr; |
72 | | struct mp_log_buffer **buffers; |
73 | | int num_buffers; |
74 | | struct mp_log_buffer *early_buffer; |
75 | | struct mp_log_buffer *early_filebuffer; |
76 | | FILE *stats_file; |
77 | | bstr buffer; |
78 | | bstr term_msg; |
79 | | bstr term_msg_tmp; |
80 | | bstr status_line; |
81 | | struct mp_log *status_log; |
82 | | bstr term_status_msg; |
83 | | // --- must be accessed atomically |
84 | | /* This is incremented every time the msglevels must be reloaded. |
85 | | * (This is perhaps better than maintaining a globally accessible and |
86 | | * synchronized mp_log tree.) */ |
87 | | atomic_ulong reload_counter; |
88 | | // --- owner thread only (caller of mp_msg_init() etc.) |
89 | | char *log_path; |
90 | | char *stats_path; |
91 | | mp_thread log_file_thread; |
92 | | // --- owner thread only, but frozen while log_file_thread is running |
93 | | FILE *log_file; |
94 | | struct mp_log_buffer *log_file_buffer; |
95 | | // --- protected by log_file_lock |
96 | | bool log_file_thread_active; // also termination signal for the thread |
97 | | int module_indent; |
98 | | }; |
99 | | |
100 | | struct mp_log { |
101 | | struct mp_log_root *root; |
102 | | const char *prefix; |
103 | | const char *verbose_prefix; |
104 | | int max_level; // minimum log level for this instance |
105 | | int level; // minimum log level for any outputs |
106 | | int terminal_level; // minimum log level for terminal output |
107 | | atomic_ulong reload_counter; |
108 | | bstr partial[MSGL_MAX + 1]; |
109 | | }; |
110 | | |
111 | | struct mp_log_buffer { |
112 | | struct mp_log_root *root; |
113 | | mp_mutex lock; |
114 | | // --- protected by lock |
115 | | struct mp_log_buffer_entry **entries; // ringbuffer |
116 | | int capacity; // total space in entries[] |
117 | | int entry0; // first (oldest) entry index |
118 | | int num_entries; // number of valid entries after entry0 |
119 | | uint64_t dropped; // number of skipped entries |
120 | | bool silent; |
121 | | // --- immutable |
122 | | void (*wakeup_cb)(void *ctx); |
123 | | void *wakeup_cb_ctx; |
124 | | int level; |
125 | | }; |
126 | | |
127 | | static const struct mp_log null_log = {0}; |
128 | | struct mp_log *const mp_null_log = (struct mp_log *)&null_log; |
129 | | |
130 | | static bool match_mod(const char *name, const char *mod) |
131 | 2.41M | { |
132 | 2.41M | if (!strcmp(mod, "all")) |
133 | 2.41M | return true; |
134 | | // Path prefix matches |
135 | 692 | bstr b = bstr0(name); |
136 | 692 | return bstr_eatstart0(&b, mod) && (bstr_eatstart0(&b, "/") || !b.len); |
137 | 2.41M | } |
138 | | |
139 | | static void update_loglevel(struct mp_log *log) |
140 | 2.84M | { |
141 | 2.84M | struct mp_log_root *root = log->root; |
142 | 2.84M | mp_mutex_lock(&root->lock); |
143 | 2.84M | log->level = MSGL_STATUS + root->verbose; // default log level |
144 | 2.84M | if (root->really_quiet) |
145 | 99 | log->level = -1; |
146 | 5.26M | for (int n = 0; root->msg_levels && root->msg_levels[n * 2 + 0]; n++) { |
147 | 2.41M | if (match_mod(log->verbose_prefix, root->msg_levels[n * 2 + 0])) |
148 | 2.41M | log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]); |
149 | 2.41M | } |
150 | 2.84M | log->terminal_level = log->level; |
151 | 4.51M | for (int n = 0; n < log->root->num_buffers; n++) { |
152 | 1.66M | int buffer_level = log->root->buffers[n]->level; |
153 | 1.66M | if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE) |
154 | 831k | buffer_level = MSGL_DEBUG; |
155 | 1.66M | if (buffer_level != MP_LOG_BUFFER_MSGL_TERM) |
156 | 831k | log->level = MPMAX(log->level, buffer_level); |
157 | 1.66M | } |
158 | 2.84M | if (log->root->log_file) |
159 | 0 | log->level = MPMAX(log->level, MSGL_DEBUG); |
160 | 2.84M | if (log->root->stats_file) |
161 | 186 | log->level = MPMAX(log->level, MSGL_STATS); |
162 | 2.84M | log->level = MPMIN(log->level, log->max_level); |
163 | 2.84M | atomic_store(&log->reload_counter, atomic_load(&log->root->reload_counter)); |
164 | 2.84M | mp_mutex_unlock(&root->lock); |
165 | 2.84M | } |
166 | | |
167 | | // Set (numerically) the maximum level that should still be output for this log |
168 | | // instances. E.g. lev=MSGL_WARN => show only warnings and errors. |
169 | | void mp_msg_set_max_level(struct mp_log *log, int lev) |
170 | 0 | { |
171 | 0 | if (!log->root) |
172 | 0 | return; |
173 | 0 | mp_mutex_lock(&log->root->lock); |
174 | 0 | log->max_level = MPCLAMP(lev, -1, MSGL_MAX); |
175 | 0 | mp_mutex_unlock(&log->root->lock); |
176 | 0 | update_loglevel(log); |
177 | 0 | } |
178 | | |
179 | | // Get the current effective msg level. |
180 | | // Thread-safety: see mp_msg(). |
181 | | int mp_msg_level(struct mp_log *log) |
182 | 208M | { |
183 | 208M | struct mp_log_root *root = log->root; |
184 | 208M | if (!root) |
185 | 4.47M | return -1; |
186 | 204M | if (atomic_load_explicit(&log->reload_counter, memory_order_relaxed) != |
187 | 204M | atomic_load_explicit(&root->reload_counter, memory_order_relaxed)) |
188 | 2.84M | { |
189 | 2.84M | update_loglevel(log); |
190 | 2.84M | } |
191 | 204M | return log->level; |
192 | 208M | } |
193 | | |
194 | | static inline int term_msg_fileno(struct mp_log_root *root, int lev) |
195 | 236M | { |
196 | 236M | return root->force_stderr ? STDERR_FILENO : STDOUT_FILENO; |
197 | 236M | } |
198 | | |
199 | | static inline FILE *term_msg_fp(struct mp_log_root *root, int lev) |
200 | 117M | { |
201 | 117M | return term_msg_fileno(root, lev) == STDERR_FILENO ? stderr : stdout; |
202 | 117M | } |
203 | | |
204 | | static inline bool is_status_output(struct mp_log_root *root, int lev) |
205 | 151k | { |
206 | 151k | if (lev == MSGL_STATUS) |
207 | 2.28k | return true; |
208 | 149k | int msg_out = term_msg_fileno(root, lev); |
209 | 149k | int status_out = term_msg_fileno(root, MSGL_STATUS); |
210 | 149k | if (msg_out != status_out && root->isatty[msg_out] != root->isatty[status_out]) |
211 | 0 | return false; |
212 | 149k | return true; |
213 | 149k | } |
214 | | |
215 | | // Reposition cursor and clear lines for outputting the status line. In certain |
216 | | // cases, like term OSD and subtitle display, the status can consist of |
217 | | // multiple lines. |
218 | | static void prepare_prefix(struct mp_log_root *root, bstr *out, int lev, int term_lines) |
219 | 151k | { |
220 | 151k | int new_lines = lev == MSGL_STATUS ? term_lines : 0; |
221 | 151k | out->len = 0; |
222 | | |
223 | 151k | if (!is_status_output(root, lev)) |
224 | 0 | return; |
225 | | |
226 | 151k | if (!root->isatty[term_msg_fileno(root, lev)]) { |
227 | 151k | if (root->status_lines) |
228 | 96 | bstr_xappend(root, out, bstr0("\n")); |
229 | 151k | root->status_lines = new_lines; |
230 | 151k | return; |
231 | 151k | } |
232 | | |
233 | | // Set cursor state |
234 | 0 | if (new_lines && !root->status_lines) { |
235 | 0 | bstr_xappend(root, out, bstr0(TERM_ESC_HIDE_CURSOR)); |
236 | 0 | } else if (!new_lines && root->status_lines) { |
237 | 0 | bstr_xappend(root, out, bstr0(TERM_ESC_RESTORE_CURSOR)); |
238 | 0 | } |
239 | |
|
240 | 0 | int line_skip = 0; |
241 | 0 | if (root->status_lines) { |
242 | | // Clear previous status line |
243 | 0 | bstr_xappend(root, out, bstr0("\033[1K\r")); |
244 | 0 | bstr up_clear = bstr0("\033[A\033[K"); |
245 | 0 | for (int i = 1; i < root->status_lines; ++i) |
246 | 0 | bstr_xappend(root, out, up_clear); |
247 | 0 | mp_assert(root->status_lines > 0 && root->blank_lines >= root->status_lines); |
248 | 0 | line_skip = root->blank_lines - root->status_lines; |
249 | 0 | } |
250 | | |
251 | 0 | if (new_lines) |
252 | 0 | line_skip -= MPMAX(0, root->blank_lines - new_lines); |
253 | |
|
254 | 0 | if (line_skip) |
255 | 0 | bstr_xappend_asprintf(root, out, line_skip > 0 ? "\033[%dA" : "\033[%dB", abs(line_skip)); |
256 | |
|
257 | 0 | root->blank_lines = MPMAX(0, root->blank_lines - term_lines); |
258 | 0 | root->status_lines = new_lines; |
259 | 0 | root->blank_lines += root->status_lines; |
260 | 0 | } |
261 | | |
262 | | static void msg_flush_status_line(struct mp_log_root *root, bool clear) |
263 | 612k | { |
264 | 612k | if (!root->status_lines) |
265 | 612k | goto done; |
266 | | |
267 | 4 | FILE *fp = term_msg_fp(root, MSGL_STATUS); |
268 | 4 | if (!clear) { |
269 | 4 | if (root->isatty[term_msg_fileno(root, MSGL_STATUS)]) |
270 | 0 | fprintf(fp, TERM_ESC_RESTORE_CURSOR); |
271 | 4 | fprintf(fp, "\n"); |
272 | 4 | root->blank_lines = 0; |
273 | 4 | root->status_lines = 0; |
274 | 4 | goto done; |
275 | 4 | } |
276 | | |
277 | 0 | bstr term_msg = {0}; |
278 | 0 | prepare_prefix(root, &term_msg, MSGL_STATUS, 0); |
279 | 0 | if (term_msg.len) { |
280 | 0 | fprintf(fp, "%.*s", BSTR_P(term_msg)); |
281 | 0 | talloc_free(term_msg.start); |
282 | 0 | } |
283 | |
|
284 | 612k | done: |
285 | 612k | root->status_line.len = 0; |
286 | 612k | } |
287 | | |
288 | | void mp_msg_flush_status_line(struct mp_log *log, bool clear) |
289 | 612k | { |
290 | 612k | if (!log->root) |
291 | 0 | return; |
292 | | |
293 | 612k | mp_mutex_lock(&log->root->lock); |
294 | 612k | msg_flush_status_line(log->root, clear); |
295 | 612k | mp_mutex_unlock(&log->root->lock); |
296 | 612k | } |
297 | | |
298 | | void mp_msg_set_term_title(struct mp_log *log, const char *title) |
299 | 19 | { |
300 | 19 | if (log->root && title) { |
301 | | // Lock because printf to terminal is not necessarily atomic. |
302 | 19 | mp_mutex_lock(&log->root->lock); |
303 | 19 | fprintf(term_msg_fp(log->root, MSGL_STATUS), "\033]0;%s\007", title); |
304 | 19 | mp_mutex_unlock(&log->root->lock); |
305 | 19 | } |
306 | 19 | } |
307 | | |
308 | | bool mp_msg_has_status_line(struct mpv_global *global) |
309 | 2.19k | { |
310 | 2.19k | struct mp_log_root *root = global->log->root; |
311 | 2.19k | mp_mutex_lock(&root->lock); |
312 | 2.19k | bool r = root->status_lines > 0; |
313 | 2.19k | mp_mutex_unlock(&root->lock); |
314 | 2.19k | return r; |
315 | 2.19k | } |
316 | | |
317 | | static void set_term_color(void *talloc_ctx, bstr *text, int c) |
318 | 0 | { |
319 | 0 | if (c == -1) { |
320 | 0 | bstr_xappend(talloc_ctx, text, bstr0("\033[0m")); |
321 | 0 | return; |
322 | 0 | } |
323 | | // Pure black to gray |
324 | 0 | if (c == 0) |
325 | 0 | c += 8; |
326 | | // Pure white to light one |
327 | 0 | if (c == 15) |
328 | 0 | c -= 8; |
329 | 0 | bstr_xappend_asprintf(talloc_ctx, text, "\033[%d%dm", c >= 8 ? 9 : 3, c & 7); |
330 | 0 | } |
331 | | |
332 | | static void set_msg_color(void *talloc_ctx, bstr *text, int lev) |
333 | 0 | { |
334 | 0 | static const int v_colors[] = { |
335 | 0 | [MSGL_FATAL] = 9, // bright red |
336 | 0 | [MSGL_ERR] = 1, // red |
337 | 0 | [MSGL_WARN] = 3, // yellow |
338 | 0 | [MSGL_INFO] = -1, // default |
339 | 0 | [MSGL_STATUS] = -1, // default |
340 | 0 | [MSGL_V] = 2, // green |
341 | 0 | [MSGL_DEBUG] = 4, // blue |
342 | 0 | [MSGL_TRACE] = 8, // bright black aka. gray |
343 | 0 | }; |
344 | 0 | set_term_color(talloc_ctx, text, v_colors[lev]); |
345 | 0 | } |
346 | | |
347 | | static void pretty_print_module(struct mp_log_root *root, bstr *text, |
348 | | const char *prefix, int lev) |
349 | 2.33k | { |
350 | 2.33k | size_t prefix_len = strlen(prefix); |
351 | 2.33k | root->module_indent = MPMAX(10, MPMAX(root->module_indent, prefix_len)); |
352 | 2.33k | bool color = root->color[term_msg_fileno(root, lev)]; |
353 | | |
354 | | // Use random color based on the name of the module |
355 | 2.33k | if (color) { |
356 | 0 | unsigned int mod = 0; |
357 | 0 | for (int i = 0; i < prefix_len; ++i) |
358 | 0 | mod = mod * 33 + prefix[i]; |
359 | 0 | set_term_color(root, text, (mod + 1) % 15 + 1); |
360 | 0 | } |
361 | | |
362 | 2.33k | bstr_xappend_asprintf(root, text, "%*s", root->module_indent, prefix); |
363 | 2.33k | if (color) |
364 | 0 | set_term_color(root, text, -1); |
365 | 2.33k | bstr_xappend(root, text, bstr0(": ")); |
366 | 2.33k | if (color) |
367 | 0 | set_msg_color(root, text, lev); |
368 | 2.33k | } |
369 | | |
370 | | static bool test_terminal_level(struct mp_log *log, int lev) |
371 | 117M | { |
372 | 117M | return lev <= log->terminal_level && log->root->use_terminal && |
373 | 117M | !(lev == MSGL_STATUS && terminal_in_background()); |
374 | 117M | } |
375 | | |
376 | | static void append_terminal_line(struct mp_log *log, int lev, |
377 | | bstr text, bstr *term_msg, int *line_w, int term_w) |
378 | 149k | { |
379 | 149k | struct mp_log_root *root = log->root; |
380 | | |
381 | 149k | size_t start = term_msg->len; |
382 | | |
383 | 149k | if (root->show_time) |
384 | 3.19k | bstr_xappend_asprintf(root, term_msg, "[%10.6f] ", mp_time_sec()); |
385 | | |
386 | 149k | const char *log_prefix = (lev >= MSGL_V) || root->verbose || root->module |
387 | 149k | ? log->verbose_prefix : log->prefix; |
388 | 149k | if (log_prefix) { |
389 | 140k | if (root->module) { |
390 | 2.33k | pretty_print_module(root, term_msg, log_prefix, lev); |
391 | 138k | } else { |
392 | 138k | bstr_xappend_asprintf(root, term_msg, "[%s] ", log_prefix); |
393 | 138k | } |
394 | 140k | } |
395 | | |
396 | 149k | bstr_xappend(root, term_msg, text); |
397 | | |
398 | 149k | const unsigned char *cut_pos = NULL; |
399 | 149k | int ellipsis_width = 2; |
400 | 149k | int width = term_disp_width(bstr_splice(*term_msg, start, term_msg->len), |
401 | 149k | term_w - ellipsis_width, &cut_pos); |
402 | 149k | if (cut_pos) { |
403 | 0 | int new_len = cut_pos - term_msg->start; |
404 | 0 | bstr rem = bstrdup(NULL, (bstr){(unsigned char *)cut_pos, term_msg->len - new_len}); |
405 | 0 | void *ptr = rem.start; |
406 | 0 | term_msg->len = new_len; |
407 | |
|
408 | 0 | bstr_xappend(root, term_msg, bstr0("..")); |
409 | |
|
410 | 0 | while (rem.len) { |
411 | 0 | if (bstr_eatstart0(&rem, "\n")) { |
412 | 0 | bstr_xappend(root, term_msg, bstr0("\n")); |
413 | 0 | continue; |
414 | 0 | } |
415 | 0 | if (bstr_eatstart0(&rem, "\033[")) { |
416 | 0 | bstr_xappend(root, term_msg, bstr0("\033[")); |
417 | |
|
418 | 0 | while (rem.len && !((*rem.start >= '@' && *rem.start <= '~') || *rem.start == 'm')) { |
419 | 0 | bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1)); |
420 | 0 | rem = bstr_cut(rem, 1); |
421 | 0 | } |
422 | 0 | bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1)); |
423 | 0 | } |
424 | 0 | rem = bstr_cut(rem, 1); |
425 | 0 | } |
426 | 0 | talloc_free(ptr); |
427 | |
|
428 | 0 | width += ellipsis_width; |
429 | 0 | } |
430 | 149k | *line_w = root->isatty[term_msg_fileno(root, lev)] ? width : 0; |
431 | 149k | } |
432 | | |
433 | | static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer) |
434 | 66.8M | { |
435 | 66.8M | mp_assert(buffer->num_entries); |
436 | 66.8M | struct mp_log_buffer_entry *res = buffer->entries[buffer->entry0]; |
437 | 66.8M | buffer->entry0 = (buffer->entry0 + 1) % buffer->capacity; |
438 | 66.8M | buffer->num_entries -= 1; |
439 | 66.8M | return res; |
440 | 66.8M | } |
441 | | |
442 | | static void write_msg_to_buffers(struct mp_log *log, int lev, bstr text) |
443 | 135M | { |
444 | 135M | struct mp_log_root *root = log->root; |
445 | 202M | for (int n = 0; n < root->num_buffers; n++) { |
446 | 67.3M | struct mp_log_buffer *buffer = root->buffers[n]; |
447 | 67.3M | bool wakeup = false; |
448 | 67.3M | mp_mutex_lock(&buffer->lock); |
449 | 67.3M | int buffer_level = buffer->level; |
450 | 67.3M | if (buffer_level == MP_LOG_BUFFER_MSGL_TERM) |
451 | 33.6M | buffer_level = log->terminal_level; |
452 | 67.3M | if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE) |
453 | 33.6M | buffer_level = MPMAX(log->terminal_level, MSGL_DEBUG); |
454 | 67.3M | if (lev <= buffer_level && lev != MSGL_STATUS) { |
455 | 66.8M | if (buffer->level == MP_LOG_BUFFER_MSGL_LOGFILE) { |
456 | | // If the buffer is full, block until we can write again, |
457 | | // unless there's no write thread (died, or early filebuffer) |
458 | 33.6M | bool dead = false; |
459 | 34.8M | while (buffer->num_entries == buffer->capacity && !dead) { |
460 | | // Temporary unlock is OK; buffer->level is immutable, and |
461 | | // buffer can't go away because the global log lock is held. |
462 | 1.14M | mp_mutex_unlock(&buffer->lock); |
463 | 1.14M | mp_mutex_lock(&root->log_file_lock); |
464 | 1.14M | if (root->log_file_thread_active) { |
465 | 0 | mp_cond_wait(&root->log_file_wakeup, |
466 | 0 | &root->log_file_lock); |
467 | 1.14M | } else { |
468 | 1.14M | dead = true; |
469 | 1.14M | } |
470 | 1.14M | mp_mutex_unlock(&root->log_file_lock); |
471 | 1.14M | mp_mutex_lock(&buffer->lock); |
472 | 1.14M | } |
473 | 33.6M | } |
474 | 66.8M | if (buffer->num_entries == buffer->capacity) { |
475 | 20.2M | struct mp_log_buffer_entry *skip = log_buffer_read(buffer); |
476 | 20.2M | talloc_free(skip); |
477 | 20.2M | buffer->dropped += 1; |
478 | 20.2M | } |
479 | 66.8M | struct mp_log_buffer_entry *entry = talloc_ptrtype(NULL, entry); |
480 | 66.8M | *entry = (struct mp_log_buffer_entry) { |
481 | 66.8M | .prefix = talloc_strdup(entry, log->verbose_prefix), |
482 | 66.8M | .level = lev, |
483 | 66.8M | .text = bstrdup0(entry, text), |
484 | 66.8M | }; |
485 | 66.8M | int pos = (buffer->entry0 + buffer->num_entries) % buffer->capacity; |
486 | 66.8M | buffer->entries[pos] = entry; |
487 | 66.8M | buffer->num_entries += 1; |
488 | 66.8M | if (buffer->wakeup_cb && !buffer->silent) |
489 | 0 | wakeup = true; |
490 | 66.8M | } |
491 | 67.3M | mp_mutex_unlock(&buffer->lock); |
492 | 67.3M | if (wakeup) |
493 | 0 | buffer->wakeup_cb(buffer->wakeup_cb_ctx); |
494 | 67.3M | } |
495 | 135M | } |
496 | | |
497 | | static void dump_stats(struct mp_log *log, int lev, bstr text) |
498 | 264 | { |
499 | 264 | struct mp_log_root *root = log->root; |
500 | 264 | if (lev == MSGL_STATS && root->stats_file) |
501 | 27 | fprintf(root->stats_file, "%"PRId64" %.*s\n", mp_time_ns(), BSTR_P(text)); |
502 | 264 | } |
503 | | |
504 | | static void write_term_msg(struct mp_log *log, int lev, bstr text, bstr *out) |
505 | 117M | { |
506 | 117M | struct mp_log_root *root = log->root; |
507 | 117M | bool print_term = test_terminal_level(log, lev); |
508 | 117M | int fileno = term_msg_fileno(root, lev); |
509 | 117M | int term_w = 0, term_h = 0; |
510 | 117M | if (print_term && root->isatty[fileno]) |
511 | 0 | terminal_get_size(&term_w, &term_h); |
512 | | |
513 | 117M | out->len = 0; |
514 | | |
515 | | // Split away each line. Normally we require full lines; buffer partial |
516 | | // lines if they happen. |
517 | 117M | root->term_msg_tmp.len = 0; |
518 | 117M | int term_msg_lines = 0; |
519 | | |
520 | 117M | bstr str = text; |
521 | 253M | while (str.len) { |
522 | 143M | bstr line = bstr_getline(str, &str); |
523 | 143M | if (line.start[line.len - 1] != '\n') { |
524 | 8.45M | mp_assert(str.len == 0); |
525 | 8.45M | str = line; |
526 | 8.45M | break; |
527 | 8.45M | } |
528 | | |
529 | 135M | bool clip = bstr_eatstart0(&line, TERM_MSG_0); |
530 | 135M | if (print_term) { |
531 | 149k | int line_w; |
532 | 149k | append_terminal_line(log, lev, line, &root->term_msg_tmp, &line_w, |
533 | 149k | clip && term_w ? term_w : INT_MAX); |
534 | 149k | term_msg_lines += (!line_w || !term_w) |
535 | 149k | ? 1 : (line_w + term_w - 1) / term_w; |
536 | 149k | } |
537 | 135M | write_msg_to_buffers(log, lev, line); |
538 | 135M | } |
539 | | |
540 | 117M | if (lev == MSGL_STATUS) { |
541 | 2.28k | int line_w = 0; |
542 | 2.28k | bool clip = bstr_eatstart0(&str, TERM_MSG_0); |
543 | 2.28k | if (str.len && print_term) |
544 | 100 | append_terminal_line(log, lev, str, &root->term_msg_tmp, &line_w, |
545 | 100 | clip && term_w ? term_w : INT_MAX); |
546 | 2.28k | term_msg_lines += !term_w ? (str.len ? 1 : 0) |
547 | 2.28k | : (line_w + term_w - 1) / term_w; |
548 | 117M | } else if (str.len) { |
549 | 8.45M | bstr_xappend(NULL, &log->partial[lev], str); |
550 | 8.45M | } |
551 | | |
552 | 117M | if (print_term && (root->term_msg_tmp.len || lev == MSGL_STATUS)) { |
553 | 151k | prepare_prefix(root, out, lev, term_msg_lines); |
554 | 151k | if (root->color[fileno] && root->term_msg_tmp.len) { |
555 | 0 | set_msg_color(root, out, lev); |
556 | 0 | set_term_color(root, &root->term_msg_tmp, -1); |
557 | 0 | } |
558 | 151k | bstr_xappend(root, out, root->term_msg_tmp); |
559 | 151k | } |
560 | 117M | } |
561 | | |
562 | | void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va) |
563 | 150M | { |
564 | 150M | if (!mp_msg_test(log, lev)) |
565 | 32.3M | return; // do not display |
566 | | |
567 | 117M | struct mp_log_root *root = log->root; |
568 | | |
569 | 117M | mp_mutex_lock(&root->lock); |
570 | | |
571 | 117M | root->buffer.len = 0; |
572 | | |
573 | 117M | if (log->partial[lev].len) |
574 | 8.45M | bstr_xappend(root, &root->buffer, log->partial[lev]); |
575 | 117M | log->partial[lev].len = 0; |
576 | | |
577 | 117M | if (bstr_xappend_vasprintf(root, &root->buffer, format, va) < 0) { |
578 | 0 | bstr_xappend(root, &root->buffer, bstr0("format error: ")); |
579 | 0 | bstr_xappend(root, &root->buffer, bstr0(format)); |
580 | 0 | } |
581 | | |
582 | | // Remember last status message and restore it to ensure that it is |
583 | | // always displayed |
584 | 117M | if (lev == MSGL_STATUS) { |
585 | 2.20k | root->status_log = log; |
586 | 2.20k | root->status_line.len = 0; |
587 | | // Use bstr_xappend instead bstrdup to reuse allocated memory |
588 | 2.20k | if (root->buffer.len) |
589 | 8 | bstr_xappend(root, &root->status_line, root->buffer); |
590 | 2.20k | } |
591 | | |
592 | 117M | if (lev == MSGL_STATS) { |
593 | 264 | dump_stats(log, lev, root->buffer); |
594 | 117M | } else if (lev == MSGL_STATUS && !test_terminal_level(log, lev)) { |
595 | | /* discard */ |
596 | 117M | } else { |
597 | 117M | write_term_msg(log, lev, root->buffer, &root->term_msg); |
598 | | |
599 | 117M | FILE *stream = term_msg_fp(root, lev); |
600 | 117M | if (root->term_msg.len) { |
601 | 149k | root->term_status_msg.len = 0; |
602 | 149k | if (lev != MSGL_STATUS && root->status_line.len && root->status_log && |
603 | 149k | is_status_output(root, lev) && test_terminal_level(root->status_log, MSGL_STATUS)) |
604 | 92 | { |
605 | 92 | write_term_msg(root->status_log, MSGL_STATUS, root->status_line, |
606 | 92 | &root->term_status_msg); |
607 | 92 | } |
608 | 149k | fwrite(root->term_msg.start, root->term_msg.len, 1, stream); |
609 | 149k | if (root->term_status_msg.len) |
610 | 92 | fwrite(root->term_status_msg.start, root->term_status_msg.len, 1, stream); |
611 | 149k | fflush(stream); |
612 | 149k | } |
613 | 117M | } |
614 | | |
615 | 117M | mp_mutex_unlock(&root->lock); |
616 | 117M | } |
617 | | |
618 | | static void destroy_log(void *ptr) |
619 | 7.00M | { |
620 | 7.00M | struct mp_log *log = ptr; |
621 | | // This is not managed via talloc itself, because mp_msg calls must be |
622 | | // thread-safe, while talloc is not thread-safe. |
623 | 70.0M | for (int lvl = 0; lvl <= MSGL_MAX; ++lvl) |
624 | 63.0M | talloc_free(log->partial[lvl].start); |
625 | 7.00M | } |
626 | | |
627 | | // Create a new log context, which uses talloc_ctx as talloc parent, and parent |
628 | | // as logical parent. |
629 | | // The name is the prefix put before the output. It's usually prefixed by the |
630 | | // parent's name. If the name starts with "/", the parent's name is not |
631 | | // prefixed (except in verbose mode), and if it starts with "!", the name is |
632 | | // not printed at all (except in verbose mode). |
633 | | // If name is NULL, the parent's name/prefix is used. |
634 | | // Thread-safety: fully thread-safe, but keep in mind that talloc is not (so |
635 | | // talloc_ctx must be owned by the current thread). |
636 | | struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent, |
637 | | const char *name) |
638 | 7.00M | { |
639 | 7.00M | mp_assert(parent); |
640 | 7.00M | struct mp_log *log = talloc_zero(talloc_ctx, struct mp_log); |
641 | 7.00M | if (!parent->root) |
642 | 0 | return log; // same as null_log |
643 | 7.00M | talloc_set_destructor(log, destroy_log); |
644 | 7.00M | log->root = parent->root; |
645 | 7.00M | log->max_level = MSGL_MAX; |
646 | 7.00M | if (name) { |
647 | 6.92M | if (name[0] == '!') { |
648 | 1.18M | name = &name[1]; |
649 | 5.73M | } else if (name[0] == '/') { |
650 | 0 | name = &name[1]; |
651 | 0 | log->prefix = talloc_strdup(log, name); |
652 | 5.73M | } else { |
653 | 5.73M | log->prefix = parent->prefix |
654 | 5.73M | ? talloc_asprintf(log, "%s/%s", parent->prefix, name) |
655 | 5.73M | : talloc_strdup(log, name); |
656 | 5.73M | } |
657 | 6.92M | log->verbose_prefix = parent->prefix |
658 | 6.92M | ? talloc_asprintf(log, "%s/%s", parent->prefix, name) |
659 | 6.92M | : talloc_strdup(log, name); |
660 | 6.92M | if (log->prefix && !log->prefix[0]) |
661 | 164k | log->prefix = NULL; |
662 | 6.92M | if (!log->verbose_prefix[0]) |
663 | 164k | log->verbose_prefix = "global"; |
664 | 6.92M | } else { |
665 | 86.6k | log->prefix = talloc_strdup(log, parent->prefix); |
666 | 86.6k | log->verbose_prefix = talloc_strdup(log, parent->verbose_prefix); |
667 | 86.6k | } |
668 | 7.00M | return log; |
669 | 7.00M | } |
670 | | |
671 | | void mp_msg_init(struct mpv_global *global) |
672 | 164k | { |
673 | 164k | mp_assert(!global->log); |
674 | | |
675 | 164k | struct mp_log_root *root = talloc_zero(NULL, struct mp_log_root); |
676 | 164k | *root = (struct mp_log_root){ |
677 | 164k | .global = global, |
678 | 164k | .reload_counter = 1, |
679 | 164k | }; |
680 | | |
681 | 164k | mp_mutex_init(&root->lock); |
682 | 164k | mp_mutex_init(&root->log_file_lock); |
683 | 164k | mp_cond_init(&root->log_file_wakeup); |
684 | | |
685 | 164k | struct mp_log dummy = { .root = root }; |
686 | 164k | struct mp_log *log = mp_log_new(root, &dummy, ""); |
687 | | |
688 | 164k | global->log = log; |
689 | 164k | } |
690 | | |
691 | | static MP_THREAD_VOID log_file_thread(void *p) |
692 | 0 | { |
693 | 0 | struct mp_log_root *root = p; |
694 | |
|
695 | 0 | mp_thread_set_name("log"); |
696 | |
|
697 | 0 | mp_mutex_lock(&root->log_file_lock); |
698 | |
|
699 | 0 | while (root->log_file_thread_active) { |
700 | 0 | struct mp_log_buffer_entry *e = |
701 | 0 | mp_msg_log_buffer_read(root->log_file_buffer); |
702 | 0 | if (e) { |
703 | 0 | mp_mutex_unlock(&root->log_file_lock); |
704 | 0 | fprintf(root->log_file, "[%8.3f][%c][%s] %s", |
705 | 0 | mp_time_sec(), |
706 | 0 | mp_log_levels[e->level][0], e->prefix, e->text); |
707 | 0 | fflush(root->log_file); |
708 | 0 | mp_mutex_lock(&root->log_file_lock); |
709 | 0 | talloc_free(e); |
710 | | // Multiple threads might be blocked if the log buffer was full. |
711 | 0 | mp_cond_broadcast(&root->log_file_wakeup); |
712 | 0 | } else { |
713 | 0 | mp_cond_wait(&root->log_file_wakeup, &root->log_file_lock); |
714 | 0 | } |
715 | 0 | } |
716 | |
|
717 | 0 | mp_mutex_unlock(&root->log_file_lock); |
718 | |
|
719 | 0 | MP_THREAD_RETURN(); |
720 | 0 | } |
721 | | |
722 | | static void wakeup_log_file(void *p) |
723 | 0 | { |
724 | 0 | struct mp_log_root *root = p; |
725 | |
|
726 | 0 | mp_mutex_lock(&root->log_file_lock); |
727 | 0 | mp_cond_broadcast(&root->log_file_wakeup); |
728 | 0 | mp_mutex_unlock(&root->log_file_lock); |
729 | 0 | } |
730 | | |
731 | | // Only to be called from the main thread. |
732 | | static void terminate_log_file_thread(struct mp_log_root *root) |
733 | 164k | { |
734 | 164k | bool wait_terminate = false; |
735 | | |
736 | 164k | mp_mutex_lock(&root->log_file_lock); |
737 | 164k | if (root->log_file_thread_active) { |
738 | 0 | root->log_file_thread_active = false; |
739 | 0 | mp_cond_broadcast(&root->log_file_wakeup); |
740 | 0 | wait_terminate = true; |
741 | 0 | } |
742 | 164k | mp_mutex_unlock(&root->log_file_lock); |
743 | | |
744 | 164k | if (wait_terminate) |
745 | 0 | mp_thread_join(root->log_file_thread); |
746 | | |
747 | 164k | mp_msg_log_buffer_destroy(root->log_file_buffer); |
748 | 164k | root->log_file_buffer = NULL; |
749 | | |
750 | 164k | if (root->log_file) |
751 | 0 | fclose(root->log_file); |
752 | 164k | root->log_file = NULL; |
753 | 164k | } |
754 | | |
755 | | // If opt is different from *current_path, update *current_path and return true. |
756 | | // No lock must be held; passed values must be accessible without. |
757 | | static bool check_new_path(struct mpv_global *global, char *opt, |
758 | | char **current_path) |
759 | 607k | { |
760 | 607k | void *tmp = talloc_new(NULL); |
761 | 607k | bool res = false; |
762 | | |
763 | 607k | char *new_path = mp_get_user_path(tmp, global, opt); |
764 | 607k | if (!new_path) |
765 | 606k | new_path = ""; |
766 | | |
767 | 607k | char *old_path = *current_path ? *current_path : ""; |
768 | 607k | if (strcmp(old_path, new_path) != 0) { |
769 | 365 | talloc_free(*current_path); |
770 | 365 | *current_path = NULL; |
771 | 365 | if (new_path[0]) |
772 | 365 | *current_path = talloc_strdup(NULL, new_path); |
773 | 365 | res = true; |
774 | 365 | } |
775 | | |
776 | 607k | talloc_free(tmp); |
777 | | |
778 | 607k | return res; |
779 | 607k | } |
780 | | |
781 | | void mp_msg_update_msglevels(struct mpv_global *global, struct MPOpts *opts) |
782 | 303k | { |
783 | 303k | struct mp_log_root *root = global->log->root; |
784 | | |
785 | 303k | mp_mutex_lock(&root->lock); |
786 | | |
787 | 303k | root->verbose = opts->verbose; |
788 | 303k | root->really_quiet = opts->msg_really_quiet; |
789 | 303k | root->module = opts->msg_module; |
790 | 303k | root->use_terminal = opts->use_terminal; |
791 | 303k | root->show_time = opts->msg_time; |
792 | | |
793 | 303k | if (root->really_quiet) |
794 | 15 | root->status_lines = 0; |
795 | | |
796 | 311k | for (int i = STDOUT_FILENO; i <= STDERR_FILENO && root->use_terminal; ++i) { |
797 | 7.48k | root->isatty[i] = isatty(i); |
798 | 7.48k | root->color[i] = opts->msg_color && root->isatty[i]; |
799 | 7.48k | } |
800 | | |
801 | 303k | m_option_type_msglevels.free(&root->msg_levels); |
802 | 303k | m_option_type_msglevels.copy(NULL, &root->msg_levels, &opts->msg_levels); |
803 | | |
804 | 303k | atomic_fetch_add(&root->reload_counter, 1); |
805 | 303k | mp_mutex_unlock(&root->lock); |
806 | | |
807 | 303k | if (check_new_path(global, opts->log_file, &root->log_path)) { |
808 | 0 | terminate_log_file_thread(root); |
809 | 0 | if (root->log_path) { |
810 | 0 | root->log_file = fopen(root->log_path, "wb"); |
811 | 0 | if (root->log_file) { |
812 | | |
813 | | // if a logfile is created and the early filebuf still exists, |
814 | | // flush and destroy the early buffer |
815 | 0 | mp_mutex_lock(&root->lock); |
816 | 0 | struct mp_log_buffer *earlybuf = root->early_filebuffer; |
817 | 0 | if (earlybuf) |
818 | 0 | root->early_filebuffer = NULL; // but it still logs msgs |
819 | 0 | mp_mutex_unlock(&root->lock); |
820 | |
|
821 | 0 | if (earlybuf) { |
822 | | // flush, destroy before creating the normal logfile buf, |
823 | | // as once the new one is created (specifically, its write |
824 | | // thread), then MSGL_LOGFILE messages become blocking, but |
825 | | // the early logfile buf is without dequeue - can deadlock. |
826 | | // note: timestamp is unknown, we use 0.000 as indication. |
827 | | // note: new messages while iterating are still flushed. |
828 | 0 | struct mp_log_buffer_entry *e; |
829 | 0 | while ((e = mp_msg_log_buffer_read(earlybuf))) { |
830 | 0 | fprintf(root->log_file, "[%8.3f][%c][%s] %s", 0.0, |
831 | 0 | mp_log_levels[e->level][0], e->prefix, e->text); |
832 | 0 | talloc_free(e); |
833 | 0 | } |
834 | 0 | mp_msg_log_buffer_destroy(earlybuf); // + remove from root |
835 | 0 | } |
836 | |
|
837 | 0 | root->log_file_buffer = |
838 | 0 | mp_msg_log_buffer_new(global, FILE_BUF, MP_LOG_BUFFER_MSGL_LOGFILE, |
839 | 0 | wakeup_log_file, root); |
840 | 0 | root->log_file_thread_active = true; |
841 | 0 | if (mp_thread_create(&root->log_file_thread, log_file_thread, |
842 | 0 | root)) |
843 | 0 | { |
844 | 0 | root->log_file_thread_active = false; |
845 | 0 | terminate_log_file_thread(root); |
846 | 0 | } |
847 | 0 | } else { |
848 | 0 | mp_err(global->log, "Failed to open log file '%s'\n", |
849 | 0 | root->log_path); |
850 | 0 | } |
851 | 0 | } |
852 | 0 | } |
853 | | |
854 | 303k | if (check_new_path(global, opts->dump_stats, &root->stats_path)) { |
855 | 365 | bool open_error = false; |
856 | | |
857 | 365 | mp_mutex_lock(&root->lock); |
858 | 365 | if (root->stats_file) |
859 | 0 | fclose(root->stats_file); |
860 | 365 | root->stats_file = NULL; |
861 | 365 | if (root->stats_path) { |
862 | 365 | root->stats_file = fopen(root->stats_path, "wb"); |
863 | 365 | open_error = !root->stats_file; |
864 | 365 | } |
865 | 365 | mp_mutex_unlock(&root->lock); |
866 | | |
867 | 365 | if (open_error) { |
868 | 263 | mp_err(global->log, "Failed to open stats file '%s'\n", |
869 | 263 | root->stats_path); |
870 | 263 | } |
871 | 365 | } |
872 | 303k | } |
873 | | |
874 | | void mp_msg_force_stderr(struct mpv_global *global, bool force_stderr) |
875 | 33 | { |
876 | 33 | struct mp_log_root *root = global->log->root; |
877 | | |
878 | 33 | mp_mutex_lock(&root->lock); |
879 | 33 | root->force_stderr = force_stderr; |
880 | 33 | mp_mutex_unlock(&root->lock); |
881 | 33 | } |
882 | | |
883 | | // Only to be called from the main thread. |
884 | | bool mp_msg_has_log_file(struct mpv_global *global) |
885 | 607k | { |
886 | 607k | struct mp_log_root *root = global->log->root; |
887 | | |
888 | 607k | return !!root->log_file; |
889 | 607k | } |
890 | | |
891 | | void mp_msg_uninit(struct mpv_global *global) |
892 | 164k | { |
893 | 164k | struct mp_log_root *root = global->log->root; |
894 | 164k | mp_msg_flush_status_line(global->log, true); |
895 | 164k | if (root->really_quiet && root->isatty[term_msg_fileno(root, MSGL_STATUS)]) |
896 | 0 | fprintf(term_msg_fp(root, MSGL_STATUS), TERM_ESC_RESTORE_CURSOR); |
897 | 164k | terminate_log_file_thread(root); |
898 | 164k | mp_msg_log_buffer_destroy(root->early_buffer); |
899 | 164k | mp_msg_log_buffer_destroy(root->early_filebuffer); |
900 | 164k | mp_assert(root->num_buffers == 0); |
901 | 164k | if (root->stats_file) |
902 | 102 | fclose(root->stats_file); |
903 | 164k | talloc_free(root->stats_path); |
904 | 164k | talloc_free(root->log_path); |
905 | 164k | m_option_type_msglevels.free(&root->msg_levels); |
906 | 164k | mp_mutex_destroy(&root->lock); |
907 | 164k | mp_mutex_destroy(&root->log_file_lock); |
908 | 164k | mp_cond_destroy(&root->log_file_wakeup); |
909 | 164k | talloc_free(root); |
910 | 164k | global->log = NULL; |
911 | 164k | } |
912 | | |
913 | | // early logging store log messages before they have a known destination. |
914 | | // there are two early log buffers which are similar logically, and both cease |
915 | | // function (if still exist, independently) once the log destination is known, |
916 | | // or mpv init is complete (typically, after all clients/scripts init is done). |
917 | | // |
918 | | // - "normal" early_buffer, holds early terminal-level logs, and is handed over |
919 | | // to the first client which requests such log buffer, so that it sees older |
920 | | // messages too. further clients which request a log buffer get a new one |
921 | | // which accumulates messages starting at this point in time. |
922 | | // |
923 | | // - early_filebuffer - early log-file messages until a log file name is known. |
924 | | // main cases where meaningful messages are accumulated before the filename |
925 | | // is known are when log-file is set at mpv.conf, or from script/client init. |
926 | | // once a file name is known, the early buffer is flushed and destroyed. |
927 | | // unlike the "proper" log-file buffer, the early filebuffer is not backed by |
928 | | // a write thread, and hence non-blocking (can overwrite old messages). |
929 | | // it's also bigger than the actual file buffer (early: 5000, actual: 100). |
930 | | |
931 | | static void mp_msg_set_early_logging_raw(struct mpv_global *global, bool enable, |
932 | | struct mp_log_buffer **root_logbuf, |
933 | | int size, int level) |
934 | 606k | { |
935 | 606k | struct mp_log_root *root = global->log->root; |
936 | 606k | mp_mutex_lock(&root->lock); |
937 | | |
938 | 606k | if (enable != !!*root_logbuf) { |
939 | 606k | if (enable) { |
940 | 307k | mp_mutex_unlock(&root->lock); |
941 | 307k | struct mp_log_buffer *buf = |
942 | 307k | mp_msg_log_buffer_new(global, size, level, NULL, NULL); |
943 | 307k | mp_mutex_lock(&root->lock); |
944 | 307k | mp_assert(!*root_logbuf); // no concurrent calls to this function |
945 | 307k | *root_logbuf = buf; |
946 | 307k | } else { |
947 | 298k | struct mp_log_buffer *buf = *root_logbuf; |
948 | 298k | *root_logbuf = NULL; |
949 | 298k | mp_mutex_unlock(&root->lock); |
950 | 298k | mp_msg_log_buffer_destroy(buf); |
951 | 298k | return; |
952 | 298k | } |
953 | 606k | } |
954 | | |
955 | 307k | mp_mutex_unlock(&root->lock); |
956 | 307k | } |
957 | | |
958 | | void mp_msg_set_early_logging(struct mpv_global *global, bool enable) |
959 | 303k | { |
960 | 303k | struct mp_log_root *root = global->log->root; |
961 | | |
962 | 303k | mp_msg_set_early_logging_raw(global, enable, &root->early_buffer, |
963 | 303k | EARLY_TERM_BUF, MP_LOG_BUFFER_MSGL_TERM); |
964 | | |
965 | | // normally MSGL_LOGFILE buffer gets a write thread, but not the early buf |
966 | 303k | mp_msg_set_early_logging_raw(global, enable, &root->early_filebuffer, |
967 | 303k | EARLY_FILE_BUF, MP_LOG_BUFFER_MSGL_LOGFILE); |
968 | 303k | } |
969 | | |
970 | | struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global, |
971 | | int size, int level, |
972 | | void (*wakeup_cb)(void *ctx), |
973 | | void *wakeup_cb_ctx) |
974 | 307k | { |
975 | 307k | struct mp_log_root *root = global->log->root; |
976 | | |
977 | 307k | mp_mutex_lock(&root->lock); |
978 | | |
979 | 307k | if (level == MP_LOG_BUFFER_MSGL_TERM) { |
980 | | // The first thing which creates a terminal-level log buffer gets the |
981 | | // early log buffer, if it exists. This is supposed to enable a script |
982 | | // to grab log messages from before it was initialized. It's OK that |
983 | | // this works only for 1 script and only once. |
984 | 153k | if (root->early_buffer) { |
985 | 0 | struct mp_log_buffer *buffer = root->early_buffer; |
986 | 0 | root->early_buffer = NULL; |
987 | 0 | mp_msg_log_buffer_resize(buffer, size); |
988 | 0 | buffer->wakeup_cb = wakeup_cb; |
989 | 0 | buffer->wakeup_cb_ctx = wakeup_cb_ctx; |
990 | 0 | mp_mutex_unlock(&root->lock); |
991 | 0 | return buffer; |
992 | 0 | } |
993 | 153k | } |
994 | | |
995 | 307k | mp_assert(size > 0); |
996 | | |
997 | 307k | struct mp_log_buffer *buffer = talloc_ptrtype(NULL, buffer); |
998 | 307k | *buffer = (struct mp_log_buffer) { |
999 | 307k | .root = root, |
1000 | 307k | .level = level, |
1001 | 307k | .entries = talloc_array(buffer, struct mp_log_buffer_entry *, size), |
1002 | 307k | .capacity = size, |
1003 | 307k | .wakeup_cb = wakeup_cb, |
1004 | 307k | .wakeup_cb_ctx = wakeup_cb_ctx, |
1005 | 307k | }; |
1006 | | |
1007 | 307k | mp_mutex_init(&buffer->lock); |
1008 | | |
1009 | 307k | MP_TARRAY_APPEND(root, root->buffers, root->num_buffers, buffer); |
1010 | | |
1011 | 307k | atomic_fetch_add(&root->reload_counter, 1); |
1012 | 307k | mp_mutex_unlock(&root->lock); |
1013 | | |
1014 | 307k | return buffer; |
1015 | 307k | } |
1016 | | |
1017 | | void mp_msg_log_buffer_resize(struct mp_log_buffer *buffer, int size) |
1018 | 0 | { |
1019 | 0 | mp_mutex_lock(&buffer->lock); |
1020 | |
|
1021 | 0 | mp_assert(size > 0); |
1022 | 0 | if (buffer->capacity < size && |
1023 | 0 | buffer->entry0 + buffer->num_entries <= buffer->capacity) { |
1024 | | // shortcut if buffer doesn't wrap |
1025 | 0 | buffer->entries = talloc_realloc(buffer, buffer->entries, |
1026 | 0 | struct mp_log_buffer_entry *, size); |
1027 | 0 | } else if (buffer->capacity != size) { |
1028 | 0 | struct mp_log_buffer_entry **entries = |
1029 | 0 | talloc_array(buffer, struct mp_log_buffer_entry *, size); |
1030 | 0 | int num_entries = 0; |
1031 | 0 | for (int i = buffer->num_entries - 1; i >= 0; i--) { |
1032 | 0 | int entry = (buffer->entry0 + i) % buffer->num_entries; |
1033 | 0 | struct mp_log_buffer_entry *res = buffer->entries[entry]; |
1034 | 0 | if (num_entries < size) { |
1035 | 0 | entries[num_entries++] = res; |
1036 | 0 | } else { |
1037 | 0 | talloc_free(res); |
1038 | 0 | buffer->dropped += 1; |
1039 | 0 | } |
1040 | 0 | } |
1041 | 0 | talloc_free(buffer->entries); |
1042 | 0 | buffer->entries = entries; |
1043 | 0 | buffer->entry0 = 0; |
1044 | 0 | buffer->num_entries = num_entries; |
1045 | 0 | } |
1046 | 0 | buffer->capacity = size; |
1047 | |
|
1048 | 0 | mp_mutex_unlock(&buffer->lock); |
1049 | 0 | } |
1050 | | |
1051 | | void mp_msg_log_buffer_set_silent(struct mp_log_buffer *buffer, bool silent) |
1052 | 0 | { |
1053 | 0 | mp_mutex_lock(&buffer->lock); |
1054 | 0 | buffer->silent = silent; |
1055 | 0 | mp_mutex_unlock(&buffer->lock); |
1056 | 0 | } |
1057 | | |
1058 | | void mp_msg_log_buffer_destroy(struct mp_log_buffer *buffer) |
1059 | 961k | { |
1060 | 961k | if (!buffer) |
1061 | 653k | return; |
1062 | | |
1063 | 307k | struct mp_log_root *root = buffer->root; |
1064 | | |
1065 | 307k | mp_mutex_lock(&root->lock); |
1066 | | |
1067 | 307k | for (int n = 0; n < root->num_buffers; n++) { |
1068 | 307k | if (root->buffers[n] == buffer) { |
1069 | 307k | MP_TARRAY_REMOVE_AT(root->buffers, root->num_buffers, n); |
1070 | 307k | goto found; |
1071 | 307k | } |
1072 | 307k | } |
1073 | | |
1074 | 0 | MP_ASSERT_UNREACHABLE(); |
1075 | | |
1076 | 307k | found: |
1077 | | |
1078 | 46.9M | while (buffer->num_entries) |
1079 | 46.6M | talloc_free(log_buffer_read(buffer)); |
1080 | | |
1081 | 307k | mp_mutex_destroy(&buffer->lock); |
1082 | 307k | talloc_free(buffer); |
1083 | | |
1084 | 307k | atomic_fetch_add(&root->reload_counter, 1); |
1085 | 307k | mp_mutex_unlock(&root->lock); |
1086 | 307k | } |
1087 | | |
1088 | | // Return a queued message, or if the buffer is empty, NULL. |
1089 | | // Thread-safety: one buffer can be read by a single thread only. |
1090 | | struct mp_log_buffer_entry *mp_msg_log_buffer_read(struct mp_log_buffer *buffer) |
1091 | 0 | { |
1092 | 0 | struct mp_log_buffer_entry *res = NULL; |
1093 | |
|
1094 | 0 | mp_mutex_lock(&buffer->lock); |
1095 | |
|
1096 | 0 | if (!buffer->silent && buffer->num_entries) { |
1097 | 0 | if (buffer->dropped) { |
1098 | 0 | res = talloc_ptrtype(NULL, res); |
1099 | 0 | *res = (struct mp_log_buffer_entry) { |
1100 | 0 | .prefix = "overflow", |
1101 | 0 | .level = MSGL_FATAL, |
1102 | 0 | .text = talloc_asprintf(res, |
1103 | 0 | "log message buffer overflow: %"PRId64" messages skipped\n", |
1104 | 0 | buffer->dropped), |
1105 | 0 | }; |
1106 | 0 | buffer->dropped = 0; |
1107 | 0 | } else { |
1108 | 0 | res = log_buffer_read(buffer); |
1109 | 0 | } |
1110 | 0 | } |
1111 | |
|
1112 | 0 | mp_mutex_unlock(&buffer->lock); |
1113 | |
|
1114 | 0 | return res; |
1115 | 0 | } |
1116 | | |
1117 | | // Thread-safety: fully thread-safe, but keep in mind that the lifetime of |
1118 | | // log must be guaranteed during the call. |
1119 | | // Never call this from signal handlers. |
1120 | | void mp_msg(struct mp_log *log, int lev, const char *format, ...) |
1121 | 150M | { |
1122 | 150M | va_list va; |
1123 | 150M | va_start(va, format); |
1124 | 150M | mp_msg_va(log, lev, format, va); |
1125 | 150M | va_end(va); |
1126 | 150M | } |
1127 | | |
1128 | | const char *const mp_log_levels[MSGL_MAX + 1] = { |
1129 | | [MSGL_FATAL] = "fatal", |
1130 | | [MSGL_ERR] = "error", |
1131 | | [MSGL_WARN] = "warn", |
1132 | | [MSGL_INFO] = "info", |
1133 | | [MSGL_STATUS] = "status", |
1134 | | [MSGL_V] = "v", |
1135 | | [MSGL_DEBUG] = "debug", |
1136 | | [MSGL_TRACE] = "trace", |
1137 | | [MSGL_STATS] = "stats", |
1138 | | }; |
1139 | | |
1140 | | const int mp_mpv_log_levels[MSGL_MAX + 1] = { |
1141 | | [MSGL_FATAL] = MPV_LOG_LEVEL_FATAL, |
1142 | | [MSGL_ERR] = MPV_LOG_LEVEL_ERROR, |
1143 | | [MSGL_WARN] = MPV_LOG_LEVEL_WARN, |
1144 | | [MSGL_INFO] = MPV_LOG_LEVEL_INFO, |
1145 | | [MSGL_STATUS] = 0, // never used |
1146 | | [MSGL_V] = MPV_LOG_LEVEL_V, |
1147 | | [MSGL_DEBUG] = MPV_LOG_LEVEL_DEBUG, |
1148 | | [MSGL_TRACE] = MPV_LOG_LEVEL_TRACE, |
1149 | | [MSGL_STATS] = 0, // never used |
1150 | | }; |
1151 | | |
1152 | | int mp_msg_find_level(const char *s) |
1153 | 2.56M | { |
1154 | 20.5M | for (int n = 0; n < MP_ARRAY_SIZE(mp_log_levels); n++) { |
1155 | 20.5M | if (mp_log_levels[n] && !strcasecmp(s, mp_log_levels[n])) |
1156 | 2.56M | return n; |
1157 | 20.5M | } |
1158 | 2.87k | return -1; |
1159 | 2.56M | } |