move logtail facade config code to the poster, add examples

Signed-off-by: Christine Dodrill <me@christine.website>
This commit is contained in:
Cadey Ratio 2021-09-06 20:09:07 -04:00
parent b09220fbe2
commit c647ac7323
11 changed files with 278 additions and 141 deletions

20
Cargo.lock generated
View File

@ -128,6 +128,26 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "example-logtail-facade"
version = "0.1.0"
dependencies = [
"log",
"logtail-facade",
"tokio",
]
[[package]]
name = "example-logtail-poster"
version = "0.1.0"
dependencies = [
"logtail",
"logtail-poster",
"serde",
"serde_json",
"tokio",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"

View File

@ -1,2 +1,2 @@
[workspace] [workspace]
members = ["crates/*"] members = [ "crates/*", "examples/*" ]

View File

@ -10,7 +10,7 @@ default = [ "log-facade" ]
log-facade = [ "log" ] log-facade = [ "log" ]
[dependencies] [dependencies]
log = { version = "0.4", optional = true } log = { version = "0.4", optional = true, features = [ "std" ] }
serde = { version = "1", features = [ "derive" ] } serde = { version = "1", features = [ "derive" ] }
serde_json = "1" serde_json = "1"

View File

@ -1,109 +1,90 @@
use serde::{Deserialize, Serialize}; use log::{Level, Metadata, Record};
use std::{env, ffi::OsString, fs, io, path::PathBuf}; use logtail_poster::Egress;
use std::{
env,
sync::{Arc, Mutex},
};
#[cfg(feature = "log-facade")] pub struct LogtailLogger {
mod log; ing: Arc<Mutex<logtail_poster::Ingress>>,
#[cfg(feature = "log-facade")] threshold: Level,
pub use self::log::*;
pub(crate) fn cache_dir() -> Option<OsString> {
let dir = env::var_os("STATE_DIRECTORY").or_else(|| {
env::var_os("HOME").and_then(|dir| {
let mut dir: PathBuf = dir.into();
dir.push(".cache");
dir.push("rebterlai");
Some(OsString::from(dir))
})
});
dir
} }
pub(crate) fn state_file(name: &str) -> Option<PathBuf> { #[derive(serde::Serialize)]
let mut dir: PathBuf = cache_dir()?.into(); struct LogData<'a> {
dir.push(&format!("{}.json", name)); level: &'a str,
Some(dir) target: &'a str,
module_path: &'a str,
file: &'a str,
line: u32,
message: String,
} }
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] impl log::Log for LogtailLogger {
pub struct Config { fn enabled(&self, metadata: &Metadata) -> bool {
private_id: String, metadata.level() <= self.threshold
public_id: String,
}
impl Config {
pub fn new() -> Self {
let private_id = logtail::PrivateID::new();
Self {
private_id: private_id.as_hex(),
public_id: private_id.as_public().as_hex(),
}
} }
pub fn load<S>(collection: S) -> io::Result<Self> fn log(&self, record: &Record) {
where if self.enabled(record.metadata()) {
S: Into<String>, let ld = LogData {
{ level: record.level().as_str(),
let dir = cache_dir() target: record.target(),
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "can't find directory"))?; module_path: record.module_path().unwrap_or("???"),
if let Err(_) = fs::metadata(&dir) { file: record.file().unwrap_or("???"),
fs::create_dir_all(&dir)?; line: record.line().unwrap_or(0),
} message: format!("{}", record.args()),
println!("{:?}", dir); };
let fname = state_file(&collection.into()).ok_or_else(|| { if let Ok(val) = serde_json::to_value(&ld) {
io::Error::new( if let Ok(mut ing) = self.ing.lock() {
io::ErrorKind::InvalidInput, if let Err(why) = ing.send(val) {
"can't derive logtail config filename", eprintln!("logtail_facade::LogtailLogger::log: can't send json value to buffer: {}", why);
) }
})?; }
match fs::metadata(&fname) {
Ok(_) => {
let fin = fs::File::open(&fname)?;
let cfg = serde_json::from_reader(io::BufReader::new(fin))
.or_else(|why| Err(io::Error::new(io::ErrorKind::Other, why)))?;
Ok(cfg)
}
Err(_) => {
let cfg = Self::new();
let mut fout = fs::File::create(&fname)?;
serde_json::to_writer(&mut fout, &cfg)
.or_else(|why| Err(io::Error::new(io::ErrorKind::Other, why)))?;
Ok(cfg)
} }
} }
} }
fn flush(&self) {}
} }
#[cfg(test)] pub fn init(collection: String) -> Result<Egress, Box<dyn std::error::Error>> {
mod tests { let cfg = logtail_poster::Config::load(collection.clone())?;
use super::*; let target = env::var("TS_LOG_TARGET")
use tempdir::TempDir; .unwrap_or(logtail_poster::DEFAULT_HOST.to_string())
.to_string();
#[test] let (mut ing, eg) = logtail_poster::Builder::default()
#[serial_test::serial] .collection(collection)
fn cache_dir() { .base_url(target)
let home = TempDir::new("cache").unwrap(); .buffer_size(256)
std::env::set_var("STATE_DIRECTORY", home.path().to_str().unwrap()); .private_id(cfg.private_id())
let dir = super::cache_dir(); .build()?;
assert!(dir.is_some()); let threshold = if cfg!(debug_assertions) {
assert_eq!(dir.unwrap(), home.path().to_str().unwrap()); Level::Debug
} else {
Level::Info
};
home.close().unwrap(); ing.send(serde_json::to_value(&ProgramStarted {
} msg: "Program started".to_string(),
})?)?;
let ing = Arc::new(Mutex::new(ing.clone()));
#[test] let logger = LogtailLogger {
#[serial_test::serial] ing: ing.clone(),
fn new_and_load() { threshold: threshold.clone(),
let home = TempDir::new("cache").unwrap(); };
std::env::set_var("STATE_DIRECTORY", home.path().to_str().unwrap());
let cfg = Config::load("foo.bar").unwrap(); log::set_boxed_logger(Box::new(logger))
let cfg2 = Config::load("foo.bar").unwrap(); .map(|()| log::set_max_level(threshold.to_level_filter()))?;
assert_eq!(cfg, cfg2); Ok(eg)
}
home.close().unwrap();
} #[derive(serde::Serialize)]
struct ProgramStarted {
msg: String,
} }

