use mlua::{Lua, LuaSerdeExt, Result as LuaResult, Table, Value};
const VERSION: &str = "0.1.0";
const DESCRIPTION: &str = "Lua logging bridge to Rust tracing";
const MAX_TABLE_ELEMENTS: usize = 1000;
const MAX_NESTING_DEPTH: usize = 10;
#[derive(Debug, Clone, Default)]
struct LuaCallerInfo {
source: String,
line: usize,
fn_name: String,
}
fn get_caller_info(lua: &Lua) -> LuaCallerInfo {
lua.inspect_stack(1, |debug| {
let source_info = debug.source();
let source = source_info
.short_src
.map(|s| s.to_string())
.unwrap_or_default();
let line = debug.current_line().unwrap_or(0);
let names = debug.names();
let fn_name = names.name.map(|s| s.to_string()).unwrap_or_default();
LuaCallerInfo {
source,
line,
fn_name,
}
})
.unwrap_or_default()
}
fn value_to_string(lua: &Lua, value: Value) -> String {
match value {
Value::Nil => String::new(),
Value::Boolean(b) => b.to_string(),
Value::Integer(i) => i.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => s.to_string_lossy().to_string(),
Value::Table(ref t) => table_to_string(lua, t, &value),
_ => lua_tostring(lua, &value),
}
}
fn table_to_string(lua: &Lua, table: &Table, original_value: &Value) -> String {
let element_count = count_table_elements(table);
if element_count > MAX_TABLE_ELEMENTS {
return format!("<table: {} elements>", element_count);
}
let deserialize_options = mlua::DeserializeOptions::default().deny_recursive_tables(true);
match lua.from_value_with::<serde_json::Value>(original_value.clone(), deserialize_options) {
Ok(json_value) => {
if json_depth(&json_value) > MAX_NESTING_DEPTH {
return lua_tostring(lua, original_value);
}
match serde_json::to_string(&json_value) {
Ok(s) => s,
Err(_) => lua_tostring(lua, original_value),
}
}
Err(_) => lua_tostring(lua, original_value),
}
}
fn count_table_elements(table: &Table) -> usize {
table.pairs::<Value, Value>().count()
}
fn json_depth(value: &serde_json::Value) -> usize {
match value {
serde_json::Value::Array(arr) => 1 + arr.iter().map(json_depth).max().unwrap_or(0),
serde_json::Value::Object(obj) => 1 + obj.values().map(json_depth).max().unwrap_or(0),
_ => 0,
}
}
fn lua_tostring(lua: &Lua, value: &Value) -> String {
let tostring: Result<mlua::Function, _> = lua.globals().get("tostring");
match tostring {
Ok(func) => match func.call::<String>(value.clone()) {
Ok(s) => s,
Err(_) => "<unconvertible value>".to_string(),
},
Err(_) => "<unconvertible value>".to_string(),
}
}
pub fn register(lua: &Lua) -> LuaResult<Table> {
let module = lua.create_table()?;
module.set("_VERSION", VERSION)?;
module.set("_DESCRIPTION", DESCRIPTION)?;
module.set("trace", lua.create_function(log_trace)?)?;
module.set("debug", lua.create_function(log_debug)?)?;
module.set("info", lua.create_function(log_info)?)?;
module.set("warn", lua.create_function(log_warn)?)?;
module.set("error", lua.create_function(log_error)?)?;
Ok(module)
}
fn log_trace(lua: &Lua, value: Value) -> LuaResult<()> {
let msg = value_to_string(lua, value);
let caller = get_caller_info(lua);
tracing::trace!(
lua_source = %caller.source,
lua_line = caller.line,
lua_fn = %caller.fn_name,
"{}",
msg
);
Ok(())
}
fn log_debug(lua: &Lua, value: Value) -> LuaResult<()> {
let msg = value_to_string(lua, value);
let caller = get_caller_info(lua);
tracing::debug!(
lua_source = %caller.source,
lua_line = caller.line,
lua_fn = %caller.fn_name,
"{}",
msg
);
Ok(())
}
fn log_info(lua: &Lua, value: Value) -> LuaResult<()> {
let msg = value_to_string(lua, value);
let caller = get_caller_info(lua);
tracing::info!(
lua_source = %caller.source,
lua_line = caller.line,
lua_fn = %caller.fn_name,
"{}",
msg
);
Ok(())
}
fn log_warn(lua: &Lua, value: Value) -> LuaResult<()> {
let msg = value_to_string(lua, value);
let caller = get_caller_info(lua);
tracing::warn!(
lua_source = %caller.source,
lua_line = caller.line,
lua_fn = %caller.fn_name,
"{}",
msg
);
Ok(())
}
fn log_error(lua: &Lua, value: Value) -> LuaResult<()> {
let msg = value_to_string(lua, value);
let caller = get_caller_info(lua);
tracing::error!(
lua_source = %caller.source,
lua_line = caller.line,
lua_fn = %caller.fn_name,
"{}",
msg
);
Ok(())
}