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",
|
"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"
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*"]
|
members = [ "crates/*", "examples/*" ]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 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))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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