tracing-better-stack 0.1.0

A tracing-subscriber layer for Better Stack (Logtail) logging
Documentation
use std::time::Duration;

/// Configuration for the Better Stack tracing layer.
///
/// This struct contains all the configuration options for sending logs to Better Stack.
/// Use [`BetterStackConfig::builder`] to create a new configuration.
///
/// # Example
///
/// ```rust
/// use std::time::Duration;
/// use tracing_better_stack::BetterStackConfig;
///
/// let config = BetterStackConfig::builder(
///     "s1234567.us-east-9.betterstackdata.com",
///     "source_token_here"
/// )
/// .batch_size(200)
/// .batch_timeout(Duration::from_secs(10))
/// .include_location(false)
/// .build();
/// ```
#[derive(Debug, Clone)]
pub struct BetterStackConfig {
    /// The ingesting host provided by Better Stack for your source.
    ///
    /// This is the hostname where logs will be sent, without the protocol.
    /// Better Stack provides unique hosts for each source, typically in the format:
    /// `s1234567.us-east-9.betterstackdata.com`
    ///
    /// You can find this in your Better Stack dashboard when viewing your source.
    pub ingesting_host: String,

    /// The source token for authentication with Better Stack.
    ///
    /// This token authenticates your application with Better Stack.
    /// It should be kept secret and not committed to version control.
    /// You can find this in your Better Stack dashboard when viewing your source.
    pub source_token: String,

    /// Maximum number of events to batch before sending.
    ///
    /// Once this limit is reached, the batch will be sent immediately,
    /// even if the batch timeout hasn't expired.
    ///
    /// Default: 100
    pub batch_size: usize,

    /// Maximum time to wait before sending a batch.
    ///
    /// If this duration elapses and there are any events in the batch,
    /// they will be sent even if the batch size hasn't been reached.
    ///
    /// Default: 5 seconds
    pub batch_timeout: Duration,

    /// Maximum number of retry attempts for failed requests.
    ///
    /// When a request to Better Stack fails, the layer will retry
    /// with exponential backoff up to this many times.
    ///
    /// Default: 3
    pub max_retries: usize,

    /// Initial delay before the first retry attempt.
    ///
    /// This delay will be doubled for each subsequent retry,
    /// up to `max_retry_delay`.
    ///
    /// Default: 100ms
    pub initial_retry_delay: Duration,

    /// Maximum delay between retry attempts.
    ///
    /// The retry delay won't exceed this value, even with
    /// exponential backoff.
    ///
    /// Default: 10 seconds
    pub max_retry_delay: Duration,

    /// Whether to include file and line location in log events.
    ///
    /// When enabled, adds a `location` field to each log event
    /// containing the source file path and line number.
    ///
    /// Default: true
    pub include_location: bool,

    /// Whether to include span context in log events.
    ///
    /// When enabled, adds a `spans` field to each log event
    /// containing information about the active spans and their fields.
    ///
    /// Default: true
    pub include_spans: bool,
}

impl BetterStackConfig {
    /// Creates a new configuration builder with the required ingesting host and source token.
    ///
    /// This is the recommended way to create a `BetterStackConfig`. The builder
    /// provides a fluent API for setting optional configuration values.
    ///
    /// # Arguments
    ///
    /// * `ingesting_host` - The Better Stack ingesting host for your source
    /// * `source_token` - Your Better Stack source token for authentication
    ///
    /// # Example
    ///
    /// ```rust
    /// use tracing_better_stack::BetterStackConfig;
    ///
    /// let config = BetterStackConfig::builder(
    ///     "s1234567.us-east-9.betterstackdata.com",
    ///     "your_source_token"
    /// )
    /// .batch_size(50)
    /// .build();
    /// ```
    pub fn builder(
        ingesting_host: impl Into<String>,
        source_token: impl Into<String>,
    ) -> BetterStackConfigBuilder {
        BetterStackConfigBuilder::new(ingesting_host, source_token)
    }

    /// Returns the full ingestion URL for sending logs to Better Stack.
    ///
    /// This is an internal method used by the sender module.
    /// It constructs the complete URL from the ingesting host,
    /// automatically adding the appropriate protocol:
    /// - `https://` for production Better Stack hosts
    /// - `http://` for localhost or 127.0.0.1 (useful for testing)
    pub(crate) fn ingestion_url(&self) -> String {
        // For testing with localhost, use http instead of https
        if self.ingesting_host.starts_with("localhost")
            || self.ingesting_host.starts_with("127.0.0.1")
        {
            format!("http://{}", self.ingesting_host)
        } else {
            format!("https://{}", self.ingesting_host)
        }
    }
}

/// Builder for creating a [`BetterStackConfig`] with a fluent API.
///
/// This builder is created via [`BetterStackConfig::builder`] and provides
/// methods to customize the configuration before building the final config.
///
/// # Example
///
/// ```rust
/// use std::time::Duration;
/// use tracing_better_stack::BetterStackConfig;
///
/// let config = BetterStackConfig::builder(
///     "s1234567.us-east-9.betterstackdata.com",
///     "source_token"
/// )
/// .batch_size(200)
/// .batch_timeout(Duration::from_secs(10))
/// .max_retries(5)
/// .include_location(false)
/// .build();
/// ```
pub struct BetterStackConfigBuilder {
    config: BetterStackConfig,
}