View File

@ -1,46 +0,0 @@
use log::{Level, Metadata, Record};
use std::sync::{Arc, Mutex};
pub struct LogtailLogger {
ing: Arc<Mutex<logtail_poster::Ingress>>,
threshold: Level,
}
#[derive(serde::Serialize)]
struct LogData<'a> {
level: &'a str,
target: &'a str,
module_path: &'a str,
file: &'a str,
line: u32,
message: String,
}
impl log::Log for LogtailLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.threshold
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let ld = LogData {
level: record.level().as_str(),
target: record.target(),
module_path: record.module_path().unwrap_or("???"),
file: record.file().unwrap_or("???"),
line: record.line().unwrap_or(0),
message: format!("{}", record.args()),
};
if let Ok(val) = serde_json::to_value(&ld) {
if let Ok(mut ing) = self.ing.lock() {
if let Err(why) = ing.send(val) {
eprintln!("logtail_facade::jog::LogtailLogger::log: can't send json value to buffer: {}", why);
}
}
}
}
}
fn flush(&self) {}
}

View File

@ -0,0 +1,112 @@
use serde::{Deserialize, Serialize};
use std::{env, ffi::OsString, fs, io, path::PathBuf};
#[cfg(feature = "log-facade")]
mod log;
#[cfg(feature = "log-facade")]
pub use self::log::*;
pub(crate) fn cache_dir() -> Option<OsString> {
let dir = env::var_os("STATE_DIRECTORY").or_else(|| {
env::var_os("HOME").and_then(|dir| {
let mut dir: PathBuf = dir.into();
dir.push(".cache");
dir.push("rebterlai");
Some(OsString::from(dir))
})
});
dir
}
pub(crate) fn state_file(name: &str) -> Option<PathBuf> {
let mut dir: PathBuf = cache_dir()?.into();
dir.push(&format!("{}.json", name));
Some(dir)
}
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct Config {
private_id: String,
public_id: String,
}
impl Config {
pub fn new() -> Self {
let private_id = logtail::PrivateID::new();
Self {
private_id: private_id.as_hex(),
public_id: private_id.as_public().as_hex(),
}
}
pub fn private_id(self) -> logtail::PrivateID {
logtail::PrivateID::from_hex(self.private_id).unwrap()
}
pub fn load<S>(collection: S) -> io::Result<Self>
where
S: Into<String>,
{
let dir = cache_dir()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "can't find directory"))?;
if let Err(_) = fs::metadata(&dir) {
fs::create_dir_all(&dir)?;
}
let fname = state_file(&collection.into()).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"can't derive logtail config filename",
)
})?;
match fs::metadata(&fname) {
Ok(_) => {
let fin = fs::File::open(&fname)?;
let cfg = serde_json::from_reader(io::BufReader::new(fin))
.or_else(|why| Err(io::Error::new(io::ErrorKind::Other, why)))?;
Ok(cfg)
}
Err(_) => {
let cfg = Self::new();
let mut fout = fs::File::create(&fname)?;
serde_json::to_writer(&mut fout, &cfg)
.or_else(|why| Err(io::Error::new(io::ErrorKind::Other, why)))?;
Ok(cfg)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempdir::TempDir;
#[test]
#[serial_test::serial]
fn cache_dir() {
let home = TempDir::new("cache").unwrap();
std::env::set_var("STATE_DIRECTORY", home.path().to_str().unwrap());
let dir = super::cache_dir();
assert!(dir.is_some());
assert_eq!(dir.unwrap(), home.path().to_str().unwrap());
home.close().unwrap();
}
#[test]
#[serial_test::serial]
fn new_and_load() {
let home = TempDir::new("cache").unwrap();
std::env::set_var("STATE_DIRECTORY", home.path().to_str().unwrap());
let cfg = Config::load("foo.bar").unwrap();
let cfg2 = Config::load("foo.bar").unwrap();
assert_eq!(cfg, cfg2);
home.close().unwrap();
}
}

View File

@ -7,8 +7,11 @@ This facilitates writing logs to a logtail server. This is a port of
use reqwest::Client; use reqwest::Client;
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
mod config;
pub use self::config::*;
/// DefaultHost is the default URL to upload logs to when Builder.base_url isn't provided. /// DefaultHost is the default URL to upload logs to when Builder.base_url isn't provided.
const DEFAULT_HOST: &'static str = "https://log.tailscale.io"; pub const DEFAULT_HOST: &'static str = "https://log.tailscale.io";
/** /**
Builds a send/recv pair for the logtail service. Create a new Builder with the [Builder::default] Builds a send/recv pair for the logtail service. Create a new Builder with the [Builder::default]
@ -120,8 +123,8 @@ pub enum Error {
#[error("no collection defined")] #[error("no collection defined")]
NoCollection, NoCollection,
#[error("can't put to in-memory buffer")] #[error("can't put to in-memory buffer: {0}")]
TXFail, TXFail(String),
#[error("can't get from in-memory buffer: {0}")] #[error("can't get from in-memory buffer: {0}")]
RXFail(#[from] ring_channel::TryRecvError), RXFail(#[from] ring_channel::TryRecvError),
@ -169,7 +172,7 @@ impl Ingress {
match self.tx.send(val) { match self.tx.send(val) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(_) => Err(Error::TXFail), Err(why) => Err(Error::TXFail(format!("{}", why))),
} }
} }
} }

View File

@ -0,0 +1,11 @@
[package]
name = "example-logtail-facade"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4"
logtail-facade = { path = "../../crates/logtail-facade" }
tokio = { version = "1", features = [ "full" ] }

View File

@ -0,0 +1,13 @@
use log::{debug, error, info, warn};
#[tokio::main]
async fn main() {
let mut eg = logtail_facade::init("rebterlai.example-logtail-facade".to_string()).unwrap();
error!("error");
warn!("warn");
info!("info");
debug!("debug");
eg.post().await.unwrap()
}

View File

@ -0,0 +1,14 @@
[package]
name = "example-logtail-poster"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
tokio = { version = "1", features = [ "full" ] }
logtail = { path = "../../crates/logtail" }
logtail-poster = { path = "../../crates/logtail-poster" }

View File

@ -0,0 +1,29 @@
use logtail_poster::*;
#[derive(Clone, serde::Serialize)]
struct Data {
pub msg: String,
}
#[tokio::main]
async fn main() {
let collection = "rebterlai.example.logtail-poster".to_string();
let cfg = logtail_poster::Config::load(collection.clone()).unwrap();
let (mut ing, mut eg) = Builder::default()
.collection(collection)
.private_id(cfg.private_id())
.user_agent("rebterlai/test".to_string())
.base_url("http://127.0.0.1:48283".to_string())
.build()
.unwrap();
ing.send(
serde_json::to_value(Data {
msg: "Hello, world!".to_string(),
})
.unwrap(),
)
.unwrap();
eg.post().await.unwrap();
}