Skip to content

Commit d88fa7f

Browse files
authored
Merge pull request #789 from vlovich/fix-log-panic
Fix panic introduced upstream if debug log level is disabled
2 parents 446118d + 420c302 commit d88fa7f

File tree

4 files changed

+134
-7
lines changed

4 files changed

+134
-7
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

llama-cpp-2/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ tracing-core = { workspace = true }
1717

1818
[dev-dependencies]
1919
encoding_rs = { workspace = true }
20+
tracing-subscriber = { workspace = true }
2021

2122
[features]
2223
default = ["openmp", "android-shared-stdcxx"]

llama-cpp-2/src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,19 +375,20 @@ extern "C" fn logs_to_trace(
375375

376376
let log_state = unsafe { &*(data as *const log::State) };
377377

378+
if log_state.options.disabled {
379+
return;
380+
}
381+
378382
// If the log level is disabled, we can just return early
379383
if !log_state.is_enabled_for_level(level) {
384+
log_state.update_previous_level_for_disabled_log(level);
380385
return;
381386
}
382387

383388
let text = unsafe { std::ffi::CStr::from_ptr(text) };
384389
let text = text.to_string_lossy();
385390
let text: &str = text.borrow();
386391

387-
if log_state.options.disabled {
388-
return;
389-
}
390-
391392
// As best I can tell llama.cpp / ggml require all log format strings at call sites to have the '\n'.
392393
// If it's missing, it means that you expect more logs via CONT (or there's a typo in the codebase). To
393394
// distinguish typo from intentional support for CONT, we have to buffer until the next message comes in

llama-cpp-2/src/log.rs

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,16 +252,140 @@ impl State {
252252
}
253253
}
254254

255+
pub(super) fn update_previous_level_for_disabled_log(
256+
&self,
257+
level: llama_cpp_sys_2::ggml_log_level,
258+
) {
259+
if level != llama_cpp_sys_2::GGML_LOG_LEVEL_CONT {
260+
self.previous_level
261+
.store(level as i32, std::sync::atomic::Ordering::Release);
262+
}
263+
}
264+
255265
/// Checks whether the given log level is enabled by the current tracing subscriber.
256266
pub(super) fn is_enabled_for_level(&self, level: llama_cpp_sys_2::ggml_log_level) -> bool {
257267
// CONT logs do not need to check if they are enabled.
258-
if level == llama_cpp_sys_2::GGML_LOG_LEVEL_CONT {
259-
return true;
260-
}
268+
let level = if level == llama_cpp_sys_2::GGML_LOG_LEVEL_CONT {
269+
self.previous_level
270+
.load(std::sync::atomic::Ordering::Relaxed)
271+
as llama_cpp_sys_2::ggml_log_level
272+
} else {
273+
level
274+
};
261275
let (meta, _) = meta_for_level(level);
262276
tracing::dispatcher::get_default(|dispatcher| dispatcher.enabled(meta))
263277
}
264278
}
265279

266280
pub(super) static LLAMA_STATE: OnceLock<Box<State>> = OnceLock::new();
267281
pub(super) static GGML_STATE: OnceLock<Box<State>> = OnceLock::new();
282+
283+
#[cfg(test)]
284+
mod tests {
285+
use crate::logs_to_trace;
286+
use std::sync::{Arc, Mutex};
287+
use tracing::subscriber::DefaultGuard;
288+
use tracing_subscriber::util::SubscriberInitExt;
289+
290+
use super::*;
291+
292+
struct Logger {
293+
#[allow(unused)]
294+
guard: DefaultGuard,
295+
logs: Arc<Mutex<Vec<String>>>,
296+
}
297+
298+
#[derive(Clone)]
299+
struct VecWriter(Arc<Mutex<Vec<String>>>);
300+
301+
impl std::io::Write for VecWriter {
302+
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
303+
let log_line = String::from_utf8(buf.to_vec()).map_err(|_| {
304+
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid UTF-8")
305+
})?;
306+
self.0.lock().unwrap().push(log_line);
307+
Ok(buf.len())
308+
}
309+
310+
fn flush(&mut self) -> std::io::Result<()> {
311+
Ok(())
312+
}
313+
}
314+
315+
fn create_logger(max_level: tracing::Level) -> Logger {
316+
let logs = Arc::new(Mutex::new(vec![]));
317+
let writer = VecWriter(logs.clone());
318+
319+
Logger {
320+
guard: tracing_subscriber::fmt()
321+
.with_max_level(max_level)
322+
.with_ansi(false)
323+
.without_time()
324+
.with_file(false)
325+
.with_line_number(false)
326+
.with_level(false)
327+
.with_target(false)
328+
.with_writer(move || writer.clone())
329+
.finish()
330+
.set_default(),
331+
logs,
332+
}
333+
}
334+
335+
#[test]
336+
fn cont_disabled_log() {
337+
let logger = create_logger(tracing::Level::INFO);
338+
let mut log_state = Box::new(State::new(Module::LlamaCpp, LogOptions::default()));
339+
let log_ptr = log_state.as_mut() as *mut State as *mut std::os::raw::c_void;
340+
341+
logs_to_trace(
342+
llama_cpp_sys_2::GGML_LOG_LEVEL_DEBUG,
343+
c"Hello ".as_ptr(),
344+
log_ptr,
345+
);
346+
logs_to_trace(
347+
llama_cpp_sys_2::GGML_LOG_LEVEL_CONT,
348+
c"world\n".as_ptr(),
349+
log_ptr,
350+
);
351+
352+
assert!(logger.logs.lock().unwrap().is_empty());
353+
354+
logs_to_trace(
355+
llama_cpp_sys_2::GGML_LOG_LEVEL_DEBUG,
356+
c"Hello ".as_ptr(),
357+
log_ptr,
358+
);
359+
logs_to_trace(
360+
llama_cpp_sys_2::GGML_LOG_LEVEL_CONT,
361+
c"world".as_ptr(),
362+
log_ptr,
363+
);
364+
logs_to_trace(
365+
llama_cpp_sys_2::GGML_LOG_LEVEL_CONT,
366+
c"\n".as_ptr(),
367+
log_ptr,
368+
);
369+
}
370+
371+
#[test]
372+
fn cont_enabled_log() {
373+
let logger = create_logger(tracing::Level::INFO);
374+
let mut log_state = Box::new(State::new(Module::LlamaCpp, LogOptions::default()));
375+
let log_ptr = log_state.as_mut() as *mut State as *mut std::os::raw::c_void;
376+
377+
logs_to_trace(
378+
llama_cpp_sys_2::GGML_LOG_LEVEL_INFO,
379+
c"Hello ".as_ptr(),
380+
log_ptr,
381+
);
382+
logs_to_trace(
383+
llama_cpp_sys_2::GGML_LOG_LEVEL_CONT,
384+
c"world\n".as_ptr(),
385+
log_ptr,
386+
);
387+
388+
// Not sure where the extra \n comes from.
389+
assert_eq!(*logger.logs.lock().unwrap(), vec!["Hello world\n\n"]);
390+
}
391+
}

0 commit comments

Comments
 (0)