Skip to content
Snippets Groups Projects
Commit f3364158 authored by Dmitry Ivanov's avatar Dmitry Ivanov
Browse files

feat(audit): change audit log format from plain to json

This patch changes the way audit records are formatted. Previously
we would format them as plain strings (we still do that in tlog),
now we use json to store all KV pairs including the message.

TODO: optimize string allocations using a stream formatter.

Example:

```
{
  "time": "2023-11-16T22:48:08.297+0300",
  "level": "WARN",
  "auth_type": "chap-sha1",
  "message": "created user `idris`",
  "title": "create_user",
  "pid": 66625,
  "cord_name": "main",
  "fiber_id": 111,
  "fiber_name": "raft_main_loop",
  "file": "src/storage.rs",
  "line": 2633
}
```
parent c42d0ba3
No related branches found
No related tags found
1 merge request!738feat(audit): change audit log format from plain to json
Pipeline #27760 passed
......@@ -36,6 +36,14 @@ mod ffi {
/// NOTE: this does not reclaim the underlying memory.
pub fn log_destroy(log: *mut Log);
/// Set log format by name specified as cstring.
/// Known good names: "plain", "json".
/// Returns 0 in case of success, -1 otherwise.
pub fn log_set_format_by_name(
log: *mut Log,
format: *const core::ffi::c_char,
) -> core::ffi::c_int;
/// Emit a new log entry.
/// This function uses `printf`-ish calling convention.
pub fn log_say(
......@@ -46,7 +54,7 @@ mod ffi {
error: *const core::ffi::c_char,
format: *const core::ffi::c_char,
...
) -> ::core::ffi::c_int;
) -> core::ffi::c_int;
}
}
......@@ -71,6 +79,11 @@ impl Log {
return Err(TarantoolError::last());
}
// SAFETY: this call is safe as long as the log object is initialized
// (per above) and the format name is a proper nul-terminated cstring.
let res = unsafe { ffi::log_set_format_by_name(log, tarantool::c_ptr!("json")) };
assert_eq!(res, 0, "failed to set log's format to json");
Ok(Self(log))
}
}
......@@ -99,7 +112,7 @@ impl slog::Drain for Log {
let line = record.line() as core::ffi::c_int;
// Format the message using tlog's capabilities.
let msg = tlog::StrSerializer::format_message(record, values);
let msg = tlog::JsonSerializer::format_message(record, values);
let msg = CString::new(msg).unwrap();
// SAFETY: All arguments' invariants have already been checked.
......@@ -110,6 +123,10 @@ impl slog::Drain for Log {
file.as_ptr(),
line,
std::ptr::null(),
// The special "json" format string causes `say_format_json`
// to think that the message is already json-formatted.
// This is exactly how tarantool's lua `log` implements json fmt.
tarantool::c_ptr!("json"),
msg.as_ptr(),
);
}
......
......@@ -2,7 +2,7 @@ use ::tarantool::log::{say, SayLevel};
use once_cell::sync::Lazy;
use std::collections::HashMap;
/// For mapping one loggging level to another.
/// For mapping one logging level to another.
/// This helps circumvent rust's coherence & orphan rules.
pub struct MapLevel<T>(pub T);
......@@ -61,6 +61,8 @@ pub fn highlight_key(key: impl Into<String> + AsRef<str>, color: Option<Color>)
}
}
/// A helper for serializing slog's record to plain string.
/// We use this for picodata's regular log (tlog).
pub struct StrSerializer {
str: String,
}
......@@ -82,6 +84,7 @@ impl slog::Serializer for StrSerializer {
pub static mut DONT_LOG_KV_FOR_NEXT_SENDING_FROM: bool = false;
impl StrSerializer {
/// Format slog's record as plain string. Most suitable for implementing [`slog::Drain`].
pub fn format_message(record: &slog::Record, values: &slog::OwnedKVList) -> String {
let mut s = StrSerializer {
str: format!("{}", record.msg()),
......@@ -111,6 +114,40 @@ impl StrSerializer {
}
}
/// A helper for serializing slog's record to json.
/// We use this for picodata's audit log.
pub struct JsonSerializer {
map: serde_json::Map<String, serde_json::Value>,
}
impl slog::Serializer for JsonSerializer {
fn emit_arguments(&mut self, key: slog::Key, val: &std::fmt::Arguments) -> slog::Result {
// TODO: optimize excessive string allocations here and below.
self.map.insert(key.to_string(), val.to_string().into());
Ok(())
}
}
impl JsonSerializer {
/// Format slog's record as json. Most suitable for implementing [`slog::Drain`].
pub fn format_message(record: &slog::Record, values: &slog::OwnedKVList) -> String {
let mut s = JsonSerializer {
map: serde_json::Map::new(),
};
let message = record.msg().to_string();
s.map.insert("message".into(), message.into());
use slog::KV;
// It's safe to use .unwrap() here since
// JsonSerializer doesn't return anything but Ok()
record.kv().serialize(record, &mut s).unwrap();
values.serialize(record, &mut s).unwrap();
serde_json::Value::from(s.map).to_string()
}
}
/// A default root suitable for logging all around the project.
pub fn root() -> &'static slog::Logger {
static ROOT: Lazy<slog::Logger> = Lazy::new(|| slog::Logger::root(Drain, slog::o!()));
......
tarantool-sys @ 004ceffa
Subproject commit 317a5cc1cec4351038f1bd3b376081d755c64431
Subproject commit 004ceffaf43974a79b18ca0c514301f661c43050
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment