Module: LaunchDarklyObservability

Defined in:
lib/launchdarkly_observability.rb,
lib/launchdarkly_observability/hook.rb,
lib/launchdarkly_observability/rails.rb,
lib/launchdarkly_observability/plugin.rb,
lib/launchdarkly_observability/version.rb,
lib/launchdarkly_observability/middleware.rb,
lib/launchdarkly_observability/source_context.rb,
lib/launchdarkly_observability/otel_log_bridge.rb,
lib/launchdarkly_observability/opentelemetry_config.rb,
lib/launchdarkly_observability/instrumentation_log_filter.rb

Overview

NOTE: rails.rb is required at the bottom of this file, after the LaunchDarklyObservability module body has been fully defined. Its Railtie registers a config.after_initialize hook that runs synchronously when the gem is required lazily after Rails has booted. That hook references module constants and class << self methods, so they must already exist when the require runs. See the require at the end of this file.

Defined Under Namespace

Modules: ControllerHelpers, SourceContext, ViewHelpers Classes: Hook, InstrumentationLogFilter, Middleware, OpenTelemetryConfig, OtelLogBridge, Plugin, Railtie

Constant Summary collapse

DEFAULT_ENDPOINT =

Default OTLP endpoint for LaunchDarkly Observability

'https://otel.observability.app.launchdarkly.com:4318'
PROJECT_ID_ATTRIBUTE =

Resource attribute keys

'launchdarkly.project_id'
SDK_NAME_ATTRIBUTE =
'telemetry.sdk.name'
SDK_VERSION_ATTRIBUTE =
'telemetry.sdk.version'
SDK_LANGUAGE_ATTRIBUTE =
'telemetry.sdk.language'
DISTRO_NAME_ATTRIBUTE =
'telemetry.distro.name'
DISTRO_VERSION_ATTRIBUTE =
'telemetry.distro.version'
FEATURE_FLAG_KEY =

OpenTelemetry semantic convention attribute keys for feature flags See: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-events/

'feature_flag.key'
FEATURE_FLAG_PROVIDER_NAME =
'feature_flag.provider.name'
FEATURE_FLAG_CONTEXT_ID =
'feature_flag.context.id'
FEATURE_FLAG_SET_ID =
'feature_flag.set.id'
FEATURE_FLAG_RESULT_VALUE =
'feature_flag.result.value'
FEATURE_FLAG_RESULT_VARIANT =
'feature_flag.result.variant'
FEATURE_FLAG_RESULT_VARIATION_INDEX =
'feature_flag.result.variationIndex'
FEATURE_FLAG_RESULT_REASON_KIND =
'feature_flag.result.reason.kind'
FEATURE_FLAG_RESULT_REASON_IN_EXPERIMENT =
'feature_flag.result.reason.inExperiment'
FEATURE_FLAG_RESULT_REASON_ERROR_KIND =
'feature_flag.result.reason.errorKind'
FEATURE_FLAG_RESULT_REASON_RULE_ID =
'feature_flag.result.reason.ruleId'
FEATURE_FLAG_RESULT_REASON_RULE_INDEX =
'feature_flag.result.reason.ruleIndex'
ERROR_TYPE =
'error.type'
ERROR_MESSAGE =
'error.message'
VERSION =

x-release-please-version

'0.3.0'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.instancePlugin? (readonly)

Returns The current plugin instance.

Returns:

  • (Plugin, nil)

    The current plugin instance



59
60
61
# File 'lib/launchdarkly_observability.rb', line 59

def instance
  @instance
end

Class Method Details

.current_trace_idString?

Get the current trace ID

Examples:

Get trace ID for logging

trace_id = LaunchDarklyObservability.current_trace_id
logger.info "Processing request: #{trace_id}"

Returns:

  • (String, nil)

    The current trace ID in hex format



163
164
165
166
167
168
169
170
# File 'lib/launchdarkly_observability.rb', line 163

def current_trace_id
  return nil unless defined?(OpenTelemetry)

  span = OpenTelemetry::Trace.current_span
  return nil unless span&.context&.valid?

  span.context.hex_trace_id
end

.flushObject

Flush all pending telemetry data



173
174
175
# File 'lib/launchdarkly_observability.rb', line 173

def flush
  @instance&.flush
end

.in_span(name, attributes: {}) {|span| ... } ⇒ Object

Create a custom span for manual instrumentation

This method matches the OpenTelemetry API naming convention for consistency.

Examples:

Create a custom span

LaunchDarklyObservability.in_span('database-query') do |span|
  span.set_attribute('db.table', 'users')
  perform_query
end

Parameters:

  • name (String)

    The span name

  • attributes (Hash) (defaults to: {})

    Optional span attributes

Yields:

  • (span)

    Block to execute within the span context

Returns:

  • The result of the block



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/launchdarkly_observability.rb', line 97

def in_span(name, attributes: {})
  unless defined?(OpenTelemetry) && OpenTelemetry.tracer_provider
    return yield if block_given?
    return
  end

  tracer = OpenTelemetry.tracer_provider.tracer(
    'launchdarkly-observability',
    LaunchDarklyObservability::VERSION
  )

  tracer.in_span(name, attributes: attributes) do |span|
    yield(span) if block_given?
  end
end

.init(project_id: nil, sdk_key: nil, **options) ⇒ Plugin

Initialize the observability plugin

Parameters:

  • project_id (String, nil) (defaults to: nil)

    LaunchDarkly project ID (optional - SDK key will be extracted from client if not provided)

  • sdk_key (String, nil) (defaults to: nil)

    LaunchDarkly SDK key (optional - will be extracted from client if not provided)

  • options (Hash)

    Additional configuration options

Options Hash (**options):

  • :otlp_endpoint (String)

    Custom OTLP endpoint URL

  • :environment (String)

    Deployment environment (optional - inferred from SDK key by default)

  • :service_name (String)

    Service name for traces

  • :service_version (String)

    Service version

  • :instrumentations (Hash)

    Configuration for auto-instrumentations

Returns:

  • (Plugin)

    The initialized plugin



72
73
74
# File 'lib/launchdarkly_observability.rb', line 72

def init(project_id: nil, sdk_key: nil, **options)
  @instance = Plugin.new(project_id: project_id, sdk_key: sdk_key, **options)
end

.initialized?Boolean

Check if the plugin has been initialized

Returns:

  • (Boolean)

    true if initialized



79
80
81
# File 'lib/launchdarkly_observability.rb', line 79

def initialized?
  !@instance.nil?
end

.install_rails_instrumentation(project_id: nil, otlp_endpoint: DEFAULT_ENDPOINT, **options) ⇒ Boolean

Install OpenTelemetry auto-instrumentation during Rails boot.

The OTel Rails-family instrumentations (ActionPack, ActiveRecord, ...) patch via ActiveSupport.on_load hooks that fire while Rails is booting. If the LaunchDarkly client is created lazily (e.g. from a model on first request), the plugin's #register runs after those hooks have fired and the instrumentations report "failed to install". The Rails Railtie calls this during boot so instrumentation attaches regardless of when the client is created. Exporters are still configured later, when the client registers the plugin.

The project_id needed for the resource is resolved from the LAUNCHDARKLY_SDK_KEY environment variable, which is present at boot in the common case even when the client object is created lazily. If it cannot be resolved, instrumentation is left to #register (which warns if it then runs after boot).

Parameters:

  • project_id (String, nil) (defaults to: nil)

    explicit project id; falls back to ENV

  • otlp_endpoint (String) (defaults to: DEFAULT_ENDPOINT)

    OTLP endpoint (only relevant for exporters)

  • options (Hash)

    additional OpenTelemetryConfig options

