use std::{env::var, io::Write, path::PathBuf};
use tracing::{
span::{self, Id, Record},
Event,
};
use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
use tracing_subscriber::{
fmt::{format::DefaultFields, Layer},
layer::Context,
Layer as LayerTrait, Registry,
};
use crate::{
errors::TelemetryError, telemetry_config, telemetry_formatter::TelemetryFormatter, Result,
};
pub struct TelemetryLayer {
pub inner_layer: Layer<Registry, DefaultFields, TelemetryFormatter, NonBlocking>,
}
impl TelemetryLayer {
pub fn __new() -> Result<(Self, WorkerGuard)> {
Self::__new_with_helpers(&mut DefaultNewHelpers)
}
fn __new_with_helpers(helpers: &mut impl NewHelpers) -> Result<(Self, WorkerGuard)> {
let (writer, guard) = {
if var("FUELUP_NO_TELEMETRY").is_ok() {
helpers.create_non_blocking_sink()
} else {
let telemetry_pkg_name = var("TELEMETRY_PKG_NAME");
if telemetry_pkg_name.is_err() || var("TELEMETRY_PKG_VERSION").is_err() {
return Err(TelemetryError::InvalidUsage);
}
helpers.create_non_blocking_appender(tracing_appender::rolling::hourly(
PathBuf::from(telemetry_config()?.fuelup_tmp.clone()),
format!(
"{}.telemetry",
telemetry_pkg_name.map_err(|_| TelemetryError::UnreadableCrateName)?
),
))
}
};
let inner_layer = tracing_subscriber::fmt::layer()
.with_writer(writer)
.with_ansi(false)
.event_format(TelemetryFormatter::new());
Ok((Self { inner_layer }, guard))
}
}
pub fn set_trace_id_env_to_new_uuid() {
let trace_id = uuid::Uuid::new_v4().to_string();
std::env::set_var("TRACE_ID", trace_id);
}
trait NewHelpers {
fn create_non_blocking_sink(&mut self) -> (NonBlocking, WorkerGuard) {
tracing_appender::non_blocking(std::io::sink())
}
fn create_non_blocking_appender(
&mut self,
writer: impl Write + Send + 'static,
) -> (NonBlocking, WorkerGuard) {
tracing_appender::non_blocking(writer)
}
}
struct DefaultNewHelpers;
impl NewHelpers for DefaultNewHelpers {}
impl LayerTrait<Registry> for TelemetryLayer {
fn on_close(&self, id: Id, ctx: Context<'_, Registry>) {
self.inner_layer.on_close(id, ctx);
}
fn on_enter(&self, id: &span::Id, ctx: Context<'_, Registry>) {
self.inner_layer.on_enter(id, ctx);
}
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, Registry>) {
self.inner_layer.on_event(event, ctx);
}
fn on_exit(&self, id: &span::Id, ctx: Context<'_, Registry>) {
self.inner_layer.on_exit(id, ctx);
}
fn on_id_change(&self, old: &span::Id, new: &span::Id, ctx: Context<'_, Registry>) {
self.inner_layer.on_id_change(old, new, ctx);
}
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, Registry>) {
self.inner_layer.on_new_span(attrs, id, ctx);
}
fn on_follows_from(&self, span: &span::Id, follows: &span::Id, ctx: Context<'_, Registry>) {
self.inner_layer.on_follows_from(span, follows, ctx);
}
fn on_record(&self, span: &Id, values: &Record<'_>, ctx: Context<'_, Registry>) {
self.inner_layer.on_record(span, values, ctx);
}
}
#[cfg(test)]
mod __new {
use super::*;
use crate::setup_fuelup_home;
use rusty_fork::rusty_fork_test;
use std::env::{remove_var, set_var};
rusty_fork_test! {
#[test]
fn opt_out_is_true() {
setup_fuelup_home();
set_var("FUELUP_NO_TELEMETRY", "true");
#[derive(Default)]
struct OptOutHelpers {
create_non_blocking_sink_called: bool,
create_non_blocking_appender_called: bool,
}
impl NewHelpers for OptOutHelpers {
fn create_non_blocking_sink(&mut self) -> (NonBlocking, WorkerGuard) {
self.create_non_blocking_sink_called = true;
tracing_appender::non_blocking(std::io::sink())
}
fn create_non_blocking_appender(
&mut self,
writer: impl Write + Send + 'static,
) -> (NonBlocking, WorkerGuard) {
self.create_non_blocking_appender_called = true;
tracing_appender::non_blocking(writer)
}
}
let mut helpers = OptOutHelpers::default();
let result = TelemetryLayer::__new_with_helpers(&mut helpers);
assert!(result.is_ok());
assert!(helpers.create_non_blocking_sink_called);
assert!(!helpers.create_non_blocking_appender_called);
}
#[test]
fn opt_out_is_empty() {
setup_fuelup_home();
set_var("FUELUP_NO_TELEMETRY", "");
#[derive(Default)]
struct OptOutHelpers {
create_non_blocking_sink_called: bool,
create_non_blocking_appender_called: bool,
}
impl NewHelpers for OptOutHelpers {
fn create_non_blocking_sink(&mut self) -> (NonBlocking, WorkerGuard) {
self.create_non_blocking_sink_called = true;
tracing_appender::non_blocking(std::io::sink())
}
fn create_non_blocking_appender(
&mut self,
writer: impl Write + Send + 'static,
) -> (NonBlocking, WorkerGuard) {
self.create_non_blocking_appender_called = true;
tracing_appender::non_blocking(writer)
}
}
let mut helpers = OptOutHelpers::default();
let result = TelemetryLayer::__new_with_helpers(&mut helpers);
assert!(result.is_ok());
assert!(helpers.create_non_blocking_sink_called);
assert!(!helpers.create_non_blocking_appender_called);
}
#[test]
fn telemetry_pkg_name_is_not_set() {
setup_fuelup_home();
remove_var("TELEMETRY_PKG_NAME");
set_var("TELEMETRY_PKG_VERSION", "1.0.0");
let result = TelemetryLayer::__new();
assert_eq!(result.err(), Some(TelemetryError::InvalidUsage));
}
#[test]
fn telemetry_pkg_version_is_not_set() {
setup_fuelup_home();
remove_var("TELEMETRY_PKG_VERSION");
set_var("TELEMETRY_PKG_NAME", "test_pkg_name");
let result = TelemetryLayer::__new();
assert_eq!(result.err(), Some(TelemetryError::InvalidUsage));
}
#[test]
fn ok() {
setup_fuelup_home();
set_var("TELEMETRY_PKG_NAME", "test_pkg_name");
set_var("TELEMETRY_PKG_VERSION", "1.0.0");
#[derive(Default)]
struct OkHelpers {
create_non_blocking_sink_called: bool,
create_non_blocking_appender_called: bool,
}
impl NewHelpers for OkHelpers {
fn create_non_blocking_sink(&mut self) -> (NonBlocking, WorkerGuard) {
self.create_non_blocking_sink_called = true;
tracing_appender::non_blocking(std::io::sink())
}
fn create_non_blocking_appender(
&mut self,
writer: impl Write + Send + 'static,
) -> (NonBlocking, WorkerGuard) {
self.create_non_blocking_appender_called = true;
tracing_appender::non_blocking(writer)
}
}
let mut helpers = OkHelpers::default();
let result = TelemetryLayer::__new_with_helpers(&mut helpers);
assert!(result.is_ok());
assert!(!helpers.create_non_blocking_sink_called);
assert!(helpers.create_non_blocking_appender_called);
}
}
}