Verified Commit c0777b5b authored by Bob Van Landuyt's avatar Bob Van Landuyt 💬 Committed by GitLab
Browse files

Merge branch 'fix/service-name-and-duplication' into 'master'

fix: Multiple tracing bug fixes

See merge request !239

Merged-by: Bob Van Landuyt's avatarBob Van Landuyt <bob@gitlab.com>
Approved-by: Bob Van Landuyt's avatarBob Van Landuyt <bob@gitlab.com>
Reviewed-by: Bob Van Landuyt's avatarBob Van Landuyt <bob@gitlab.com>
Reviewed-by: Hercules Merscher's avatarHercules Merscher <hmerscher@gitlab.com>
Reviewed-by: default avatarGitLab Duo <gitlab-duo@gitlab.com>
Co-authored-by: Hercules Merscher's avatarHercules Merscher <hmerscher@gitlab.com>
parents 43cb38c4 54c5fb6b
Loading
Loading
Loading
Loading
Loading
+20 −1
Original line number Diff line number Diff line
@@ -41,6 +41,8 @@ module Labkit

      start_time = ::Labkit::System.monotonic_time

      inject_trace_context(request)

      ActiveSupport::Notifications.instrument ::Labkit::EXTERNAL_HTTP_NOTIFICATION_TOPIC, create_request_payload(request) do |payload|
        response =
          begin
@@ -57,7 +59,7 @@ module Labkit

    def create_request_payload(request)
      payload = {
        method: request.method,
        method: request.method
      }

      if request.uri.nil?
@@ -84,5 +86,22 @@ module Labkit

      payload
    end

    def inject_trace_context(request)
      return unless Labkit::Tracing.enabled?

      tracer = Labkit::Tracing::TracingUtils.tracer
      span = tracer.active_span
      return if span.nil?

      carrier = {}
      tracer.inject_context(span, carrier)

      carrier.each do |key, value|
        request[key] = value
      end
    rescue StandardError
      warn "Labkit::NetHttpPublisher: trace context propagation failed"
    end
  end
end
+3 −1
Original line number Diff line number Diff line
@@ -30,7 +30,9 @@ module Labkit
      autoload :OpentracingTracer, "labkit/tracing/adapters/opentracing_tracer"
    end

    DEFAULT_SERVICE_NAME = :'labkit-service'
    # Must be a String, not Symbol, as OpenTelemetry requires resource attribute values
    # to be strings, integers, floats, or booleans
    DEFAULT_SERVICE_NAME = "labkit-service"

    # Module-level attribute for storing the configured service name
    # Set by Factory.create_tracer when a tracer is created
+48 −23
Original line number Diff line number Diff line
@@ -35,13 +35,18 @@ export GITLAB_TRACING="otlp://localhost:4318"

**Automatic Initialization**

When `GITLAB_TRACING` is set, LabKit automatically creates and configures a tracer when the gem is loaded. No manual initialization is required in most cases.
When `GITLAB_TRACING` is set, LabKit automatically creates and configures a tracer:

