From 4759d6011464065843e977e80a05592b1fa58ec9 Mon Sep 17 00:00:00 2001 From: wasup-yash Date: Tue, 10 Oct 2023 21:59:06 +0530 Subject: [PATCH] Updated JsonLogger and its Tests Signed-off-by: wasup-yash Signed-off-by: Sascha Grunert --- .gitignore | 1 + Cargo.lock | 18 ++ conmon-rs/common/proto/conmon.capnp | 2 + conmon-rs/server/Cargo.toml | 1 + conmon-rs/server/src/container_log.rs | 74 +++-- conmon-rs/server/src/json_logger.rs | 182 +++++++++++ conmon-rs/server/src/lib.rs | 1 + internal/proto/conmon.capnp.go | 437 +++++++++++++------------- pkg/client/client.go | 4 + pkg/client/client_test.go | 43 +++ 10 files changed, 523 insertions(+), 240 deletions(-) create mode 100644 conmon-rs/server/src/json_logger.rs diff --git a/.gitignore b/.gitignore index ca61ab07d8..2fc85f5b36 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ .build latest-*.txt /*.tar.gz +.vscode diff --git a/Cargo.lock b/Cargo.lock index eec7beaec9..c768a3ad9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,6 +358,7 @@ dependencies = [ "regex", "sendfd", "serde", + "serde_json", "shadow-rs", "signal-hook", "strum", @@ -1702,6 +1703,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + [[package]] name = "same-file" version = "1.0.6" @@ -1747,6 +1754,17 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.8" diff --git a/conmon-rs/common/proto/conmon.capnp b/conmon-rs/common/proto/conmon.capnp index 2f78088a78..a5b3ded083 100644 --- a/conmon-rs/common/proto/conmon.capnp +++ b/conmon-rs/common/proto/conmon.capnp @@ -58,6 +58,8 @@ interface Conmon { enum Type { # The CRI logger, requires `path` to be set. containerRuntimeInterface @0; + # The JSON logger, requires `path` to be set. + json @1; } } diff --git a/conmon-rs/server/Cargo.toml b/conmon-rs/server/Cargo.toml index 574f92b7be..0aa5a17f14 100644 --- a/conmon-rs/server/Cargo.toml +++ b/conmon-rs/server/Cargo.toml @@ -32,6 +32,7 @@ prctl = "1.0.0" regex = "1.10.2" sendfd = { version = "0.4.3", features = ["tokio"] } serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" shadow-rs = "0.24.1" signal-hook = "0.3.17" strum = { version = "0.25.0", features = ["derive"] } diff --git a/conmon-rs/server/src/container_log.rs b/conmon-rs/server/src/container_log.rs index fff7f7ce20..5f287358ba 100644 --- a/conmon-rs/server/src/container_log.rs +++ b/conmon-rs/server/src/container_log.rs @@ -1,8 +1,8 @@ -use crate::{container_io::Pipe, cri_logger::CriLogger}; +use crate::{container_io::Pipe, cri_logger::CriLogger, json_logger::JsonLogger}; use anyhow::Result; use capnp::struct_list::Reader; use conmon_common::conmon_capnp::conmon::log_driver::{Owned, Type}; -use futures::future::join_all; +use futures::{future::join_all, FutureExt}; use std::sync::Arc; use tokio::{io::AsyncBufRead, sync::RwLock}; @@ -16,6 +16,7 @@ pub struct ContainerLog { #[derive(Debug)] enum LogDriver { ContainerRuntimeInterface(CriLogger), + Json(JsonLogger), } impl ContainerLog { @@ -24,25 +25,32 @@ impl ContainerLog { Arc::new(RwLock::new(Self::default())) } - /// Create a new SharedContainerLog from an capnp owned reader. pub fn from(reader: Reader) -> Result { let drivers = reader .iter() - .flat_map(|x| -> Result<_> { - Ok(match x.get_type()? { + .map(|x| -> Result<_> { + match x.get_type()? { Type::ContainerRuntimeInterface => { - LogDriver::ContainerRuntimeInterface(CriLogger::new( + Ok(LogDriver::ContainerRuntimeInterface(CriLogger::new( x.get_path()?, if x.get_max_size() > 0 { Some(x.get_max_size() as usize) } else { None }, - )?) + )?)) } - }) + Type::Json => Ok(LogDriver::Json(JsonLogger::new( + x.get_path()?, + if x.get_max_size() > 0 { + Some(x.get_max_size() as usize) + } else { + None + }, + )?)), + } }) - .collect(); + .collect::>>()?; Ok(Arc::new(RwLock::new(Self { drivers }))) } @@ -52,7 +60,10 @@ impl ContainerLog { self.drivers .iter_mut() .map(|x| match x { - LogDriver::ContainerRuntimeInterface(ref mut cri_logger) => cri_logger.init(), + LogDriver::ContainerRuntimeInterface(ref mut cri_logger) => { + cri_logger.init().boxed() + } + LogDriver::Json(ref mut json_logger) => json_logger.init().boxed(), }) .collect::>(), ) @@ -68,7 +79,10 @@ impl ContainerLog { self.drivers .iter_mut() .map(|x| match x { - LogDriver::ContainerRuntimeInterface(ref mut cri_logger) => cri_logger.reopen(), + LogDriver::ContainerRuntimeInterface(ref mut cri_logger) => { + cri_logger.reopen().boxed() + } + LogDriver::Json(ref mut json_logger) => json_logger.reopen().boxed(), }) .collect::>(), ) @@ -81,21 +95,33 @@ impl ContainerLog { /// Write the contents of the provided reader into all loggers. pub async fn write(&mut self, pipe: Pipe, bytes: T) -> Result<()> where - T: AsyncBufRead + Unpin + Copy, + T: AsyncBufRead + Unpin + Clone, { - join_all( - self.drivers - .iter_mut() - .map(|x| match x { - LogDriver::ContainerRuntimeInterface(ref mut cri_logger) => { - cri_logger.write(pipe, bytes) + let futures = self + .drivers + .iter_mut() + .map(|x| { + async fn box_future<'a, T: AsyncBufRead + Unpin + Clone>( + logger: &mut LogDriver, + pipe: Pipe, + bytes: T, + ) -> Result<()> { + match logger { + LogDriver::ContainerRuntimeInterface(cri_logger) => { + cri_logger.write(pipe, bytes).await + } + LogDriver::Json(json_logger) => json_logger.write(pipe, bytes).await, } - }) - .collect::>(), - ) - .await - .into_iter() - .collect::>>()?; + } + + box_future(x, pipe, bytes.clone()) + }) + .collect::>(); + + join_all(futures) + .await + .into_iter() + .collect::>>()?; Ok(()) } } diff --git a/conmon-rs/server/src/json_logger.rs b/conmon-rs/server/src/json_logger.rs new file mode 100644 index 0000000000..211f1f83cd --- /dev/null +++ b/conmon-rs/server/src/json_logger.rs @@ -0,0 +1,182 @@ +use crate::container_io::Pipe; +use anyhow::{Context, Result}; +use getset::{CopyGetters, Getters, Setters}; +use serde_json::json; +use std::{ + marker::Unpin, + path::{Path, PathBuf}, +}; +use tokio::{ + fs::{File, OpenOptions}, + io::{AsyncBufRead, AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}, +}; +use tracing::debug; + +#[derive(Debug, CopyGetters, Getters, Setters)] +pub struct JsonLogger { + #[getset(get)] + path: PathBuf, + + #[getset(set)] + file: Option>, + + #[getset(get_copy)] + max_log_size: Option, + + #[getset(get_copy, set)] + bytes_written: usize, +} + +impl JsonLogger { + const ERR_UNINITIALIZED: &'static str = "logger not initialized"; + + pub fn new>(path: T, max_log_size: Option) -> Result { + Ok(Self { + path: path.as_ref().into(), + file: None, + max_log_size, + bytes_written: 0, + }) + } + + pub async fn init(&mut self) -> Result<()> { + debug!("Initializing JSON logger in path {}", self.path().display()); + self.set_file(Self::open(self.path()).await?.into()); + Ok(()) + } + + pub async fn write(&mut self, pipe: Pipe, bytes: T) -> Result<()> + where + T: AsyncBufRead + Unpin, + { + let mut reader = BufReader::new(bytes); + let mut line_buf = Vec::new(); + + while reader.read_until(b'\n', &mut line_buf).await? > 0 { + let log_entry = json!({ + "timestamp": format!("{:?}", std::time::SystemTime::now()), + "pipe": match pipe { + Pipe::StdOut => "stdout", + Pipe::StdErr => "stderr", + }, + "message": String::from_utf8_lossy(&line_buf).trim().to_string() + }); + + let log_str = log_entry.to_string(); + let bytes = log_str.as_bytes(); + self.bytes_written += bytes.len(); + + if let Some(max_size) = self.max_log_size { + if self.bytes_written > max_size { + self.reopen().await?; + self.bytes_written = 0; + } + } + + let file = self.file.as_mut().context(Self::ERR_UNINITIALIZED)?; + file.write_all(bytes).await?; + file.write_all(b"\n").await?; + self.flush().await?; + line_buf.clear(); + } + + Ok(()) + } + + pub async fn reopen(&mut self) -> Result<()> { + debug!("Reopen JSON log {}", self.path().display()); + self.file + .as_mut() + .context(Self::ERR_UNINITIALIZED)? + .get_ref() + .sync_all() + .await?; + self.init().await + } + + pub async fn flush(&mut self) -> Result<()> { + self.file + .as_mut() + .context(Self::ERR_UNINITIALIZED)? + .flush() + .await + .context("flush file writer") + } + + async fn open>(path: T) -> Result> { + Ok(BufWriter::new( + OpenOptions::new() + .create(true) + .read(true) + .truncate(true) + .write(true) + .open(&path) + .await + .context(format!("open log file path '{}'", path.as_ref().display()))?, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + use tokio::io::AsyncReadExt; + + #[tokio::test] + async fn test_json_logger_new() { + let logger = JsonLogger::new("/tmp/test.log", Some(1000)).unwrap(); + assert_eq!(logger.path().to_str().unwrap(), "/tmp/test.log"); + assert_eq!(logger.max_log_size().unwrap(), 1000); + } + + #[tokio::test] + async fn test_json_logger_init() { + let mut logger = JsonLogger::new("/tmp/test_init.log", Some(1000)).unwrap(); + logger.init().await.unwrap(); + assert!(logger.file.is_some()); + } + + #[tokio::test] + async fn test_json_logger_write() { + let mut logger = JsonLogger::new("/tmp/test_write.log", Some(1000)).unwrap(); + logger.init().await.unwrap(); + + let cursor = Cursor::new(b"Test log message\n".to_vec()); + logger.write(Pipe::StdOut, cursor).await.unwrap(); + + // Read back from the file + let mut file = File::open("/tmp/test_write.log").await.unwrap(); + let mut contents = String::new(); + file.read_to_string(&mut contents).await.unwrap(); + + // Check if the file contains the logged message + assert!(contents.contains("Test log message")); + } + + #[tokio::test] + async fn test_json_logger_reopen() { + let mut logger = JsonLogger::new("/tmp/test_reopen.log", Some(1000)).unwrap(); + logger.init().await.unwrap(); + + // Write to the file + let cursor = Cursor::new(b"Test log message before reopen\n".to_vec()); + logger.write(Pipe::StdOut, cursor).await.unwrap(); + + // Reopen the file + logger.reopen().await.unwrap(); + + // Write to the file again + let cursor = Cursor::new(b"Test log message after reopen\n".to_vec()); + logger.write(Pipe::StdOut, cursor).await.unwrap(); + + // Read back from the file + let mut file = File::open("/tmp/test_reopen.log").await.unwrap(); + let mut contents = String::new(); + file.read_to_string(&mut contents).await.unwrap(); + + // Check if the file contains the logged message + assert!(contents.contains("Test log message after reopen")); + assert!(!contents.contains("Test log message before reopen")); + } +} diff --git a/conmon-rs/server/src/lib.rs b/conmon-rs/server/src/lib.rs index 72f3237cd2..c47ef04cc2 100644 --- a/conmon-rs/server/src/lib.rs +++ b/conmon-rs/server/src/lib.rs @@ -18,6 +18,7 @@ mod cri_logger; mod fd_socket; mod init; mod journal; +mod json_logger; mod listener; mod oom_watcher; mod pause; diff --git a/internal/proto/conmon.capnp.go b/internal/proto/conmon.capnp.go index 4fcf17db7f..1e636b4229 100644 --- a/internal/proto/conmon.capnp.go +++ b/internal/proto/conmon.capnp.go @@ -1341,6 +1341,7 @@ const Conmon_LogDriver_Type_TypeID = 0xf026e3d750335bc1 // Values of Conmon_LogDriver_Type. const ( Conmon_LogDriver_Type_containerRuntimeInterface Conmon_LogDriver_Type = 0 + Conmon_LogDriver_Type_json Conmon_LogDriver_Type = 1 ) // String returns the enum's constant name. @@ -1348,6 +1349,8 @@ func (c Conmon_LogDriver_Type) String() string { switch c { case Conmon_LogDriver_Type_containerRuntimeInterface: return "containerRuntimeInterface" + case Conmon_LogDriver_Type_json: + return "json" default: return "" @@ -1360,6 +1363,8 @@ func Conmon_LogDriver_TypeFromString(c string) Conmon_LogDriver_Type { switch c { case "containerRuntimeInterface": return Conmon_LogDriver_Type_containerRuntimeInterface + case "json": + return Conmon_LogDriver_Type_json default: return 0 @@ -4563,222 +4568,222 @@ func (p Conmon_startFdSocket_Results_Future) Response() Conmon_StartFdSocketResp return Conmon_StartFdSocketResponse_Future{Future: p.Future.Field(0, nil)} } -const schema_ffaaf7385bc4adad = "x\xda\xc4Y{p\x14gr\xef\x9e\xd9\xd5h\xf5`" + - "5\x99\x95\x00\x81\"\x15\x87/\xa0\x1c\xc7C8\xc6\x14" + - "W\x92\x00\x1d\x81\x03\x9fF\x8bM\x0e\xee\x1c\x86\xddA" + - "ZX\xed,3\xb3\x808;2wq\xd9\xa7;l" + - "\xa3\xe0\xb2M\x85*d\x1b\x02\x18b\xfc\x80\x18\x8c]" + - "\x80\xa1\x0c\xd8\xc4\x81\x04b\\\xc6\x01cb\x9b\xf2\x8b" + - "\xd8\xa9`\x17dR\xfd\xcd\xceC+\x19\xaf\x14\xa7\xf2" + - "\x07\x85\xb6\xe77\xdd\xdf\xf7u\x7f\xdd\xfd\xeb\x99\xf0y" + - "\xa8!0\xb1\xf4\xad!\xc0\xc9;\x83\x05\x96q\xb1C" + - "\xdf\xbai\xd6oA\xfc\x01\x02\x04Q\x00\xa8\x9b'\xbc" + - "\x8f\x80\x92\"\xd4\x03Z\xff\xf9\xf4\xb1\x9f<\xb6\xfe\xf3" + - ".?`\xad\x0dx\x94\x01\xde\x9dR\xbbt3?\xf7" + - "\xf7~\xc0>\xe1\x1d\x02\x9cd\x80\xbfZ\x1c\xde\xf07" + - "\x15\x8b\x18\xc0\xbaZ\xb7\xf4\xfc\x13\x1f\xdd\xf6\x0f\x10\x14" + - "\x08\xf8\xa9\xf0\x0eJ\xa1B\xfa3X\xf80\x02Zo" + - "\xfc\xf1\x9a\x05\xe1\xad\x0f<\x9e\x83fj/\x87\xdeG" + - "\x09\x8b\x04\x00\xe9F\x88T\xdf\xff\xf1\x1d{\xef\xfc\xed" + - "\xe7\x9b\xfd\xb6'\x16\xfd\x07\xd9n*\"\xc0\x13\x8b>" + - "Z\xde4;\xfcdom\x01\xc2%\x8av\xa0\xb4\xb6" + - "H\x00\xde\xaa\xf8\xe6\xd2\xd3g\xa3S\xb6\x83\xfc\x03\xec" + - "c\xf4W\x84\xcb0\xa3+\x8aV\x01Z\xa3\x9f}\xed" + - "T\xd7\xb4\xf1;\xfcFO\x16\x9d&\xa3\x17\x99\xd1\xae" + - "\xbfS\xff\xe4\xd0\xfe\x9f\x11\x80\xf3\xb4\x01\xd6aq\x17" + - "J\xc3\x8bIUy\xf1m\x80\xd6\xaa\xc5\xc7\x9e]#" + - "_\xde\xd9\xcf\xf2\xaa\x8a\xbbQ\xba\xb5\x98\x96'\xcd\x9c" + - "\xb0\xefl]\xed\xae\xdc\xe5q\x84\x13\x097\x96\xe9\xbc" + - "\xa5\xf8Y@kr\xcf\x0b{\x1f\xfal\xf5\xdf\x13\x9a" + - "\xcb\xdd\xcc\x99\xe2e(}\\<\x14@\xba\xca\xd0\xbf" + - ">ue\xdbC\xbfo\xdc\x93\xab\x9b'\xf4\xefJ\x0e" + - "\xa3\xd4SB\x7fn*\xa9&\xef\xf0\x17>(z\xa8" + - "q\xfc\xde\xfe\xbcs\xb4\xf4\x04J\x17Ki%\xe7K" + - "\xe9\x1c\xdc\xe7\xe2H\xde\xda\xb5\xeb\xc8\xa2)\xff\xb5\xc3" + - "\xa2s\xb8QZ\x89u\xa5C\x16 )\x0e\x0b\x9c\x14" + - "\x12\x05\x00k\xec'\x0fn}\xe0\x99\xe8\xfe\xfe\x94_" + - "-;\x8c6L\x0a\x8a\xa4\xfc\xc4\xde\xedS\xbf\xb9\xb4" + - "j\x7f\xee\xc2\xc9~\xdd\xed\xe2i\x94\xee$t\x9d," + - ">\xc0\x03Ze\x8b\xde\xfa\xc9'w\xff\xfbQ\xbf\xd3" + - "\x8e\x96\xb3(=WN\xfa>T^\xe6\x9aN&_" + - "\xf7\x03n\x94\x17q\x80Ry\x05\x03|\xf0\xdf\xcbZ" + - "\xd3\xe3\xdf\xf4\x03n\xad\xe8f\xb1\xc6\x00\xcb\x8b\x8fE" + - "B\xf5\xc6?\xfa\x01j\xc5a\x02d\x18\xe0Z\xf9\xab" + - "\x8fUN\xdb\xdf\x0b\xf0h\x05[\xc3v\x06\xa8l<" + - "59\x9c\x9a\xf5O9\x07\xc0|q\xb2\xe2I\x94." + - "W\xd0\x01\\\xac \xcf=\xf5\xe5\xb6\xc5{\xd6G\xce" + - "\xf6\x89\xb2\xf5C\x97\xa1\xb4e(!{\x86v\x02Z" + - "\xd7\xef\x9fv_U\xd5\xd9s\xfd\xc6\xcf\x99\xa1WP" + - "\xba\xca\xd0\x9f\x0e\xfd\x10\xd0\xda\xf8\xa7\xab\xd2w/\x99" + - "\xfa^\x0e\x9a\x05\xe5\xf1a\xef\xa0ty\x18[\xc40" + - "Z\xf1\xf5\xa9\xd7_\xdd<-\xfdo\xb9\xaa\x83\xecF" + - "\x0f\xefB\xa9j8\xfd9|\xf8\x02\x0a\x9f;\xd3\xb3" + - "\xc4\x1f\xb6\x0c\xb9\xe0?\x81u\x95\x7fD\x87\xbc\xa5\x92" + - "\xf4M\xf8\xf5\xac\xedw'\xa4K~\xc0\xf1\xca\x8d\xcc" + - "M\x0c\xf0g\xd2k\xbbS\xeb\xaf\\\xee\xe5\xa6\xca+" + - "\x04\x10G\x10\xe0\xd0\xa2\xba\xe6\x7f\xbd\xf4\xc3/@\x1c" + - "\xc7yw\x01\xb0n\xe2\x88n\x94f\x8f\xa0\xb57\x8d" + - "\xa8\x06\xb4N}V\xbd\xf3\x8d\xcb?\xfb2w\xed!" + - "\xd2\xd94b#J\xbf\"t\xdd/F\xdc\xc6\x01Z" + - "[W<\xf5\xc8\xb5Q\xe2W\xb9\xf7\x8amuW\xd5" + - "\xfb(\x1d\xafbQU\xf5:m\xf5\xa5\x8d\x1b\x1e>" + - "2i\xd6W\xbd\xb2D\xb5\x9d%\xaai\xa1\xe5\x7f\xb9" + - "\xf6B\xed\xc7\x97z\x01\xb0\xe6\x04\x01\xcak\x08p\x00" + - "w\x14\xffr\xd9G\xd7\xfc\x80\xdbk\xd8V\xe71\xc0" + - "\xb5\x9eg\xea\xee;\xf9\xc2\xd7\xfdd\x8f\x155'P" + - "ZWC\xd9\xa3\xfb\x9f\xe7\xb4\xbfw\xe3\xe5or\x82" + - "\x8a9?Q\xf3$Jkk\xe8L\xee\xadY\x05\xe3" + - "\xacD\xcaT\xf5\x94\x92,\x18\x9f\xd65S\x1b\x1f\xd3" + - "R\xedZ\xea\xc71%\x9dJO\x9da\xffPW\xab" + - "\xb1hG*6CK\x99J\"\xa5\xea\xa3\x9b\x15]" + - "P\xda\x0d9\xc0\x07\x00\x02\x08 \x96N\x07\x90\x0by" + - "\x94#\x1cv\xea\xea\x8a\x8cj\x98X\xe6\x9d! \x96" + - "\x01\xe6e.\xa6\xab\x8a\xa9\xde\xa1\xb4\xabFZ\x89\xa9" + - "\xc6\xe8\x16\xd5\xc8\x08I\xb3\x97\xb99\x00r\x09\x8f\xf2" + - "0\x0e-]5\xd2Z\xcaP\x01\x00\xcb\xbcz\xf2\xbf" + - "1\xd9\xac\xe8\x0a\x9f\xcf\x06\xddZ7\x00k3r\xac" + - "\xb5\x906\xde0\x9b\x11\xe5\x91\xae\xc1=K\x00\xe4\x17" + - "y\x94\x0fr(\"F\x90\x84\xaf,\x04\x90\x0f\xf0(" + - "\xbf\xcd\xa1\xc8q\x11\xe4\x00\xc43\x84\xfc\x17\x1e\xe5/" + - "8\x14y>\x82<\x80\xf8)\x09?\xe11Z\x88\x1c" + - "\x8a\x81@\x04\x03\x94Jq\x0e@4\x80\xe1&\xc2\xeaU\x89" + - "\xb8\xd9\x86\x02p(\x00\xd6\xb7\xa9\x89\xd66\xd3\xf9\xf9" + - "}\xb8\xf0\xa6'a*\xba\xf9\xd3xT\x8b-W\xcd" + - "\x167/\xe5d\xceZ\xef\"\xf6\x7f\xd1\xf9o3\xc1" + - "k)y>\xa2G\x8f\xc4\x9e5^S'\xf6\xfc\xc6" + - "\xe3\x08b\xcf~\xaf\x17\x14\xb7\xb4\xf8(\xda\x16\xdd\xeb" + - "x\xc5-\x87\xbd\x16E\xdc~\xc2\xeb\x9c\xc5\xe7N{" + - "\xc9A\xdc\xa7\xfb\x98\xdb\xbe5\xbe\xbe}_\x97\x8fr" + - "\xbe\xd2\xed\xd1+\xf1\xd0\x0e_\xbfv\xf4y\x1f+>" + - "~\xd8\xd7\xcb\x9fl\xf11\xe0\x93'\xbc\xb2!\x9e\xe9" + - "\xf6\x11\xa4s;|T\xec\xfc\xf3\xbe&\xefb\x97\xe5" + - "\xdc~\xa8\xb7\xc3\xcf\x15\xf0\x8e\x13\xec\xb2\xe6&\xc8\x16" + - "\x07\xc8n]b\xa5\x0a\xa8[N^\x83j\x96\xd9," + - "\xe7\x9d\xa0\xf3\x92\xa3\xac)\xb7\x1ft\xa2\x1e,\xe7\x11" + - "\xe7{\x96Me\x96\x93\xda\xa0\xda\xb6\xed\xfe\xae\xb7\xf5" + - "ZN!\xc2VO\xa1_\xe6(rn\x1c:W." + - "\xcc\xf4\xe5\x8a\x8dj[\xadS\xd2\xf9^\xfd\x96a\x82" + - "S\xbe\xd0\xc3p\xbd\xea>\x0b_\xcb\x83\xf9\x96\x90\x0d" + - "u\xcc\xc6\xba\xb3\x84\x1c\xb1\xb3\x84\xf9\xeaj\x93\xfe\xe1" + - "<%\xdd\x942\xf5\x0e\x00\xb9\x86\x0f\x02\xb8$\x13\x1d" + - "\"$^\x9d\x0e\x9cxY@\x8fQ\xa0\xc3#\xc5s" + - "\xbf\x01N<% \xe7\x8eq\xd0!\x0d\xe2\xd1n\xe0" + - "\xc4C\x02\xf2\xee\xbc\x02\x1d\x0e,\xee\xa1\xf7v\x09\x18" + - "p\xe9\x14:\x93\x14\xb1g#p\xe2&\x01\x83.#" + - "F\x87\xb5\x89\xeb\xf7\x03'\xae\x13\xb0\xc0\x1d\xfa\xa03" + - "\x1e\x12\xd7v\x01'\xde+\xa0\xe0\xf2`t\x18\x8e\xb8" + - "B\x07NL\x08TC(\x0e\x1b\xd0\x8ae\x83\x09\xb3" + - "a\x01\x0dh9\xbc\x02\x9d`A\xbd\x01-\xa7\x96\xfb" + - "\x91\xba\x1b\x05Y(\xaf\x12\xd4\xe8\xe5\xf1\x19Z\xaa\xde" + - "~\xc5\xb5w\x87\x82\x8eC\x81\xf4\x18Y\xff@5s" + - "P\x03\xfa\xeb\xeb\x00R\x9b\x97\xe4\xfb\xe9\xca\xc6p\xdf" + - "{?\x9as\xd3\xec\x02\xdf\xe0\x98\x96\x9e\xc3J\x80\xe8" + - "N\xea\xd2_B\x8f-H{p!@\xf4E\x92\x1f" + - "D\x0e\xd1\xe6\x0b\xd2+\xac\xa9?@\xe2c\xe8\x15\x1a" + - "\xe9(#\x01\x07I\xfe&z\xb5F:\x8e-\x00\xd1" + - "c$\xff\x80\x91\x06\xde&\x0d\x17q\x19@\xf4\x02\xc9" + - "\xaf3\xd2\x10\xb0I\xc3\xd7\xcc\xec5F&8\x0eE" + - "!\x18!.+\x89\x1c\xc9\xcb8\"\x13$/,\x88" + - "`!\x804\x96\xc9\xc7\x90|&\xc9CB\x04C\x00" + - "R#\xb7\x04 \xda@\xf2_\x92\xbc\xa80\x82E\x00" + - "\xd2/\x98\xfc/H\x1e'yq(\x82\xc5\x00\x92\xc2" + - "\xd1\xbe\x16\x93\xfc\x1e\x92\x97\x14E\xb0\x04@\xea\xe0\xa6" + - "\x03DM\x92?B\xf2R\x8c`)\x80\xb4\x8e\xd3\x01" + - "\xa2\x7f \xf9\xe3$\x1fR\x1c\xc1!\x00\xd2\xa3L\xbe" + - "\x81\xe4\xbbI\x1e.\x89`\x18@\xda\xc5\xf4l#\xf9" + - "\x11\xaeW\xd9\xb5\x96dR\xf1\xa4\xda\xac\x00\xef+h" + - "\xa6\xaa\xb7'RJ\x92\x82 \xdbEU\x1bf<\x91" + - "r{*uu\xc2d\xc4\x06\xfbp\x1eMko\xa2" + - "\xa7\x10V\xcc\xb6>O\x93N\xde\xe6u\x1f\xe5\xf0M" + - "?\x18*\x96T\x95T&=\x03\xf8\xf6x\x1f\xc2\x95" + - "\xd4\x96(\xc9F\x1d\xf8\xbe|+\xa6\xb5\xb7+\xa9x" + - "#\x08z\xdf\x87\x83o\":\xd5\xd4\xca\xbb\x14\xff\x82" + - "soD\xacw\x09\xc2\xb0W\xb7\xedv\xcdR\xe2\xf1" + - "\x84\x99\xd0RP\xad$\x7f\x1awU\x85\xec\xc5u&" + - "Uey_\xf1\xa08D\x8bjd\x92|\xbe\xc4\xcb" + - "m\x0fr\x88\x84p\x13\x8b\x86\xbfs\xcc!/\x06\xc0" + - "w\xb3\x17\xb7\xd9\x18\x00{\xc9\xa6\xe4\xfc)\x92\xdbm" + - "\x0d`\xc2a\xf8\xd3\xa5\xb3\xa1\xef6\xe5\xb6;\x03\x1e" + - "\xdd\x0c\xd6mn'88\xbe\xbc\"#\xa8F.\xdb" + - "\xaa\xf4\xd8\x89\xd8?\xdd\xe2\xfa\xd2-\x7f2\xf9?f" + - "Z\xac\x9f\x09SQd,\x85-\xe5\xd6Q\x8c\xba\x8e" + - "\xa3\xff8\xf1\x16\xfa\x8f\x17\xabj\x010 \x96\x8f\x02" + - "\x10\x12\xe9\x98\x90RM!\x9d\x88\x873\x86\xaa\x0b\x19" + - "\xd3\xc8\xcb?\xfd4\x8b\xbeYA\x99{l\x0a\x1d\xc6" + - "b\xfb0\x9cSK\x10\x1d\x8a\xf3(\xa7}\x1c\xa9\x9d" + - "\x84m<\xca&\x95\xae\x1a\x9b#\xad\xa0\xb7\xd3<\xca" + - "\xf7pvV\x9d\xa1\xc5\x99\x8b\x03\xc0a\x00\xb0\xde0" + - "\xe3Z\xc6t\x0e\x93~\xaa\xba\xee\x9e\xad\x99hW\xe3" + - "?\xcf\x98\xbeL=\xb8\xeaL\xb1\xc5\xf7\x19\x16-\xf3" + - "\xc5_,\x0b\x86\xb0\xde\x9c\x88c!pX\x98g\xe0" + - "9\x0ds\xb67&#\xc3\\#OP\xd8m\xe0Q" + - "\xde\xec\x0b\xbbM\x0b\x01\xe4\xbf\xe5Q\xde\xe6\x0b\xbb-" + - ":\x80\xfc4\x8f\xf2n\x0e1;-\xdc\xd5\x0d \xef" + - "\xe6Q>@u\x9f\xb79\xe6>\x0a\xda\x97x\x94\x8f" + - "P\xd1\x0f\xb0\xa2/\x1e\xa2\x93>\xc8\xa3\xfcn\xef\xa0" + - "5\xd8]\xcf\xa9\x80\xac\xc3S\x0d\x03\xaa\x13Z\xca7" + - "\xae3L-\xdd\xb8\xd4TQ\x8fR=l\xd2p\xe9" + - "\xf79g\x18D\xd2ei\xc3\xc4<\xd3\x86K\xec\x06" + - "\x91x\x07\x96\xa0\\z;\x80\x94\xd8\xcf\x14\xb1Y\x09" + - "\xeby\x0d\xec]f;\x80\x9d9$R\xff\xf1\xfc\x8e" + - "4\xda\xc1\xcf\"-x\x1a\xc0\x0dxNo\xc9\xa4\xe8" + - "\xa2\xcd&\x95K\x19\xed\x1a\xd0h\xcbw\xb7\xc6\xb8\xad" + - "o\x88\xf5\xa6\xee\xe0\xda\xe9|E\xa4V\xad\x84\xc4\xc3" + - "\xd0K\x1eR9\x8er\xe6\xd9#Y\xeb\xcb\xd9\xad\xef" + - "p\x9c\x0a\x10\x8d\x90\xbc\x06\xbd+ U1\xf5#I" + - ">\x06\xbd[ \xdd\xc2\xf05\xce\\\\,\x08\xda\xad" + - "\xefX\xa4\xd6t\x0c\xc9'\x93\\(\xb0[\xdf\x89\xac" + - "U\x9e@\xf2i$/\x14\xec\xd6\xf7v\xa6\x7f\x0a\xc9" + - "g\x92}\xbdn\xa8sk\x07Y \xbc\xb2Z\xe3\xc6\xeb" + - ")\xcaoo\xda_>\x9c\xf4vfa\xf6\xc3\xc7\x05" + - "_}8O\x81\xfd6\x8f\xf2W\xe4\xdf\x06;\xbf]" + - "\xa5\x1b\xf6\x05\x8f\xf2u\xdf\x0c\xedk\xcao\xd7x\x8c" + - "\x06\xfc\xac\x06\xe9\xe8[\xdc\x08qH\xcdp\x16Q," + - "B&\xa0\x8f\xd4\x8cC\"\x0b?\"\xf9\x14\xec\x9d\x12" + - ")\xd8\xb5\x8c\x19\x05^\x8d9#\xc5\xcelo\x9d\xdb" + - "U\xf7\xc3\x15\xfe\x9f;\xed\xc14_y7\x95\xee\xa0" + - "n\xd0M\xa5\x9d\xb0s\xda\xe4o\xcf\xa3\xee\xf0l\x00" + - "\x06\xfb~\xf7lQ\x8dp\xfe\x1fc\xdca\xe2\x00l" + - "\xe6L\x93}3\xd4\xbc\x92\xa53hbs&\xc1\xd4" + - ";r>\xc8\x8c\xf2>\xc8\xb8M\xc2\xb8I\xde\x17\x19" + - "a\xb9\xda\xe1\xce\x92W*\xc9\x8c{\xbf\xff'\x00\x00" + - "\xff\xff\xb7$=\xfd" +const schema_ffaaf7385bc4adad = "x\xda\xc4Y}p\x14\xe7y\x7f\x9e\xdd\x93V\xa7\x0f" + + "N\xdb=\x09\x10\xa8\xd2\x10HA\x0d\xe1C\xb8&\x0c" + + "\x19I\x80B!\xe0hu`ZH\\\x96\xbbE:" + + "\xb8\xdb=v\xf7\x00\x11\xa72I=v\x94\xd81*" + + "\x9e\xd8L\x99A\xb6\xa1\x80\xa1\xc6\x1fP\x83\xc1\x03\x18" + + "\xc6\x80M]h\xa1\xc6c\\0\xa6\xb6\x19\x7fQ\xbb" + + "S\xec\x81n\xe7y\xef\xf6C'\x19\x9fTw\xf2\x07" + + "\x83\xee\xd9\xdf>\xef\xc7\xf3\xf9{v\xe2\xe7\xc1\xc6\xc0" + + "\xa4\xb27\x86\x00'\xef*(\xb4\xcd\xcb\x1d\xc6\xb6\xcd" + + "\xb3\x7f\x05\xe2w\x10\xa0\x00\x05\x80\xfa\xf9\xc2\xbb\x08(" + + ")B\x03\xa0\xfd_O\x9d\xf8\xe1\xef7|\xda\xe5\x07" + + "\xac\xcf\x00\x1ee\x80\xb7\xa7\xd6-\xdf\xc2\xcf\xfb\x8d\x1f" + + "\xb0_x\x8b\x00\xa7\x19\xe0\xaf\x97\x866\xfem\xe5\x12" + + "\x06\xb0\xaf\xd7/\xbf\xf8\xf8\x07w\xfe#\x14\x08\x04\xfc" + + "Xx\x0b\xa5`\x11\xfdYP\xf4;\x04\xb4_\xfb\xe3" + + "u\x8bB\xdb\x1ex,\x07\xcd\xd4^\x0d\xbe\x8b\x12\x16" + + "\x0b\x00\xd2\xad \xa9\xbe\xff\xc3\xbb\xf6-\xfc\xd5\xa7[" + + "\xfckO*\xfeOZ\xbb\xb9\x98\x00\x8f/\xf9`e" + + "\xf3\x9c\xd0\x13\xbd\xb5\x05\x08\x17/\xde\x89\xd2\xfab\x01" + + "x\xbb\xf2\xab+O\x9d\x8fL\xdd\x01\xf2w\xb0\xcf\xa2" + + "?#\\\x9a-\xba\xaax\x0d\xa0=\xfa\x99W\xcet" + + "M\x9f\xb0\xd3\xbf\xe8\xe9\xe2\xb3\xb4\xe8e\xb6h\xd7\xdf" + + "\xab\x7fr\xe4\xc0\x8f\x09\xc0y\xda\x00\xeb\xb1\xa4\x0b\xa5" + + "\xe1%\xa4\xaa\xa2\xe4N@{\xcd\xd2\x13\xcf\xac\x93\xaf" + + "\xee\xeag{\xd5%\xdd(\xddQB\xdb\x93fM\xdc" + + "\x7f\xbe\xbenw\xee\xf68\xc2\x89\x84\x1b\xc7t\x8e)" + + "y\x06\xd0\x9e\xd2\xf3\xfc\xbe\x87?Y\xfb\x0f\x84\xe6r" + + "\x0fs\xaed\x05J\x1f\x96\x0c\x05\x90\xae3\xf4\xcf\xcf" + + "\\\xdb\xfe\xf0o\x9a\xf6\xe6\xea\xe6\x09\xfd\xeb\xd2\xa3(" + + "\xf5\x94\xd2\x9f\x9bKk\xc8:\xfc\xa5\xf7\x8a\x1fn\x9a" + + "\xb0\xaf?\xeb\x1c/;\x85\xd2\xe52\xda\xc9\xc52\xba" + + "\x07\xf7\xb98\x92\xb7w\xef>\xb6d\xea\x7f\xef\xb4\xe9" + + "\x1en\x95Ua}\xd9\x90EH\x8aC\x02'\x05E" + + "\x01\xc0\x1e\xf7\xd1\x83\xdb\x1ex:r\xa0?\xe5\xd7\xcb" + + "\x8fb\x06&\x15\x88\xa4\xfc\xd4\xbe\x1d\xd3\xbe\xba\xb2\xe6" + + "@\xee\xc6i\xfd\xfa\x1f\x88gQZH\xe8zY|" + + "\x80\x07\xb4\xcb\x97\xbc\xf1\xc3\x8f\xee\xf9\x8f\xe3~\xa3\x1d" + + "\xaf`^z\xa1\x82\xf4\xbd\xaf\xbc\xc45\x9fN\xbc\xea" + + "\x07\xdc\xaa(\xe6\x00\xa5\x8aJ\x06x\xef\x7fV\xb4\xa5" + + "&\xbc\xee\x07\xdcQ\xd9\xcd|\x8d\x01V\x96\x9c\x08\x07" + + "\x1b\xcc\x7f\xf2\x03\xd4\xca\xa3\x04H3\xc0\x8d\x8a\x97\x7f" + + "_5\xfd@/\xc0\xa3\x95l\x0f;\x18\xa0\xaa\xe9\xcc" + + "\x94\x906\xfb\x9fs.\x80\xd9\xe2t\xe5\x13(]\xad" + + "\xa4\x0b\xb8\\I\x96{\xf2\xf3\xedK\xf7n\x08\x9f\xef" + + "\xe3e\x1b\x86\xae@i\xebPB\xf6\x0c\xed\x04\xb4o" + + "\xde?\xfd\xbe\xea\xea\xf3\x17\xfa\xf5\x9fsC\xaf\xa1t" + + "\x9d\xa1?\x1e\xfa>\xa0\xbd\xe9O\xd7\xa4\xeeY6\xed" + + "\x9d\x1c4s\xca\x93\xc3\xdeB\xe9\xea0\xb6\x89a\xb4" + + "\xe3\x9b\xd3n\xbe\xbcez\xea\xdfsU\x17\xb0\x88\x1e" + + "\xde\x85R\xf5p\xfas\xf8\xf0E\xe4>\x0bS\xb3\xc5" + + "\xef\xb6\x0e\xb9\xe4\xbf\x81\x87\xaa\xfe\x88.yk\x15\xe9" + + "\x9b\xf8\xf3\xd9;\xee\x89KW\xfc\x80\x93U\x9b\x98\x99" + + "\x18\xe0\xcf\xa4W\xf6h\x1b\xae]\xede\xa6\xaak\x04" + + "\x10G\x10\xe0\xc8\x92\xfa\x96\x7f\xbb\xf2\xdd\xcf@\x1c\xcf" + + "y\xb1\x00X?iD7JsF\xd0\xde\x9bGP" + + "\xf0\x9d\xf9\xa4f\xd7kW\x7f\xfcy\xee\xde\x83\xa4s" + + "\xe1\x88M(%G\xb0D1\xe2N\x0e\xd0\xde\xb6\xea" + + "\xc9Gn\x8c\x12\xbf\xc8\x8d+v\xd4C\xd5\xef\xa2t" + + "\xa1\x9a]h\xf5\xabt\xd4\x177m\xfc\xdd\xb1\xc9\xb3" + + "\xbf\xf0o\xf4b\x0d\xcb\x12\xd7kh\xa3\x15\x7f\xb5\xfe" + + "R\xdd\x87Wz\x01\xc4\xdaS\x04\x18SK\x80\x83\xb8" + + "\xb3\xe4\xa7+>\xb8\xe1\x07\xcc\xa9eG\xfd\x19\x03\xdc" + + "\xe8y\xba\xfe\xbe\xd3\xcf\x7f\xd9O\xf6X_{\x0a\xa5" + + "\xcd\xb5\x94=\xba\xffen\xf2\x9d[/}\x95\xe3T" + + "\xcc\xf8\x1d\xb5O\xa0\xb4\xa1\x96\xee\xe4\xa1\xda50\xde" + + "\x8ek\x96jhJ\xa2pB\xca\xd0-}BT\xd7" + + "\x92\xba\xf6\xfd\xa8\x92\xd2R\xd3ff~\xa8k\xd5h" + + "\xa4C\x8b\xce\xd45K\x89k\xaa1\xbaE1\x04%" + + "i\xca\x01>\x00\x10@\x00\xb1l\x06\x80\\\xc4\xa3\x1c" + + "\xe6\xb0\xd3PW\xa5U\xd3\xc2r\xef\x0e\x01\xb1\x1c0" + + "\xaf\xe5\xa2\x86\xaaX\xea]JR5SJT5G" + + "\xb7\xaafZHX\xbd\x96\x9b\x0b \x97\xf2(\x0f\xe3" + + "\xd06T3\xa5k\xa6\x0a\x00X\xee\xd5\x93\xff\xcb\x92" + + "-\x8a\xa1\xf0\xf9\x1c\xd0\xadu\x03Xmf\xcej\xad" + + "\xa4\x8d7\xad\x16Dy\xa4\xbb\xe0\xdee\x00\xf2\x0b<" + + "\xca\x879\x14\x11\xc3H\xc2C\x8b\x01\xe4\x83<\xcao" + + "r(r\\\x189\x00\xf1\x1c!\xff\x95G\xf93\x0e" + + "E\x9e\x0f#\x0f ~L\xc2\x8fx\x8c\x14!\x87b" + + " \x10\xc6\x00\xa5R\x9c\x0b\x10\x09 \x8f\x91r\x92\x17" + + "\x14\x84\xb1\x00@*\xc3\xc9\x00\x91\"\x92\x87I^X" + + "\x18\xc6B\x00Id\xf8r\x92\x7f\x0f9\xb4\x93\xaa\xa5" + + "\xc4\x14K\x01\xe1'\x89\x18\x96\x01\x87e\x80\xb6\x96=" + + "\x0a\xf0\xaa\x89C\x00[x\xc4\x90\x97\xaf\x00Ih\xa7" + + "\xe3\xb1\xf9J*\x15\x07Aksa\xa5\xc0\xb1\x87m" + + "\xb7{\xb8L1\xd5\x16\xc5j'\x03\x93\xac\x14\xb0&" + + "\xa5\xc7\xe6\xc4\x9c_\xde\xbe\x00\x9c\x97\xcb\xbd@\xc8n" + + "`p\xb61S\xba\xa0\x99*\x19\xc7\xe7\x0d\x8b\xb3\xfe" + + "7\x96\xeb\xff\xf8\xe5^\x8f1\x80\xd5\x0dUO\xa9\xda" + + "<\xbd\xcd\x0b\xb5V\xb5\xc6L\xe7\xed\xfcn\xfb\x93\xe3" + + "\x8e\x05\xb7Y\xb4\xd5Y\x94\xce\x1a\xd23g\xcd\xebM" + + "\xf7\x9a\xfco\xcaE\xeeF\xc7\xd5\x01\xc8\xa3y\x94'" + + "r\xe8x\xf0x\x92\x8d\xe5Q\x9e\xc2a\xc8\xeaH\xa9" + + "9\x9e\x12\x02\x0c\xa5\x14\xab\xdd5m>\xf7\xa6X\x96" + + "\x12m\xef\x95\x9f\x94$\xe6\x11\xben9\x1b\xc0}\xcd" + + "l3\xf4tj\xbe\xa2)m\xaa\x01\xc0\x8e\xcc\xe2P" + + "\x9cAj\xc4\xe0\\\x80N\xb3\xc3\xb4\xd4d\xcc\x8e2" + + "\xf0r\x13\x00\xf2R\xde\xc4N\xd2\x9a1*\xe6m\x89" + + "\xbbU\xc3\x8c\xeb\x1a\xcb$&\xb2LR\xea\x9e\xbd\x99" + + "\xce\xde\xc8\xa3<\xcf3\xc3\x1cJ\x0f\x7f\xce\xa3\xbc\x80" + + "\x12\x09f\x12\x89L\x8e\xd5\xc2\xa3\x9c\xe0\xb0s\xb5j" + + ",\xd3M\x15\x118D\xf8\xba\xd0\x1fP\xe0\x05ns" + + "\x82yz\xdb,#\x14_\xad\x1ar\x00\xfdU\x1d\xeb" + + "B\x0b:R\xaa\xff*v\xf19_\x93w\xb9\xcbv\xa2\x1f" + + "\x1a2\xee\xe7\x0ax\xc7\x08\x99\xb2\xe6&\xc8V\x07\xc8" + + "\xa2.\xbeZ\x054l'\xafA\x0d\xcbl\xb6\xf3N" + + "\x81\xf3\x92\xa3\xac9\xb7\x1ft\xbc\x1el\xe7\x11\xe7{" + + "\x96Me\xb6\x93\xda\xa0&\xb3\xb6\xfb\xbb!\xa3\xd7v" + + "\x0a\x11\xb6y\x0a\xfd2G\x91\x13q\xe8\x84\\\x88\xe9" + + "\xcb\x15\x9b5\x19\xb5NI\xe7{\xf5[\xa6\x05N\xf9" + + "B\x0f\xc3\xf5\xaa\xfb\xcc}m\x0f\xe6\xdbB\xd6\xd51" + + "\xeb\xeb\xce\x16r\xc4\xce\x16\x16\xa8k-\xfa\x87\xf3\x95" + + "T\xb3f\x19\x1d\x00r-_\x00\xe0\x92Lt\x88\x90" + + "x}\x06p\xe2U\x01=F\x81\x0e\x8f\x14/\xfc\x12" + + "8\xf1\x8c\x80\x9c;\xc6A\x874\x88\xc7\xbb\x81\x13\x8f" + + "\x08\xc8\xbb\xf3\x0at8\xb0\xb8\x97\xde\xdb-`\xc0\xa5" + + "S\xe8LR\xc4\x9eM\xc0\x89\x9b\x05,p\x191:" + + "\xacM\xdcp\x008\xf1!\x01\x0b\xdd\xa1\x0f:\xe3!" + + "q}\x17p\xe2/\x04\x14\\\x1e\x8c\x0e\xc3\x11W\x19" + + "\xc0\x89q\x81j\x08\xf9a#\xda\xd1\xac3a\xd6-" + + "\xa0\x11m\x87W\xa0\xe3,h4\xa2\xed\xd4r?\xd2" + + "p\xbd \x0b\xe5U\x82\x9a\xbd,>S\xd7\x1a2\xaf" + + "\xb8\xeb\xdd\xa5\xa0cP =f\xd6>P\xc3\x0c\xd4" + + "\x88\xfe\xfa:\x80\xd4\xe6%\xf9~\xba\xb2\xb1\xdc\xb7\xde" + + "\x8f\xe6DZ\xa6\xc07:KK\xcfb\x15@d\x17" + + "u\xe9/\xa2\xc7\x16\xa4\xbd\xb8\x18 \xf2\x02\xc9\x0f#" + + "\x87\x98\xe1\x0b\xd2!\xd6\xd4\x1f$\xf1\x09\xf4\x0a\x8dt" + + "\x9c\x91\x80\xc3$\x7f\x1d\xbdZ#\x9d\xc4V\x80\xc8\x09" + + "\x92\xbf\xc7H\x03\x9f!\x0d\x97q\x05@\xe4\x12\xc9o" + + "2\xd2\x10\xc8\x90\x86/\xd9\xb27\x18\x99\xe08\x14\x85" + + "\x820qYI\xe4H^\xce\x11\x99 yQa\x18" + + "\x8b\x00\xa4qL>\x96\xe4\xb3H\x1e\x14\xc2\x18\x04\x90" + + "\x9a\xb8e\x00\x91F\x92\xff\x94\xe4\xc5Ea,\x06\x90" + + "\xfe\x92\xc9\xff\x82\xe41\x92\x97\x04\xc3X\x02 )\x1c" + + "\x9dk)\xc9\xef%yiq\x18K\x01\xa4\x0en\x06" + + "@\xc4\"\xf9#$/\xc30\x96\x11\x03\xe6\x0c\x80\xc8" + + "oI\xfe\x18\xc9\x87\x94\x84q\x08\x80\xf4(\x93o$" + + "\xf9\x1e\x92\x87J\xc3\x18\x02\x90v3=\xdbI~\x8c" + + "\xebUv\xedei-\x96P[\x14\xe0}\x05\xcdR" + + "\x8dd\\S\x12\xe4\x04\xd9.\xaa\xc6\xb4bq\xcd\xed" + + "\xa9\xd4\xb5q\x8b\x11\x1b\xec\xc3yt=\xd9LO!" + + "\xa4X\xed}\x9e&\x9c\xbc\xcd\x1b>\xca\xe1\x9b~0" + + "T4\xa1*Z:5\x13\xf8d\xac\x0f\xe1J\xe8\xcb" + + "\x94D\x93\x01|_\xbe\x15\xd5\x93IE\x8b5\x81`" + + "\xf4}8\xf8&\xa2S\xd5V\xdf\xad\xf87\x9c\x1b\x11" + + "\xd1\xde%\x08C^\xdd\xce\xb4k\xb6\x12\x8b\xc5\xad\xb8" + + "\xaeA\x8d\x92\xf8Q\xccU\x15\xccl\xae3\xa1*+" + + "\xfb\x8a\x07\xc5!ZU3\x9d\xe0\xf3%^n{\x90" + + "C$\x84\xdb\xach\xfa;\xc7\x1c\xf2b\x02|3{" + + "q\x9b\x8d\x01\xb0\x97lJ\xce\x9f\"\xb9\xdd\xd6\x00&" + + "\x1c\xa6?]:\x07\xfa\xe6\xa5\xdcvg\xc0\xa3\x9b\xc1" + + "\x9a\xcd\xed\x04\x07\xc7\x97W\xa5\x05\xd5\xcce[U\x1e" + + ";\x11\xfb\xa7[\\_\xba\xe5O&\xff\xcfL\x8b\xf5" + + "3!*\x8a\x8c\xa5\xb0\xad\xdc1\x8aQ\xd7\xf1\xf4\x1f" + + "'\x8e\xa1\xffx\xb1\xba\x0e\x00\x03b\xc5(\x00!\x9e" + + "\x8a\x0a\x9aj\x09\xa9x,\x946UCH[f^" + + "\xf6\xe9\xa7Y\xf4\xcd\x0a\xca\xddkS\xe82\x96f." + + "\xc3\xb9\xb58\xd1\xa1\x18\x8fr\xca\xc7\x91\x92$l\xe7" + + "Q\xb6\xa8t\xd5f8\xd2*z;\xc5\xa3|/\x97" + + "\xc9\xaa3\xf5\x183q\x008\x0c\x006\x98VLO" + + "[\xcee\xd2O\xd50\xdc\xbb\xb5\xe2I5\xf6\x93\xb4" + + "\xe5\xcb\xd4\x83\xab\xce\xe4[|\x9fa\xd1\x0a\x9f\xffE" + + "\xb3`\x08\x19-\xf1\x18\x16\x01\x87Ey:\x9e\xd30" + + "g{cZd\x98\xbb\xc8\xe3\xe4v\x1by\x94\xb7\xf8" + + "\xdcn\xf3b\x00\xf9\xefx\x94\xb7\xfb\xdcn\xab\x01 " + + "?\xc5\xa3\xbc\x87C\xccN\x0bww\x03\xc8{x\x94" + + "\x0fR\xdd\xe73\x1cs?9\xed\x8b<\xca\xc7\xa8\xe8" + + "\x07X\xd1\x17\x8f\xd0M\x1f\xe6Q~\xbb\xb7\xd3\x9a," + + "\xd6s* \xeb\xf0T\xd3\x84\x9a\xb8\xae\xf9\xc6u\xa6" + + "\xa5\xa7\x9a\x96[*\x1a\x11\xaa\x87\xcd:.\xff6\xe7" + + "\x0c\x83H\xba,mX\x98g\xdap\x89\xdd \x12\xef" + + "\xc0\x12\x94Ko\x07\x90\x12\xfb\x99\"\xb6(!#\xaf" + + "\x81\xbd\xcbl\x07p2\x87D\x1a\xdf_\xd0\x91B\xd5" + + "7\x10;\xcb\xb2JE\x1d\x80\xeb\xf7\x9c\xd1\x9a\xd6(" + + "\xde\xe6\x90\xe6\xe5D\xabB+L]\x1b\xd8\x98\xcb\x17" + + "gc\xdd68\xc8\xfaTw\x88\xedt\xc1\"R\xdb" + + "VJ\xe2a\xe8%\x12\xa9\x02G9\xb3\xed\x91\xac\x0d" + + "\xe62m\xf0p\x9c\x06\x10\x09\x93\xbc\x16\xbdp\x90\xaa" + + "\x99\xfa\x91$\x1f\x8b^DHc\x18\xbe\xd6\x99\x91\x8b" + + "\x85\x05\x996x\x1cR\x9b:\x96\xe4SH.\x14f" + + "\xda\xe0I\xacm\x9eH\xf2\xe9$/\x122m\xf0\x0f" + + "\x98\xfe\xa9$\x9fE\xf2`Q\xb6\x0df\xedz#\xc9" + + "\xe7!\x87v\xca\xd0\xa3\xaai\xce\x01t\xd3\x87C\xb4" + + "\x9c\x00\x13,\xa5\xcd\xf9\xbb\x81\xba\xb9\xb8\xe5kU\xe3" + + "\x89\xd8,\xc5\x02T]\x88\xa5\x18m\xaa\x071\xd2\xa6" + + "EW\x0d\x82O\xa7\x1dU\x8c6\xfdn\xd5\x80\x90\xd9" + + "G\xbc\xc0P}\xfazE\xab\x13\xc1\x83,\x16^\x89" + + "\xadu}\xf7\x0c\xe5\xba\xd73_A\x9cTwnq" + + "\xf6#\xc8%_\xad\xb8HN\xfe&\x8f\xf2\x17d\xdf" + + "\xc6L\xae\xbbN\xd1\xf6\x19\x8f\xf2M\xdf<\xedK\xca" + + "u7x\x8c\x04\xfc\x0c\x07\xe9\xea[]\x0fq\x08\xce" + + "p\xe6Q\xccC&\xa2\x8f\xe0\x8cG\"\x0e\xdf#\xf9" + + "T\xec\x9d\x1e\xc9\xe3\xf5\xb4\x15\x01^\x8d:\xe3\xc5\xce" + + "l\x9f\x9d\xdba\xf7\xc3\x1b\xfe\xc0]\xf7`\x1a\xb1\xbc" + + "\x1bLwh7\xe8\x063\x93\xbcsZ\xe6\xaf\xcf\xa9" + + "\xee m\x00\x0b\xf6\xfd\x06\xda\xaa\x9a\xa1\xfc?\xcc\xb8" + + "\x83\xc5\x01\xac\x993Y\xf6\xcdS\xf3J\x96\xce\xd0\x89" + + "\xcd\x9c\x04\xcb\xe8\xc8\xf983\xca\xfb8\xe36\x0c\xe3" + + "'{_g\x84\x95j\x87;W^\xad$\xd2n|" + + "\xffo\x00\x00\x00\xff\xff\xfe\x11AT" func RegisterSchema(reg *schemas.Registry) { reg.Register(&schemas.Schema{ diff --git a/pkg/client/client.go b/pkg/client/client.go index 9c2a59e8e3..0927d423ec 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -685,6 +685,7 @@ const ( // LogDriverTypeContainerRuntimeInterface is the Kubernetes CRI logger // type. LogDriverTypeContainerRuntimeInterface LogDriverType = iota + LogDriverTypeJSONLogger LogDriverType = iota ) // CreateContainerResponse is the response of the CreateContainer method. @@ -903,6 +904,9 @@ func (c *ConmonClient) initLogDrivers(req *proto.Conmon_CreateContainerRequest, if logDriver.Type == LogDriverTypeContainerRuntimeInterface { n.SetType(proto.Conmon_LogDriver_Type_containerRuntimeInterface) } + if logDriver.Type == LogDriverTypeJSONLogger { + n.SetType(proto.Conmon_LogDriver_Type_json) + } if err := n.SetPath(logDriver.Path); err != nil { return fmt.Errorf("set log driver path: %w", err) } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index ee52785585..78bfae5736 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -672,3 +672,46 @@ var _ = Describe("ConmonClient", func() { }) }) }) + +var _ = Describe("JSONLogger", func() { + var tr *testRunner + var sut *client.ConmonClient + + AfterEach(func() { + Expect(os.RemoveAll(tr.tmpDir)).To(Succeed()) + if sut != nil { + Expect(sut.Shutdown()).To(Succeed()) + } + }) + + Describe("Logging", func() { + for _, terminal := range []bool{true, false} { + It(testName("should log in JSON format", terminal), func() { + tr = newTestRunner() + tr.createRuntimeConfigWithProcessArgs(terminal, []string{"invalid"}, nil) + + sut = tr.configGivenEnv() + _, err := sut.CreateContainer(context.Background(), &client.CreateContainerConfig{ + ID: tr.ctrID, + BundlePath: tr.tmpDir, + Terminal: terminal, + LogDrivers: []client.ContainerLogDriver{{ + Type: client.LogDriverTypeJSONLogger, + Path: tr.logPath(), + }}, + }) + Expect(err).To(HaveOccurred()) + + logContent, err := os.ReadFile(tr.logPath()) + Expect(err).NotTo(HaveOccurred()) + // Check if the log content is in JSON format + // This is a basic check assuming that a valid JSON starts and ends with braces '{' and '}' + Expect(strings.TrimSpace(string(logContent))).To(SatisfyAll( + Not(BeEmpty()), + HavePrefix("{"), + HaveSuffix("}"), + )) + }) + } + }) +})