Skip to main content

aviutl2/
logger.rs

1//! AviUtl2のロガーへのインターフェースを提供します。
2//!
3//! # Examples
4//!
5//! AviUtl2のロガーに直接書き込むことができます。
6//!
7//! ```rust
8//! aviutl2::logger::write_plugin_log("This is a plugin log message.").unwrap();
9//! aviutl2::logger::write_info_log("This is an info log message.").unwrap();
10//! aviutl2::logger::write_warn_log("This is a warning log message.").unwrap();
11//! aviutl2::logger::write_error_log("This is an error log message.").unwrap();
12//! aviutl2::logger::write_verbose_log("This is a verbose log message.").unwrap();
13//!
14//! aviutl2::lprintln!("This is a plugin log message.");  // デフォルトはpluginログに出力
15//! aviutl2::lprintln!(plugin, "This is also a plugin log message.");
16//! aviutl2::lprintln!(info, "This is an info log message.");
17//! aviutl2::lprintln!(warn, "This is a warning log message.");
18//! aviutl2::lprintln!(error, "This is an error log message.");
19//! aviutl2::lprintln!(verbose, "This is a verbose log message.");
20//!
21//! aviutl2::ldbg!(42); // dbg!マクロに相当
22//! ```
23//!
24//! [`tracing`]クレートと組み合わせることもできます。
25//!
26//! ```rust
27//! aviutl2::tracing_subscriber::fmt()
28//!     .with_max_level(if cfg!(debug_assertions) {
29//!         tracing::Level::DEBUG
30//!     } else {
31//!         tracing::Level::INFO
32//!     })
33//!     .event_format(aviutl2::logger::AviUtl2Formatter)
34//!     .with_writer(aviutl2::logger::AviUtl2LogWriter)
35//!     .init();
36//!
37//! tracing::info!("This is an info log message using tracing.");
38//! ```
39
40use crate::common::{CWString, NullByteError};
41use tracing_log::NormalizeEvent;
42use tracing_subscriber::fmt::FormatFields;
43
44// NOTE:
45// InitializeLoggerは可能な限り早く実行されるらしいので、まぁ捨てられるログはないとしていいはず...
46
47/// [`tracing_subscriber::fmt::FormatEvent`]を実装する構造体。
48///
49/// AviUtl2風のログフォーマットでイベントをフォーマットします。
50#[derive(Debug, Clone, Default)]
51pub struct AviUtl2Formatter;
52
53impl<C, N> tracing_subscriber::fmt::FormatEvent<C, N> for AviUtl2Formatter
54where
55    C: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
56    N: for<'a> tracing_subscriber::fmt::FormatFields<'a> + 'static,
57{
58    fn format_event(
59        &self,
60        ctx: &tracing_subscriber::fmt::FmtContext<'_, C, N>,
61        mut writer: tracing_subscriber::fmt::format::Writer<'_>,
62        event: &tracing::Event<'_>,
63    ) -> std::fmt::Result {
64        let meta = event.normalized_metadata();
65        let meta = meta.as_ref().unwrap_or_else(|| event.metadata());
66        let target = meta.target();
67        write!(writer, "[{target}] ")?;
68        ctx.format_fields(writer.by_ref(), event)?;
69        writer.write_str("\n")?;
70        Ok(())
71    }
72}
73
74/// [`tracing_subscriber::fmt::MakeWriter`]を実装する構造体。
75///
76/// AviUtl2のログに書き込みます。
77#[derive(Debug, Clone, Default)]
78pub struct AviUtl2LogWriter;
79
80impl tracing_subscriber::fmt::MakeWriter<'_> for AviUtl2LogWriter {
81    type Writer = LockedInternalWriter;
82
83    fn make_writer(&self) -> Self::Writer {
84        LockedInternalWriter::plugin()
85    }
86
87    fn make_writer_for(&'_ self, meta: &tracing::Metadata<'_>) -> Self::Writer {
88        match *meta.level() {
89            tracing::Level::ERROR => LockedInternalWriter::error(),
90            tracing::Level::WARN => LockedInternalWriter::warn(),
91            tracing::Level::INFO => LockedInternalWriter::info(),
92            tracing::Level::DEBUG | tracing::Level::TRACE => LockedInternalWriter::verbose(),
93        }
94    }
95}
96
97static INTERNAL_WRITER_MUTEX_PLUGIN: std::sync::LazyLock<std::sync::Mutex<InternalWriter>> =
98    std::sync::LazyLock::new(|| {
99        std::sync::Mutex::new(InternalWriter::new(InternalWriterLevel::Plugin))
100    });
101static INTERNAL_WRITER_MUTEX_INFO: std::sync::LazyLock<std::sync::Mutex<InternalWriter>> =
102    std::sync::LazyLock::new(|| {
103        std::sync::Mutex::new(InternalWriter::new(InternalWriterLevel::Info))
104    });
105static INTERNAL_WRITER_MUTEX_WARN: std::sync::LazyLock<std::sync::Mutex<InternalWriter>> =
106    std::sync::LazyLock::new(|| {
107        std::sync::Mutex::new(InternalWriter::new(InternalWriterLevel::Warn))
108    });
109static INTERNAL_WRITER_MUTEX_ERROR: std::sync::LazyLock<std::sync::Mutex<InternalWriter>> =
110    std::sync::LazyLock::new(|| {
111        std::sync::Mutex::new(InternalWriter::new(InternalWriterLevel::Error))
112    });
113static INTERNAL_WRITER_MUTEX_VERBOSE: std::sync::LazyLock<std::sync::Mutex<InternalWriter>> =
114    std::sync::LazyLock::new(|| {
115        std::sync::Mutex::new(InternalWriter::new(InternalWriterLevel::Verbose))
116    });
117
118pub struct LockedInternalWriter {
119    mutex: &'static std::sync::Mutex<InternalWriter>,
120}
121
122impl LockedInternalWriter {
123    pub fn plugin() -> Self {
124        Self {
125            mutex: &INTERNAL_WRITER_MUTEX_PLUGIN,
126        }
127    }
128
129    pub fn info() -> Self {
130        Self {
131            mutex: &INTERNAL_WRITER_MUTEX_INFO,
132        }
133    }
134
135    pub fn warn() -> Self {
136        Self {
137            mutex: &INTERNAL_WRITER_MUTEX_WARN,
138        }
139    }
140
141    pub fn error() -> Self {
142        Self {
143            mutex: &INTERNAL_WRITER_MUTEX_ERROR,
144        }
145    }
146
147    pub fn verbose() -> Self {
148        Self {
149            mutex: &INTERNAL_WRITER_MUTEX_VERBOSE,
150        }
151    }
152}
153impl std::io::Write for LockedInternalWriter {
154    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
155        let mut writer = self.mutex.lock().unwrap();
156        writer.write(buf)
157    }
158
159    fn flush(&mut self) -> std::io::Result<()> {
160        let mut writer = self.mutex.lock().unwrap();
161        writer.flush()
162    }
163}
164
165enum InternalWriterLevel {
166    Plugin,
167    Info,
168    Warn,
169    Error,
170    Verbose,
171}
172
173struct InternalWriter {
174    level: InternalWriterLevel,
175    buffer: Vec<u8>,
176}
177impl InternalWriter {
178    fn new(level: InternalWriterLevel) -> Self {
179        Self {
180            level,
181            buffer: Vec::new(),
182        }
183    }
184}
185
186impl std::io::Write for InternalWriter {
187    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
188        self.buffer.extend_from_slice(buf);
189        self.flush()?;
190        Ok(buf.len())
191    }
192
193    fn flush(&mut self) -> std::io::Result<()> {
194        while let Some(pos) = self.buffer.iter().position(|&b| b == b'\n') {
195            let line = self.buffer.drain(..=pos).collect::<Vec<u8>>();
196            let line = String::from_utf8_lossy(&line);
197            let line = line.trim_end_matches('\n');
198            match self.level {
199                InternalWriterLevel::Plugin => {
200                    let _ = write_plugin_log(line);
201                }
202                InternalWriterLevel::Info => {
203                    let _ = write_info_log(line);
204                }
205                InternalWriterLevel::Warn => {
206                    let _ = write_warn_log(line);
207                }
208                InternalWriterLevel::Error => {
209                    let _ = write_error_log(line);
210                }
211                InternalWriterLevel::Verbose => {
212                    let _ = write_verbose_log(line);
213                }
214            }
215        }
216        Ok(())
217    }
218}
219
220/// プラグイン用ログに出力する[`dbg!`]マクロ。
221///
222/// # See Also
223/// <https://github.com/rust-lang/rust/blob/29483883eed69d5fb4db01964cdf2af4d86e9cb2/library/std/src/macros.rs#L352>
224#[macro_export]
225macro_rules! ldbg {
226    () => {
227        $crate::lprintln!(verbose, "[{}:{}:{}]", ::std::file!(), ::std::line!(), ::std::column!());
228    };
229    ($val:expr $(,)?) => {
230        match $val {
231            tmp => {
232                $crate::lprintln!(verbose, "[{}:{}:{}] {} = {:#?}",
233                    ::std::file!(),
234                    ::std::line!(),
235                    ::std::column!(),
236                    ::std::stringify!($val),
237                    &&tmp as &dyn ::std::fmt::Debug,
238                );
239                tmp
240            }
241        }
242    };
243    ($($val:expr),+ $(,)?) => {
244        ($($crate::ldbg!($val)),+,)
245    };
246}
247
248/// プラグイン用ログに出力する[`println!`]マクロ。
249///
250/// ```rust
251/// # use aviutl2::lprintln;
252/// lprintln!("This is a plugin log message.");  // デフォルトはpluginログに出力
253/// lprintln!(plugin, "This is also a plugin log message.");
254/// lprintln!(info, "This is an info log message.");
255/// lprintln!(warn, "This is a warning log message.");
256/// lprintln!(error, "This is an error log message.");
257/// lprintln!(verbose, "This is a verbose log message.");
258/// ```
259#[macro_export]
260macro_rules! lprintln {
261    (plugin, $($arg:tt)*) => {
262        ::std::mem::drop($crate::logger::write_plugin_log(&format!($($arg)*)));
263    };
264    (info, $($arg:tt)*) => {
265        ::std::mem::drop($crate::logger::write_info_log(&format!($($arg)*)));
266    };
267    (warn, $($arg:tt)*) => {
268        ::std::mem::drop($crate::logger::write_warn_log(&format!($($arg)*)));
269    };
270    (error, $($arg:tt)*) => {
271        ::std::mem::drop($crate::logger::write_error_log(&format!($($arg)*)));
272    };
273    (verbose, $($arg:tt)*) => {
274        ::std::mem::drop($crate::logger::write_verbose_log(&format!($($arg)*)));
275    };
276    ($($arg:tt)*) => {
277        $crate::lprintln!(plugin, $($arg)*);
278    };
279}
280
281#[cfg(feature = "wrap_log")]
282fn log_length_limit(kind_length: usize) -> usize {
283    static DLL_LENGTH: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
284    let dll_length = *DLL_LENGTH.get_or_init(|| {
285        process_path::get_dylib_path()
286            .map_or(0, |path| path.file_name().unwrap().to_string_lossy().len())
287    });
288    // [01/23 08:43:47] [VERBOSE] [Plugin::vi5.aux2] ...
289    1023 - 35 - dll_length - kind_length
290}
291#[cfg(not(feature = "wrap_log"))]
292fn log_length_limit(_kind_length: usize) -> usize {
293    // wrap_logが無効な場合は制限なし
294    usize::MAX
295}
296
297fn split_into_chunks(message: &str, kind_length: usize) -> Vec<String> {
298    // 二分探索みたいなことをすればもっと効率的にできるけど面倒なので...
299    let log_length_limit = log_length_limit(kind_length);
300    let mut chunks = Vec::with_capacity(message.len() / log_length_limit + 1);
301    let mut current_chunk = String::new();
302    for letter in message.chars() {
303        let letter_len = letter.len_utf8();
304        if current_chunk.len() + letter_len > log_length_limit {
305            chunks.push(std::mem::take(&mut current_chunk));
306        }
307        current_chunk.push(letter);
308    }
309    if !current_chunk.is_empty() {
310        chunks.push(current_chunk);
311    }
312    chunks
313}
314
315/// プラグイン用ログにメッセージを書き込みます。
316///
317/// # Note
318///
319/// ロガーが初期化されていない場合は何も行いません。
320///
321/// # See Also
322///
323/// - [`ldbg!`]
324/// - [`lprintln!`]
325pub fn write_plugin_log(message: &str) -> Result<(), NullByteError> {
326    with_logger_handle(|handle| unsafe {
327        for chunk in split_into_chunks(message, "PLUGIN".len()) {
328            let wide_message = CWString::new(&chunk)?;
329            ((*handle).log)(handle, wide_message.as_ptr());
330        }
331        Ok(())
332    })
333    .unwrap_or(Ok(()))
334}
335
336#[duplicate::duplicate_item(
337    level       function_name       log_method;
338    ["ERROR"]   [write_error_log]   [error];
339    ["WARN"]    [write_warn_log]    [warn];
340    ["INFO"]    [write_info_log]    [info];
341    ["VERBOSE"] [write_verbose_log] [verbose];
342)]
343#[doc = concat!("ログに", level, "レベルのメッセージを書き込みます。")]
344///
345/// # Note
346///
347/// ロガーが初期化されていない場合は何も行いません。
348///
349/// # See Also
350///
351/// - [`ldbg!`]
352/// - [`lprintln!`]
353pub fn function_name(message: &str) -> Result<(), NullByteError> {
354    with_logger_handle(|handle| unsafe {
355        for chunk in split_into_chunks(message, level.len()) {
356            let wide_message = CWString::new(&chunk)?;
357            ((*handle).log_method)(handle, wide_message.as_ptr());
358        }
359        Ok(())
360    })
361    .unwrap_or(Ok(()))
362}
363
364struct InternalLoggerHandle(*mut aviutl2_sys::logger2::LOG_HANDLE);
365unsafe impl Send for InternalLoggerHandle {}
366
367static LOGGER_HANDLE: std::sync::OnceLock<std::sync::Mutex<InternalLoggerHandle>> =
368    std::sync::OnceLock::new();
369
370#[doc(hidden)]
371pub fn __initialize_logger(handle: *mut aviutl2_sys::logger2::LOG_HANDLE) {
372    let internal_handle = InternalLoggerHandle(handle);
373    LOGGER_HANDLE
374        .set(std::sync::Mutex::new(internal_handle))
375        .unwrap_or_else(|_| {
376            panic!("Logger has already been initialized");
377        });
378}
379
380#[doc(hidden)]
381pub fn __initialize_logger_unwind(handle: *mut aviutl2_sys::logger2::LOG_HANDLE) {
382    if let Err(panic_info) =
383        crate::utils::catch_unwind_with_panic_info(|| __initialize_logger(handle))
384    {
385        crate::tracing::error!("Panic occurred during InitializeLogger: {}", panic_info);
386        let _ = crate::logger::write_error_log(&panic_info);
387    }
388}
389
390impl InternalLoggerHandle {
391    fn ptr(&self) -> *mut aviutl2_sys::logger2::LOG_HANDLE {
392        self.0
393    }
394}
395
396fn with_logger_handle<F, T>(f: F) -> Option<T>
397where
398    F: FnOnce(*mut aviutl2_sys::logger2::LOG_HANDLE) -> T,
399{
400    let handle = LOGGER_HANDLE.get()?;
401    let handle = handle.lock().unwrap();
402    let handle_ptr = handle.ptr();
403    Some(f(handle_ptr))
404}
405
406#[cfg(test)]
407mod tests {
408    #[test]
409    fn test_can_compile_ldbg() {
410        let x = 42;
411        ldbg!();
412        ldbg!(x);
413        ldbg!(x + 1, x * 2);
414    }
415
416    #[test]
417    fn test_can_compile_lprintln() {
418        lprintln!("This is a test log message.");
419        lprintln!(info, "This is an info log message.");
420        lprintln!(warn, "This is a warning log message.");
421        lprintln!(error, "This is an error log message.");
422        lprintln!(verbose, "This is a verbose log message.");
423    }
424
425    #[test]
426    #[cfg(feature = "wrap_log")]
427    fn test_split_into_chunks() {
428        let message = "a".repeat(5000);
429        let chunks = super::split_into_chunks(&message, "VERBOSE".len());
430        let dylib_name = process_path::get_dylib_path()
431            .and_then(|path| {
432                path.file_name()
433                    .map(|name| name.to_string_lossy().into_owned())
434            })
435            .unwrap();
436        for chunk in chunks {
437            assert!(chunk.len() <= super::log_length_limit("VERBOSE".len()));
438            assert!(
439                format!(
440                    "[01/23 08:43:47] [VERBOSE] [Plugin::{}] {}",
441                    dylib_name, chunk
442                )
443                .len()
444                    <= 1023
445            );
446        }
447    }
448}