- **Rails applications**: Railtie automatically inserts Labkit::Tracing::RackMiddleware into the middleware stack
- **Non-Rails applications**: Tracer is auto-initialized, but `Labkit::Tracing::RackMiddleware` must be manually added to your middleware stack for HTTP request tracing (see [Rack Middleware](#rack-middleware))

Auto-initialization provides:
- Automatic tracer creation with connection string settings
- Automatic tracer creation with connection string settings (sampler, exporter, service name)
- Service name from `service_name` query parameter (defaults to `"labkit-service"`)
- All available OpenTelemetry instrumentation enabled by default (`c.use_all()`)
- LabKit-specific instrumentation enabled automatically:
- Selective OpenTelemetry instrumentation for components that don't conflict with LabKit:
  - `ConcurrentRuby`, `Net::HTTP`, `ActionPack`, `ActionMailer`, `ActiveJob`
- LabKit's own instrumentation for components it handles directly:
  - HTTP request tracing (RackMiddleware for Rails)
  - Rails components (ActiveRecord, ActionView, ActiveSupport) - if Rails is available
  - Redis instrumentation - if Redis gem is loaded
  - External HTTP instrumentation (Net::HTTP, Excon, HTTPClient)
@@ -73,6 +78,7 @@ The following connection string formats and query parameters are supported:
  - **Behavior**: Outputs spans immediately to stdout with no remote export
  - **Benefits**: No external collector required, instant feedback, simple debugging


### Sampling

**Default Behavior:** When no sampler is specified, probabilistic sampling is used with a **0.1% sample rate** (1 in 1000 traces).
@@ -159,7 +165,7 @@ This seamless integration means you can:

### Automatic Initialization (Default Behavior)

When `GITLAB_TRACING` is set, LabKit automatically creates and configures a tracer when the gem loads:
When `GITLAB_TRACING` is set, LabKit automatically creates and configures a tracer:

```bash
# Set environment variable with service name
@@ -169,14 +175,23 @@ export GITLAB_TRACING="otlp://localhost:4318?service_name=my-api&sampler=probabi
export GITLAB_TRACING="otlp://localhost:4318"
```

**Rails Applications:**
- Initialization happens automatically during Rails boot via Railtie
- Runs after all other gem initializers (including `opentelemetry-instrumentation-rails`)
- LabKit's TracerProvider is the final configuration used by the application
- **No application code changes required** - just set the environment variable

**Non-Rails Applications (Sinatra, Grape, standalone Ruby):**
- Tracer auto-initialization happens automatically when `require 'gitlab-labkit'` is called.
- However, you must manually add `Labkit::Tracing::RackMiddleware` to your middleware stack for HTTP request tracing. See the [Rack Middleware](#rack-middleware) section for details.

Auto-initialization:
- Creates tracer with connection string settings (sampler, exporter, endpoints)
- Uses service name from `service_name` query parameter (defaults to `"labkit-service"`)
- Enables all available OpenTelemetry instrumentation
- Enables selective OpenTelemetry instrumentation (non-conflicting with LabKit's own)
- Enables LabKit instrumentation for ActiveRecord, ActionView, ActiveSupport, Redis, and External HTTP
- Sets up the global `OpenTelemetry.tracer_provider`

**No application code changes required** - just set the environment variable.

### Automatic Rails Middleware Insertion

When using Rails, the `Labkit::Tracing::RackMiddleware` is automatically inserted into your middleware stack when `GITLAB_TRACING` is set. No manual configuration needed!
@@ -214,8 +229,8 @@ You can override auto-initialization by calling `Factory.create_tracer` in your
# config/initializers/tracing.rb
# Override auto-initialization with custom configuration
Labkit::Tracing::Factory.create_tracer("my-custom-service", ENV["GITLAB_TRACING"]) do |c|
  # Selective instrumentation instead of c.use_all()
  c.use 'OpenTelemetry::Instrumentation::Rails'
  # Custom OTel instrumentation
  c.use 'OpenTelemetry::Instrumentation::ConcurrentRuby'
  c.use 'OpenTelemetry::Instrumentation::Sidekiq'

  # Add custom span processors
@@ -232,7 +247,7 @@ end
```

**When to use manual initialization:**
- You need selective instrumentation (not `c.use_all()`)
- You need different OTel instrumentations than the defaults
- You need custom span processors
- You need to add resource attributes
- You want to override the service name from code instead of the connection string
@@ -289,11 +304,16 @@ end

### Initialization Order and Precedence

**Auto-initialization:**
1. Runs when the gem is loaded (`require 'gitlab-labkit'`)
**Auto-initialization (all applications):**
1. Runs automatically when `require 'gitlab-labkit'` is called
2. Only runs if `GITLAB_TRACING` environment variable is set
3. Uses `service_name` query parameter or defaults to `"labkit-service"`
4. Attempts to enable all instrumentation with `c.use_all()`
4. Enables selective OpenTelemetry instrumentation (ConcurrentRuby, Net::HTTP, ActionPack, ActionMailer, ActiveJob) to avoid conflicts with LabKit's own instrumentation
5. Enables LabKit instrumentation for ActiveRecord, ActionView, ActiveSupport, Redis, and External HTTP

**Additional Rails behavior:**
1. Railtie runs after user config initializers (via `initializer ... after: :load_config_initializers`)
2. Automatically inserts `Labkit::Tracing::RackMiddleware` into the middleware stack

**Manual initialization:**
1. Runs in your application initializer (e.g., `config/initializers/tracing.rb`)
@@ -308,7 +328,7 @@ end
   - Span processors with OTLP exporter

2. **Configuration block** runs second and can:
   - Add automatic instrumentation (`use`, `use_all`)
   - Add automatic instrumentation (`use`)
   - Add additional span processors
   - Merge additional resource attributes
   - Override service_name if explicitly set in the block
@@ -570,8 +590,13 @@ If you need to add custom metadata (deployment environment, version, etc.), over
# config/initializers/tracing.rb

Labkit::Tracing::Factory.create_tracer("my-rails-app", ENV["GITLAB_TRACING"]) do |c|
  # Enable all available OpenTelemetry instrumentation
  c.use_all()
  # Selective instrumentation (avoid conflicting with LabKit's own instrumentation
  # for ActiveRecord, ActionView, ActiveSupport, Redis, Rack, and Sidekiq)
  c.use("OpenTelemetry::Instrumentation::ConcurrentRuby")
  c.use("OpenTelemetry::Instrumentation::Net::HTTP")
  c.use("OpenTelemetry::Instrumentation::ActionPack") if defined?(ActionPack)
  c.use("OpenTelemetry::Instrumentation::ActionMailer") if defined?(ActionMailer)
  c.use("OpenTelemetry::Instrumentation::ActiveJob") if defined?(ActiveJob)

  # Add deployment metadata
  c.resource = c.resource.merge(
@@ -588,18 +613,18 @@ end

Note: Middleware is still automatically inserted even with custom tracer initialization.

### With Selective Instrumentation (Manual Override)
### With Custom Instrumentation (Manual Override)

If you want to control exactly which components are instrumented instead of using `c.use_all()`, override tracer initialization:
If you want to control exactly which OTel components are instrumented, override tracer initialization:

**Important:** When you manually call `Factory.create_tracer`, auto-initialization still runs but is replaced by your manual configuration. The LabKit-specific instrumentation (Rails, Redis, ExternalHttp) is still automatically enabled unless you explicitly disable auto-initialization.

```ruby
# config/initializers/tracing.rb
# Override auto-initialization for selective OpenTelemetry instrumentation
# Override auto-initialization for custom OpenTelemetry instrumentation
Labkit::Tracing::Factory.create_tracer("my-rails-app", ENV["GITLAB_TRACING"]) do |c|
  # Selective OpenTelemetry instrumentation (instead of c.use_all())
  c.use 'OpenTelemetry::Instrumentation::Rails'
  # Only enable the OTel instrumentations you need
  c.use 'OpenTelemetry::Instrumentation::ConcurrentRuby'
  c.use 'OpenTelemetry::Instrumentation::Sidekiq'
end

@@ -643,7 +668,7 @@ If you're not seeing HTTP request traces in Rails:

2. Check Rails logs for auto-insertion message:
    ```
   Labkit::Tracing: Automatically inserted RackMiddleware after Rails::Rack::Logger
    Labkit::Tracing::RackMiddleware automatically inserted after Labkit::Middleware::Rack
    ```

3. If using custom middleware positioning, verify the order is correct:
+7 −8
Original line number Diff line number Diff line
@@ -7,27 +7,26 @@ module Labkit
    # https://edgeapi.rubyonrails.org/classes/ActiveSupport/Notifications/Instrumenter.html#method-c-new
    class AbstractInstrumenter
      def start(_name, _id, payload)
        scope = Labkit::Tracing::TracingUtils.tracer.start_active_span(span_name(payload))
        span_wrapper = Labkit::Tracing::TracingUtils.tracer.start_active_span(span_name(payload))

        scope_stack.push scope
        scope_stack.push span_wrapper
      end

      def finish(_name, _id, payload)
        scope = scope_stack.pop
        span = scope.span
        span_wrapper = scope_stack.pop

        Labkit::Tracing::TracingUtils.log_common_fields_on_span(span, span_name(payload))
        Labkit::Tracing::TracingUtils.log_common_fields_on_span(span_wrapper, span_name(payload))

        # exception_object is the standard exception payload from ActiveSupport::Notifications
        # https://github.com/rails/rails/blob/v6.0.3.1/activesupport/lib/active_support/notifications/instrumenter.rb#L26
        exception = payload[:exception_object].presence || payload[:exception].presence
        Labkit::Tracing::TracingUtils.log_exception_on_span(span, exception)
        span_wrapper.set_error(exception)

        tags(payload).each do |k, v|
          span.set_tag(k, v)
          span_wrapper.set_tag(k, v)
        end

        scope.close
        span_wrapper.close
      end

      def scope_stack
+4 −0
Original line number Diff line number Diff line
@@ -40,6 +40,10 @@ module Labkit
          end
        end

        def close(**opts)
          finish(**opts)
        end

        def context
          span.context
        end
Loading