move logtail facade config code to the poster, add examples
Signed-off-by: Christine Dodrill <me@christine.website>
This commit is contained in:
parent
b09220fbe2
commit
c647ac7323
|
@ -128,6 +128,26 @@ dependencies = [
|
|||
"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]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
[workspace]
|
||||
members = ["crates/*"]
|
||||
members = [ "crates/*", "examples/*" ]
|
||||
|
|
|
@ -10,7 +10,7 @@ default = [ "log-facade" ]
|
|||
log-facade = [ "log" ]
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true, features = [ "std" ] }
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
serde_json = "1"
|
||||
|
||||
|
|
|
@ -1,109 +1,90 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{env, ffi::OsString, fs, io, path::PathBuf};
|
||||
use log::{Level, Metadata, Record};
|
||||
use logtail_poster::Egress;
|
||||
use std::{
|
||||
env,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[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 struct LogtailLogger {
|
||||
ing: Arc<Mutex<logtail_poster::Ingress>>,
|
||||
threshold: Level,
|
||||
}
|
||||
|
||||
pub(crate) fn state_file(name: &str) -> Option<PathBuf> {
|
||||
let mut dir: PathBuf = cache_dir()?.into();
|
||||
dir.push(&format!("{}.json", name));
|
||||
Some(dir)
|
||||
#[derive(serde::Serialize)]
|
||||
struct LogData<'a> {
|
||||
level: &'a str,
|
||||
target: &'a str,
|
||||
module_path: &'a str,
|
||||
file: &'a str,
|
||||
line: u32,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
|
||||
pub struct Config {
|
||||
private_id: String,
|
||||
public_id: String,
|
||||
impl log::Log for LogtailLogger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
metadata.level() <= self.threshold
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Self {
|
||||
let private_id = logtail::PrivateID::new();
|
||||
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()),
|
||||
};
|
||||
|
||||
Self {
|
||||
private_id: private_id.as_hex(),
|
||||
public_id: private_id.as_public().as_hex(),
|
||||
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::LogtailLogger::log: can't send json value to buffer: {}", why);
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
}
|
||||
println!("{:?}", 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();
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
#[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());
|
||||
pub fn init(collection: String) -> Result<Egress, Box<dyn std::error::Error>> {
|
||||
let cfg = logtail_poster::Config::load(collection.clone())?;
|
||||
let target = env::var("TS_LOG_TARGET")
|
||||
.unwrap_or(logtail_poster::DEFAULT_HOST.to_string())
|
||||
.to_string();
|
||||
|
||||
let cfg = Config::load("foo.bar").unwrap();
|
||||
let cfg2 = Config::load("foo.bar").unwrap();
|
||||
let (mut ing, eg) = logtail_poster::Builder::default()
|
||||
.collection(collection)
|
||||
.base_url(target)
|
||||
.buffer_size(256)
|
||||
.private_id(cfg.private_id())
|
||||
.build()?;
|
||||
|
||||
assert_eq!(cfg, cfg2);
|
||||
let threshold = if cfg!(debug_assertions) {
|
||||
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()));
|
||||
|
||||
let logger = LogtailLogger {
|
||||
ing: ing.clone(),
|
||||
threshold: threshold.clone(),
|
||||
};
|
||||
|
||||
log::set_boxed_logger(Box::new(logger))
|
||||
.map(|()| log::set_max_level(threshold.to_level_filter()))?;
|
||||
|
||||
Ok(eg)
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct ProgramStarted {
|
||||
msg: String,
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -7,8 +7,11 @@ This facilitates writing logs to a logtail server. This is a port of
|
|||
use reqwest::Client;
|
||||
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.
|
||||
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]
|
||||
|
@ -120,8 +123,8 @@ pub enum Error {
|
|||
#[error("no collection defined")]
|
||||
NoCollection,
|
||||
|
||||
#[error("can't put to in-memory buffer")]
|
||||
TXFail,
|
||||
#[error("can't put to in-memory buffer: {0}")]
|
||||
TXFail(String),
|
||||
|
||||
#[error("can't get from in-memory buffer: {0}")]
|
||||
RXFail(#[from] ring_channel::TryRecvError),
|
||||
|
@ -169,7 +172,7 @@ impl Ingress {
|
|||
|
||||
match self.tx.send(val) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(Error::TXFail),
|
||||
Err(why) => Err(Error::TXFail(format!("{}", why))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" ] }
|
|
@ -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()
|
||||
}
|
|
@ -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" }
|
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue