From 335660389e25e77100389e94d917f4ec1051f2bf Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Thu, 13 Mar 2025 18:20:52 +0000 Subject: [PATCH 1/2] support setting token programatically --- src/lib.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b013a59..0554b6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,23 @@ //! - The [Logfire documentation](https://logfire.pydantic.dev/docs/) for more information about Logfire in general. //! - The [Logfire GitHub repository](https://github.com/pydantic/logfire) for the source of the documentation, the Python SDK and an issue tracker for general questions about Logfire. //! +//! > ***Initial release - feedback wanted!*** +//! > +//! > This is an initial release of the Logfire Rust SDK. We've been using it internally to build +//! > Logfire for some time, and it is serving us well. As we're using it ourselves in production, +//! > we figured it's ready for everyone else also using Logfire. +//! > +//! > We are continually iterating to make this SDK better. We'd love your feedback on all aspects +//! > of the SDK and are keen to make the design as idiomatic and performant as possible. There are +//! > also many features currently supported by the Python SDK which are not yet supported by this +//! > SDK; please open issues to help us prioritize these to close this gap. For example, we have not +//! > yet implemented scrubbing in this Rust SDK, although we are aware it is important! +//! > +//! > In particular, the current coupling to `tracing` is an open design point. By building on top +//! > of tracing we get widest compatibility and a relatively simple SDK, however to make +//! > Logfire-specific adjustments we might prefer in future to move `tracing` to be an optional +//! > integration. +//! //! ## Getting Started //! //! To use Logfire in your Rust project, add the following to your `Cargo.toml`: @@ -45,6 +62,15 @@ //! //! ## Configuration //! +//! After adding basic setup as per above, the most two important environment variables are: +//! - `LOGFIRE_TOKEN` - (required) the token to send data to the Logfire platform +//! - `RUST_LOG` - (optional) the level of verbosity to send to the Logfire platform. By default +//! logs are captured at `TRACE` level so that all data is available for you to analyze in the +//! Logfire platform. +//! +//! All environment variables supported by the Rust Opentelemetry SDK are also supported by the +//! Logfire SDK. +//! //! ## Integrations //! //! ### With `tracing` @@ -99,6 +125,12 @@ mod internal; #[derive(Debug, Error)] #[non_exhaustive] pub enum ConfigureError { + /// No token was provided to send to logfire. + #[error( + "A logfire token is required from either `logfire::configure().with_token()` or the `LOGFIRE_TOKEN` environment variable" + )] + TokenRequired, + /// Logfire has already been configured #[error("Logfire has already been configured")] AlreadyConfigured, @@ -195,6 +227,7 @@ impl FromStr for ConsoleMode { pub fn configure() -> LogfireConfigBuilder { LogfireConfigBuilder { send_to_logfire: None, + token: None, console_mode: ConsoleMode::Fallback, additional_span_processors: Vec::new(), advanced: None, @@ -208,7 +241,7 @@ pub struct LogfireConfigBuilder { // TODO: support all options supported by the Python SDK // local: bool, send_to_logfire: Option, - // token: Option, + token: Option, // service_name: Option, // service_version: Option, // environment: Option, @@ -257,6 +290,15 @@ impl LogfireConfigBuilder { self } + /// The token to use for the Logfire platform. + /// + /// Defaults to the value of `LOGFIRE_TOKEN` if set. + #[must_use] + pub fn with_token>(mut self, token: T) -> Self { + self.token = Some(token.into()); + self + } + /// Whether to log to the console. #[must_use] pub fn console_mode(mut self, console_mode: ConsoleMode) -> Self { @@ -307,7 +349,7 @@ impl LogfireConfigBuilder { tracer, subscriber, tracer_provider, - logfire_token, + token: logfire_token, send_to_logfire, base_url, } = self.build_parts(None)?; @@ -354,7 +396,10 @@ impl LogfireConfigBuilder { self, env: Option<&HashMap>, ) -> Result { - let logfire_token = get_optional_env("LOGFIRE_TOKEN", env)?; + let mut token = self.token; + if token.is_none() { + token = get_optional_env("LOGFIRE_TOKEN", env)?; + } let send_to_logfire = match self.send_to_logfire { Some(send_to_logfire) => send_to_logfire, @@ -366,7 +411,7 @@ impl LogfireConfigBuilder { let send_to_logfire = match send_to_logfire { SendToLogfire::Yes => true, - SendToLogfire::IfTokenPresent => logfire_token.is_some(), + SendToLogfire::IfTokenPresent => token.is_some(), SendToLogfire::No => false, }; @@ -382,13 +427,13 @@ impl LogfireConfigBuilder { }; if send_to_logfire { + if token.is_none() { + return Err(ConfigureError::TokenRequired); + } tracer_provider_builder = tracer_provider_builder.with_span_processor( BatchSpanProcessor::builder(RemovePendingSpansExporter::new(LogfireSpanExporter { write_console: self.console_mode == ConsoleMode::Force, - inner: Some(span_exporter( - logfire_token.as_deref(), - &advanced_options.base_url, - )?), + inner: Some(span_exporter(token.as_deref(), &advanced_options.base_url)?), })) .with_batch_config( BatchConfigBuilder::default() @@ -445,7 +490,7 @@ impl LogfireConfigBuilder { }, subscriber: Arc::new(subscriber), tracer_provider, - logfire_token, + token, send_to_logfire, base_url: advanced_options.base_url, }) @@ -489,7 +534,7 @@ struct LogfireParts { tracer: LogfireTracer, subscriber: Arc, tracer_provider: SdkTracerProvider, - logfire_token: Option, + token: Option, send_to_logfire: bool, base_url: String, } From 25ec38fbb68f56dd1faf3e5e87bc448a115a6477 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Thu, 13 Mar 2025 18:39:16 +0000 Subject: [PATCH 2/2] coverage, test fixes --- src/lib.rs | 64 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0554b6f..9e89bf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -794,6 +794,7 @@ macro_rules! feature_required { ($feature_name:literal, $functionality:expr, $if_enabled:expr) => {{ #[cfg(feature = $feature_name)] { + let _ = $functionality; Ok($if_enabled) } @@ -1185,7 +1186,7 @@ mod tests { "code.lineno", ), value: I64( - 1080, + 1126, ), }, KeyValue { @@ -1311,7 +1312,7 @@ mod tests { "code.lineno", ), value: I64( - 1081, + 1127, ), }, KeyValue { @@ -1447,7 +1448,7 @@ mod tests { "code.lineno", ), value: I64( - 1081, + 1127, ), }, KeyValue { @@ -1589,7 +1590,7 @@ mod tests { "code.lineno", ), value: I64( - 1082, + 1128, ), }, KeyValue { @@ -1731,7 +1732,7 @@ mod tests { "code.lineno", ), value: I64( - 1084, + 1130, ), }, KeyValue { @@ -1901,7 +1902,7 @@ mod tests { "code.lineno", ), value: I64( - 1085, + 1131, ), }, KeyValue { @@ -1980,7 +1981,7 @@ mod tests { ), value: String( Owned( - "src/lib.rs:1086:17", + "src/lib.rs:1132:17", ), ), }, @@ -2047,7 +2048,7 @@ mod tests { "code.lineno", ), value: I64( - 513, + 558, ), }, KeyValue { @@ -2145,7 +2146,7 @@ mod tests { "code.lineno", ), value: I64( - 1080, + 1126, ), }, KeyValue { @@ -2245,13 +2246,23 @@ mod tests { #[test] fn test_send_to_logfire() { for (env, setting, expected) in [ - (vec![], None, true), - (vec![("LOGFIRE_SEND_TO_LOGFIRE", "no")], None, false), - (vec![("LOGFIRE_SEND_TO_LOGFIRE", "yes")], None, true), + (vec![], None, Err(ConfigureError::TokenRequired)), + (vec![("LOGFIRE_TOKEN", "a")], None, Ok(true)), + (vec![("LOGFIRE_SEND_TO_LOGFIRE", "no")], None, Ok(false)), + ( + vec![("LOGFIRE_SEND_TO_LOGFIRE", "yes")], + None, + Err(ConfigureError::TokenRequired), + ), + ( + vec![("LOGFIRE_SEND_TO_LOGFIRE", "yes"), ("LOGFIRE_TOKEN", "a")], + None, + Ok(true), + ), ( vec![("LOGFIRE_SEND_TO_LOGFIRE", "if-token-present")], None, - false, + Ok(false), ), ( vec![ @@ -2259,12 +2270,22 @@ mod tests { ("LOGFIRE_TOKEN", "a"), ], None, - true, + Ok(true), ), ( - vec![("LOGFIRE_SEND_TO_LOGFIRE", "no")], + vec![("LOGFIRE_SEND_TO_LOGFIRE", "no"), ("LOGFIRE_TOKEN", "a")], Some(SendToLogfire::Yes), - true, + Ok(true), + ), + ( + vec![("LOGFIRE_SEND_TO_LOGFIRE", "no"), ("LOGFIRE_TOKEN", "a")], + Some(SendToLogfire::IfTokenPresent), + Ok(true), + ), + ( + vec![("LOGFIRE_SEND_TO_LOGFIRE", "no")], + Some(SendToLogfire::IfTokenPresent), + Ok(false), ), ] { let env = env.into_iter().map(|(k, v)| (k.into(), v.into())).collect(); @@ -2274,9 +2295,16 @@ mod tests { config = config.send_to_logfire(value); } - let parts = config.build_parts(Some(&env)).unwrap(); + let result = config + .build_parts(Some(&env)) + .map(|parts| parts.send_to_logfire); - assert_eq!(parts.send_to_logfire, expected); + match (expected, result) { + (Ok(exp), Ok(actual)) => assert_eq!(exp, actual), + // compare strings because ConfigureError doesn't implement PartialEq + (Err(exp), Err(actual)) => assert_eq!(exp.to_string(), actual.to_string()), + (expected, result) => panic!("expected {expected:?}, got {result:?}"), + } } } }