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
-
.instance ⇒ Plugin?
readonly
The current plugin instance.
Class Method Summary collapse
-
.current_trace_id ⇒ String?
Get the current trace ID.
-
.flush ⇒ Object
Flush all pending telemetry data.
-
.in_span(name, attributes: {}) {|span| ... } ⇒ Object
Create a custom span for manual instrumentation.
-
.init(project_id: nil, sdk_key: nil, **options) ⇒ Plugin
Initialize the observability plugin.
-
.initialized? ⇒ Boolean
Check if the plugin has been initialized.
-
.install_rails_instrumentation(project_id: nil, otlp_endpoint: DEFAULT_ENDPOINT, **options) ⇒ Boolean
Install OpenTelemetry auto-instrumentation during Rails boot.
-
.instrumentation_installed_at_boot? ⇒ Boolean
Whether auto-instrumentation was installed during Rails boot by LaunchDarklyObservability.install_rails_instrumentation.
-
.logger(output = $stdout) ⇒ OtelLogBridge, Logger
Create a Logger that writes to both a local IO and the OTel Logs pipeline.
-
.record_exception(exception, attributes: {}) ⇒ Object
Record an exception in the current span.
-
.reset_instrumentation_state! ⇒ Object
private
Reset boot-instrumentation state.
-
.shutdown ⇒ Object
Shutdown the plugin and flush remaining data.
Class Attribute Details
.instance ⇒ Plugin? (readonly)
Returns The current plugin instance.
59 60 61 |
# File 'lib/launchdarkly_observability.rb', line 59 def instance @instance end |
Class Method Details
.current_trace_id ⇒ String?
Get the current trace ID
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 |
.flush ⇒ Object
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.
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
72 73 74 |
# File 'lib/launchdarkly_observability.rb', line 72 def init(project_id: nil, sdk_key: nil, **) @instance = Plugin.new(project_id: project_id, sdk_key: sdk_key, **) end |
.initialized? ⇒ Boolean
Check if the plugin has been 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).
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, **) 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, **) .install_instrumentation_only @instrumentation_installed_at_boot = true rescue StandardError => e warn "[LaunchDarklyObservability] Could not install Rails auto-instrumentation at boot: #{e.}" 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.
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).
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
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.) 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 |
.shutdown ⇒ Object
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 |