2010-01-30 23:24:23 +00:00
|
|
|
/*
|
2015-04-13 07:36:54 +00:00
|
|
|
* This file is part of mpv.
|
2010-01-30 23:24:23 +00:00
|
|
|
*
|
2017-09-21 11:50:18 +00:00
|
|
|
* mpv is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2010-01-30 23:24:23 +00:00
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is distributed in the hope that it will be useful,
|
2010-01-30 23:24:23 +00:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2017-09-21 11:50:18 +00:00
|
|
|
* GNU Lesser General Public License for more details.
|
2010-01-30 23:24:23 +00:00
|
|
|
*
|
2017-09-21 11:50:18 +00:00
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2010-01-30 23:24:23 +00:00
|
|
|
*/
|
|
|
|
|
2023-10-19 14:26:26 +00:00
|
|
|
#include <assert.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdatomic.h>
|
|
|
|
#include <stdint.h>
|
2001-08-16 22:13:20 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2006-03-27 08:38:32 +00:00
|
|
|
#include <string.h>
|
2013-07-31 19:40:30 +00:00
|
|
|
|
2016-01-11 18:03:40 +00:00
|
|
|
#include "mpv_talloc.h"
|
2001-08-16 22:13:20 +00:00
|
|
|
|
2013-12-18 18:04:30 +00:00
|
|
|
#include "common/common.h"
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/global.h"
|
2024-09-21 14:32:27 +00:00
|
|
|
#include "misc/codepoint_width.h"
|
2013-12-18 18:04:30 +00:00
|
|
|
#include "options/options.h"
|
2016-09-28 13:04:30 +00:00
|
|
|
#include "options/path.h"
|
2011-10-22 14:24:16 +00:00
|
|
|
#include "osdep/io.h"
|
2024-09-21 14:32:27 +00:00
|
|
|
#include "osdep/terminal.h"
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
#include "osdep/threads.h"
|
2014-02-28 21:45:34 +00:00
|
|
|
#include "osdep/timer.h"
|
2001-09-20 10:27:24 +00:00
|
|
|
|
2014-10-08 12:15:14 +00:00
|
|
|
#include "libmpv/client.h"
|
|
|
|
|
2014-01-16 20:24:39 +00:00
|
|
|
#include "msg.h"
|
|
|
|
#include "msg_control.h"
|
2001-08-16 22:13:20 +00:00
|
|
|
|
2024-01-26 10:28:37 +00:00
|
|
|
// log buffer size (lines) logfile level
|
2023-01-23 08:56:12 +00:00
|
|
|
#define FILE_BUF 100
|
2019-11-17 23:44:12 +00:00
|
|
|
|
2024-01-26 10:28:37 +00:00
|
|
|
// lines to accumulate before any client requests the terminal loglevel
|
|
|
|
#define EARLY_TERM_BUF 100
|
|
|
|
|
2022-12-15 16:09:03 +00:00
|
|
|
// logfile lines to accumulate during init before we know the log file name.
|
|
|
|
// thousands of logfile lines during init can happen (especially with many
|
|
|
|
// scripts, big config, etc), so we set 5000. If it cycles and messages are
|
|
|
|
// overwritten, then the first (virtual) log line indicates how many were lost.
|
|
|
|
#define EARLY_FILE_BUF 5000
|
|
|
|
|
2013-07-31 19:40:30 +00:00
|
|
|
struct mp_log_root {
|
|
|
|
struct mpv_global *global;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex lock;
|
|
|
|
mp_mutex log_file_lock;
|
|
|
|
mp_cond log_file_wakeup;
|
2020-01-30 13:16:20 +00:00
|
|
|
// --- protected by lock
|
2015-02-06 15:48:52 +00:00
|
|
|
char **msg_levels;
|
2014-02-06 15:49:50 +00:00
|
|
|
bool use_terminal; // make accesses to stderr/stdout
|
2013-12-21 22:11:12 +00:00
|
|
|
bool module;
|
2014-02-28 21:45:34 +00:00
|
|
|
bool show_time;
|
2015-06-17 20:23:08 +00:00
|
|
|
int blank_lines; // number of lines usable by status
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
int status_lines; // number of current status lines
|
2023-10-18 19:13:08 +00:00
|
|
|
bool color[STDERR_FILENO + 1];
|
|
|
|
bool isatty[STDERR_FILENO + 1];
|
2013-12-21 22:11:12 +00:00
|
|
|
int verbose;
|
2017-06-23 18:42:20 +00:00
|
|
|
bool really_quiet;
|
2013-12-21 22:11:12 +00:00
|
|
|
bool force_stderr;
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
struct mp_log_buffer **buffers;
|
|
|
|
int num_buffers;
|
2019-11-17 23:44:12 +00:00
|
|
|
struct mp_log_buffer *early_buffer;
|
2022-12-15 16:09:03 +00:00
|
|
|
struct mp_log_buffer *early_filebuffer;
|
2014-04-17 19:47:00 +00:00
|
|
|
FILE *stats_file;
|
2020-01-30 13:16:20 +00:00
|
|
|
bstr buffer;
|
2023-10-16 17:58:53 +00:00
|
|
|
bstr term_msg;
|
2023-10-19 01:55:33 +00:00
|
|
|
bstr term_msg_tmp;
|
2023-10-19 02:27:14 +00:00
|
|
|
bstr status_line;
|
|
|
|
struct mp_log *status_log;
|
|
|
|
bstr term_status_msg;
|
2013-12-21 22:11:12 +00:00
|
|
|
// --- must be accessed atomically
|
2013-12-18 18:04:30 +00:00
|
|
|
/* This is incremented every time the msglevels must be reloaded.
|
|
|
|
* (This is perhaps better than maintaining a globally accessible and
|
|
|
|
* synchronized mp_log tree.) */
|
2014-05-20 23:04:47 +00:00
|
|
|
atomic_ulong reload_counter;
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
// --- owner thread only (caller of mp_msg_init() etc.)
|
|
|
|
char *log_path;
|
|
|
|
char *stats_path;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_thread log_file_thread;
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
// --- owner thread only, but frozen while log_file_thread is running
|
|
|
|
FILE *log_file;
|
|
|
|
struct mp_log_buffer *log_file_buffer;
|
|
|
|
// --- protected by log_file_lock
|
|
|
|
bool log_file_thread_active; // also termination signal for the thread
|
2023-10-16 17:58:53 +00:00
|
|
|
int module_indent;
|
2013-07-31 19:40:30 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct mp_log {
|
|
|
|
struct mp_log_root *root;
|
|
|
|
const char *prefix;
|
|
|
|
const char *verbose_prefix;
|
2020-05-10 14:40:26 +00:00
|
|
|
int max_level; // minimum log level for this instance
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
int level; // minimum log level for any outputs
|
|
|
|
int terminal_level; // minimum log level for terminal output
|
2014-05-20 23:04:47 +00:00
|
|
|
atomic_ulong reload_counter;
|
2023-10-18 23:40:30 +00:00
|
|
|
bstr partial[MSGL_MAX + 1];
|
2013-07-31 19:40:30 +00:00
|
|
|
};
|
|
|
|
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
struct mp_log_buffer {
|
|
|
|
struct mp_log_root *root;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex lock;
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
// --- protected by lock
|
2019-11-22 00:08:20 +00:00
|
|
|
struct mp_log_buffer_entry **entries; // ringbuffer
|
|
|
|
int capacity; // total space in entries[]
|
|
|
|
int entry0; // first (oldest) entry index
|
|
|
|
int num_entries; // number of valid entries after entry0
|
|
|
|
uint64_t dropped; // number of skipped entries
|
|
|
|
bool silent;
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
// --- immutable
|
2014-06-06 17:24:30 +00:00
|
|
|
void (*wakeup_cb)(void *ctx);
|
|
|
|
void *wakeup_cb_ctx;
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
int level;
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
};
|
|
|
|
|
2013-12-18 18:04:30 +00:00
|
|
|
static const struct mp_log null_log = {0};
|
|
|
|
struct mp_log *const mp_null_log = (struct mp_log *)&null_log;
|
|
|
|
|
2015-02-06 15:48:52 +00:00
|
|
|
static bool match_mod(const char *name, const char *mod)
|
2013-12-18 18:04:30 +00:00
|
|
|
{
|
2015-02-06 15:48:52 +00:00
|
|
|
if (!strcmp(mod, "all"))
|
2013-12-18 18:04:30 +00:00
|
|
|
return true;
|
|
|
|
// Path prefix matches
|
|
|
|
bstr b = bstr0(name);
|
2015-02-06 15:48:52 +00:00
|
|
|
return bstr_eatstart0(&b, mod) && (bstr_eatstart0(&b, "/") || !b.len);
|
2013-12-18 18:04:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void update_loglevel(struct mp_log *log)
|
2002-08-21 21:31:20 +00:00
|
|
|
{
|
2015-02-06 15:48:52 +00:00
|
|
|
struct mp_log_root *root = log->root;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
2017-06-23 19:04:41 +00:00
|
|
|
log->level = MSGL_STATUS + root->verbose; // default log level
|
2021-02-21 13:11:22 +00:00
|
|
|
if (root->really_quiet)
|
|
|
|
log->level = -1;
|
2015-06-20 19:40:47 +00:00
|
|
|
for (int n = 0; root->msg_levels && root->msg_levels[n * 2 + 0]; n++) {
|
|
|
|
if (match_mod(log->verbose_prefix, root->msg_levels[n * 2 + 0]))
|
|
|
|
log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]);
|
2013-12-18 18:04:30 +00:00
|
|
|
}
|
2015-06-20 19:40:47 +00:00
|
|
|
log->terminal_level = log->level;
|
msg: fix "terminal-default" logging mode
console.lua uses "terminal-default" logging, which is supposed to return
all messages logged to the terminal to the API. Internally, this is
translated to MP_LOG_BUFFER_MSGL_TERM, which is MSGL_MAX+1, because it's
not an actual log level (blame C for not having proper sum types or
something).
Unfortunately, this unintentionally raised the internal log level to
MSGL_MAX+1. It still functioned as intended, because log messages were
simply filtered at a "later" point. But it led to every message being
formatted even if not needed. More importantly, it made mp_msg_test()
pointless (code calls this to avoid logging in "expensive" cases and if
the messages would just get discarded). Also, this broke libplacebo
logging, because the code to map the log messages did not expect a level
higher than MSGL_MAX (mp_msg_level() returned MSGL_MAX+1 too).
Fix this by not letting the dummy level value be used as log level.
Messages at terminal log level will always make it to the inner log
message dispatcher function (i.e. mp_msg_va() will call
write_msg_to_buffers()), so log buffers which use the dummy log level
don't need to adjust the actual log level at all.
2019-12-16 20:31:54 +00:00
|
|
|
for (int n = 0; n < log->root->num_buffers; n++) {
|
|
|
|
int buffer_level = log->root->buffers[n]->level;
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE)
|
|
|
|
buffer_level = MSGL_DEBUG;
|
msg: fix "terminal-default" logging mode
console.lua uses "terminal-default" logging, which is supposed to return
all messages logged to the terminal to the API. Internally, this is
translated to MP_LOG_BUFFER_MSGL_TERM, which is MSGL_MAX+1, because it's
not an actual log level (blame C for not having proper sum types or
something).
Unfortunately, this unintentionally raised the internal log level to
MSGL_MAX+1. It still functioned as intended, because log messages were
simply filtered at a "later" point. But it led to every message being
formatted even if not needed. More importantly, it made mp_msg_test()
pointless (code calls this to avoid logging in "expensive" cases and if
the messages would just get discarded). Also, this broke libplacebo
logging, because the code to map the log messages did not expect a level
higher than MSGL_MAX (mp_msg_level() returned MSGL_MAX+1 too).
Fix this by not letting the dummy level value be used as log level.
Messages at terminal log level will always make it to the inner log
message dispatcher function (i.e. mp_msg_va() will call
write_msg_to_buffers()), so log buffers which use the dummy log level
don't need to adjust the actual log level at all.
2019-12-16 20:31:54 +00:00
|
|
|
if (buffer_level != MP_LOG_BUFFER_MSGL_TERM)
|
|
|
|
log->level = MPMAX(log->level, buffer_level);
|
|
|
|
}
|
2015-01-26 10:31:02 +00:00
|
|
|
if (log->root->log_file)
|
2017-09-28 10:28:48 +00:00
|
|
|
log->level = MPMAX(log->level, MSGL_DEBUG);
|
2014-04-17 19:47:00 +00:00
|
|
|
if (log->root->stats_file)
|
|
|
|
log->level = MPMAX(log->level, MSGL_STATS);
|
2020-05-10 14:40:26 +00:00
|
|
|
log->level = MPMIN(log->level, log->max_level);
|
2014-05-20 23:04:47 +00:00
|
|
|
atomic_store(&log->reload_counter, atomic_load(&log->root->reload_counter));
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
2013-12-18 18:04:30 +00:00
|
|
|
}
|
|
|
|
|
2020-05-10 14:40:26 +00:00
|
|
|
// Set (numerically) the maximum level that should still be output for this log
|
|
|
|
// instances. E.g. lev=MSGL_WARN => show only warnings and errors.
|
|
|
|
void mp_msg_set_max_level(struct mp_log *log, int lev)
|
|
|
|
{
|
|
|
|
if (!log->root)
|
|
|
|
return;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&log->root->lock);
|
2020-05-10 14:40:26 +00:00
|
|
|
log->max_level = MPCLAMP(lev, -1, MSGL_MAX);
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&log->root->lock);
|
2020-05-10 14:40:26 +00:00
|
|
|
update_loglevel(log);
|
|
|
|
}
|
|
|
|
|
2018-11-10 11:53:33 +00:00
|
|
|
// Get the current effective msg level.
|
2013-12-22 11:29:16 +00:00
|
|
|
// Thread-safety: see mp_msg().
|
2018-11-10 11:53:33 +00:00
|
|
|
int mp_msg_level(struct mp_log *log)
|
2013-12-18 18:04:30 +00:00
|
|
|
{
|
2014-05-20 23:04:47 +00:00
|
|
|
struct mp_log_root *root = log->root;
|
2015-04-23 19:08:19 +00:00
|
|
|
if (!root)
|
2018-11-10 11:53:33 +00:00
|
|
|
return -1;
|
2015-03-02 18:09:31 +00:00
|
|
|
if (atomic_load_explicit(&log->reload_counter, memory_order_relaxed) !=
|
|
|
|
atomic_load_explicit(&root->reload_counter, memory_order_relaxed))
|
|
|
|
{
|
2013-12-18 18:04:30 +00:00
|
|
|
update_loglevel(log);
|
2015-03-02 18:09:31 +00:00
|
|
|
}
|
2018-11-10 11:53:33 +00:00
|
|
|
return log->level;
|
2013-12-18 15:55:10 +00:00
|
|
|
}
|
|
|
|
|
2023-10-18 19:13:08 +00:00
|
|
|
static inline int term_msg_fileno(struct mp_log_root *root, int lev)
|
|
|
|
{
|
2024-05-05 14:38:02 +00:00
|
|
|
return root->force_stderr ? STDERR_FILENO : STDOUT_FILENO;
|
2023-10-18 19:13:08 +00:00
|
|
|
}
|
|
|
|
|
2024-05-13 19:49:17 +00:00
|
|
|
static inline FILE *term_msg_fp(struct mp_log_root *root, int lev)
|
|
|
|
{
|
|
|
|
return term_msg_fileno(root, lev) == STDERR_FILENO ? stderr : stdout;
|
|
|
|
}
|
|
|
|
|
2024-05-13 22:45:36 +00:00
|
|
|
static inline bool is_status_output(struct mp_log_root *root, int lev)
|
|
|
|
{
|
|
|
|
if (lev == MSGL_STATUS)
|
|
|
|
return true;
|
|
|
|
int msg_out = term_msg_fileno(root, lev);
|
|
|
|
int status_out = term_msg_fileno(root, MSGL_STATUS);
|
|
|
|
if (msg_out != status_out && root->isatty[msg_out] != root->isatty[status_out])
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
// Reposition cursor and clear lines for outputting the status line. In certain
|
|
|
|
// cases, like term OSD and subtitle display, the status can consist of
|
|
|
|
// multiple lines.
|
2023-10-16 17:58:53 +00:00
|
|
|
static void prepare_prefix(struct mp_log_root *root, bstr *out, int lev, int term_lines)
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
{
|
2023-10-16 17:58:53 +00:00
|
|
|
int new_lines = lev == MSGL_STATUS ? term_lines : 0;
|
|
|
|
out->len = 0;
|
|
|
|
|
2024-05-13 22:45:36 +00:00
|
|
|
if (!is_status_output(root, lev))
|
|
|
|
return;
|
|
|
|
|
2023-10-18 19:13:08 +00:00
|
|
|
if (!root->isatty[term_msg_fileno(root, lev)]) {
|
|
|
|
if (root->status_lines)
|
|
|
|
bstr_xappend(root, out, bstr0("\n"));
|
|
|
|
root->status_lines = new_lines;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
// Set cursor state
|
|
|
|
if (new_lines && !root->status_lines) {
|
2024-03-23 03:01:02 +00:00
|
|
|
bstr_xappend(root, out, bstr0(TERM_ESC_HIDE_CURSOR));
|
2023-10-16 17:58:53 +00:00
|
|
|
} else if (!new_lines && root->status_lines) {
|
2024-03-23 03:01:02 +00:00
|
|
|
bstr_xappend(root, out, bstr0(TERM_ESC_RESTORE_CURSOR));
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
}
|
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
int line_skip = 0;
|
|
|
|
if (root->status_lines) {
|
|
|
|
// Clear previous status line
|
|
|
|
bstr_xappend(root, out, bstr0("\033[1K\r"));
|
|
|
|
bstr up_clear = bstr0("\033[A\033[K");
|
|
|
|
for (int i = 1; i < root->status_lines; ++i)
|
|
|
|
bstr_xappend(root, out, up_clear);
|
2024-03-15 08:52:04 +00:00
|
|
|
assert(root->status_lines > 0 && root->blank_lines >= root->status_lines);
|
|
|
|
line_skip = root->blank_lines - root->status_lines;
|
2023-10-16 17:58:53 +00:00
|
|
|
}
|
2014-01-14 16:37:55 +00:00
|
|
|
|
2024-03-15 08:52:04 +00:00
|
|
|
if (new_lines)
|
|
|
|
line_skip -= MPMAX(0, root->blank_lines - new_lines);
|
|
|
|
|
|
|
|
if (line_skip)
|
|
|
|
bstr_xappend_asprintf(root, out, line_skip > 0 ? "\033[%dA" : "\033[%dB", abs(line_skip));
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
root->blank_lines = MPMAX(0, root->blank_lines - term_lines);
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
root->status_lines = new_lines;
|
2023-10-16 17:58:53 +00:00
|
|
|
root->blank_lines += root->status_lines;
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
}
|
|
|
|
|
2024-05-13 20:13:41 +00:00
|
|
|
static void msg_flush_status_line(struct mp_log_root *root, bool clear)
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
{
|
2024-05-13 20:13:41 +00:00
|
|
|
if (!root->status_lines)
|
2023-12-15 18:40:43 +00:00
|
|
|
goto done;
|
|
|
|
|
2024-05-13 20:13:41 +00:00
|
|
|
FILE *fp = term_msg_fp(root, MSGL_STATUS);
|
2023-12-15 18:40:43 +00:00
|
|
|
if (!clear) {
|
2024-05-13 20:13:41 +00:00
|
|
|
if (root->isatty[term_msg_fileno(root, MSGL_STATUS)])
|
2024-05-13 19:49:17 +00:00
|
|
|
fprintf(fp, TERM_ESC_RESTORE_CURSOR);
|
|
|
|
fprintf(fp, "\n");
|
2024-05-13 20:13:41 +00:00
|
|
|
root->blank_lines = 0;
|
|
|
|
root->status_lines = 0;
|
2023-12-15 18:40:43 +00:00
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
bstr term_msg = {0};
|
2024-05-13 20:13:41 +00:00
|
|
|
prepare_prefix(root, &term_msg, MSGL_STATUS, 0);
|
2023-12-15 18:40:43 +00:00
|
|
|
if (term_msg.len) {
|
2024-05-13 19:49:17 +00:00
|
|
|
fprintf(fp, "%.*s", BSTR_P(term_msg));
|
2023-12-15 18:40:43 +00:00
|
|
|
talloc_free(term_msg.start);
|
2020-01-30 13:16:20 +00:00
|
|
|
}
|
2023-12-15 18:40:43 +00:00
|
|
|
|
|
|
|
done:
|
2024-05-13 20:13:41 +00:00
|
|
|
root->status_line.len = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mp_msg_flush_status_line(struct mp_log *log, bool clear)
|
|
|
|
{
|
|
|
|
if (!log->root)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mp_mutex_lock(&log->root->lock);
|
|
|
|
msg_flush_status_line(log->root, clear);
|
2023-12-15 18:40:43 +00:00
|
|
|
mp_mutex_unlock(&log->root->lock);
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
}
|
|
|
|
|
2020-05-25 18:37:37 +00:00
|
|
|
void mp_msg_set_term_title(struct mp_log *log, const char *title)
|
|
|
|
{
|
|
|
|
if (log->root && title) {
|
|
|
|
// Lock because printf to terminal is not necessarily atomic.
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&log->root->lock);
|
2024-05-13 19:49:17 +00:00
|
|
|
fprintf(term_msg_fp(log->root, MSGL_STATUS), "\033]0;%s\007", title);
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&log->root->lock);
|
2020-05-25 18:37:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
bool mp_msg_has_status_line(struct mpv_global *global)
|
|
|
|
{
|
2020-01-30 13:16:20 +00:00
|
|
|
struct mp_log_root *root = global->log->root;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
2020-01-30 13:16:20 +00:00
|
|
|
bool r = root->status_lines > 0;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line,
showing player OSD messages on the terminal, and showing subtitles on
terminal (the latter two only if there is no video window, or if
terminal OSD is forced).
This didn't handle some corner cases correctly. For example, showing an
OSD message on the terminal always cleared the previous line, even if
the line was an important message (or even just the command prompt, if
most other messages were silenced).
Attempt to handle this correctly by keeping track of how many lines the
terminal OSD currently consists of. Since there could be race conditions
with other messages being printed, implement this in msg.c. Now msg.c
expects that MSGL_STATUS messages rewrite the status line, so the caller
is forced to use a single mp_msg() call to set the status line.
Instead of littering print_status() all over the place, update the
status only once per playloop iteration in update_osd_msg(). In audio-
only mode, the status line might now be a little bit off, but it's
perhaps ok.
Print the status line only if it has changed, or if another message was
printed. This might help with extremely slow terminals, although in
audio+video mode, it'll still be updated very often (A-V sync display
changes on every frame).
Instead of hardcoding the terminal sequences, use
terminfo/termcap to get the sequences. Remove the --term-osd-esc option,
which allowed to override the hardcoded escapes - it's useless now.
The fallback for terminals with no escape sequences for moving the
cursor and clearing a line is removed. This somewhat breaks status line
display on these terminals, including the MS Windows console: instead of
querying the terminal size and clearing the line manually by padding the
output with spaces, the line is simply not cleared. I don't expect this
to be a problem on UNIX, and on MS Windows we could emulate escape
sequences. Note that terminal OSD (other than the status line) was
broken anyway on these terminals.
In osd.c, the function get_term_width() is not used anymore, so remove
it. To remind us that the MS Windows console apparently adds a line
break when writint the last column, adjust screen_width in terminal-
win.c accordingly.
2014-01-13 19:05:41 +00:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
static void set_term_color(void *talloc_ctx, bstr *text, int c)
|
2014-08-21 20:11:38 +00:00
|
|
|
{
|
2024-03-08 07:22:47 +00:00
|
|
|
if (c == -1) {
|
|
|
|
bstr_xappend(talloc_ctx, text, bstr0("\033[0m"));
|
|
|
|
return;
|
|
|
|
}
|
2024-09-13 19:06:42 +00:00
|
|
|
// Pure black to gray
|
|
|
|
if (c == 0)
|
|
|
|
c += 8;
|
|
|
|
// Pure white to light one
|
|
|
|
if (c == 15)
|
|
|
|
c -= 8;
|
|
|
|
bstr_xappend_asprintf(talloc_ctx, text, "\033[%d%dm", c >= 8 ? 9 : 3, c & 7);
|
2014-08-21 20:11:38 +00:00
|
|
|
}
|
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
static void set_msg_color(void *talloc_ctx, bstr *text, int lev)
|
2008-04-12 12:51:07 +00:00
|
|
|
{
|
2024-09-13 19:06:42 +00:00
|
|
|
static const int v_colors[] = {
|
|
|
|
[MSGL_FATAL] = 9, // bright red
|
|
|
|
[MSGL_ERR] = 1, // red
|
|
|
|
[MSGL_WARN] = 3, // yellow
|
|
|
|
[MSGL_INFO] = -1, // default
|
|
|
|
[MSGL_STATUS] = -1, // default
|
|
|
|
[MSGL_V] = 2, // green
|
|
|
|
[MSGL_DEBUG] = 4, // blue
|
|
|
|
[MSGL_TRACE] = 8, // bright black aka. gray
|
|
|
|
};
|
2024-03-08 07:22:47 +00:00
|
|
|
set_term_color(talloc_ctx, text, v_colors[lev]);
|
2008-04-12 12:51:07 +00:00
|
|
|
}
|
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
static void pretty_print_module(struct mp_log_root *root, bstr *text,
|
|
|
|
const char *prefix, int lev)
|
2014-04-10 08:01:38 +00:00
|
|
|
{
|
2023-10-16 17:58:53 +00:00
|
|
|
size_t prefix_len = strlen(prefix);
|
|
|
|
root->module_indent = MPMAX(10, MPMAX(root->module_indent, prefix_len));
|
2023-10-18 19:13:08 +00:00
|
|
|
bool color = root->color[term_msg_fileno(root, lev)];
|
2023-10-16 17:58:53 +00:00
|
|
|
|
2014-04-10 08:01:38 +00:00
|
|
|
// Use random color based on the name of the module
|
2023-10-18 19:13:08 +00:00
|
|
|
if (color) {
|
2014-04-10 08:01:38 +00:00
|
|
|
unsigned int mod = 0;
|
|
|
|
for (int i = 0; i < prefix_len; ++i)
|
|
|
|
mod = mod * 33 + prefix[i];
|
2023-10-16 17:58:53 +00:00
|
|
|
set_term_color(root, text, (mod + 1) % 15 + 1);
|
2014-04-10 08:01:38 +00:00
|
|
|
}
|
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
bstr_xappend_asprintf(root, text, "%*s", root->module_indent, prefix);
|
2023-10-18 19:13:08 +00:00
|
|
|
if (color)
|
2023-10-16 17:58:53 +00:00
|
|
|
set_term_color(root, text, -1);
|
|
|
|
bstr_xappend(root, text, bstr0(": "));
|
2023-10-18 19:13:08 +00:00
|
|
|
if (color)
|
2023-10-16 17:58:53 +00:00
|
|
|
set_msg_color(root, text, lev);
|
2014-04-10 08:01:38 +00:00
|
|
|
}
|
|
|
|
|
2014-10-08 11:11:55 +00:00
|
|
|
static bool test_terminal_level(struct mp_log *log, int lev)
|
2009-07-25 04:24:39 +00:00
|
|
|
{
|
2015-06-20 19:40:47 +00:00
|
|
|
return lev <= log->terminal_level && log->root->use_terminal &&
|
2014-10-08 11:11:55 +00:00
|
|
|
!(lev == MSGL_STATUS && terminal_in_background());
|
|
|
|
}
|
2013-12-20 20:07:10 +00:00
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
static void append_terminal_line(struct mp_log *log, int lev,
|
2024-09-23 00:13:17 +00:00
|
|
|
bstr text, bstr *term_msg, int *line_w, int term_w)
|
2023-10-16 17:58:53 +00:00
|
|
|
{
|
|
|
|
struct mp_log_root *root = log->root;
|
2014-04-10 08:01:38 +00:00
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
size_t start = term_msg->len;
|
2014-01-16 20:23:27 +00:00
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
if (root->show_time)
|
|
|
|
bstr_xappend_asprintf(root, term_msg, "[%10.6f] ", mp_time_sec());
|
|
|
|
|
|
|
|
const char *log_prefix = (lev >= MSGL_V) || root->verbose || root->module
|
|
|
|
? log->verbose_prefix : log->prefix;
|
|
|
|
if (log_prefix) {
|
2014-10-08 11:11:55 +00:00
|
|
|
if (root->module) {
|
2023-10-16 17:58:53 +00:00
|
|
|
pretty_print_module(root, term_msg, log_prefix, lev);
|
2014-10-08 11:11:55 +00:00
|
|
|
} else {
|
2023-10-16 17:58:53 +00:00
|
|
|
bstr_xappend_asprintf(root, term_msg, "[%s] ", log_prefix);
|
2014-10-08 11:11:55 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-16 20:23:27 +00:00
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
bstr_xappend(root, term_msg, text);
|
2024-09-23 00:13:17 +00:00
|
|
|
|
|
|
|
const unsigned char *cut_pos = NULL;
|
2024-10-18 16:08:06 +00:00
|
|
|
int ellipsis_width = 2;
|
2024-09-23 00:13:17 +00:00
|
|
|
int width = term_disp_width(bstr_splice(*term_msg, start, term_msg->len),
|
2024-10-17 22:18:50 +00:00
|
|
|
term_w - ellipsis_width, &cut_pos);
|
2024-09-23 00:13:17 +00:00
|
|
|
if (cut_pos) {
|
|
|
|
int new_len = cut_pos - term_msg->start;
|
2024-10-20 18:40:02 +00:00
|
|
|
bstr rem = bstrdup(NULL, (bstr){(unsigned char *)cut_pos, term_msg->len - new_len});
|
|
|
|
void *ptr = rem.start;
|
2024-09-23 00:13:17 +00:00
|
|
|
term_msg->len = new_len;
|
|
|
|
|
2024-10-18 16:08:06 +00:00
|
|
|
bstr_xappend(root, term_msg, bstr0(".."));
|
2024-09-23 00:13:17 +00:00
|
|
|
|
|
|
|
while (rem.len) {
|
2024-10-20 18:40:02 +00:00
|
|
|
if (bstr_eatstart0(&rem, "\n")) {
|
|
|
|
bstr_xappend(root, term_msg, bstr0("\n"));
|
|
|
|
continue;
|
|
|
|
}
|
2024-09-23 00:13:17 +00:00
|
|
|
if (bstr_eatstart0(&rem, "\033[")) {
|
|
|
|
bstr_xappend(root, term_msg, bstr0("\033["));
|
|
|
|
|
|
|
|
while (rem.len && !((*rem.start >= '@' && *rem.start <= '~') || *rem.start == 'm')) {
|
|
|
|
bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1));
|
|
|
|
rem = bstr_cut(rem, 1);
|
|
|
|
}
|
|
|
|
bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1));
|
|
|
|
}
|
|
|
|
rem = bstr_cut(rem, 1);
|
|
|
|
}
|
2024-10-20 18:40:02 +00:00
|
|
|
talloc_free(ptr);
|
2024-09-23 00:13:17 +00:00
|
|
|
|
2024-10-17 22:18:50 +00:00
|
|
|
width += ellipsis_width;
|
2024-09-23 00:13:17 +00:00
|
|
|
}
|
2024-10-20 18:40:02 +00:00
|
|
|
*line_w = root->isatty[term_msg_fileno(root, lev)] ? width : 0;
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
}
|
|
|
|
|
2019-11-22 00:08:20 +00:00
|
|
|
static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer)
|
|
|
|
{
|
|
|
|
assert(buffer->num_entries);
|
|
|
|
struct mp_log_buffer_entry *res = buffer->entries[buffer->entry0];
|
|
|
|
buffer->entry0 = (buffer->entry0 + 1) % buffer->capacity;
|
|
|
|
buffer->num_entries -= 1;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2023-10-16 17:58:53 +00:00
|
|
|
static void write_msg_to_buffers(struct mp_log *log, int lev, bstr text)
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
{
|
|
|
|
struct mp_log_root *root = log->root;
|
|
|
|
for (int n = 0; n < root->num_buffers; n++) {
|
|
|
|
struct mp_log_buffer *buffer = root->buffers[n];
|
msg: fix some locking issues
The wakeup_log_file callback was still assuming that mp_msg_lock was
used to control the log file thread, but this changed while I was
writing this code, and forgot to update it. (It doesn't change any
state, which is untypical for condition variable usage. The state that
is changed is protected by another lock instead. But log_file_lock still
needs to be acquired to ensure the signal isn't sent while the thread is
right before the pthread_cond_wait() call, when the lock is held, but
the signal would still be lost.)
Because the buffer's wakeup callback now acquires the lock, the wakeup
callback must be called outside of the buffer lock, to keep the lock
order (log_file_lock > mp_log_buffer.lock). Fortunately, the wakeup
callback is immutable, or we would have needed another dumb leaf lock.
mp_msg_has_log_file() made a similar outdated assumption. But now access
to the log_file field is much trickier; just define that it's only to be
called from the thread that manages the msg state. (The calling code
could also just check whether the log-file option changed instead, but
currently that would be slightly more messy.)
2020-01-30 13:00:41 +00:00
|
|
|
bool wakeup = false;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&buffer->lock);
|
2015-06-20 19:40:47 +00:00
|
|
|
int buffer_level = buffer->level;
|
|
|
|
if (buffer_level == MP_LOG_BUFFER_MSGL_TERM)
|
|
|
|
buffer_level = log->terminal_level;
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE)
|
|
|
|
buffer_level = MPMAX(log->terminal_level, MSGL_DEBUG);
|
2015-06-20 19:40:47 +00:00
|
|
|
if (lev <= buffer_level && lev != MSGL_STATUS) {
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
if (buffer->level == MP_LOG_BUFFER_MSGL_LOGFILE) {
|
2022-12-15 16:09:03 +00:00
|
|
|
// If the buffer is full, block until we can write again,
|
|
|
|
// unless there's no write thread (died, or early filebuffer)
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
bool dead = false;
|
|
|
|
while (buffer->num_entries == buffer->capacity && !dead) {
|
|
|
|
// Temporary unlock is OK; buffer->level is immutable, and
|
|
|
|
// buffer can't go away because the global log lock is held.
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&buffer->lock);
|
|
|
|
mp_mutex_lock(&root->log_file_lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
if (root->log_file_thread_active) {
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_cond_wait(&root->log_file_wakeup,
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
&root->log_file_lock);
|
|
|
|
} else {
|
|
|
|
dead = true;
|
|
|
|
}
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->log_file_lock);
|
|
|
|
mp_mutex_lock(&buffer->lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
}
|
|
|
|
}
|
2019-11-22 00:08:20 +00:00
|
|
|
if (buffer->num_entries == buffer->capacity) {
|
|
|
|
struct mp_log_buffer_entry *skip = log_buffer_read(buffer);
|
|
|
|
talloc_free(skip);
|
|
|
|
buffer->dropped += 1;
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
}
|
2019-11-22 00:08:20 +00:00
|
|
|
struct mp_log_buffer_entry *entry = talloc_ptrtype(NULL, entry);
|
|
|
|
*entry = (struct mp_log_buffer_entry) {
|
|
|
|
.prefix = talloc_strdup(entry, log->verbose_prefix),
|
|
|
|
.level = lev,
|
2023-10-16 17:58:53 +00:00
|
|
|
.text = bstrdup0(entry, text),
|
2019-11-22 00:08:20 +00:00
|
|
|
};
|
|
|
|
int pos = (buffer->entry0 + buffer->num_entries) % buffer->capacity;
|
|
|
|
buffer->entries[pos] = entry;
|
|
|
|
buffer->num_entries += 1;
|
|
|
|
if (buffer->wakeup_cb && !buffer->silent)
|
msg: fix some locking issues
The wakeup_log_file callback was still assuming that mp_msg_lock was
used to control the log file thread, but this changed while I was
writing this code, and forgot to update it. (It doesn't change any
state, which is untypical for condition variable usage. The state that
is changed is protected by another lock instead. But log_file_lock still
needs to be acquired to ensure the signal isn't sent while the thread is
right before the pthread_cond_wait() call, when the lock is held, but
the signal would still be lost.)
Because the buffer's wakeup callback now acquires the lock, the wakeup
callback must be called outside of the buffer lock, to keep the lock
order (log_file_lock > mp_log_buffer.lock). Fortunately, the wakeup
callback is immutable, or we would have needed another dumb leaf lock.
mp_msg_has_log_file() made a similar outdated assumption. But now access
to the log_file field is much trickier; just define that it's only to be
called from the thread that manages the msg state. (The calling code
could also just check whether the log-file option changed instead, but
currently that would be slightly more messy.)
2020-01-30 13:00:41 +00:00
|
|
|
wakeup = true;
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
}
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&buffer->lock);
|
msg: fix some locking issues
The wakeup_log_file callback was still assuming that mp_msg_lock was
used to control the log file thread, but this changed while I was
writing this code, and forgot to update it. (It doesn't change any
state, which is untypical for condition variable usage. The state that
is changed is protected by another lock instead. But log_file_lock still
needs to be acquired to ensure the signal isn't sent while the thread is
right before the pthread_cond_wait() call, when the lock is held, but
the signal would still be lost.)
Because the buffer's wakeup callback now acquires the lock, the wakeup
callback must be called outside of the buffer lock, to keep the lock
order (log_file_lock > mp_log_buffer.lock). Fortunately, the wakeup
callback is immutable, or we would have needed another dumb leaf lock.
mp_msg_has_log_file() made a similar outdated assumption. But now access
to the log_file field is much trickier; just define that it's only to be
called from the thread that manages the msg state. (The calling code
could also just check whether the log-file option changed instead, but
currently that would be slightly more messy.)
2020-01-30 13:00:41 +00:00
|
|
|
if (wakeup)
|
|
|
|
buffer->wakeup_cb(buffer->wakeup_cb_ctx);
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-18 23:42:59 +00:00
|
|
|
static void dump_stats(struct mp_log *log, int lev, bstr text)
|
2014-04-17 19:47:00 +00:00
|
|
|
{
|
|
|
|
struct mp_log_root *root = log->root;
|
2015-10-14 16:50:58 +00:00
|
|
|
if (lev == MSGL_STATS && root->stats_file)
|
2023-10-18 23:42:59 +00:00
|
|
|
fprintf(root->stats_file, "%"PRId64" %.*s\n", mp_time_ns(), BSTR_P(text));
|
2014-04-17 19:47:00 +00:00
|
|
|
}
|
|
|
|
|
2023-10-19 01:55:33 +00:00
|
|
|
static void write_term_msg(struct mp_log *log, int lev, bstr text, bstr *out)
|
|
|
|
{
|
|
|
|
struct mp_log_root *root = log->root;
|
|
|
|
bool print_term = test_terminal_level(log, lev);
|
|
|
|
int fileno = term_msg_fileno(root, lev);
|
|
|
|
int term_w = 0, term_h = 0;
|
|
|
|
if (print_term && root->isatty[fileno])
|
|
|
|
terminal_get_size(&term_w, &term_h);
|
|
|
|
|
|
|
|
out->len = 0;
|
|
|
|
|
|
|
|
// Split away each line. Normally we require full lines; buffer partial
|
|
|
|
// lines if they happen.
|
|
|
|
root->term_msg_tmp.len = 0;
|
|
|
|
int term_msg_lines = 0;
|
|
|
|
|
|
|
|
bstr str = text;
|
|
|
|
while (str.len) {
|
|
|
|
bstr line = bstr_getline(str, &str);
|
|
|
|
if (line.start[line.len - 1] != '\n') {
|
|
|
|
assert(str.len == 0);
|
|
|
|
str = line;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-10-11 19:05:18 +00:00
|
|
|
bool clip = bstr_eatstart0(&line, TERM_MSG_0);
|
2023-10-19 01:55:33 +00:00
|
|
|
if (print_term) {
|
|
|
|
int line_w;
|
2024-09-23 00:13:17 +00:00
|
|
|
append_terminal_line(log, lev, line, &root->term_msg_tmp, &line_w,
|
2024-10-17 21:42:08 +00:00
|
|
|
clip && term_w ? term_w : INT_MAX);
|
2023-10-19 01:55:33 +00:00
|
|
|
term_msg_lines += (!line_w || !term_w)
|
|
|
|
? 1 : (line_w + term_w - 1) / term_w;
|
|
|
|
}
|
|
|
|
write_msg_to_buffers(log, lev, line);
|
|
|
|
}
|
|
|
|
|
2023-11-20 18:14:19 +00:00
|
|
|
if (lev == MSGL_STATUS) {
|
2023-10-19 01:55:33 +00:00
|
|
|
int line_w = 0;
|
2024-10-20 20:13:48 +00:00
|
|
|
bool clip = bstr_eatstart0(&str, TERM_MSG_0);
|
2023-11-20 18:14:19 +00:00
|
|
|
if (str.len && print_term)
|
2024-09-23 00:13:17 +00:00
|
|
|
append_terminal_line(log, lev, str, &root->term_msg_tmp, &line_w,
|
2024-10-20 20:13:48 +00:00
|
|
|
clip && term_w ? term_w : INT_MAX);
|
2023-10-19 01:55:33 +00:00
|
|
|
term_msg_lines += !term_w ? (str.len ? 1 : 0)
|
|
|
|
: (line_w + term_w - 1) / term_w;
|
|
|
|
} else if (str.len) {
|
|
|
|
bstr_xappend(NULL, &log->partial[lev], str);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (print_term && (root->term_msg_tmp.len || lev == MSGL_STATUS)) {
|
|
|
|
prepare_prefix(root, out, lev, term_msg_lines);
|
|
|
|
if (root->color[fileno] && root->term_msg_tmp.len) {
|
|
|
|
set_msg_color(root, out, lev);
|
|
|
|
set_term_color(root, &root->term_msg_tmp, -1);
|
|
|
|
}
|
|
|
|
bstr_xappend(root, out, root->term_msg_tmp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
|
|
|
|
{
|
|
|
|
if (!mp_msg_test(log, lev))
|
|
|
|
return; // do not display
|
|
|
|
|
2014-10-08 11:11:55 +00:00
|
|
|
struct mp_log_root *root = log->root;
|
2016-03-01 21:34:14 +00:00
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
2020-01-30 13:16:20 +00:00
|
|
|
|
2016-03-01 21:34:14 +00:00
|
|
|
root->buffer.len = 0;
|
|
|
|
|
2023-10-18 23:40:30 +00:00
|
|
|
if (log->partial[lev].len)
|
|
|
|
bstr_xappend(root, &root->buffer, log->partial[lev]);
|
|
|
|
log->partial[lev].len = 0;
|
2016-03-01 21:34:14 +00:00
|
|
|
|
2024-09-26 20:45:07 +00:00
|
|
|
if (bstr_xappend_vasprintf(root, &root->buffer, format, va) < 0) {
|
|
|
|
bstr_xappend(root, &root->buffer, bstr0("format error: "));
|
|
|
|
bstr_xappend(root, &root->buffer, bstr0(format));
|
|
|
|
}
|
2016-03-01 21:34:14 +00:00
|
|
|
|
2023-10-19 02:27:14 +00:00
|
|
|
// Remember last status message and restore it to ensure that it is
|
|
|
|
// always displayed
|
|
|
|
if (lev == MSGL_STATUS) {
|
|
|
|
root->status_log = log;
|
|
|
|
root->status_line.len = 0;
|
|
|
|
// Use bstr_xappend instead bstrdup to reuse allocated memory
|
|
|
|
if (root->buffer.len)
|
|
|
|
bstr_xappend(root, &root->status_line, root->buffer);
|
|
|
|
}
|
|
|
|
|
2014-10-08 11:11:55 +00:00
|
|
|
if (lev == MSGL_STATS) {
|
2023-10-18 23:42:59 +00:00
|
|
|
dump_stats(log, lev, root->buffer);
|
2014-10-08 11:11:55 +00:00
|
|
|
} else if (lev == MSGL_STATUS && !test_terminal_level(log, lev)) {
|
|
|
|
/* discard */
|
|
|
|
} else {
|
2023-10-19 01:55:33 +00:00
|
|
|
write_term_msg(log, lev, root->buffer, &root->term_msg);
|
2014-10-08 11:11:55 +00:00
|
|
|
|
2024-05-13 19:49:17 +00:00
|
|
|
FILE *stream = term_msg_fp(root, lev);
|
2023-10-19 01:55:33 +00:00
|
|
|
if (root->term_msg.len) {
|
2024-10-12 18:27:00 +00:00
|
|
|
root->term_status_msg.len = 0;
|
|
|
|
if (lev != MSGL_STATUS && root->status_line.len && root->status_log &&
|
|
|
|
is_status_output(root, lev) && test_terminal_level(root->status_log, MSGL_STATUS))
|
|
|
|
{
|
|
|
|
write_term_msg(root->status_log, MSGL_STATUS, root->status_line,
|
|
|
|
&root->term_status_msg);
|
|
|
|
}
|
2024-03-21 04:58:33 +00:00
|
|
|
fwrite(root->term_msg.start, root->term_msg.len, 1, stream);
|
|
|
|
if (root->term_status_msg.len)
|
|
|
|
fwrite(root->term_status_msg.start, root->term_status_msg.len, 1, stream);
|
2023-10-19 01:55:33 +00:00
|
|
|
fflush(stream);
|
2014-10-08 11:11:55 +00:00
|
|
|
}
|
|
|
|
}
|
2013-12-20 20:07:10 +00:00
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
2001-08-16 22:13:20 +00:00
|
|
|
}
|
2009-07-06 16:54:38 +00:00
|
|
|
|
2016-03-01 21:34:14 +00:00
|
|
|
static void destroy_log(void *ptr)
|
|
|
|
{
|
|
|
|
struct mp_log *log = ptr;
|
|
|
|
// This is not managed via talloc itself, because mp_msg calls must be
|
|
|
|
// thread-safe, while talloc is not thread-safe.
|
2023-10-13 18:00:24 +00:00
|
|
|
for (int lvl = 0; lvl <= MSGL_MAX; ++lvl)
|
2023-10-18 23:40:30 +00:00
|
|
|
talloc_free(log->partial[lvl].start);
|
2016-03-01 21:34:14 +00:00
|
|
|
}
|
|
|
|
|
2013-07-31 19:40:30 +00:00
|
|
|
// Create a new log context, which uses talloc_ctx as talloc parent, and parent
|
|
|
|
// as logical parent.
|
|
|
|
// The name is the prefix put before the output. It's usually prefixed by the
|
|
|
|
// parent's name. If the name starts with "/", the parent's name is not
|
|
|
|
// prefixed (except in verbose mode), and if it starts with "!", the name is
|
2013-08-05 17:05:48 +00:00
|
|
|
// not printed at all (except in verbose mode).
|
2014-08-24 21:34:20 +00:00
|
|
|
// If name is NULL, the parent's name/prefix is used.
|
2013-12-22 11:29:16 +00:00
|
|
|
// Thread-safety: fully thread-safe, but keep in mind that talloc is not (so
|
|
|
|
// talloc_ctx must be owned by the current thread).
|
2013-07-31 19:40:30 +00:00
|
|
|
struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent,
|
|
|
|
const char *name)
|
|
|
|
{
|
|
|
|
assert(parent);
|
|
|
|
struct mp_log *log = talloc_zero(talloc_ctx, struct mp_log);
|
2013-12-18 18:04:30 +00:00
|
|
|
if (!parent->root)
|
|
|
|
return log; // same as null_log
|
2016-03-01 21:34:14 +00:00
|
|
|
talloc_set_destructor(log, destroy_log);
|
2013-07-31 19:40:30 +00:00
|
|
|
log->root = parent->root;
|
2020-05-10 14:40:26 +00:00
|
|
|
log->max_level = MSGL_MAX;
|
2014-08-24 21:34:20 +00:00
|
|
|
if (name) {
|
|
|
|
if (name[0] == '!') {
|
|
|
|
name = &name[1];
|
|
|
|
} else if (name[0] == '/') {
|
|
|
|
name = &name[1];
|
|
|
|
log->prefix = talloc_strdup(log, name);
|
|
|
|
} else {
|
|
|
|
log->prefix = parent->prefix
|
|
|
|
? talloc_asprintf(log, "%s/%s", parent->prefix, name)
|
|
|
|
: talloc_strdup(log, name);
|
|
|
|
}
|
|
|
|
log->verbose_prefix = parent->prefix
|
2013-07-31 19:40:30 +00:00
|
|
|
? talloc_asprintf(log, "%s/%s", parent->prefix, name)
|
|
|
|
: talloc_strdup(log, name);
|
2014-08-24 21:34:20 +00:00
|
|
|
if (log->prefix && !log->prefix[0])
|
|
|
|
log->prefix = NULL;
|
|
|
|
if (!log->verbose_prefix[0])
|
|
|
|
log->verbose_prefix = "global";
|
|
|
|
} else {
|
|
|
|
log->prefix = talloc_strdup(log, parent->prefix);
|
|
|
|
log->verbose_prefix = talloc_strdup(log, parent->verbose_prefix);
|
2013-07-31 19:40:30 +00:00
|
|
|
}
|
|
|
|
return log;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mp_msg_init(struct mpv_global *global)
|
|
|
|
{
|
|
|
|
assert(!global->log);
|
|
|
|
|
|
|
|
struct mp_log_root *root = talloc_zero(NULL, struct mp_log_root);
|
2014-05-20 23:04:47 +00:00
|
|
|
*root = (struct mp_log_root){
|
|
|
|
.global = global,
|
2023-10-19 10:16:12 +00:00
|
|
|
.reload_counter = 1,
|
2014-05-20 23:04:47 +00:00
|
|
|
};
|
2013-07-31 19:40:30 +00:00
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_init(&root->lock);
|
|
|
|
mp_mutex_init(&root->log_file_lock);
|
|
|
|
mp_cond_init(&root->log_file_wakeup);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
|
2013-07-31 19:40:30 +00:00
|
|
|
struct mp_log dummy = { .root = root };
|
|
|
|
struct mp_log *log = mp_log_new(root, &dummy, "");
|
2013-12-21 20:40:45 +00:00
|
|
|
|
2013-07-31 19:40:30 +00:00
|
|
|
global->log = log;
|
|
|
|
}
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
static MP_THREAD_VOID log_file_thread(void *p)
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
{
|
|
|
|
struct mp_log_root *root = p;
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_thread_set_name("log");
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->log_file_lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
|
|
|
|
while (root->log_file_thread_active) {
|
|
|
|
struct mp_log_buffer_entry *e =
|
|
|
|
mp_msg_log_buffer_read(root->log_file_buffer);
|
|
|
|
if (e) {
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->log_file_lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
fprintf(root->log_file, "[%8.3f][%c][%s] %s",
|
2023-10-26 19:24:41 +00:00
|
|
|
mp_time_sec(),
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
mp_log_levels[e->level][0], e->prefix, e->text);
|
|
|
|
fflush(root->log_file);
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->log_file_lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
talloc_free(e);
|
|
|
|
// Multiple threads might be blocked if the log buffer was full.
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_cond_broadcast(&root->log_file_wakeup);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
} else {
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_cond_wait(&root->log_file_wakeup, &root->log_file_lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->log_file_lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
MP_THREAD_RETURN();
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void wakeup_log_file(void *p)
|
|
|
|
{
|
|
|
|
struct mp_log_root *root = p;
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->log_file_lock);
|
|
|
|
mp_cond_broadcast(&root->log_file_wakeup);
|
|
|
|
mp_mutex_unlock(&root->log_file_lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only to be called from the main thread.
|
|
|
|
static void terminate_log_file_thread(struct mp_log_root *root)
|
|
|
|
{
|
|
|
|
bool wait_terminate = false;
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->log_file_lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
if (root->log_file_thread_active) {
|
|
|
|
root->log_file_thread_active = false;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_cond_broadcast(&root->log_file_wakeup);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
wait_terminate = true;
|
|
|
|
}
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->log_file_lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
|
|
|
|
if (wait_terminate)
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_thread_join(root->log_file_thread);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
|
|
|
|
mp_msg_log_buffer_destroy(root->log_file_buffer);
|
|
|
|
root->log_file_buffer = NULL;
|
|
|
|
|
|
|
|
if (root->log_file)
|
|
|
|
fclose(root->log_file);
|
|
|
|
root->log_file = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If opt is different from *current_path, update *current_path and return true.
|
|
|
|
// No lock must be held; passed values must be accessible without.
|
|
|
|
static bool check_new_path(struct mpv_global *global, char *opt,
|
|
|
|
char **current_path)
|
2016-09-19 17:56:40 +00:00
|
|
|
{
|
2016-09-28 13:04:30 +00:00
|
|
|
void *tmp = talloc_new(NULL);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
bool res = false;
|
2016-09-28 13:04:30 +00:00
|
|
|
|
|
|
|
char *new_path = mp_get_user_path(tmp, global, opt);
|
|
|
|
if (!new_path)
|
|
|
|
new_path = "";
|
|
|
|
|
2016-09-19 17:56:40 +00:00
|
|
|
char *old_path = *current_path ? *current_path : "";
|
|
|
|
if (strcmp(old_path, new_path) != 0) {
|
|
|
|
talloc_free(*current_path);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
*current_path = NULL;
|
2023-11-18 22:23:18 +00:00
|
|
|
if (new_path[0])
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
*current_path = talloc_strdup(NULL, new_path);
|
|
|
|
res = true;
|
2016-09-19 17:56:40 +00:00
|
|
|
}
|
|
|
|
|
2016-09-28 13:04:30 +00:00
|
|
|
talloc_free(tmp);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
|
|
|
|
return res;
|
2016-09-19 17:56:40 +00:00
|
|
|
}
|
|
|
|
|
2018-05-21 14:25:52 +00:00
|
|
|
void mp_msg_update_msglevels(struct mpv_global *global, struct MPOpts *opts)
|
2013-12-18 18:04:30 +00:00
|
|
|
{
|
|
|
|
struct mp_log_root *root = global->log->root;
|
2013-12-21 22:11:12 +00:00
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
2013-12-21 22:11:12 +00:00
|
|
|
|
|
|
|
root->verbose = opts->verbose;
|
2017-06-23 18:42:20 +00:00
|
|
|
root->really_quiet = opts->msg_really_quiet;
|
2013-12-21 22:11:12 +00:00
|
|
|
root->module = opts->msg_module;
|
2014-02-06 15:49:50 +00:00
|
|
|
root->use_terminal = opts->use_terminal;
|
2014-02-28 21:45:34 +00:00
|
|
|
root->show_time = opts->msg_time;
|
2024-01-11 14:33:48 +00:00
|
|
|
|
|
|
|
if (root->really_quiet)
|
|
|
|
root->status_lines = 0;
|
|
|
|
|
2023-10-18 19:13:08 +00:00
|
|
|
for (int i = STDOUT_FILENO; i <= STDERR_FILENO && root->use_terminal; ++i) {
|
|
|
|
root->isatty[i] = isatty(i);
|
|
|
|
root->color[i] = opts->msg_color && root->isatty[i];
|
|
|
|
}
|
2013-12-21 22:11:12 +00:00
|
|
|
|
2015-02-06 15:48:52 +00:00
|
|
|
m_option_type_msglevels.free(&root->msg_levels);
|
2018-05-21 14:25:52 +00:00
|
|
|
m_option_type_msglevels.copy(NULL, &root->msg_levels, &opts->msg_levels);
|
2013-12-21 22:11:12 +00:00
|
|
|
|
2014-05-20 23:04:47 +00:00
|
|
|
atomic_fetch_add(&root->reload_counter, 1);
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
2016-09-19 17:56:40 +00:00
|
|
|
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
if (check_new_path(global, opts->log_file, &root->log_path)) {
|
|
|
|
terminate_log_file_thread(root);
|
|
|
|
if (root->log_path) {
|
|
|
|
root->log_file = fopen(root->log_path, "wb");
|
|
|
|
if (root->log_file) {
|
2022-12-15 16:09:03 +00:00
|
|
|
|
|
|
|
// if a logfile is created and the early filebuf still exists,
|
|
|
|
// flush and destroy the early buffer
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
2022-12-15 16:09:03 +00:00
|
|
|
struct mp_log_buffer *earlybuf = root->early_filebuffer;
|
|
|
|
if (earlybuf)
|
|
|
|
root->early_filebuffer = NULL; // but it still logs msgs
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
2022-12-15 16:09:03 +00:00
|
|
|
|
|
|
|
if (earlybuf) {
|
|
|
|
// flush, destroy before creating the normal logfile buf,
|
|
|
|
// as once the new one is created (specifically, its write
|
|
|
|
// thread), then MSGL_LOGFILE messages become blocking, but
|
|
|
|
// the early logfile buf is without dequeue - can deadlock.
|
|
|
|
// note: timestamp is unknown, we use 0.000 as indication.
|
|
|
|
// note: new messages while iterating are still flushed.
|
|
|
|
struct mp_log_buffer_entry *e;
|
|
|
|
while ((e = mp_msg_log_buffer_read(earlybuf))) {
|
|
|
|
fprintf(root->log_file, "[%8.3f][%c][%s] %s", 0.0,
|
|
|
|
mp_log_levels[e->level][0], e->prefix, e->text);
|
|
|
|
talloc_free(e);
|
|
|
|
}
|
|
|
|
mp_msg_log_buffer_destroy(earlybuf); // + remove from root
|
|
|
|
}
|
|
|
|
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
root->log_file_buffer =
|
2023-01-23 08:56:12 +00:00
|
|
|
mp_msg_log_buffer_new(global, FILE_BUF, MP_LOG_BUFFER_MSGL_LOGFILE,
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
wakeup_log_file, root);
|
|
|
|
root->log_file_thread_active = true;
|
2023-10-21 02:55:41 +00:00
|
|
|
if (mp_thread_create(&root->log_file_thread, log_file_thread,
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
root))
|
|
|
|
{
|
|
|
|
root->log_file_thread_active = false;
|
|
|
|
terminate_log_file_thread(root);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mp_err(global->log, "Failed to open log file '%s'\n",
|
|
|
|
root->log_path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (check_new_path(global, opts->dump_stats, &root->stats_path)) {
|
|
|
|
bool open_error = false;
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
if (root->stats_file)
|
|
|
|
fclose(root->stats_file);
|
|
|
|
root->stats_file = NULL;
|
|
|
|
if (root->stats_path) {
|
|
|
|
root->stats_file = fopen(root->stats_path, "wb");
|
|
|
|
open_error = !root->stats_file;
|
|
|
|
}
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
2016-09-28 13:04:30 +00:00
|
|
|
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
if (open_error) {
|
|
|
|
mp_err(global->log, "Failed to open stats file '%s'\n",
|
|
|
|
root->stats_path);
|
|
|
|
}
|
|
|
|
}
|
2013-12-18 18:04:30 +00:00
|
|
|
}
|
|
|
|
|
2013-12-21 22:11:12 +00:00
|
|
|
void mp_msg_force_stderr(struct mpv_global *global, bool force_stderr)
|
|
|
|
{
|
|
|
|
struct mp_log_root *root = global->log->root;
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
2013-12-21 22:11:12 +00:00
|
|
|
root->force_stderr = force_stderr;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
2017-05-22 16:30:39 +00:00
|
|
|
}
|
|
|
|
|
msg: fix some locking issues
The wakeup_log_file callback was still assuming that mp_msg_lock was
used to control the log file thread, but this changed while I was
writing this code, and forgot to update it. (It doesn't change any
state, which is untypical for condition variable usage. The state that
is changed is protected by another lock instead. But log_file_lock still
needs to be acquired to ensure the signal isn't sent while the thread is
right before the pthread_cond_wait() call, when the lock is held, but
the signal would still be lost.)
Because the buffer's wakeup callback now acquires the lock, the wakeup
callback must be called outside of the buffer lock, to keep the lock
order (log_file_lock > mp_log_buffer.lock). Fortunately, the wakeup
callback is immutable, or we would have needed another dumb leaf lock.
mp_msg_has_log_file() made a similar outdated assumption. But now access
to the log_file field is much trickier; just define that it's only to be
called from the thread that manages the msg state. (The calling code
could also just check whether the log-file option changed instead, but
currently that would be slightly more messy.)
2020-01-30 13:00:41 +00:00
|
|
|
// Only to be called from the main thread.
|
2017-05-22 16:30:39 +00:00
|
|
|
bool mp_msg_has_log_file(struct mpv_global *global)
|
|
|
|
{
|
|
|
|
struct mp_log_root *root = global->log->root;
|
|
|
|
|
msg: fix some locking issues
The wakeup_log_file callback was still assuming that mp_msg_lock was
used to control the log file thread, but this changed while I was
writing this code, and forgot to update it. (It doesn't change any
state, which is untypical for condition variable usage. The state that
is changed is protected by another lock instead. But log_file_lock still
needs to be acquired to ensure the signal isn't sent while the thread is
right before the pthread_cond_wait() call, when the lock is held, but
the signal would still be lost.)
Because the buffer's wakeup callback now acquires the lock, the wakeup
callback must be called outside of the buffer lock, to keep the lock
order (log_file_lock > mp_log_buffer.lock). Fortunately, the wakeup
callback is immutable, or we would have needed another dumb leaf lock.
mp_msg_has_log_file() made a similar outdated assumption. But now access
to the log_file field is much trickier; just define that it's only to be
called from the thread that manages the msg state. (The calling code
could also just check whether the log-file option changed instead, but
currently that would be slightly more messy.)
2020-01-30 13:00:41 +00:00
|
|
|
return !!root->log_file;
|
2013-12-21 22:11:12 +00:00
|
|
|
}
|
|
|
|
|
2013-07-31 19:40:30 +00:00
|
|
|
void mp_msg_uninit(struct mpv_global *global)
|
|
|
|
{
|
2014-04-17 19:47:00 +00:00
|
|
|
struct mp_log_root *root = global->log->root;
|
2023-12-15 18:38:07 +00:00
|
|
|
mp_msg_flush_status_line(global->log, true);
|
2024-05-13 19:49:17 +00:00
|
|
|
if (root->really_quiet && root->isatty[term_msg_fileno(root, MSGL_STATUS)])
|
|
|
|
fprintf(term_msg_fp(root, MSGL_STATUS), TERM_ESC_RESTORE_CURSOR);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
terminate_log_file_thread(root);
|
|
|
|
mp_msg_log_buffer_destroy(root->early_buffer);
|
2022-12-15 16:09:03 +00:00
|
|
|
mp_msg_log_buffer_destroy(root->early_filebuffer);
|
2019-11-17 23:44:12 +00:00
|
|
|
assert(root->num_buffers == 0);
|
2014-04-17 19:47:00 +00:00
|
|
|
if (root->stats_file)
|
|
|
|
fclose(root->stats_file);
|
2016-09-19 17:56:40 +00:00
|
|
|
talloc_free(root->stats_path);
|
|
|
|
talloc_free(root->log_path);
|
2015-02-06 15:48:52 +00:00
|
|
|
m_option_type_msglevels.free(&root->msg_levels);
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_destroy(&root->lock);
|
|
|
|
mp_mutex_destroy(&root->log_file_lock);
|
|
|
|
mp_cond_destroy(&root->log_file_wakeup);
|
2014-04-17 19:47:00 +00:00
|
|
|
talloc_free(root);
|
2013-07-31 19:40:30 +00:00
|
|
|
global->log = NULL;
|
|
|
|
}
|
|
|
|
|
2022-12-15 16:09:03 +00:00
|
|
|
// early logging store log messages before they have a known destination.
|
|
|
|
// there are two early log buffers which are similar logically, and both cease
|
|
|
|
// function (if still exist, independently) once the log destination is known,
|
|
|
|
// or mpv init is complete (typically, after all clients/scripts init is done).
|
|
|
|
//
|
|
|
|
// - "normal" early_buffer, holds early terminal-level logs, and is handed over
|
|
|
|
// to the first client which requests such log buffer, so that it sees older
|
|
|
|
// messages too. further clients which request a log buffer get a new one
|
|
|
|
// which accumulates messages starting at this point in time.
|
|
|
|
//
|
|
|
|
// - early_filebuffer - early log-file messages until a log file name is known.
|
|
|
|
// main cases where meaningful messages are accumulated before the filename
|
|
|
|
// is known are when log-file is set at mpv.conf, or from script/client init.
|
|
|
|
// once a file name is known, the early buffer is flushed and destroyed.
|
|
|
|
// unlike the "proper" log-file buffer, the early filebuffer is not backed by
|
|
|
|
// a write thread, and hence non-blocking (can overwrite old messages).
|
|
|
|
// it's also bigger than the actual file buffer (early: 5000, actual: 100).
|
|
|
|
|
|
|
|
static void mp_msg_set_early_logging_raw(struct mpv_global *global, bool enable,
|
|
|
|
struct mp_log_buffer **root_logbuf,
|
|
|
|
int size, int level)
|
2019-11-17 23:44:12 +00:00
|
|
|
{
|
|
|
|
struct mp_log_root *root = global->log->root;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
2019-11-17 23:44:12 +00:00
|
|
|
|
2022-12-15 16:09:03 +00:00
|
|
|
if (enable != !!*root_logbuf) {
|
2019-11-17 23:44:12 +00:00
|
|
|
if (enable) {
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
2019-11-17 23:44:12 +00:00
|
|
|
struct mp_log_buffer *buf =
|
2022-12-15 16:09:03 +00:00
|
|
|
mp_msg_log_buffer_new(global, size, level, NULL, NULL);
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
2022-12-15 16:09:03 +00:00
|
|
|
assert(!*root_logbuf); // no concurrent calls to this function
|
|
|
|
*root_logbuf = buf;
|
2019-11-17 23:44:12 +00:00
|
|
|
} else {
|
2022-12-15 16:09:03 +00:00
|
|
|
struct mp_log_buffer *buf = *root_logbuf;
|
|
|
|
*root_logbuf = NULL;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
2019-11-17 23:44:12 +00:00
|
|
|
mp_msg_log_buffer_destroy(buf);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
2019-11-17 23:44:12 +00:00
|
|
|
}
|
|
|
|
|
2022-12-15 16:09:03 +00:00
|
|
|
void mp_msg_set_early_logging(struct mpv_global *global, bool enable)
|
|
|
|
{
|
|
|
|
struct mp_log_root *root = global->log->root;
|
|
|
|
|
|
|
|
mp_msg_set_early_logging_raw(global, enable, &root->early_buffer,
|
2024-01-26 10:28:37 +00:00
|
|
|
EARLY_TERM_BUF, MP_LOG_BUFFER_MSGL_TERM);
|
2022-12-15 16:09:03 +00:00
|
|
|
|
|
|
|
// normally MSGL_LOGFILE buffer gets a write thread, but not the early buf
|
|
|
|
mp_msg_set_early_logging_raw(global, enable, &root->early_filebuffer,
|
|
|
|
EARLY_FILE_BUF, MP_LOG_BUFFER_MSGL_LOGFILE);
|
|
|
|
}
|
|
|
|
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global,
|
2014-06-06 17:24:30 +00:00
|
|
|
int size, int level,
|
|
|
|
void (*wakeup_cb)(void *ctx),
|
|
|
|
void *wakeup_cb_ctx)
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
{
|
|
|
|
struct mp_log_root *root = global->log->root;
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
|
2019-11-17 23:44:12 +00:00
|
|
|
if (level == MP_LOG_BUFFER_MSGL_TERM) {
|
|
|
|
// The first thing which creates a terminal-level log buffer gets the
|
|
|
|
// early log buffer, if it exists. This is supposed to enable a script
|
|
|
|
// to grab log messages from before it was initialized. It's OK that
|
|
|
|
// this works only for 1 script and only once.
|
|
|
|
if (root->early_buffer) {
|
|
|
|
struct mp_log_buffer *buffer = root->early_buffer;
|
|
|
|
root->early_buffer = NULL;
|
2024-01-26 10:28:37 +00:00
|
|
|
mp_msg_log_buffer_resize(buffer, size);
|
2019-11-21 23:34:08 +00:00
|
|
|
buffer->wakeup_cb = wakeup_cb;
|
|
|
|
buffer->wakeup_cb_ctx = wakeup_cb_ctx;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
2019-11-17 23:44:12 +00:00
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-22 00:08:20 +00:00
|
|
|
assert(size > 0);
|
|
|
|
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
struct mp_log_buffer *buffer = talloc_ptrtype(NULL, buffer);
|
|
|
|
*buffer = (struct mp_log_buffer) {
|
|
|
|
.root = root,
|
|
|
|
.level = level,
|
2019-11-22 00:08:20 +00:00
|
|
|
.entries = talloc_array(buffer, struct mp_log_buffer_entry *, size),
|
|
|
|
.capacity = size,
|
2014-06-06 17:24:30 +00:00
|
|
|
.wakeup_cb = wakeup_cb,
|
|
|
|
.wakeup_cb_ctx = wakeup_cb_ctx,
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
};
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_init(&buffer->lock);
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
MP_TARRAY_APPEND(root, root->buffers, root->num_buffers, buffer);
|
|
|
|
|
2014-05-20 23:04:47 +00:00
|
|
|
atomic_fetch_add(&root->reload_counter, 1);
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
2024-01-26 10:28:37 +00:00
|
|
|
void mp_msg_log_buffer_resize(struct mp_log_buffer *buffer, int size)
|
|
|
|
{
|
|
|
|
mp_mutex_lock(&buffer->lock);
|
|
|
|
|
|
|
|
assert(size > 0);
|
|
|
|
if (buffer->capacity < size &&
|
|
|
|
buffer->entry0 + buffer->num_entries <= buffer->capacity) {
|
|
|
|
// shortcut if buffer doesn't wrap
|
|
|
|
buffer->entries = talloc_realloc(buffer, buffer->entries,
|
|
|
|
struct mp_log_buffer_entry *, size);
|
|
|
|
} else if (buffer->capacity != size) {
|
|
|
|
struct mp_log_buffer_entry **entries =
|
|
|
|
talloc_array(buffer, struct mp_log_buffer_entry *, size);
|
|
|
|
int num_entries = 0;
|
|
|
|
for (int i = buffer->num_entries - 1; i >= 0; i--) {
|
|
|
|
int entry = (buffer->entry0 + i) % buffer->num_entries;
|
|
|
|
struct mp_log_buffer_entry *res = buffer->entries[entry];
|
|
|
|
if (num_entries < size) {
|
|
|
|
entries[num_entries++] = res;
|
|
|
|
} else {
|
|
|
|
talloc_free(res);
|
|
|
|
buffer->dropped += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
talloc_free(buffer->entries);
|
|
|
|
buffer->entries = entries;
|
|
|
|
buffer->entry0 = 0;
|
|
|
|
buffer->num_entries = num_entries;
|
|
|
|
}
|
|
|
|
buffer->capacity = size;
|
|
|
|
|
|
|
|
mp_mutex_unlock(&buffer->lock);
|
|
|
|
}
|
|
|
|
|
2019-11-17 23:44:12 +00:00
|
|
|
void mp_msg_log_buffer_set_silent(struct mp_log_buffer *buffer, bool silent)
|
|
|
|
{
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&buffer->lock);
|
2019-11-22 00:08:20 +00:00
|
|
|
buffer->silent = silent;
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&buffer->lock);
|
2019-11-17 23:44:12 +00:00
|
|
|
}
|
|
|
|
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
void mp_msg_log_buffer_destroy(struct mp_log_buffer *buffer)
|
|
|
|
{
|
|
|
|
if (!buffer)
|
|
|
|
return;
|
|
|
|
|
|
|
|
struct mp_log_root *root = buffer->root;
|
2020-01-30 13:16:20 +00:00
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&root->lock);
|
2020-01-30 13:16:20 +00:00
|
|
|
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
for (int n = 0; n < root->num_buffers; n++) {
|
|
|
|
if (root->buffers[n] == buffer) {
|
|
|
|
MP_TARRAY_REMOVE_AT(root->buffers, root->num_buffers, n);
|
|
|
|
goto found;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-10 18:26:51 +00:00
|
|
|
MP_ASSERT_UNREACHABLE();
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
|
|
|
|
found:
|
|
|
|
|
2019-11-22 00:08:20 +00:00
|
|
|
while (buffer->num_entries)
|
|
|
|
talloc_free(log_buffer_read(buffer));
|
msg: make --log-file buffered through a thread
Until now --log-file performed a blocking write to the log file, which
made any calling thread block for I/O. It even explicitly flushed after
every line (to make it tail-able, or to ensure a hard crash wouldn't
lose any of the output). This wasn't so good, because it could cause
real playback problems, which made it infeasible to enable it by
default.
Try to buffer it through a ring buffer and a thread. There's no other
choice but to use a thread, since async I/O on files is generally a big
and unportable pain. (We very much prefer portable pain.) Fortunately,
there's already a ring buffer (mp_log_buffer, normally for the client
API logging hook). This still involves some pretty messy locking. Give
each mp_log_buffer its own lock to make this easier.
This still makes calling threads block if the log buffer is full (unlike
with client API log buffers, which just drop messages). I don't want log
messages to get lost for this purpose. This also made locking pretty
complicated (without it, mp_log_buffer wouldn't have needed its own
lock). Maybe I'll remove this blocking again when it turns out to be
nonsense.
(We could avoid wasting an entire thread by "reusing" some other thread.
E.g. pick some otherwise not real time thread, and make it react to the
log buffer's wakeup callback. But let's not. It's complicated to abuse
random threads for this. It'd also raise locking complexity, because we
still want it to block on a full buffer.)
2020-01-29 22:34:59 +00:00
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_destroy(&buffer->lock);
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
talloc_free(buffer);
|
|
|
|
|
2014-05-20 23:04:47 +00:00
|
|
|
atomic_fetch_add(&root->reload_counter, 1);
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&root->lock);
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return a queued message, or if the buffer is empty, NULL.
|
|
|
|
// Thread-safety: one buffer can be read by a single thread only.
|
|
|
|
struct mp_log_buffer_entry *mp_msg_log_buffer_read(struct mp_log_buffer *buffer)
|
|
|
|
{
|
2019-11-22 00:08:20 +00:00
|
|
|
struct mp_log_buffer_entry *res = NULL;
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&buffer->lock);
|
2019-11-22 00:08:20 +00:00
|
|
|
|
|
|
|
if (!buffer->silent && buffer->num_entries) {
|
|
|
|
if (buffer->dropped) {
|
|
|
|
res = talloc_ptrtype(NULL, res);
|
|
|
|
*res = (struct mp_log_buffer_entry) {
|
|
|
|
.prefix = "overflow",
|
|
|
|
.level = MSGL_FATAL,
|
2019-11-22 00:10:27 +00:00
|
|
|
.text = talloc_asprintf(res,
|
|
|
|
"log message buffer overflow: %"PRId64" messages skipped\n",
|
|
|
|
buffer->dropped),
|
2019-11-22 00:08:20 +00:00
|
|
|
};
|
|
|
|
buffer->dropped = 0;
|
|
|
|
} else {
|
|
|
|
res = log_buffer_read(buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&buffer->lock);
|
2019-11-22 00:08:20 +00:00
|
|
|
|
|
|
|
return res;
|
msg: add a mechanism to output messages to a ringbuffer
Until now, mp_msg output always went to the terminal. There was no way
to grab the stream of output messages. But this will be needed by
various future changes: Lua scripts, slave mode, client library...
This commit allows registering a ring buffer. A callback would be more
straight-forward, but since msg.c sits at the bottom of the lock
hierarchy (it's used by virtually everything), this would probably be a
nightmare. A ring buffer will be simpler and more predictable in the
long run.
We allocate new memory for each ringbuffer entry, which is probably a
bit expensive. We could try to be clever and somehow pack the data
directly into the buffer, but I felt like this wouldn't be worth the
complexity. You'd have to copy the data a bunch of times anyway. I'm
hoping that we can get away with using the ringbuffer mechanism for
low frequency important messages only (and not e.g. for high volume
debug messages), so the cost doesn't matter that much.
A ringbuffer has a simple, single log level. I considered allowing
--msglevel style per-prefix configuration for each ringbuffer, but
that would have been pretty complicated to implement, and wouldn't
have been that useful either.
2014-01-16 20:26:31 +00:00
|
|
|
}
|
|
|
|
|
2013-12-22 11:29:16 +00:00
|
|
|
// Thread-safety: fully thread-safe, but keep in mind that the lifetime of
|
|
|
|
// log must be guaranteed during the call.
|
|
|
|
// Never call this from signal handlers.
|
2013-12-21 20:49:13 +00:00
|
|
|
void mp_msg(struct mp_log *log, int lev, const char *format, ...)
|
2013-07-31 19:40:30 +00:00
|
|
|
{
|
|
|
|
va_list va;
|
|
|
|
va_start(va, format);
|
2013-12-21 20:49:13 +00:00
|
|
|
mp_msg_va(log, lev, format, va);
|
2013-07-31 19:40:30 +00:00
|
|
|
va_end(va);
|
|
|
|
}
|
2013-12-18 18:04:30 +00:00
|
|
|
|
2014-10-10 11:44:08 +00:00
|
|
|
const char *const mp_log_levels[MSGL_MAX + 1] = {
|
2013-12-18 18:04:30 +00:00
|
|
|
[MSGL_FATAL] = "fatal",
|
|
|
|
[MSGL_ERR] = "error",
|
|
|
|
[MSGL_WARN] = "warn",
|
|
|
|
[MSGL_INFO] = "info",
|
|
|
|
[MSGL_STATUS] = "status",
|
|
|
|
[MSGL_V] = "v",
|
2013-12-21 20:41:18 +00:00
|
|
|
[MSGL_DEBUG] = "debug",
|
|
|
|
[MSGL_TRACE] = "trace",
|
2014-04-17 19:47:00 +00:00
|
|
|
[MSGL_STATS] = "stats",
|
2013-12-18 18:04:30 +00:00
|
|
|
};
|
|
|
|
|
2014-10-09 19:51:10 +00:00
|
|
|
const int mp_mpv_log_levels[MSGL_MAX + 1] = {
|
2014-10-08 12:15:14 +00:00
|
|
|
[MSGL_FATAL] = MPV_LOG_LEVEL_FATAL,
|
|
|
|
[MSGL_ERR] = MPV_LOG_LEVEL_ERROR,
|
|
|
|
[MSGL_WARN] = MPV_LOG_LEVEL_WARN,
|
|
|
|
[MSGL_INFO] = MPV_LOG_LEVEL_INFO,
|
|
|
|
[MSGL_STATUS] = 0, // never used
|
|
|
|
[MSGL_V] = MPV_LOG_LEVEL_V,
|
|
|
|
[MSGL_DEBUG] = MPV_LOG_LEVEL_DEBUG,
|
|
|
|
[MSGL_TRACE] = MPV_LOG_LEVEL_TRACE,
|
|
|
|
[MSGL_STATS] = 0, // never used
|
|
|
|
};
|
|
|
|
|
2015-02-06 15:48:52 +00:00
|
|
|
int mp_msg_find_level(const char *s)
|
2013-12-18 18:04:30 +00:00
|
|
|
{
|
2014-01-16 20:34:47 +00:00
|
|
|
for (int n = 0; n < MP_ARRAY_SIZE(mp_log_levels); n++) {
|
2023-10-23 23:39:32 +00:00
|
|
|
if (mp_log_levels[n] && !strcasecmp(s, mp_log_levels[n]))
|
2015-02-06 15:48:52 +00:00
|
|
|
return n;
|
2013-12-18 18:04:30 +00:00
|
|
|
}
|
2015-02-06 15:48:52 +00:00
|
|
|
return -1;
|
2013-12-18 18:04:30 +00:00
|
|
|
}
|