Verified Commit b1e4598d authored by Hercules Merscher's avatar Hercules Merscher 🌴
Browse files

feat: Labkit::Tracing is a thin wrapper

parent 1d2d8c5c
Loading
Loading
Loading
Loading
+23 −1
Original line number Diff line number Diff line
@@ -114,7 +114,7 @@ module Labkit
    #
    # @example Using OpenTelemetry-specific APIs
    #   tracer = Labkit::Tracing.tracer
    #   tracer.start_span("custom-span") do |span|
    #   tracer.in_span("custom-span") do |span|
    #     span.add_event("custom-event", attributes: { "key" => "value" })
    #   end
    #
@@ -125,6 +125,28 @@ module Labkit
      TracingUtils.tracer.tracer
    end

    # Returns the currently active span from OpenTelemetry.
    # This provides direct access to the OpenTelemetry span API.
    #
    # @example Adding attributes to the current span
    #   span = Labkit::Tracing.current_span
    #   span.set_attribute("user.id", user.id) if span.recording?
    #
    # @example Adding events
    #   span = Labkit::Tracing.current_span
    #   span.add_event("cache_miss", attributes: { "key" => cache_key })
    #
    # @example Conditional expensive operations
    #   span = Labkit::Tracing.current_span
    #   if span.recording?
    #     span.set_attribute("expensive_data", compute_expensive_data)
    #   end
    #
    # @return [OpenTelemetry::Trace::Span] The current span (may be a no-op span when tracing is disabled)
    def self.current_span
      OpenTelemetry::Trace.current_span
    end

    # This will run a block with a span
    # @param operation_name [String] The operation name for the span
    # @param tags [Hash] Tags to assign to the span
+94 −52
Original line number Diff line number Diff line
@@ -106,6 +106,55 @@ export GITLAB_TRACING="otlp://user:pass@collector.prod.example.com:4318/v1/trace
export GITLAB_TRACING="otlp://console?service_name=my-app&sampler=const&sampler_param=0"
```

## Using OpenTelemetry APIs Directly

**Labkit is a thin wrapper around OpenTelemetry** - it handles initialization and provides sensible defaults, but you should use OpenTelemetry APIs directly for instrumentation.

### What Labkit Provides Out-of-the-Box

- **Automatic tracer initialization** from `GITLAB_TRACING` environment variable
- **Connection string parsing** for OTLP endpoints, samplers, and exporters
- **Default service name** (`labkit-service`) with query parameter override
- **Automatic instrumentation** for Rails, Redis, and external HTTP requests
- **Correlation ID injection** into all spans automatically
- **Security sanitization** for SQL queries, URLs, and Redis commands

### What You Should Use Directly

For all span creation and manipulation, use OpenTelemetry APIs:

```ruby
# Get Labkit-configured tracer
tracer = Labkit::Tracing.tracer

# Get current span
span = Labkit::Tracing.current_span

# Use OpenTelemetry APIs for everything else
tracer.in_span("operation") do |span|
  span.set_attribute("key", "value")
  span.add_event("event_name", attributes: { "detail" => "info" })
  span.record_exception(exception) if exception
  span.status = OpenTelemetry::Trace::Status.error("Failed")
end
```

**Benefits of direct OTel usage:**
- Full access to OpenTelemetry capabilities (events, exceptions, status, links)
- Works with all OpenTelemetry documentation and examples
- Compatible with other OTel libraries and tools
- Future-proof as OpenTelemetry evolves

### Context Sharing

Labkit and OpenTelemetry share the same global tracer provider and context propagation mechanism. This means spans created by Labkit are visible to OpenTelemetry APIs and vice versa.

This seamless integration means you can:
- Use Labkit for initialization and defaults
- Use OpenTelemetry APIs directly for instrumentation
- Mix both approaches in the same codebase
- Trust that context propagates correctly across both

## Usage

### Automatic Initialization (Default Behavior)
@@ -192,35 +241,50 @@ end

### Manual Span Creation

Use `Labkit::Tracing.with_tracing` to create custom spans:
Labkit provides setup and defaults, but you should use OpenTelemetry APIs directly for creating spans and adding instrumentation:

**Creating spans with the tracer:**

```ruby
Labkit::Tracing.with_tracing(
  operation_name: "process_data",
  tags: { "user_id" => user.id, "data_size" => data.size }
) do |span|
  # Your code here
# Get the tracer (configured by Labkit with connection string settings)
tracer = Labkit::Tracing.tracer

# Create a span with OpenTelemetry API
tracer.in_span("process_data", attributes: { "user_id" => user.id }) do |span|
  result = process_data(data)
  # Add additional tags during execution
  span.set_tag("result_count", result.count)

  span.set_attribute("result_count", result.count)
  span.add_event("processing_complete", attributes: { "duration_ms" => 123 })

  result
end
```

With parent span context:
**Working with the current span:**

```ruby
# Get the current active span
parent_span = Labkit::Tracing::TracingUtils.active_span