Returns:

  • (Boolean)

    whether instrumentation was installed



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/launchdarkly_observability.rb', line 212

def install_rails_instrumentation(project_id: nil, otlp_endpoint: DEFAULT_ENDPOINT, **options)
  return false if @instrumentation_installed_at_boot

  project_id ||= ENV.fetch('LAUNCHDARKLY_SDK_KEY', nil)
  return false if project_id.nil? || project_id.empty?

  # If something already configured an SDK tracer provider (e.g. the client
  # was created in a config/initializer during boot), don't reconfigure —
  # that would replace the provider and drop its exporters.
  return false if OpenTelemetry.tracer_provider.is_a?(OpenTelemetry::SDK::Trace::TracerProvider)

  OpenTelemetryConfig.new(project_id: project_id, otlp_endpoint: otlp_endpoint, **options)
                     .install_instrumentation_only
  @instrumentation_installed_at_boot = true
rescue StandardError => e
  warn "[LaunchDarklyObservability] Could not install Rails auto-instrumentation at boot: #{e.message}"
  false
end

.instrumentation_installed_at_boot?Boolean

Returns whether auto-instrumentation was installed during Rails boot by install_rails_instrumentation. When true, the plugin attaches exporters to the existing tracer provider at register time instead of reconfiguring it.

Returns:

  • (Boolean)

    whether auto-instrumentation was installed during Rails boot by install_rails_instrumentation. When true, the plugin attaches exporters to the existing tracer provider at register time instead of reconfiguring it.



187
188
189
# File 'lib/launchdarkly_observability.rb', line 187

def instrumentation_installed_at_boot?
  @instrumentation_installed_at_boot == true
end

.logger(output = $stdout) ⇒ OtelLogBridge, Logger

Create a Logger that writes to both a local IO and the OTel Logs pipeline.

Use this in non-Rails applications (Sinatra, Grape, plain Ruby) to get log export with trace correlation out of the box. Must be called after the Plugin has been registered (i.e. after LDClient.new).

Examples:

Sinatra

$logger = LaunchDarklyObservability.logger
$logger.info 'This goes to stdout AND is exported as an OTLP log record'

Parameters:

  • output (IO) (defaults to: $stdout)

    Local IO destination (default: $stdout)

Returns:

  • (OtelLogBridge, Logger)

    An OTel-bridged logger, or a plain Logger if the OTel logger provider is not yet available.



148
149
150
151
152
153
154
# File 'lib/launchdarkly_observability.rb', line 148

def logger(output = $stdout)
  if otel_logger_provider_available?
    OtelLogBridge.new(OpenTelemetry.logger_provider, io: output)
  else
    ::Logger.new(output)
  end
end

.record_exception(exception, attributes: {}) ⇒ Object

Record an exception in the current span

Examples:

Record an exception

begin
  risky_operation
rescue => e
  LaunchDarklyObservability.record_exception(e, foo: 'bar')
  raise
end

Parameters:

  • exception (Exception)

    The exception to record

  • attributes (Hash) (defaults to: {})

    Additional attributes



125
126
127
128
129
130
131
132
133
# File 'lib/launchdarkly_observability.rb', line 125

def record_exception(exception, attributes: {})
  return unless defined?(OpenTelemetry)

  span = OpenTelemetry::Trace.current_span
  return unless span

  span.record_exception(exception, attributes: SourceContext.exception_attributes(exception).merge(attributes))
  span.status = OpenTelemetry::Trace::Status.error(exception.message)
end

.reset_instrumentation_state!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Reset boot-instrumentation state. Intended for tests only.



233
234
235
# File 'lib/launchdarkly_observability.rb', line 233

def reset_instrumentation_state!
  @instrumentation_installed_at_boot = false
end

.shutdownObject

Shutdown the plugin and flush remaining data



178
179
180
181
# File 'lib/launchdarkly_observability.rb', line 178

def shutdown
  @instance&.shutdown
  @instance = nil
end