Skip to content

feat(observability): pluggable span exporter#64

Merged
bzp2010 merged 4 commits into
mainfrom
bzp/feat-o11y-pluggable-span-exporter
Apr 28, 2026
Merged

feat(observability): pluggable span exporter#64
bzp2010 merged 4 commits into
mainfrom
bzp/feat-o11y-pluggable-span-exporter

Conversation

@bzp2010
Copy link
Copy Markdown
Collaborator

@bzp2010 bzp2010 commented Apr 28, 2026

Summary by CodeRabbit

  • New Features

    • Observability now supports optional custom tracing/metrics exporters with sensible defaults.
    • Startup/shutdown signaling ensures logs, traces, and metrics flush and stop cleanly on shutdown.
  • Refactor

    • Observability functionality relocated into a public utilities area with a clearer, extensible exporter abstraction for easier integration and reuse.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 69934ec0-943f-45c3-b2ef-a7aa5180b053

📥 Commits

Reviewing files that changed from the base of the PR and between 43a9b5f and 05d4358.

📒 Files selected for processing (2)
  • src/utils/observability/mod.rs
  • src/utils/observability/trace.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils/observability/trace.rs
  • src/utils/observability/mod.rs

📝 Walkthrough

Walkthrough

Makes utils public and moves observability initialization into a new utils::observability module. Adds a type-erased span exporter and implements log/tracing/metrics init functions that return a oneshot shutdown sender and a JoinHandle wired into the existing run shutdown flow. (≤50 words)

Changes

Cohort / File(s) Summary
Library entrypoint
src/lib.rs
Made utils public and replaced the in-file observability init with a call to crate::utils::observability::init_observability, wiring the returned (oneshot::Sender<()>, JoinHandle<()>) into the existing shutdown flow.
Utils module
src/utils/mod.rs
Exports a new observability submodule (pub mod observability;).
Observability root
src/utils/observability/mod.rs
New module implementing logging, tracing, and metrics initialization plus shutdown handling: adds INSTRUMENTATION_NAME, shutdown_handler, init_observability_log/trace/metric, and top-level init_observability returning a combined shutdown sender and JoinHandle.
Tracing helpers
src/utils/observability/trace.rs
Adds DynSpanExporter trait and BoxedSpanExporter type-erasure wrapper with SpanExporter impl to allow passing optional/custom span exporters into tracing initialization.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application (run)
    participant Obs as Observability::init_observability
    participant Log as Logger
    participant Trace as Tracer/Exporter
    participant Metric as MeterProvider
    participant Shutdown as Shutdown Task

    App->>Obs: call init_observability()
    Obs->>Log: init logging (logforth / fastrace)
    Obs->>Trace: init tracing (BoxedSpanExporter or default OTLP)
    Obs->>Metric: init metrics (OTLP exporter, periodic reader)
    Obs-->>App: return (oneshot::Sender, JoinHandle)

    App->>Shutdown: send shutdown via oneshot
    Shutdown->>Log: flush/exit logger
    Shutdown->>Trace: force_flush/shutdown exporter
    Shutdown->>Metric: stop meter provider
    Shutdown-->>App: shutdown complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • api7/aisix#116: Modifies repository observability initialization and exporter/meter setup; closely related to the moved init logic.
  • api7/aisix#114: Adjusts metrics abstractions and describe_metrics usage referenced by the new metrics init.
  • api7/aisix#117: Changes tracing/metrics initialization and instrumentation configuration tied to the new span exporter and resource wiring.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Security Check ❌ Error Pull request introduces critical vulnerability through unfiltered span data export to OTLP endpoints without sanitization of sensitive attributes. Implement sanitizing span exporter wrapper, enforce TLS/HTTPS defaults, add span processor for redacting sensitive data, document security requirements, and add verification tests.