Labkit::Tracing.with_tracing(
  operation_name: "child_operation",
  tags: { "type" => "background" },
  child_of: parent_span
) do |span|
  # This span will be a child of parent_span
# Get the currently active span
span = Labkit::Tracing.current_span

# Check if the span is being recorded (respects sampling decisions)
if span.recording?
  span.set_attribute("expensive_data", compute_expensive_data)
  span.add_event("custom_event", attributes: { "key" => "value" })
end
```

**Creating child spans:**

```ruby
tracer = Labkit::Tracing.tracer

tracer.in_span("parent_operation") do |parent_span|
  # Child span is automatically created within parent context
  tracer.in_span("child_operation") do |child_span|
    child_span.set_attribute("type", "background")
    perform_operation
  end
end
```

### Initialization Order and Precedence
@@ -254,37 +318,13 @@ end
### Checking if Current Request is Sampled

```ruby
if Labkit::Tracing.sampled?
  # Current request is being traced
  # Safe to add expensive tracing operations
end
```

### Direct Access to Underlying Tracer

When LabKit's abstraction doesn't provide the functionality you need, you can access the underlying tracer implementation directly using `Labkit::Tracing.tracer`:

```ruby
# Access the native tracer API
tracer = Labkit::Tracing.tracer

# Use OpenTelemetry-specific features when using OTLP connection
if Labkit::Tracing.otlp_connection?
  # tracer is an OpenTelemetry::SDK::Trace::Tracer instance
  tracer.start_span("custom-operation") do |span|
    span.add_event("processing_started", attributes: { "batch_size" => 100 })

    # Use OpenTelemetry-specific APIs
    span.record_exception(StandardError.new("test"))
    span.status = OpenTelemetry::Trace::Status.error("Failed")
# checks if span will actually record data
span = Labkit::Tracing.current_span
if span.recording?
  span.set_attribute("expensive_data", compute_expensive_data)
end
end

This is useful when you need:
- Library-specific APIs not exposed by LabKit (e.g., OpenTelemetry events)
- Advanced span manipulation or custom exporters
- Direct integration with third-party libraries that expect native tracer instances

```

## Instrumentation

@@ -455,12 +495,15 @@ export GITLAB_TRACING="otlp://localhost:4318?sampler=const&sampler_param=1"

### Conditional Expensive Operations

Check if the current request is being sampled before adding expensive tracing data:
Use OpenTelemetry's `span.recording?` to check if expensive operations should be performed:

```ruby
if Labkit::Tracing.sampled?
  # Only execute expensive operations when trace is being captured
  span.set_tag("expensive_data", compute_expensive_data)
span = Labkit::Tracing.current_span

if span.recording?
  # Only execute when trace is being captured and sampled
  span.set_attribute("expensive_data", compute_expensive_data)
  span.add_event("detailed_event", attributes: expensive_analysis)
end
```

@@ -525,7 +568,6 @@ If you need to add custom metadata (deployment environment, version, etc.), over

```ruby
# config/initializers/tracing.rb
require 'opentelemetry/instrumentation/all'

Labkit::Tracing::Factory.create_tracer("my-rails-app", ENV["GITLAB_TRACING"]) do |c|
  # Enable all available OpenTelemetry instrumentation
+13 −0
Original line number Diff line number Diff line
# frozen_string_literal: true

# Internal adapter for OpenTelemetry span compatibility.
# This is not part of the public Labkit API.
#
# Applications should use OpenTelemetry APIs directly via:
#   - Labkit::Tracing.tracer (returns OpenTelemetry::Trace::Tracer)
#   - Labkit::Tracing.current_span (returns OpenTelemetry::Trace::Span)
#
# These adapters exist for:
#   - Internal Labkit instrumentation (Rails, Redis, etc.)
#   - Backward compatibility with OpenTracing connections
#
# @api private

module Labkit
  module Tracing
    module Adapters
+13 −0
Original line number Diff line number Diff line
# frozen_string_literal: true

# Internal adapter for OpenTelemetry tracer compatibility.
# This is not part of the public Labkit API.
#
# Applications should use OpenTelemetry APIs directly via:
#   - Labkit::Tracing.tracer (returns OpenTelemetry::Trace::Tracer)
#   - Labkit::Tracing.current_span (returns OpenTelemetry::Trace::Span)
#
# These adapters exist for:
#   - Internal Labkit instrumentation (Rails, Redis, etc.)
#   - Backward compatibility with OpenTracing connections
#
# @api private

module Labkit
  module Tracing
    module Adapters
+9 −0
Original line number Diff line number Diff line
@@ -187,6 +187,15 @@ describe Labkit::Tracing do
    end
  end

  describe ".current_span" do
    it "delegates to OpenTelemetry::Trace.current_span" do
      fake_span = double("span")
      allow(OpenTelemetry::Trace).to receive(:current_span).and_return(fake_span)

      expect(described_class.current_span).to eq(fake_span)
    end
  end

  describe ".stacktrace_operations" do
    before do
      Labkit::Tracing.instance_variable_set(:@stacktrace_operations, nil)