aboutsummaryrefslogtreecommitdiff
path: root/src/s_log.rs
diff options
context:
space:
mode:
authorHazel Atkinson <yellowsink@riseup.net>2025-04-09 21:01:51 +0100
committerHazel Atkinson <yellowsink@riseup.net>2025-04-09 21:02:11 +0100
commit31f94168f7625f6dd9f13ef97165ae7c9d2a4ab4 (patch)
treeefab3dc920fc8763cbfff62ee8055ef7d2f03e20 /src/s_log.rs
parentbf0bb179554aedf61aa0212fe2b5c489e4d5da05 (diff)
downloadcontainerspy-31f94168f7625f6dd9f13ef97165ae7c9d2a4ab4.tar.gz
containerspy-31f94168f7625f6dd9f13ef97165ae7c9d2a4ab4.tar.bz2
containerspy-31f94168f7625f6dd9f13ef97165ae7c9d2a4ab4.zip
add structured logging, fix docker exit code
Diffstat (limited to 'src/s_log.rs')
-rw-r--r--src/s_log.rs95
1 files changed, 95 insertions, 0 deletions
diff --git a/src/s_log.rs b/src/s_log.rs
new file mode 100644
index 0000000..16b4726
--- /dev/null
+++ b/src/s_log.rs
@@ -0,0 +1,95 @@
+// containerspy structured logger
+
+use std::fmt::{Display, Formatter};
+use chrono::Utc;
+
+#[allow(dead_code)]
+pub fn debug<'a>(args: impl Display, rich: impl IntoIterator<Item = (&'a str, &'a str)>) {
+ log_impl(LogLevel::Debug, args.to_string().as_str(), rich);
+}
+
+pub fn info<'a>(args: impl Display, rich: impl IntoIterator<Item = (&'a str, &'a str)>) {
+ log_impl(LogLevel::Info, args.to_string().as_str(), rich);
+}
+
+#[allow(dead_code)]
+pub fn warn<'a>(args: impl Display, rich: impl IntoIterator<Item = (&'a str, &'a str)>) {
+ log_impl(LogLevel::Warn, args.to_string().as_str(), rich);
+}
+
+#[allow(dead_code)]
+pub fn error<'a>(args: impl Display, rich: impl IntoIterator<Item = (&'a str, &'a str)>) {
+ log_impl(LogLevel::Error, args.to_string().as_str(), rich);
+}
+
+#[allow(dead_code)]
+pub fn fatal<'a>(args: impl Display, rich: impl IntoIterator<Item = (&'a str, &'a str)>) {
+ log_impl(LogLevel::Fatal, args.to_string().as_str(), rich);
+}
+
+enum LogLevel {
+ Fatal,
+ Error,
+ Warn,
+ Info,
+ Debug,
+}
+
+impl Display for LogLevel {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.write_str(
+ match self {
+ LogLevel::Fatal => "fatal",
+ LogLevel::Error => "error",
+ LogLevel::Warn => "warn",
+ LogLevel::Info => "info",
+ LogLevel::Debug => "debug",
+ }
+ )
+ }
+}
+
+fn log_impl<'a>(level: LogLevel, msg: &str, rich: impl IntoIterator<Item = (&'a str, &'a str)>) {
+ let time = Utc::now();
+ let nice_time = time.format("%F %X%.3f");
+ let full_time = time.format("%+");
+
+ let mut final_rich = vec![
+ ("ts", full_time.to_string()),
+ ("level", level.to_string()),
+ ("msg", msg.to_string())
+ ];
+
+ // i don't care anymore, just clone it all temporarily.
+ final_rich.extend(rich.into_iter().map(|(a, b)| (a, b.to_string())));
+
+ let mut buf = format!("{nice_time}");
+ for (k, v) in final_rich {
+ if needs_escaping(k) {
+ continue;
+ }
+
+ if needs_escaping(&v) {
+ buf += &format!(" {k}=\"{}\"", escape(&v));
+ } else {
+ buf += &format!(" {k}={v}");
+ }
+ }
+
+ println!("{buf}");
+}
+
+static SAFE_ALPHABET: &str = r#"abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_+.,/\\|!@#$%^&*()[]{}"#;
+
+fn needs_escaping(val: &str) -> bool {
+ for char in val.chars() {
+ if !SAFE_ALPHABET.contains(char) {
+ return true
+ }
+ }
+ false
+}
+
+fn escape(val: &str) -> String {
+ val.replace("\n", "\\n").replace("\\", "\\\\").replace("\"", "\\\"")
+} \ No newline at end of file