E2e Test Quality Review ⚠️ Warning PR lacks E2E tests for pluggable span exporter functionality and has problematic error handling with silent suppression patterns masking initialization failures. Add E2E tests verifying custom span exporter injection, initialization failure scenarios, concurrent shutdown handling, and proper error propagation instead of silent suppression.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(observability): pluggable span exporter' accurately summarizes the main change: introducing a pluggable span exporter mechanism through the new DynSpanExporter trait and BoxedSpanExporter struct in the observability module.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bzp/feat-o11y-pluggable-span-exporter

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/mod.rs`:
- Line 4: Add a public doc comment for the new exported module by adding a ///
comment above pub mod observability; that briefly explains what the
observability module provides (e.g., logging/tracing/metrics helpers), any
important usage notes, and points to more detailed docs or examples in the
observability:: submodules; ensure the comment is concise and follows crate doc
style so the new public API is documented.

In `@src/utils/observability/mod.rs`:
- Around line 10-19: Add a Rust doc comment for the public trait DynSpanExporter
explaining its purpose in the public API and describing the behavior and intent
of its methods (export, shutdown_with_timeout, force_flush, set_resource); place
a /// doc block immediately above the trait declaration that summarizes what
implementations must do, notes the async export contract and error type
(OTelSdkResult), and briefly documents each method’s role so consumers
understand usage and guarantees.
- Around line 68-85: The test test_boxed_span_exporter merely constructs and
drops a BoxedSpanExporter; update it to assert behavior: for the Some case
assert the returned exporter is the same/wrapped instance (e.g., identity or
type) and for the None branch either assert the created BoxedSpanExporter is
functional by performing a simple export/flush round-trip or replace the real
exporter with a mock exporter that records calls and assert that export was
invoked; locate and modify the test_boxed_span_exporter function and usages of
BoxedSpanExporter::new/opentelemetry_otlp::SpanExporter to add these assertions
or inject a mock exporter to verify delegation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 26e928d2-48b6-4217-b2f7-a2c71882eb17

📥 Commits

Reviewing files that changed from the base of the PR and between f792b7e and d9a8317.

📒 Files selected for processing (3)
  • src/lib.rs
  • src/utils/mod.rs
  • src/utils/observability/mod.rs

Comment thread src/utils/mod.rs
Comment thread src/utils/observability/mod.rs Outdated
Comment thread src/utils/observability/mod.rs Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib.rs (1)

61-64: ⚠️ Potential issue | 🟠 Major

Early ? returns skip deterministic observability shutdown on error paths.

At Line 61 and Line 88, errors return before the code path that sends shutdown and awaits the observability JoinHandle. That can leave teardown/flush detached instead of awaited when startup fails.

🔧 Suggested fix
 pub async fn run_with_config(
     config: Arc<config::Config>,
     ob_shutdown_signal: oneshot::Sender<()>,
     ob_shutdown_task: tokio::task::JoinHandle<()>,
 ) -> Result<()> {
-    let config_provider = config::create_provider(&config)
-        .await
-        .context("failed to create config provider")?;
+    let config_provider = match config::create_provider(&config)
+        .await
+        .context("failed to create config provider")
+    {
+        Ok(provider) => provider,
+        Err(e) => {
+            let _ = ob_shutdown_signal.send(());
+            let _ = ob_shutdown_task.await;
+            return Err(e);
+        }
+    };
     run_with_provider(
         config,
         config_provider,
         ob_shutdown_signal,
         ob_shutdown_task,
@@
 pub async fn run_with_provider(
@@
 ) -> Result<()> {
@@
-    let gateway = Arc::new(gateway::Gateway::new(
-        gateway::providers::default_provider_registry()
-            .context("failed to build default gateway provider registry")?,
-    ));
+    let provider_registry = match gateway::providers::default_provider_registry()
+        .context("failed to build default gateway provider registry")
+    {
+        Ok(registry) => registry,
+        Err(e) => {
+            let _ = config_provider.shutdown().await;
+            let _ = ob_shutdown_signal.send(());
+            let _ = ob_shutdown_task.await;
+            return Err(e);
+        }
+    };
+    let gateway = Arc::new(gateway::Gateway::new(provider_registry));

Also applies to: 86-89

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib.rs` around lines 61 - 64, Early `?` returns in the startup path
(around create_provider and run_with_provider) can bypass sending the
observability shutdown signal and awaiting the observability JoinHandle; change
the error handling so that instead of using `?` directly on the results of
config::create_provider and run_with_provider, capture their Result values, and
on Err perform the observability shutdown sequence (send shutdown signal to the
observability task and await its JoinHandle) before returning the error; locate
the startup calls to config::create_provider and run_with_provider and implement
this pattern (or introduce a small cleanup helper/guard invoked on error) so
observability teardown always runs on all error paths.
🧹 Nitpick comments (1)
src/utils/observability/trace.rs (1)

45-47: Add a doc comment for the public constructor.

BoxedSpanExporter::new is public but undocumented.

📝 Suggested fix
 impl BoxedSpanExporter {
+    /// Wrap a concrete `SpanExporter` as a type-erased boxed exporter.
     pub fn new<T: SpanExporter + 'static>(span_exporter: T) -> Self {
         Self(Box::new(span_exporter))
     }
 }