impl BetterStackConfigBuilder {
    /// Creates a new builder with the required ingesting host and source token.
    ///
    /// This method is typically not called directly. Use [`BetterStackConfig::builder`] instead.
    ///
    /// # Arguments
    ///
    /// * `ingesting_host` - The Better Stack ingesting host for your source
    /// * `source_token` - Your Better Stack source token for authentication
    pub fn new(ingesting_host: impl Into<String>, source_token: impl Into<String>) -> Self {
        Self {
            config: BetterStackConfig {
                ingesting_host: ingesting_host.into(),
                source_token: source_token.into(),
                batch_size: 100,
                batch_timeout: Duration::from_secs(5),
                max_retries: 3,
                initial_retry_delay: Duration::from_millis(100),
                max_retry_delay: Duration::from_secs(10),
                include_location: true,
                include_spans: true,
            },
        }
    }

    /// Sets the maximum number of events to batch before sending.
    ///
    /// Once this limit is reached, the batch will be sent immediately.
    ///
    /// Default: 100
    ///
    /// # Example
    ///
    /// ```rust
    /// # use tracing_better_stack::BetterStackConfig;
    /// let config = BetterStackConfig::builder("s1234567.us-east-9.betterstackdata.com", "your-source-token")
    ///     .batch_size(200)
    ///     .build();
    /// ```
    pub fn batch_size(mut self, size: usize) -> Self {
        self.config.batch_size = size;
        self
    }

    /// Sets the maximum time to wait before sending a batch.
    ///
    /// If this duration elapses and there are any events in the batch,
    /// they will be sent even if the batch size hasn't been reached.
    ///
    /// Default: 5 seconds
    ///
    /// # Example
    ///
    /// ```rust
    /// # use std::time::Duration;
    /// # use tracing_better_stack::BetterStackConfig;
    /// let config = BetterStackConfig::builder("s1234567.us-east-9.betterstackdata.com", "your-source-token")
    ///     .batch_timeout(Duration::from_secs(10))
    ///     .build();
    /// ```
    pub fn batch_timeout(mut self, timeout: Duration) -> Self {
        self.config.batch_timeout = timeout;
        self
    }

    /// Sets the maximum number of retry attempts for failed requests.
    ///
    /// When a request fails, the layer will retry with exponential backoff.
    ///
    /// Default: 3
    ///
    /// # Example
    ///
    /// ```rust
    /// # use tracing_better_stack::BetterStackConfig;
    /// let config = BetterStackConfig::builder("s1234567.us-east-9.betterstackdata.com", "your-source-token")
    ///     .max_retries(5)
    ///     .build();
    /// ```
    pub fn max_retries(mut self, retries: usize) -> Self {
        self.config.max_retries = retries;
        self
    }

    /// Sets the initial delay before the first retry attempt.
    ///
    /// This delay will be doubled for each subsequent retry.
    ///
    /// Default: 100ms
    ///
    /// # Example
    ///
    /// ```rust
    /// # use std::time::Duration;
    /// # use tracing_better_stack::BetterStackConfig;
    /// let config = BetterStackConfig::builder("s1234567.us-east-9.betterstackdata.com", "your-source-token")
    ///     .initial_retry_delay(Duration::from_millis(200))
    ///     .build();
    /// ```
    pub fn initial_retry_delay(mut self, delay: Duration) -> Self {
        self.config.initial_retry_delay = delay;
        self
    }

    /// Sets the maximum delay between retry attempts.
    ///
    /// The retry delay won't exceed this value, even with exponential backoff.
    ///
    /// Default: 10 seconds
    ///
    /// # Example
    ///
    /// ```rust
    /// # use std::time::Duration;
    /// # use tracing_better_stack::BetterStackConfig;
    /// let config = BetterStackConfig::builder("s1234567.us-east-9.betterstackdata.com", "your-source-token")
    ///     .max_retry_delay(Duration::from_secs(30))
    ///     .build();
    /// ```
    pub fn max_retry_delay(mut self, delay: Duration) -> Self {
        self.config.max_retry_delay = delay;
        self
    }

    /// Sets whether to include file and line location in log events.
    ///
    /// When enabled, adds a `location` field to each log event.
    ///
    /// Default: true
    ///
    /// # Example
    ///
    /// ```rust
    /// # use tracing_better_stack::BetterStackConfig;
    /// let config = BetterStackConfig::builder("s1234567.us-east-9.betterstackdata.com", "your-source-token")
    ///     .include_location(false)
    ///     .build();
    /// ```
    pub fn include_location(mut self, include: bool) -> Self {
        self.config.include_location = include;
        self
    }

    /// Sets whether to include span context in log events.
    ///
    /// When enabled, adds a `spans` field to each log event with active span information.
    ///
    /// Default: true
    ///
    /// # Example
    ///
    /// ```rust
    /// # use tracing_better_stack::BetterStackConfig;
    /// let config = BetterStackConfig::builder("s1234567.us-east-9.betterstackdata.com", "your-source-token")
    ///     .include_spans(false)
    ///     .build();
    /// ```
    pub fn include_spans(mut self, include: bool) -> Self {
        self.config.include_spans = include;
        self
    }

    /// Builds the final [`BetterStackConfig`] with all the configured options.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use tracing_better_stack::BetterStackConfig;
    /// let config = BetterStackConfig::builder("s1234567.us-east-9.betterstackdata.com", "your-source-token")
    ///     .batch_size(200)
    ///     .build(); // Returns the final BetterStackConfig
    /// ```
    pub fn build(self) -> BetterStackConfig {
        self.config
    }
}