use opentelemetry::{
    logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity},
    Key,
};
#[cfg(feature = "experimental_span_attributes")]
use std::borrow::Cow;
#[cfg(feature = "experimental_span_attributes")]
use std::collections::HashSet;
use tracing_core::Level;
#[cfg(feature = "experimental_metadata_attributes")]
use tracing_core::Metadata;
#[cfg(feature = "experimental_metadata_attributes")]
use tracing_log::NormalizeEvent;
use tracing_subscriber::{registry::LookupSpan, Layer};

/// Visitor to record the fields from the event record.
struct EventVisitor<'a, LR: LogRecord> {
    log_record: &'a mut LR,
}

/// Logs from the log crate have duplicated attributes that we removed here.
#[cfg(feature = "experimental_metadata_attributes")]
fn is_duplicated_metadata(field: &'static str) -> bool {
    field
        .strip_prefix("log.")
        .map(|remainder| matches!(remainder, "file" | "line" | "module_path" | "target"))
        .unwrap_or(false)
}

#[cfg(feature = "experimental_metadata_attributes")]
fn get_filename(filepath: &str) -> &str {
    if let Some((_, filename)) = filepath.rsplit_once('/') {
        return filename;
    }
    if let Some((_, filename)) = filepath.rsplit_once('\\') {
        return filename;
    }
    filepath
}

impl<'a, LR: LogRecord> EventVisitor<'a, LR> {
    fn new(log_record: &'a mut LR) -> Self {
        EventVisitor { log_record }
    }

    #[cfg(feature = "experimental_metadata_attributes")]
    fn visit_experimental_metadata(&mut self, meta: &Metadata) {
        if let Some(module_path) = meta.module_path() {
            self.log_record.add_attribute(
                Key::new("code.namespace"),
                AnyValue::from(module_path.to_owned()),
            );
        }

        if let Some(filepath) = meta.file() {
            self.log_record.add_attribute(
                Key::new("code.filepath"),
                AnyValue::from(filepath.to_owned()),
            );
            self.log_record.add_attribute(
                Key::new("code.filename"),
                AnyValue::from(get_filename(filepath).to_owned()),
            );
        }

        if let Some(line) = meta.line() {
            self.log_record
                .add_attribute(Key::new("code.lineno"), AnyValue::from(line));
        }
    }
}

impl<LR: LogRecord> tracing::field::Visit for EventVisitor<'_, LR> {
    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
        #[cfg(feature = "experimental_metadata_attributes")]
        if is_duplicated_metadata(field.name()) {
            return;
        }
        if field.name() == "message" {
            self.log_record.set_body(format!("{value:?}").into());
        } else {
            self.log_record
                .add_attribute(Key::new(field.name()), AnyValue::from(format!("{value:?}")));
        }
    }

    fn record_error(
        &mut self,
        _field: &tracing_core::Field,
        value: &(dyn std::error::Error + 'static),
    ) {
        self.log_record.add_attribute(
            Key::new("exception.message"),
            AnyValue::from(value.to_string()),
        );
        // No ability to get exception.stacktrace or exception.type from the error today.
    }

    fn record_bytes(&mut self, field: &tracing_core::Field, value: &[u8]) {
        self.log_record
            .add_attribute(Key::new(field.name()), AnyValue::from(value));
    }

    fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
        #[cfg(feature = "experimental_metadata_attributes")]
        if is_duplicated_metadata(field.name()) {
            return;
        }
        //TODO: Fix heap allocation. Check if lifetime of &str can be used
        // to optimize sync exporter scenario.
        if field.name() == "message" {
            self.log_record.set_body(AnyValue::from(value.to_owned()));
        } else {
            self.log_record
                .add_attribute(Key::new(field.name()), AnyValue::from(value.to_owned()));
        }
    }

    fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
        self.log_record
            .add_attribute(Key::new(field.name()), AnyValue::from(value));
    }

    fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
        self.log_record
            .add_attribute(Key::new(field.name()), AnyValue::from(value));
    }

    fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
        #[cfg(feature = "experimental_metadata_attributes")]
        if is_duplicated_metadata(field.name()) {
            return;
        }
        self.log_record
            .add_attribute(Key::new(field.name()), AnyValue::from(value));
    }

    // TODO: We might need to do similar for record_i128,record_u128 too
    // to avoid stringification, unless needed.
    fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
        #[cfg(feature = "experimental_metadata_attributes")]
        if is_duplicated_metadata(field.name()) {
            return;
        }
        if let Ok(signed) = i64::try_from(value) {
            self.log_record
                .add_attribute(Key::new(field.name()), AnyValue::from(signed));
        } else {
            self.log_record
                .add_attribute(Key::new(field.name()), AnyValue::from(format!("{value:?}")));
        }
    }

    fn record_i128(&mut self, field: &tracing::field::Field, value: i128) {
        #[cfg(feature = "experimental_metadata_attributes")]
        if is_duplicated_metadata(field.name()) {
            return;
        }
        if let Ok(signed) = i64::try_from(value) {
            self.log_record
                .add_attribute(Key::new(field.name()), AnyValue::from(signed));
        } else {
            self.log_record
                .add_attribute(Key::new(field.name()), AnyValue::from(format!("{value:?}")));
        }
    }

    fn record_u128(&mut self, field: &tracing::field::Field, value: u128) {
        #[cfg(feature = "experimental_metadata_attributes")]
        if is_duplicated_metadata(field.name()) {
            return;
        }
        if let Ok(signed) = i64::try_from(value) {
            self.log_record
                .add_attribute(Key::new(field.name()), AnyValue::from(signed));
        } else {
            self.log_record
                .add_attribute(Key::new(field.name()), AnyValue::from(format!("{value:?}")));
        }
    }

    // TODO: Remaining field types from AnyValue : Bytes, ListAny, Boolean
}

/// Visitor to extract fields from a tracing span.
/// Takes a mutable reference to a Vec to append fields to, and an optional
/// allowlist to include only named attributes.
#[cfg(feature = "experimental_span_attributes")]
struct SpanFieldVisitor<'a> {
    attributes: &'a mut Vec<(Key, AnyValue)>,
    allowlist: Option<&'a HashSet<Cow<'static, str>>>,
}

#[cfg(feature = "experimental_span_attributes")]
impl SpanFieldVisitor<'_> {
    #[inline]
    fn allowed(&self, field: &tracing::field::Field) -> bool {
        self.allowlist
            .map_or(true, |set| set.contains(field.name()))
    }
}

#[cfg(feature = "experimental_span_attributes")]
impl tracing::field::Visit for SpanFieldVisitor<'_> {
    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
        if self.allowed(field) {
            self.attributes.push((
                Key::from(field.name()),
                AnyValue::from(format!("{value:?}")),
            ));
        }
    }

    fn record_error(
        &mut self,
        field: &tracing::field::Field,
        value: &(dyn std::error::Error + 'static),
    ) {
        if self.allowed(field) {
            self.attributes.push((
                Key::new("exception.message"),
                AnyValue::from(value.to_string()),
            ));
        }
    }

    fn record_bytes(&mut self, field: &tracing::field::Field, value: &[u8]) {
        if self.allowed(field) {
            self.attributes
                .push((Key::from(field.name()), AnyValue::from(value)));
        }
    }

    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
        if self.allowed(field) {
            self.attributes
                .push((Key::from(field.name()), AnyValue::from(value.to_owned())));
        }
    }

    fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
        if self.allowed(field) {
            self.attributes
                .push((Key::from(field.name()), AnyValue::from(value)));
        }
    }

    fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
        if self.allowed(field) {
            if let Ok(signed) = i64::try_from(value) {
                self.attributes
                    .push((Key::from(field.name()), AnyValue::from(signed)));
            } else {
                self.attributes
                    .push((Key::from(field.name()), AnyValue::from(format!("{value}"))));
            }
        }
    }

    fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
        if self.allowed(field) {
            self.attributes
                .push((Key::from(field.name()), AnyValue::from(value)));
        }
    }

    fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
        if self.allowed(field) {
            self.attributes
                .push((Key::from(field.name()), AnyValue::from(value)));
        }
    }

    fn record_i128(&mut self, field: &tracing::field::Field, value: i128) {
        if self.allowed(field) {
            if let Ok(signed) = i64::try_from(value) {
                self.attributes
                    .push((Key::from(field.name()), AnyValue::from(signed)));
            } else {
                self.attributes
                    .push((Key::from(field.name()), AnyValue::from(format!("{value}"))));
            }
        }
    }

    fn record_u128(&mut self, field: &tracing::field::Field, value: u128) {
        if self.allowed(field) {
            if let Ok(signed) = i64::try_from(value) {
                self.attributes
                    .push((Key::from(field.name()), AnyValue::from(signed)));
            } else {
                self.attributes
                    .push((Key::from(field.name()), AnyValue::from(format!("{value}"))));
            }
        }
    }
}

/// Stored span attributes in the span's extensions.
/// Similar to how `tracing_subscriber::fmt::FormattedFields` stores formatted
/// field data directly in span extensions - the Registry's internal locking
/// provides the necessary synchronization.
#[cfg(feature = "experimental_span_attributes")]
#[derive(Debug)]
struct StoredSpanAttributes {
    attributes: Vec<(Key, AnyValue)>,
}

/// Configures which attributes from active [`tracing`] spans are copied onto
/// each emitted log record.
///
/// "Span" here refers to a [`tracing::span!`] from the [`tracing`] crate,
/// **not** an `opentelemetry::trace::Span`.
///
/// Pass this to [`OpenTelemetryTracingBridgeBuilder::with_tracing_span_attributes`] to
/// enable tracing-span attribute enrichment.
///
/// [`tracing`]: https://crates.io/crates/tracing
/// [`tracing::span!`]: https://docs.rs/tracing/latest/tracing/macro.span.html
#[cfg(feature = "experimental_span_attributes")]
pub struct TracingSpanAttributes(TracingSpanAttributesInner);

#[cfg(feature = "experimental_span_attributes")]
enum TracingSpanAttributesInner {
    All,
    Allowlist(HashSet<Cow<'static, str>>),
}

#[cfg(feature = "experimental_span_attributes")]
impl TracingSpanAttributes {
    /// Copy **all** tracing-span attributes onto log records.
    pub fn all() -> Self {
        Self(TracingSpanAttributesInner::All)
    }

    /// Copy only the tracing-span attributes whose keys are in the given
    /// allowlist.
    pub fn allowlist(keys: impl IntoIterator<Item = impl Into<Cow<'static, str>>>) -> Self {
        Self(TracingSpanAttributesInner::Allowlist(
            keys.into_iter().map(Into::into).collect(),
        ))
    }
}

#[cfg(feature = "experimental_span_attributes")]
impl std::fmt::Debug for TracingSpanAttributes {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.0 {
            TracingSpanAttributesInner::All => f.write_str("TracingSpanAttributes::all()"),
            TracingSpanAttributesInner::Allowlist(set) => f
                .debug_tuple("TracingSpanAttributes::allowlist")
                .field(set)
                .finish(),
        }
    }
}

pub struct OpenTelemetryTracingBridge<P, L>
where
    P: LoggerProvider<Logger = L> + Send + Sync,
    L: Logger + Send + Sync,
{
    logger: L,
    _phantom: std::marker::PhantomData<P>, // P is not used.
    // Tracing-span attribute enrichment configuration:
    // - `None` => disabled (default). No per-span work, no scope walk.
    // - `Some(All)` => copy all tracing-span attributes onto log records.
    // - `Some(Allowlist(set))` => copy only attributes whose keys are in `set`.
    #[cfg(feature = "experimental_span_attributes")]
    span_attributes: Option<TracingSpanAttributesInner>,
}

impl<P, L> OpenTelemetryTracingBridge<P, L>
where
    P: LoggerProvider<Logger = L> + Send + Sync,
    L: Logger + Send + Sync,
{
    pub fn new(provider: &P) -> Self {
        Self::builder(provider).build()
    }

    pub fn builder(provider: &P) -> OpenTelemetryTracingBridgeBuilder<P, L> {
        OpenTelemetryTracingBridgeBuilder {
            // Using empty scope name.
            // The name/version of this library itself can be added
            // as a Scope attribute, once a semantic convention is
            // defined for the same.
            // See https://github.com/open-telemetry/semantic-conventions/issues/1550
            logger: provider.logger(""),
            _phantom: Default::default(),
            #[cfg(feature = "experimental_span_attributes")]
            span_attributes: None,
        }
    }
}

pub struct OpenTelemetryTracingBridgeBuilder<P, L>
where
    P: LoggerProvider<Logger = L> + Send + Sync,
    L: Logger + Send + Sync,
{
    logger: L,
    _phantom: std::marker::PhantomData<P>,
    #[cfg(feature = "experimental_span_attributes")]
    span_attributes: Option<TracingSpanAttributesInner>,
}

impl<P, L> OpenTelemetryTracingBridgeBuilder<P, L>
where
    P: LoggerProvider<Logger = L> + Send + Sync,
    L: Logger + Send + Sync,
{
    /// Enable copying attributes from active [`tracing`] spans onto each
    /// emitted log record. ("Span" here means a [`tracing::span!`] from the
    /// [`tracing`] crate, **not** an `opentelemetry::trace::Span`.)
    ///
    /// By default, enrichment is **disabled** and no per-span work is
    /// performed.
    ///
    /// Use [`TracingSpanAttributes::all()`] to copy every tracing-span attribute, or
    /// [`TracingSpanAttributes::allowlist(keys)`](TracingSpanAttributes::allowlist) to copy
    /// only named attributes.
    ///
    /// Calling this method multiple times replaces any prior configuration —
    /// the last call wins.
    ///
    /// [`tracing`]: https://crates.io/crates/tracing
    /// [`tracing::span!`]: https://docs.rs/tracing/latest/tracing/macro.span.html
    #[cfg(feature = "experimental_span_attributes")]
    pub fn with_tracing_span_attributes(mut self, span_attributes: TracingSpanAttributes) -> Self {
        self.span_attributes = Some(span_attributes.0);
        self
    }

    pub fn build(self) -> OpenTelemetryTracingBridge<P, L> {
        OpenTelemetryTracingBridge {
            logger: self.logger,
            _phantom: self._phantom,
            #[cfg(feature = "experimental_span_attributes")]
            span_attributes: self.span_attributes,
        }
    }
}

impl<S, P, L> Layer<S> for OpenTelemetryTracingBridge<P, L>
where
    S: tracing::Subscriber + for<'a> LookupSpan<'a>,
    P: LoggerProvider<Logger = L> + Send + Sync + 'static,
    L: Logger + Send + Sync + 'static,
{
    fn on_event(
        &self,
        event: &tracing::Event<'_>,
        #[cfg_attr(not(feature = "experimental_span_attributes"), allow(unused_variables))]
        ctx: tracing_subscriber::layer::Context<'_, S>,
    ) {
        let metadata = event.metadata();
        let severity = severity_of_level(metadata.level());
        let target = metadata.target();
        let name = metadata.name();
        if !self.logger.event_enabled(severity, target, Some(name)) {
            // TODO: See if we need internal logs or track the count.
            return;
        }

        #[cfg(feature = "experimental_metadata_attributes")]
        let normalized_meta = event.normalized_metadata();

        #[cfg(feature = "experimental_metadata_attributes")]
        let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());

        let mut log_record = self.logger.create_log_record();

        log_record.set_target(target);
        log_record.set_event_name(name);
        log_record.set_severity_number(severity);
        log_record.set_severity_text(metadata.level().as_str());

        // Extract tracing-span attributes if enrichment is enabled.
        #[cfg(feature = "experimental_span_attributes")]
        if self.span_attributes.is_some() {
            // Collect attributes from all parent spans (root to leaf), including current span
            if let Some(scope) = ctx.event_scope(event) {
                for span_ref in scope.from_root() {
                    // Access extensions inline - each span has its own extension lock
                    let extensions = span_ref.extensions();
                    if let Some(stored) = extensions.get::<StoredSpanAttributes>() {
                        for (key, value) in stored.attributes.iter() {
                            log_record.add_attribute(key.clone(), value.clone());
                        }
                    }
                }
            }
        }

        let mut visitor = EventVisitor::new(&mut log_record);
        #[cfg(feature = "experimental_metadata_attributes")]
        visitor.visit_experimental_metadata(meta);
        // Visit fields.
        event.record(&mut visitor);

        //emit record
        self.logger.emit(log_record);
    }

    #[cfg(feature = "experimental_span_attributes")]
    fn on_new_span(
        &self,
        attrs: &tracing::span::Attributes<'_>,
        id: &tracing::span::Id,
        ctx: tracing_subscriber::layer::Context<'_, S>,
    ) {
        // Skip entirely when tracing-span attribute enrichment is disabled to
        // avoid any per-span overhead.
        let Some(config) = self.span_attributes.as_ref() else {
            return;
        };
        let allowlist = match config {
            TracingSpanAttributesInner::All => None,
            TracingSpanAttributesInner::Allowlist(set) => Some(set),
        };

        let span = ctx.span(id).expect("Span not found; this is a bug");
        let mut fields = Vec::with_capacity(attrs.fields().len());
        let mut visitor = SpanFieldVisitor {
            attributes: &mut fields,
            allowlist,
        };
        attrs.record(&mut visitor);

        // Only store if we actually found attributes to avoid empty allocations
        if !fields.is_empty() {
            let stored = StoredSpanAttributes { attributes: fields };

            let mut extensions = span.extensions_mut();
            extensions.insert(stored);
        }
    }

    #[cfg(feature = "experimental_span_attributes")]
    fn on_record(
        &self,
        id: &tracing::span::Id,
        values: &tracing::span::Record<'_>,
        ctx: tracing_subscriber::layer::Context<'_, S>,
    ) {
        let Some(config) = self.span_attributes.as_ref() else {
            return;
        };
        let allowlist = match config {
            TracingSpanAttributesInner::All => None,
            TracingSpanAttributesInner::Allowlist(set) => Some(set),
        };

        let span = ctx.span(id).expect("Span not found; this is a bug");
        let mut extensions = span.extensions_mut();

        if let Some(stored) = extensions.get_mut::<StoredSpanAttributes>() {
            // Append to existing attributes - extensions_mut() gives us mutable access
            let mut visitor = SpanFieldVisitor {
                attributes: &mut stored.attributes,
                allowlist,
            };
            values.record(&mut visitor);
        } else {
            // No existing attributes, create new storage
            let mut fields = Vec::with_capacity(values.len());
            let mut visitor = SpanFieldVisitor {
                attributes: &mut fields,
                allowlist,
            };
            values.record(&mut visitor);
            if !fields.is_empty() {
                let stored = StoredSpanAttributes { attributes: fields };
                extensions.insert(stored);
            }
        }
    }
}

const fn severity_of_level(level: &Level) -> Severity {
    match *level {
        Level::TRACE => Severity::Trace,
        Level::DEBUG => Severity::Debug,
        Level::INFO => Severity::Info,
        Level::WARN => Severity::Warn,
        Level::ERROR => Severity::Error,
    }
}

#[cfg(test)]
mod tests {
    use crate::layer;
    #[cfg(feature = "experimental_span_attributes")]
    use crate::layer::TracingSpanAttributes;
    use opentelemetry::logs::Severity;
    use opentelemetry::trace::TracerProvider;
    use opentelemetry::trace::{TraceContextExt, TraceFlags, Tracer};
    use opentelemetry::InstrumentationScope;
    use opentelemetry::{logs::AnyValue, Key};
    use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
    use opentelemetry_sdk::logs::{InMemoryLogExporter, LogProcessor};
    use opentelemetry_sdk::logs::{SdkLogRecord, SdkLoggerProvider};
    use opentelemetry_sdk::trace::{Sampler, SdkTracerProvider};
    use tracing::error;
    use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
    use tracing_subscriber::Layer;

    pub fn attributes_contains(log_record: &SdkLogRecord, key: &Key, value: &AnyValue) -> bool {
        log_record
            .attributes_iter()
            .any(|(k, v)| k == key && v == value)
    }

    #[allow(impl_trait_overcaptures)] // can only be fixed with Rust 1.82+
    fn create_tracing_subscriber(logger_provider: &SdkLoggerProvider) -> impl tracing::Subscriber {
        let level_filter = tracing_subscriber::filter::LevelFilter::WARN; // Capture WARN and ERROR levels
        let layer =
            layer::OpenTelemetryTracingBridge::new(logger_provider).with_filter(level_filter); // No filter based on target, only based on log level

        tracing_subscriber::registry().with(layer)
    }

    // cargo test --features=testing
    #[test]
    fn tracing_appender_standalone() {
        // Arrange
        let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
        let logger_provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let subscriber = create_tracing_subscriber(&logger_provider);

        // avoiding setting tracing subscriber as global as that does not
        // play well with unit tests.
        let _guard = tracing::subscriber::set_default(subscriber);

        // Act
        let small_u64value: u64 = 42;
        let big_u64value: u64 = u64::MAX;
        let small_usizevalue: usize = 42;
        let big_usizevalue: usize = usize::MAX;
        let small_u128value: u128 = 42;
        let big_u128value: u128 = u128::MAX;
        let small_i128value: i128 = 42;
        let big_i128value: i128 = i128::MAX;
        error!(name: "my-event-name", target: "my-system", event_id = 20, bytes = &b"abc"[..], error = &OTelSdkError::AlreadyShutdown as &dyn std::error::Error, small_u64value, big_u64value, small_usizevalue, big_usizevalue, small_u128value, big_u128value, small_i128value, big_i128value, user_name = "otel", user_email = "otel@opentelemetry.io");
        assert!(logger_provider.force_flush().is_ok());

        // Assert TODO: move to helper methods
        let exported_logs = exporter
            .get_emitted_logs()
            .expect("Logs are expected to be exported.");
        assert_eq!(exported_logs.len(), 1);
        let log = exported_logs
            .first()
            .expect("Atleast one log is expected to be present.");

        // Validate common fields
        assert_eq!(log.instrumentation.name(), "");
        assert_eq!(log.record.severity_number(), Some(Severity::Error));
        // Validate target
        assert_eq!(
            log.record.target().expect("target is expected").to_string(),
            "my-system"
        );
        // Validate event name
        assert_eq!(
            log.record.event_name().expect("event_name is expected"),
            "my-event-name"
        );

        // Validate trace context is none.
        assert!(log.record.trace_context().is_none());

        // Validate attributes
        #[cfg(not(feature = "experimental_metadata_attributes"))]
        assert_eq!(log.record.attributes_iter().count(), 13);
        #[cfg(feature = "experimental_metadata_attributes")]
        assert_eq!(log.record.attributes_iter().count(), 17);
        assert!(attributes_contains(
            &log.record,
            &Key::new("event_id"),
            &AnyValue::Int(20)
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("user_name"),
            &AnyValue::String("otel".into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("user_email"),
            &AnyValue::String("otel@opentelemetry.io".into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("exception.message"),
            &AnyValue::String(OTelSdkError::AlreadyShutdown.to_string().into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("small_u64value"),
            &AnyValue::Int(42.into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("big_u64value"),
            &AnyValue::String(format!("{}", u64::MAX).into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("small_usizevalue"),
            &AnyValue::Int(42.into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("big_usizevalue"),
            &AnyValue::String(format!("{}", u64::MAX).into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("small_u128value"),
            &AnyValue::Int(42.into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("big_u128value"),
            &AnyValue::String(format!("{}", u128::MAX).into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("small_i128value"),
            &AnyValue::Int(42.into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("big_i128value"),
            &AnyValue::String(format!("{}", i128::MAX).into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("bytes"),
            &AnyValue::Bytes(Box::new(b"abc".to_vec()))
        ));
        #[cfg(feature = "experimental_metadata_attributes")]
        {
            assert!(attributes_contains(
                &log.record,
                &Key::new("code.filename"),
                &AnyValue::String("layer.rs".into())
            ));
            assert!(attributes_contains(
                &log.record,
                &Key::new("code.namespace"),
                &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into())
            ));
            // The other 3 experimental_metadata_attributes are too unstable to check their value.
            // Ex.: The path will be different on a Windows and Linux machine.
            // Ex.: The line can change easily if someone makes changes in this source file.
            let attributes_key: Vec<Key> = log
                .record
                .attributes_iter()
                .map(|(key, _)| key.clone())
                .collect();
            assert!(attributes_key.contains(&Key::new("code.filepath")));
            assert!(attributes_key.contains(&Key::new("code.lineno")));
            assert!(!attributes_key.contains(&Key::new("log.target")));
        }

        // Test when target, eventname are not explicitly provided
        exporter.reset();
        error!(
            event_id = 20,
            user_name = "otel",
            user_email = "otel@opentelemetry.io"
        );
        assert!(logger_provider.force_flush().is_ok());

        // Assert TODO: move to helper methods
        let exported_logs = exporter
            .get_emitted_logs()
            .expect("Logs are expected to be exported.");
        assert_eq!(exported_logs.len(), 1);
        let log = exported_logs
            .first()
            .expect("Atleast one log is expected to be present.");

        // Validate target - tracing defaults to module path
        assert_eq!(
            log.record.target().expect("target is expected").to_string(),
            "opentelemetry_appender_tracing::layer::tests"
        );
        // Validate event name - tracing defaults to event followed source & line number
        // Assert is doing "contains" check to avoid tests failing when line number changes.
        // and also account for the fact that the module path is different on different platforms.
        // Ex.: The path will be different on a Windows and Linux machine.
        assert!(log
            .record
            .event_name()
            .expect("event_name is expected")
            .contains("event opentelemetry-appender-tracing"),);
    }

    #[test]
    fn tracing_appender_inside_tracing_context() {
        // Arrange
        let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
        let logger_provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let subscriber = create_tracing_subscriber(&logger_provider);

        // avoiding setting tracing subscriber as global as that does not
        // play well with unit tests.
        let _guard = tracing::subscriber::set_default(subscriber);

        // setup tracing as well.
        let tracer_provider = SdkTracerProvider::builder()
            .with_sampler(Sampler::AlwaysOn)
            .build();
        let tracer = tracer_provider.tracer("test-tracer");

        // Act
        let (trace_id_expected, span_id_expected) = tracer.in_span("test-span", |cx| {
            let trace_id = cx.span().span_context().trace_id();
            let span_id = cx.span().span_context().span_id();

            // logging is done inside span context.
            error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io");
            (trace_id, span_id)
        });

        assert!(logger_provider.force_flush().is_ok());

        // Assert TODO: move to helper methods
        let exported_logs = exporter
            .get_emitted_logs()
            .expect("Logs are expected to be exported.");
        assert_eq!(exported_logs.len(), 1);
        let log = exported_logs
            .first()
            .expect("Atleast one log is expected to be present.");

        // validate common fields.
        assert_eq!(log.instrumentation.name(), "");
        assert_eq!(log.record.severity_number(), Some(Severity::Error));
        // Validate target
        assert_eq!(
            log.record.target().expect("target is expected").to_string(),
            "my-system"
        );
        // Validate event name
        assert_eq!(
            log.record.event_name().expect("event_name is expected"),
            "my-event-name"
        );

        // validate trace context.
        assert!(log.record.trace_context().is_some());
        assert_eq!(
            log.record.trace_context().unwrap().trace_id,
            trace_id_expected
        );
        assert_eq!(
            log.record.trace_context().unwrap().span_id,
            span_id_expected
        );
        assert_eq!(
            log.record.trace_context().unwrap().trace_flags.unwrap(),
            TraceFlags::SAMPLED
        );

        // validate attributes.
        #[cfg(not(feature = "experimental_metadata_attributes"))]
        assert_eq!(log.record.attributes_iter().count(), 3);
        #[cfg(feature = "experimental_metadata_attributes")]
        assert_eq!(log.record.attributes_iter().count(), 7);
        assert!(attributes_contains(
            &log.record,
            &Key::new("event_id"),
            &AnyValue::Int(20.into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("user_name"),
            &AnyValue::String("otel".into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("user_email"),
            &AnyValue::String("otel@opentelemetry.io".into())
        ));
        #[cfg(feature = "experimental_metadata_attributes")]
        {
            assert!(attributes_contains(
                &log.record,
                &Key::new("code.filename"),
                &AnyValue::String("layer.rs".into())
            ));
            assert!(attributes_contains(
                &log.record,
                &Key::new("code.namespace"),
                &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into())
            ));
            // The other 3 experimental_metadata_attributes are too unstable to check their value.
            // Ex.: The path will be different on a Windows and Linux machine.
            // Ex.: The line can change easily if someone makes changes in this source file.
            let attributes_key: Vec<Key> = log
                .record
                .attributes_iter()
                .map(|(key, _)| key.clone())
                .collect();
            assert!(attributes_key.contains(&Key::new("code.filepath")));
            assert!(attributes_key.contains(&Key::new("code.lineno")));
            assert!(!attributes_key.contains(&Key::new("log.target")));
        }
    }

    #[test]
    #[ignore = "log tests set a global logger that cannot be unset and affects other tests"]
    fn tracing_appender_standalone_with_tracing_log() {
        // Arrange
        let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
        let logger_provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let subscriber = create_tracing_subscriber(&logger_provider);

        // avoiding setting tracing subscriber as global as that does not
        // play well with unit tests.
        let _guard = tracing::subscriber::set_default(subscriber);
        drop(tracing_log::LogTracer::init());

        // Act
        log::error!("log from log crate");
        assert!(logger_provider.force_flush().is_ok());

        // Assert TODO: move to helper methods
        let exported_logs = exporter
            .get_emitted_logs()
            .expect("Logs are expected to be exported.");
        assert_eq!(exported_logs.len(), 1);
        let log = exported_logs
            .first()
            .expect("Atleast one log is expected to be present.");

        // Validate common fields
        assert_eq!(log.instrumentation.name(), "");
        assert_eq!(log.record.severity_number(), Some(Severity::Error));
        // Target and EventName from Log crate are "log" and "log event" respectively.
        // Validate target
        assert_eq!(
            log.record.target().expect("target is expected").to_string(),
            "log"
        );
        // Validate event name
        assert_eq!(
            log.record.event_name().expect("event_name is expected"),
            "log event"
        );

        // Validate trace context is none.
        assert!(log.record.trace_context().is_none());

        // Attributes can be polluted when we don't use this feature.
        #[cfg(feature = "experimental_metadata_attributes")]
        assert_eq!(log.record.attributes_iter().count(), 4);

        #[cfg(feature = "experimental_metadata_attributes")]
        {
            assert!(attributes_contains(
                &log.record,
                &Key::new("code.filename"),
                &AnyValue::String("layer.rs".into())
            ));
            assert!(attributes_contains(
                &log.record,
                &Key::new("code.namespace"),
                &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into())
            ));
            // The other 3 experimental_metadata_attributes are too unstable to check their value.
            // Ex.: The path will be different on a Windows and Linux machine.
            // Ex.: The line can change easily if someone makes changes in this source file.
            let attributes_key: Vec<Key> = log
                .record
                .attributes_iter()
                .map(|(key, _)| key.clone())
                .collect();
            assert!(attributes_key.contains(&Key::new("code.filepath")));
            assert!(attributes_key.contains(&Key::new("code.lineno")));
            assert!(!attributes_key.contains(&Key::new("log.target")));
        }
    }

    #[test]
    #[ignore = "log tests set a global logger that cannot be unset and affects other tests"]
    fn tracing_appender_inside_tracing_context_with_tracing_log() {
        // Arrange
        let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
        let logger_provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let subscriber = create_tracing_subscriber(&logger_provider);

        // avoiding setting tracing subscriber as global as that does not
        // play well with unit tests.
        let _guard = tracing::subscriber::set_default(subscriber);
        drop(tracing_log::LogTracer::init());

        // setup tracing as well.
        let tracer_provider = SdkTracerProvider::builder()
            .with_sampler(Sampler::AlwaysOn)
            .build();
        let tracer = tracer_provider.tracer("test-tracer");

        // Act
        let (trace_id_expected, span_id_expected) = tracer.in_span("test-span", |cx| {
            let trace_id = cx.span().span_context().trace_id();
            let span_id = cx.span().span_context().span_id();

            // logging is done inside span context.
            log::error!(target: "my-system", "log from log crate");
            (trace_id, span_id)
        });

        assert!(logger_provider.force_flush().is_ok());

        // Assert TODO: move to helper methods
        let exported_logs = exporter
            .get_emitted_logs()
            .expect("Logs are expected to be exported.");
        assert_eq!(exported_logs.len(), 1);
        let log = exported_logs
            .first()
            .expect("Atleast one log is expected to be present.");

        // validate common fields.
        assert_eq!(log.instrumentation.name(), "");
        assert_eq!(log.record.severity_number(), Some(Severity::Error));

        // validate trace context.
        assert!(log.record.trace_context().is_some());
        assert_eq!(
            log.record.trace_context().unwrap().trace_id,
            trace_id_expected
        );
        assert_eq!(
            log.record.trace_context().unwrap().span_id,
            span_id_expected
        );
        assert_eq!(
            log.record.trace_context().unwrap().trace_flags.unwrap(),
            TraceFlags::SAMPLED
        );

        for attribute in log.record.attributes_iter() {
            println!("key: {:?}, value: {:?}", attribute.0, attribute.1);
        }

        // Attributes can be polluted when we don't use this feature.
        #[cfg(feature = "experimental_metadata_attributes")]
        assert_eq!(log.record.attributes_iter().count(), 4);

        #[cfg(feature = "experimental_metadata_attributes")]
        {
            assert!(attributes_contains(
                &log.record,
                &Key::new("code.filename"),
                &AnyValue::String("layer.rs".into())
            ));
            assert!(attributes_contains(
                &log.record,
                &Key::new("code.namespace"),
                &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into())
            ));
            // The other 3 experimental_metadata_attributes are too unstable to check their value.
            // Ex.: The path will be different on a Windows and Linux machine.
            // Ex.: The line can change easily if someone makes changes in this source file.
            let attributes_key: Vec<Key> = log
                .record
                .attributes_iter()
                .map(|(key, _)| key.clone())
                .collect();
            assert!(attributes_key.contains(&Key::new("code.filepath")));
            assert!(attributes_key.contains(&Key::new("code.lineno")));
            assert!(!attributes_key.contains(&Key::new("log.target")));
        }
    }

    #[derive(Debug)]
    struct LogProcessorWithIsEnabled {
        severity_level: Severity,
        name: String,
        target: String,
    }

    impl LogProcessorWithIsEnabled {
        fn new(severity_level: Severity, name: String, target: String) -> Self {
            LogProcessorWithIsEnabled {
                severity_level,
                name,
                target,
            }
        }
    }

    impl LogProcessor for LogProcessorWithIsEnabled {
        fn emit(&self, _record: &mut SdkLogRecord, _scope: &InstrumentationScope) {
            // no-op
        }

        fn event_enabled(&self, level: Severity, target: &str, name: Option<&str>) -> bool {
            // assert that passed in arguments are same as the ones set in the test.
            assert_eq!(self.severity_level, level);
            assert_eq!(self.target, target);
            assert_eq!(
                self.name,
                name.expect("name is expected from tracing appender")
            );
            true
        }

        fn force_flush(&self) -> OTelSdkResult {
            Ok(())
        }

        fn shutdown_with_timeout(&self, _timeout: std::time::Duration) -> OTelSdkResult {
            Ok(())
        }
    }

    #[test]
    fn is_enabled() {
        // Arrange
        let logger_provider = SdkLoggerProvider::builder()
            .with_log_processor(LogProcessorWithIsEnabled::new(
                Severity::Error,
                "my-event-name".to_string(),
                "my-system".to_string(),
            ))
            .build();

        let subscriber = create_tracing_subscriber(&logger_provider);

        // avoiding setting tracing subscriber as global as that does not
        // play well with unit tests.
        let _guard = tracing::subscriber::set_default(subscriber);

        // Name, Target and Severity are expected to be passed to the IsEnabled check
        // The validation is done in the LogProcessorWithIsEnabled struct.
        error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io");
    }

    #[test]
    fn tracing_appender_span_attributes_disabled_by_default() {
        // When `with_tracing_span_attributes` is not called on the builder, tracing-span
        // attributes must NOT be copied onto log records.
        let exporter = InMemoryLogExporter::default();
        let provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let layer = layer::OpenTelemetryTracingBridge::new(&provider).with_filter(
            tracing_subscriber::filter::filter_fn(|meta| {
                meta.is_span() || *meta.level() <= tracing::Level::ERROR
            }),
        );
        let subscriber = tracing_subscriber::registry().with(layer);
        let _guard = tracing::subscriber::set_default(subscriber);

        let span = tracing::info_span!("test_span", user_id = 123, endpoint = "/api/users");
        let _enter = span.enter();
        tracing::error!(status = 200, "test message");

        provider.force_flush().unwrap();
        let logs = exporter.get_emitted_logs().unwrap();
        assert_eq!(logs.len(), 1);
        let log = &logs[0];

        // Event attribute is still recorded.
        assert!(attributes_contains(
            &log.record,
            &Key::new("status"),
            &AnyValue::Int(200)
        ));
        // Span attributes must not appear on the log record.
        assert!(!log
            .record
            .attributes_iter()
            .any(|(k, _)| k == &Key::new("user_id")));
        assert!(!log
            .record
            .attributes_iter()
            .any(|(k, _)| k == &Key::new("endpoint")));
    }

    #[test]
    #[cfg(feature = "experimental_span_attributes")]
    fn tracing_appender_span_context_enrichment_enabled() {
        // Arrange
        let exporter = InMemoryLogExporter::default();
        let provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let layer = layer::OpenTelemetryTracingBridge::builder(&provider)
            .with_tracing_span_attributes(TracingSpanAttributes::all())
            .build()
            .with_filter(tracing_subscriber::filter::filter_fn(|meta| {
                // Allow spans at any level (needed for on_new_span to store span attributes),
                // but only allow ERROR events to prevent internal otel_info! events from leaking
                // in when internal-logs feature is enabled and tests run in parallel.
                meta.is_span() || *meta.level() <= tracing::Level::ERROR
            }));
        let subscriber = tracing_subscriber::registry().with(layer);
        let _guard = tracing::subscriber::set_default(subscriber);

        // Act
        let span = tracing::info_span!("test_span", user_id = 123, endpoint = "/api/users");
        let _enter = span.enter();
        tracing::error!(status = 200, "test message");

        provider.force_flush().unwrap();

        // Assert
        let logs = exporter.get_emitted_logs().unwrap();
        assert_eq!(logs.len(), 1);
        let log = &logs[0];

        // Should contain span attributes
        assert!(attributes_contains(
            &log.record,
            &Key::new("user_id"),
            &AnyValue::Int(123)
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("endpoint"),
            &AnyValue::String("/api/users".into())
        ));

        assert!(attributes_contains(
            &log.record,
            &Key::new("status"),
            &AnyValue::Int(200)
        ));
    }

    #[test]
    #[cfg(feature = "experimental_span_attributes")]
    fn tracing_appender_nested_spans_collect_all_parent_attributes() {
        // Arrange
        let exporter = InMemoryLogExporter::default();
        let provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let layer = layer::OpenTelemetryTracingBridge::builder(&provider)
            .with_tracing_span_attributes(TracingSpanAttributes::all())
            .build()
            .with_filter(tracing_subscriber::filter::filter_fn(|meta| {
                // Allow spans at any level (needed for on_new_span to store span attributes),
                // but only allow ERROR events to prevent internal otel_info! events from leaking
                // in when internal-logs feature is enabled and tests run in parallel.
                meta.is_span() || *meta.level() <= tracing::Level::ERROR
            }));
        let subscriber = tracing_subscriber::registry().with(layer);
        let _guard = tracing::subscriber::set_default(subscriber);

        // Act: Create nested spans with different attributes
        let outer_span = tracing::info_span!("outer", request_id = "req-123");
        let _outer_guard = outer_span.enter();

        // Create inner span nested within outer span
        let inner_span = tracing::info_span!("inner", user_id = "user-456");
        let _inner_guard = inner_span.enter();

        // Emit event in innermost span context
        tracing::error!(status = 200, "Event message");

        drop(_inner_guard);
        drop(_outer_guard);
        provider.force_flush().unwrap();

        // Assert
        let logs = exporter.get_emitted_logs().unwrap();
        assert_eq!(logs.len(), 1);
        let log = &logs[0];

        // Verify outer span attribute is included
        assert!(attributes_contains(
            &log.record,
            &Key::new("request_id"),
            &AnyValue::String("req-123".into())
        ));

        // Verify inner span attribute is included
        assert!(attributes_contains(
            &log.record,
            &Key::new("user_id"),
            &AnyValue::String("user-456".into())
        ));

        assert!(attributes_contains(
            &log.record,
            &Key::new("status"),
            &AnyValue::Int(200)
        ));
    }

    #[test]
    #[cfg(feature = "experimental_span_attributes")]
    fn tracing_appender_span_context_with_various_types() {
        // Arrange
        let exporter = InMemoryLogExporter::default();
        let provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let layer = layer::OpenTelemetryTracingBridge::builder(&provider)
            .with_tracing_span_attributes(TracingSpanAttributes::all())
            .build()
            .with_filter(tracing_subscriber::filter::filter_fn(|meta| {
                // Allow spans at any level (needed for on_new_span to store span attributes),
                // but only allow ERROR events to prevent internal otel_info! events from leaking
                // in when internal-logs feature is enabled and tests run in parallel.
                meta.is_span() || *meta.level() <= tracing::Level::ERROR
            }));
        let subscriber = tracing_subscriber::registry().with(layer);
        let _guard = tracing::subscriber::set_default(subscriber);

        // Act
        let small_u64value: u64 = 42;
        let big_u64value: u64 = u64::MAX;
        let small_u128value: u128 = 42;
        let big_u128value: u128 = u128::MAX;
        let small_i128value: i128 = 42;
        let big_i128value: i128 = i128::MAX;

        let span = tracing::info_span!(
            "test_span",
            str_field = "text",
            int_field = 42,
            float_field = 1.5,
            bool_field = true,
            small_u64value,
            big_u64value,
            small_u128value,
            big_u128value,
            small_i128value,
            big_i128value,
            bytes_field = &[1u8, 2, 3] as &[u8]
        );
        let _enter = span.enter();
        tracing::error!("test message");

        provider.force_flush().unwrap();

        // Assert
        let logs = exporter.get_emitted_logs().unwrap();
        assert_eq!(logs.len(), 1);
        let log = &logs[0];

        assert!(attributes_contains(
            &log.record,
            &Key::new("str_field"),
            &AnyValue::String("text".into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("int_field"),
            &AnyValue::Int(42)
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("float_field"),
            &AnyValue::Double(1.5)
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("bool_field"),
            &AnyValue::Boolean(true)
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("small_u64value"),
            &AnyValue::Int(42)
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("big_u64value"),
            &AnyValue::String(u64::MAX.to_string().into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("small_u128value"),
            &AnyValue::Int(42)
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("big_u128value"),
            &AnyValue::String(u128::MAX.to_string().into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("small_i128value"),
            &AnyValue::Int(42)
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("big_i128value"),
            &AnyValue::String(i128::MAX.to_string().into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("bytes_field"),
            &AnyValue::Bytes(Box::new(vec![1, 2, 3]))
        ));
    }

    #[test]
    #[cfg(feature = "experimental_span_attributes")]
    fn tracing_appender_span_record_after_creation() {
        // This test verifies that span fields recorded AFTER span creation
        // are captured via the on_record implementation.

        // Arrange
        let exporter = InMemoryLogExporter::default();
        let provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let layer = layer::OpenTelemetryTracingBridge::builder(&provider)
            .with_tracing_span_attributes(TracingSpanAttributes::all())
            .build()
            .with_filter(tracing_subscriber::filter::filter_fn(|meta| {
                // Allow spans at any level (needed for on_new_span to store span attributes),
                // but only allow ERROR events to prevent internal otel_info! events from leaking
                // in when internal-logs feature is enabled and tests run in parallel.
                meta.is_span() || *meta.level() <= tracing::Level::ERROR
            }));
        let subscriber = tracing_subscriber::registry().with(layer);
        let _guard = tracing::subscriber::set_default(subscriber);

        // Act: Create span with an empty field, then record value later
        let span = tracing::info_span!(
            "test_span",
            initial_field = "present",
            delayed_field = tracing::field::Empty
        );
        let _enter = span.enter();

        // Record the delayed field after span creation
        span.record("delayed_field", "recorded_later");

        tracing::error!(event_attr = "event_value", "test message");

        provider.force_flush().unwrap();

        // Assert
        let logs = exporter.get_emitted_logs().unwrap();
        assert_eq!(logs.len(), 1);
        let log = &logs[0];

        // The initial field should be captured
        assert!(attributes_contains(
            &log.record,
            &Key::new("initial_field"),
            &AnyValue::String("present".into())
        ));

        // The delayed field should be captured
        assert!(
            attributes_contains(
                &log.record,
                &Key::new("delayed_field"),
                &AnyValue::String("recorded_later".into())
            ),
            "delayed_field should be captured after on_record is implemented"
        );

        // Event attribute should also be captured
        assert!(attributes_contains(
            &log.record,
            &Key::new("event_attr"),
            &AnyValue::String("event_value".into())
        ));
    }

    #[test]
    #[cfg(feature = "experimental_span_attributes")]
    fn tracing_appender_span_attribute_allowlist_includes_only_named() {
        let exporter = InMemoryLogExporter::default();
        let provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let layer = layer::OpenTelemetryTracingBridge::builder(&provider)
            .with_tracing_span_attributes(TracingSpanAttributes::allowlist(["session.id"]))
            .build()
            .with_filter(tracing_subscriber::filter::filter_fn(|meta| {
                meta.is_span() || *meta.level() <= tracing::Level::ERROR
            }));
        let subscriber = tracing_subscriber::registry().with(layer);
        let _guard = tracing::subscriber::set_default(subscriber);

        let span = tracing::info_span!("test_span", session.id = "abc", user_name = "alice");
        let _enter = span.enter();
        tracing::error!("test message");

        provider.force_flush().unwrap();
        let logs = exporter.get_emitted_logs().unwrap();
        assert_eq!(logs.len(), 1);
        let log = &logs[0];

        assert!(attributes_contains(
            &log.record,
            &Key::new("session.id"),
            &AnyValue::String("abc".into())
        ));
        assert!(!log
            .record
            .attributes_iter()
            .any(|(k, _)| k == &Key::new("user_name")));
    }

    #[test]
    #[cfg(feature = "experimental_span_attributes")]
    fn tracing_appender_span_attribute_allowlist_nested_spans() {
        let exporter = InMemoryLogExporter::default();
        let provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let layer = layer::OpenTelemetryTracingBridge::builder(&provider)
            .with_tracing_span_attributes(TracingSpanAttributes::allowlist(["session.id"]))
            .build()
            .with_filter(tracing_subscriber::filter::filter_fn(|meta| {
                meta.is_span() || *meta.level() <= tracing::Level::ERROR
            }));
        let subscriber = tracing_subscriber::registry().with(layer);
        let _guard = tracing::subscriber::set_default(subscriber);

        let outer_span = tracing::info_span!("outer", session.id = "s1", request_id = "r1");
        let _outer_guard = outer_span.enter();
        let inner_span = tracing::info_span!("inner", trace_id = "t1");
        let _inner_guard = inner_span.enter();
        tracing::error!("test message");
        drop(_inner_guard);
        drop(_outer_guard);

        provider.force_flush().unwrap();
        let logs = exporter.get_emitted_logs().unwrap();
        assert_eq!(logs.len(), 1);
        let log = &logs[0];

        assert!(attributes_contains(
            &log.record,
            &Key::new("session.id"),
            &AnyValue::String("s1".into())
        ));
        assert!(!log
            .record
            .attributes_iter()
            .any(|(k, _)| k == &Key::new("request_id")));
        assert!(!log
            .record
            .attributes_iter()
            .any(|(k, _)| k == &Key::new("trace_id")));
    }

    #[test]
    #[cfg(feature = "experimental_span_attributes")]
    fn tracing_appender_span_attributes_all_copies_all() {
        // `TracingSpanAttributes::all()` enables enrichment and copies all
        // tracing-span attributes (no filtering).
        let exporter = InMemoryLogExporter::default();
        let provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let layer = layer::OpenTelemetryTracingBridge::builder(&provider)
            .with_tracing_span_attributes(TracingSpanAttributes::all())
            .build()
            .with_filter(tracing_subscriber::filter::filter_fn(|meta| {
                meta.is_span() || *meta.level() <= tracing::Level::ERROR
            }));
        let subscriber = tracing_subscriber::registry().with(layer);
        let _guard = tracing::subscriber::set_default(subscriber);

        let span = tracing::info_span!("test_span", foo = "bar");
        let _enter = span.enter();
        tracing::error!(event_attr = "val", "test message");

        provider.force_flush().unwrap();
        let logs = exporter.get_emitted_logs().unwrap();
        assert_eq!(logs.len(), 1);
        let log = &logs[0];

        assert!(attributes_contains(
            &log.record,
            &Key::new("event_attr"),
            &AnyValue::String("val".into())
        ));
        assert!(attributes_contains(
            &log.record,
            &Key::new("foo"),
            &AnyValue::String("bar".into())
        ));
    }

    #[test]
    #[cfg(feature = "experimental_span_attributes")]
    fn tracing_appender_span_attribute_allowlist_with_on_record() {
        let exporter = InMemoryLogExporter::default();
        let provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let layer = layer::OpenTelemetryTracingBridge::builder(&provider)
            .with_tracing_span_attributes(TracingSpanAttributes::allowlist(["session.id"]))
            .build()
            .with_filter(tracing_subscriber::filter::filter_fn(|meta| {
                meta.is_span() || *meta.level() <= tracing::Level::ERROR
            }));
        let subscriber = tracing_subscriber::registry().with(layer);
        let _guard = tracing::subscriber::set_default(subscriber);

        let span = tracing::info_span!(
            "test_span",
            session.id = tracing::field::Empty,
            ignored = "skip"
        );
        let _enter = span.enter();
        span.record("session.id", "late");
        tracing::error!("test message");

        provider.force_flush().unwrap();
        let logs = exporter.get_emitted_logs().unwrap();
        assert_eq!(logs.len(), 1);
        let log = &logs[0];

        assert!(attributes_contains(
            &log.record,
            &Key::new("session.id"),
            &AnyValue::String("late".into())
        ));
        assert!(!log
            .record
            .attributes_iter()
            .any(|(k, _)| k == &Key::new("ignored")));
    }

    #[test]
    #[cfg(feature = "experimental_span_attributes")]
    fn tracing_appender_empty_allowlist_copies_nothing() {
        // An empty allowlist means "allow nothing" — enrichment is enabled
        // but no span attributes match, so none are copied.
        let exporter = InMemoryLogExporter::default();
        let provider = SdkLoggerProvider::builder()
            .with_simple_exporter(exporter.clone())
            .build();

        let empty: Vec<&str> = vec![];
        let layer = layer::OpenTelemetryTracingBridge::builder(&provider)
            .with_tracing_span_attributes(TracingSpanAttributes::allowlist(empty))
            .build()
            .with_filter(tracing_subscriber::filter::filter_fn(|meta| {
                meta.is_span() || *meta.level() <= tracing::Level::ERROR
            }));
        let subscriber = tracing_subscriber::registry().with(layer);
        let _guard = tracing::subscriber::set_default(subscriber);

        let span = tracing::info_span!("test_span", user_id = 123, session.id = "abc");
        let _enter = span.enter();
        tracing::error!(event_attr = "val", "test message");

        provider.force_flush().unwrap();
        let logs = exporter.get_emitted_logs().unwrap();
        assert_eq!(logs.len(), 1);
        let log = &logs[0];

        // Event attribute is still recorded.
        assert!(attributes_contains(
            &log.record,
            &Key::new("event_attr"),
            &AnyValue::String("val".into())
        ));
        // No span attributes should appear — empty allowlist means nothing passes.
        assert!(!log
            .record
            .attributes_iter()
            .any(|(k, _)| k == &Key::new("user_id")));
        assert!(!log
            .record
            .attributes_iter()
            .any(|(k, _)| k == &Key::new("session.id")));
    }
}