As per coding guidelines, "Use /// for doc comments on public items in Rust".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/observability/trace.rs` around lines 45 - 47, Add a Rust doc
comment (using ///) to the public constructor BoxedSpanExporter::new explaining
its purpose and parameters/return (that it boxes a SpanExporter and returns a
BoxedSpanExporter); place the /// comment immediately above the impl fn
definition for pub fn new<T: SpanExporter + 'static>(span_exporter: T) -> Self
so it documents the public API per project guidelines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/observability/mod.rs`:
- Around line 110-112: The module-level doc for init_observability incorrectly
references a `span_exporter` argument (copied from init_observability_trace);
update the comment block to accurately describe init_observability's actual
parameters and behavior by removing or replacing the `span_exporter` sentence
and, if helpful, referencing the real options it accepts, making sure to remove
the misleading `span_exporter` mention and optionally point to
init_observability_trace for trace-specific details.
- Around line 28-39: The public functions shutdown_handler,
init_observability_log, init_observability_trace, init_observability_metric and
init_observability need the #[fastrace::trace] attribute applied; add the
attribute directly above each pub fn declaration (e.g., place #[fastrace::trace]
above pub fn shutdown_handler(...) and the other init_* functions) and ensure
the fastrace crate is available in the crate root so the attribute resolves (or
use the fully qualified attribute path #[fastrace::trace] as shown); no
behavioral changes are required beyond adding the attribute to each public
function.
- Around line 28-32: The compile error is caused by referencing Future in the
shutdown_handler signature without importing it; update the module to either add
use std::future::Future; at the top or qualify the bound as
std::future::Future<Output = ()> in the shutdown_handler declaration so the
trait is in scope for the F and Fut where-clauses.

In `@src/utils/observability/trace.rs`:
- Around line 24-37: The forwarding implementations on DynSpanExporter
incorrectly call Self::... which can cause ambiguous trait resolution; change
the delegations to use fully-qualified trait syntax SpanExporter::export,
SpanExporter::shutdown_with_timeout, SpanExporter::force_flush, and
SpanExporter::set_resource when calling through the inner exporter (i.e.,
replace Self::export(self, ...), Self::shutdown_with_timeout(self, ...),
Self::force_flush(self), and Self::set_resource(self, ...) with
SpanExporter::export(self, ...), SpanExporter::shutdown_with_timeout(self, ...),
SpanExporter::force_flush(self), and SpanExporter::set_resource(self, ...)
respectively) so the trait methods from SpanExporter are explicitly invoked on
the DynSpanExporter wrapper.

---

Outside diff comments:
In `@src/lib.rs`:
- Around line 61-64: Early `?` returns in the startup path (around
create_provider and run_with_provider) can bypass sending the observability
shutdown signal and awaiting the observability JoinHandle; change the error
handling so that instead of using `?` directly on the results of
config::create_provider and run_with_provider, capture their Result values, and
on Err perform the observability shutdown sequence (send shutdown signal to the
observability task and await its JoinHandle) before returning the error; locate
the startup calls to config::create_provider and run_with_provider and implement
this pattern (or introduce a small cleanup helper/guard invoked on error) so
observability teardown always runs on all error paths.

---

Nitpick comments:
In `@src/utils/observability/trace.rs`:
- Around line 45-47: Add a Rust doc comment (using ///) to the public
constructor BoxedSpanExporter::new explaining its purpose and parameters/return
(that it boxes a SpanExporter and returns a BoxedSpanExporter); place the ///
comment immediately above the impl fn definition for pub fn new<T: SpanExporter
+ 'static>(span_exporter: T) -> Self so it documents the public API per project
guidelines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 95a3fa7f-6f26-482c-9f9a-34c7ccc49e8e

📥 Commits

Reviewing files that changed from the base of the PR and between d9a8317 and 40d053a.

📒 Files selected for processing (3)
  • src/lib.rs
  • src/utils/observability/mod.rs
  • src/utils/observability/trace.rs

Comment thread src/utils/observability/mod.rs
Comment thread src/utils/observability/mod.rs
Comment thread src/utils/observability/mod.rs Outdated
Comment thread src/utils/observability/trace.rs Outdated
@bzp2010 bzp2010 merged commit 35eff35 into main Apr 28, 2026
7 checks passed
@bzp2010 bzp2010 deleted the bzp/feat-o11y-pluggable-span-exporter branch April 28, 2026 06:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant