gitlab-labkit 1.5.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.copier-answers.yml +3 -1
  3. data/.gitlab/merge_request_templates/default.md +9 -0
  4. data/.gitlab-ci-asdf-versions.yml +2 -2
  5. data/.gitlab-ci.yml +14 -15
  6. data/.pre-commit-config.yaml +1 -1
  7. data/.releaserc.json +7 -1
  8. data/.tool-versions +2 -2
  9. data/CODEOWNERS +1 -1
  10. data/doc/FIELD_STANDARDIZATION.md +148 -128
  11. data/gitlab-labkit.gemspec +2 -2
  12. data/lib/labkit/context.rb +4 -0
  13. data/lib/labkit/fields.rb +127 -42
  14. data/lib/labkit/logging/field_validator/config.rb +5 -18
  15. data/lib/labkit/logging/field_validator.rb +1 -1
  16. data/lib/labkit/net_http_publisher.rb +20 -1
  17. data/lib/labkit/rspec/README.md +0 -9
  18. data/lib/labkit/rspec/matchers/user_experience_matchers.rb +53 -0
  19. data/lib/labkit/tracing/README.md +50 -25
  20. data/lib/labkit/tracing/abstract_instrumenter.rb +7 -8
  21. data/lib/labkit/tracing/adapters/opentelemetry_span.rb +4 -0
  22. data/lib/labkit/tracing/adapters/opentelemetry_tracer.rb +12 -0
  23. data/lib/labkit/tracing/adapters/opentracing_span.rb +4 -0
  24. data/lib/labkit/tracing/adapters/opentracing_tracer.rb +9 -6
  25. data/lib/labkit/tracing/auto_initialize.rb +28 -6
  26. data/lib/labkit/tracing/external_http/request_instrumenter.rb +4 -13
  27. data/lib/labkit/tracing/open_telemetry_factory.rb +3 -13
  28. data/lib/labkit/tracing/open_tracing_factory.rb +1 -10
  29. data/lib/labkit/tracing/rails/action_view/render_template_instrumenter.rb +6 -1
  30. data/lib/labkit/tracing/rails/active_record/sql_instrumenter.rb +9 -4
  31. data/lib/labkit/tracing/tracing_utils.rb +9 -7
  32. data/lib/labkit/tracing.rb +3 -1
  33. data/lib/labkit/user_experience_sli/README.md +20 -0
  34. data/lib/labkit/user_experience_sli/experience.rb +33 -12
  35. data/lib/labkit/user_experience_sli/null.rb +5 -2
  36. data/lib/labkit/user_experience_sli.rb +15 -3
  37. data/scripts/prepare-dev-env.sh +1 -0
  38. metadata +10 -6
data/lib/labkit/fields.rb CHANGED
@@ -1,54 +1,122 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Code generated by labkit-spec. DO NOT EDIT.
4
+ #
5
+ # Source: https://gitlab.com/gitlab-org/quality/tooling/labkit-spec/-/blob/main/schema/fields.yaml (version 1.0)
6
+
3
7
  module Labkit
4
- ##
5
- # Fields is intended to be a SSOT for all of the common field names that
6
- # we emit via any observability we add to our systems.
7
- #
8
- # These fields should span multiple services.
9
- #
10
- # The goal of this package is to reduce the likelihood for typos or
11
- # subtly different naming conventions. This will help to ensure we
12
- # are able to marry up logs between different systems as a request
13
- # is being processed.
14
- #
15
- # Usage:
16
- # require 'labkit/fields'
17
- # ...
18
- # data[Labkit::Fields::GL_USER_ID] = user.id
19
- # ...
20
- #
21
- # Labkit (Go): https://gitlab.com/gitlab-org/labkit/-/tree/master/fields?ref_type=heads
22
- #
23
- # For Engineers Looking to add fields:
24
- #
25
- # These fields are derived from the Go Labkit variant. Please ensure that you've made the
26
- # respective changes in that repository prior to including the fields in this package.
27
- #
28
- # Please see the handbook page for more information
29
- # https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/observability_field_standardisation/
30
8
  module Fields
31
- # correlation_id - string
32
- #
33
- # This field is used to correlate
34
- # the logs emitted by all of our systems.
35
- # This should be present in all log line
36
- # emissions.
9
+ # Unique identifier for correlating requests across services. Should be
10
+ # present in all log line emissions.
37
11
  CORRELATION_ID = "correlation_id"
38
12
 
39
- # GitLabUserID - an integer field that
40
- # captures the user's numeric ID for logging purposes.
13
+ # GitLab user numeric ID.
41
14
  GL_USER_ID = "gl_user_id"
42
15
 
43
- # GitLabUserName - a string field that
44
- # captures the user's username for logging purposes.
16
+ # GitLab username.
45
17
  GL_USER_NAME = "gl_user_name"
46
18
 
47
- # New fields being added to this section should have
48
- # the appropriate doc comments added above. These
49
- # should clearly indicate what the intended use of the
50
- # field is and should be replicated across the labkit
51
- # variations.
19
+ # Duo Workflow definition identifier (e.g. "analytics_agent/v1"). Identifies
20
+ # which Duo Workflow agent originated a request, enabling per-agent
21
+ # filtering in Kibana and Grafana.
22
+ DUO_WORKFLOW_DEFINITION = "duo_workflow_definition"
23
+
24
+ # Error type or classification (e.g. "NoMethodError", "ValidationError").
25
+ ERROR_TYPE = "error_type"
26
+
27
+ # Detailed error message (e.g. "undefined method 'boom!' for nil").
28
+ ERROR_MESSAGE = "error_message"
29
+
30
+ # HTTP response status code.
31
+ HTTP_STATUS_CODE = "status"
32
+
33
+ # HTTP method (e.g. "GET", "POST").
34
+ HTTP_METHOD = "method"
35
+
36
+ # URL of an HTTP request containing only scheme, host, and path. Query
37
+ # strings and fragments must be omitted to avoid logging sensitive
38
+ # information.
39
+ HTTP_URL = "url"
40
+
41
+ # Duration of any operation in seconds. Not limited to HTTP requests; can be
42
+ # used for database queries, background jobs, external API calls. Uses
43
+ # float64 for sub-second precision (e.g. 0.032 for 32ms).
44
+ DURATION_S = "duration_s"
45
+
46
+ # Remote IP address of a request.
47
+ REMOTE_IP = "remote_ip"
48
+
49
+ # TCP address a service is listening on, in "host:port" format (e.g.
50
+ # "0.0.0.0:8080").
51
+ TCP_ADDRESS = "tcp_address"
52
+
53
+ # Request URI including path and query string with sensitive parameters
54
+ # masked (e.g. "?password=FILTERED").
55
+ HTTP_URI = "uri"
56
+
57
+ # HTTP Host header of a request (e.g. "api.gitlab.com").
58
+ HTTP_HOST = "host"
59
+
60
+ # HTTP protocol version (e.g. "HTTP/1.1", "HTTP/2.0").
61
+ HTTP_PROTO = "proto"
62
+
63
+ # Raw remote socket address in "host:port" format (e.g. "10.0.0.1:54321").
64
+ # Use remote_ip when only the IP is needed.
65
+ REMOTE_ADDR = "remote_addr"
66
+
67
+ # HTTP Referer header with sensitive query parameters masked.
68
+ HTTP_REFERRER = "referrer"
69
+
70
+ # HTTP User-Agent header.
71
+ HTTP_USER_AGENT = "user_agent"
72
+
73
+ # Number of bytes written to the HTTP response body.
74
+ WRITTEN_BYTES = "written_bytes"
75
+
76
+ # Content-Type of an HTTP response (e.g. "application/json").
77
+ CONTENT_TYPE = "content_type"
78
+
79
+ # Time to first byte of an HTTP response in seconds. Measures duration from
80
+ # request receipt to first response byte written.
81
+ TTFB_S = "ttfb_s"
82
+
83
+ # GitLab project numeric ID.
84
+ GL_PROJECT_ID = "gl_project_id"
85
+
86
+ # GitLab pipeline numeric ID.
87
+ GL_PIPELINE_ID = "gl_pipeline_id"
88
+
89
+ # Log event timestamp in ISO 8601 format (e.g. "2026-04-02T12:00:00.000Z").
90
+ TIMESTAMP = "timestamp"
91
+
92
+ # Log severity level (e.g. "info", "warn", "error").
93
+ SEVERITY = "severity"
94
+
95
+ # Human-readable log message describing the event.
96
+ LOG_MESSAGE = "message"
97
+
98
+ # Class name associated with a log event (e.g. exception class or worker
99
+ # class).
100
+ CLASS_NAME = "class_name"
101
+
102
+ # Name of the service or component emitting the log event.
103
+ SERVICE_NAME = "service_name"
104
+
105
+ # GitLab organization numeric ID.
106
+ GL_ORGANIZATION_ID = "gl_organization_id"
107
+
108
+ # GitLab project path.
109
+ GL_PROJECT_PATH = "gl_project_path"
110
+
111
+ # The endpoint_id of the current endpoint to be passed as caller_id when
112
+ # calling another service.
113
+ ENDPOINT_ID = "endpoint_id"
114
+
115
+ # The ID of the caller of the current service.
116
+ CALLER_ID = "caller_id"
117
+
118
+ # The endpoint_id of the original caller at the root of the call chain.
119
+ ROOT_CALLER_ID = "root_caller_id"
52
120
 
53
121
  # Get the constant name for a field value
54
122
  # @param field_value [String] The field value (e.g., "gl_user_id")
@@ -67,7 +135,24 @@ module Labkit
67
135
  # to identify and track usage of deprecated fields in the codebase.
68
136
 
69
137
  MAPPINGS = {
70
- Fields::GL_USER_ID => %w[user_id userid],
138
+ Fields::CORRELATION_ID => %w[tags.correlation_id],
139
+ Fields::GL_USER_ID => %w[user_id userid extra.user_id extra.current_user_id meta.user_id],
140
+ Fields::GL_USER_NAME => %w[username extra.user meta.user],
141
+ Fields::ERROR_MESSAGE => %w[error err error.message exception.message graphql_errors],
142
+ Fields::HTTP_STATUS_CODE => %w[status_code extra.status status_text http_status],
143
+ Fields::HTTP_URL => %w[req_url],
144
+ Fields::DURATION_S => %w[duration duration_ms elapsed_time actual_duration time_ms total_time gitaly.duration],
145
+ Fields::REMOTE_IP => %w[ip source_ip ip_address meta.remote_ip],
146
+ Fields::HTTP_HOST => %w[hostname request_host gitlab_host kubernetes.host],
147
+ Fields::GL_PROJECT_ID => %w[extra.project_id meta.project_id meta.search.project_id job_project_id target_project_id],
148
+ Fields::GL_PIPELINE_ID => %w[extra.pipeline_id meta.pipeline_id root_pipeline_id],
149
+ Fields::TIMESTAMP => %w[start_time],
150
+ Fields::SEVERITY => %w[level],
151
+ Fields::LOG_MESSAGE => %w[msg custom_message extra.message fields.message graphql.message reason color_message exception.gitaly],
152
+ Fields::CLASS_NAME => %w[class author_class exception.class extra.class extra.class_name],
153
+ Fields::SERVICE_NAME => %w[service grpc.service_name auth_service type component subcomponent],
154
+ Fields::GL_ORGANIZATION_ID => %w[organization_id],
155
+ Fields::GL_PROJECT_PATH => %w[project_path full_path root_pipeline_project_path requested_project_path auth_project_path extra.gl_project_path],
71
156
  }.freeze
72
157
 
73
158
  class << self
@@ -88,26 +88,13 @@ module Labkit
88
88
  # This file tracks deprecated logging fields that need to be migrated to standard fields.
89
89
  # Each offense represents a file using a deprecated field that should be replaced.
90
90
  #
91
- # === HOW TO FIX ===
91
+ # How to fix: https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/blob/master/doc/FIELD_STANDARDIZATION.md#how-to-fix-an-offense
92
92
  #
93
- # 1. Replace the deprecated field with the standard field constant
94
- # 2. Remove the deprecated field entirely (adding the new field is not enough)
95
- # 3. Run your tests - the offense will be automatically removed
93
+ # Adding offenses (if fixing is not immediately possible):
94
+ # LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec <spec_file>
96
95
  #
97
- # Example:
98
- # # Before
99
- # logger.info(user_id: 123)
100
- #
101
- # # After
102
- # logger.info(Labkit::Fields::GL_USER_ID => 123)
103
- #
104
- # === ADDING OFFENSES (if fixing is not immediately possible) ===
105
- #
106
- # Run: LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec <spec_file>
107
- #
108
- # === REGENERATE ENTIRE TODO ===
109
- #
110
- # Delete this file and run: LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec
96
+ # Regenerate entire TODO:
97
+ # Delete this file and run: LABKIT_LOGGING_TODO_UPDATE=true bundle exec rspec
111
98
 
112
99
  HEADER
113
100
  end
@@ -158,7 +158,7 @@ module Labkit
158
158
  lines << ("=" * 80)
159
159
  lines << "Total: #{new_offenses.size} new offense(s) in #{new_offenses.map { |o| o['callsite'] }.uniq.size} file(s)" # rubocop:disable Rails/Pluck
160
160
  lines << ""
161
- lines << "See https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/blob/master/doc/FIELD_STANDARDIZATION.md"
161
+ lines << "See https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/blob/master/doc/FIELD_STANDARDIZATION.md#new-offenses-detected"
162
162
  lines << ("=" * 80)
163
163
  lines << ""
164
164
 
@@ -41,6 +41,8 @@ module Labkit
41
41
 
42
42
  start_time = ::Labkit::System.monotonic_time
43
43
 
44
+ inject_trace_context(request)
45
+
44
46
  ActiveSupport::Notifications.instrument ::Labkit::EXTERNAL_HTTP_NOTIFICATION_TOPIC, create_request_payload(request) do |payload|
45
47
  response =
46
48
  begin
@@ -57,7 +59,7 @@ module Labkit
57
59
 
58
60
  def create_request_payload(request)
59
61
  payload = {
60
- method: request.method,
62
+ method: request.method
61
63
  }
62
64
 
63
65
  if request.uri.nil?
@@ -84,5 +86,22 @@ module Labkit
84
86
 
85
87
  payload
86
88
  end
89
+
90
+ def inject_trace_context(request)
91
+ return unless Labkit::Tracing.enabled?
92
+
93
+ tracer = Labkit::Tracing::TracingUtils.tracer
94
+ span = tracer.active_span
95
+ return if span.nil?
96
+
97
+ carrier = {}
98
+ tracer.inject_context(span, carrier)
99
+
100
+ carrier.each do |key, value|
101
+ request[key] = value
102
+ end
103
+ rescue StandardError
104
+ warn "Labkit::NetHttpPublisher: trace context propagation failed"
105
+ end
87
106
  end
88
107
  end
@@ -74,15 +74,6 @@ expect { subject }.to complete_user_experience('rails_request', error: true, suc
74
74
  expect { subject }.not_to complete_user_experience('rails_request')
75
75
  ```
76
76
 
77
- ### Legacy Matchers (Backward Compatibility)
78
-
79
- For backward compatibility, the following legacy matchers are still available but deprecated. Please migrate to the new `*_user_experience` matchers above.
80
-
81
- - `start_covered_experience`
82
- - `checkpoint_covered_experience`
83
- - `resume_covered_experience`
84
- - `complete_covered_experience`
85
-
86
77
  ## Example Usage
87
78
 
88
79
  ### In your spec_helper.rb or rails_helper.rb:
@@ -203,6 +203,59 @@ RSpec::Matchers.define :complete_user_experience do |user_experience_id, error:
203
203
  end
204
204
  end
205
205
 
206
+ # Matcher for verifying UserExperience observed metrics instrumentation.
207
+ #
208
+ # Usage:
209
+ # expect { subject }.to observed_user_experience('rails_request')
210
+ #
211
+ # This matcher verifies that the following metrics are incremented with specific labels:
212
+ # - gitlab_user_experience_checkpoint_total (with checkpoint=start)
213
+ # - gitlab_user_experience_checkpoint_total (with checkpoint=end)
214
+ # - gitlab_user_experience_total (with error=false)
215
+ # - gitlab_user_experience_apdex_total (with success=true)
216
+ #
217
+ # Parameters:
218
+ # - user_experience_id: Required. The ID of the user experience (e.g., 'rails_request')
219
+ # - error: Optional. The expected error flag for gitlab_user_experience_total (false by default)
220
+ # - success: Optional. The expected success flag for gitlab_user_experience_apdex_total (true by default)
221
+ RSpec::Matchers.define :observed_user_experience do |user_experience_id, error: false, success: true|
222
+ include Labkit::RSpec::Matchers::UserExperience
223
+
224
+ description { "observe user experience '#{user_experience_id}'" }
225
+ supports_block_expectations
226
+
227
+ match do |actual|
228
+ labels = attributes(user_experience_id)
229
+
230
+ start_before = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i
231
+ end_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
232
+ total_before = total_counter&.get(labels.merge(error: error)).to_i
233
+ apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i
234
+
235
+ actual.call
236
+
237
+ start_after = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i
238
+ end_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
239
+ total_after = total_counter&.get(labels.merge(error: error)).to_i
240
+ apdex_after = apdex_counter&.get(labels.merge(success: success)).to_i
241
+
242
+ @start_change = start_after - start_before
243
+ @end_change = end_after - end_before
244
+ @total_change = total_after - total_before
245
+ @apdex_change = apdex_after - apdex_before
246
+
247
+ @start_change == 1 && @end_change == 1 && @total_change == 1 && @apdex_change == (error ? 0 : 1)
248
+ end
249
+
250
+ failure_message do
251
+ "Failed to observe user experience '#{user_experience_id}':\n" \
252
+ "expected checkpoint='start' counter to increase by 1, but increased by #{@start_change}\n" \
253
+ "expected checkpoint='end' counter to increase by 1, but increased by #{@end_change}\n" \
254
+ "expected total='error: #{error}' counter to increase by 1, but increased by #{@total_change}\n" \
255
+ "expected apdex='success: #{success}' counter to increase by #{error ? 0 : 1}, but increased by #{@apdex_change}"
256
+ end
257
+ end
258
+
206
259
  # Backward compatibility matchers for CoveredExperience
207
260
  RSpec::Matchers.alias_matcher :start_covered_experience, :start_user_experience
208
261
  RSpec::Matchers.alias_matcher :checkpoint_covered_experience, :checkpoint_user_experience
@@ -35,13 +35,18 @@ export GITLAB_TRACING="otlp://localhost:4318"
35
35
 
36
36
  **Automatic Initialization**
37
37
 
38
- 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.
38
+ When `GITLAB_TRACING` is set, LabKit automatically creates and configures a tracer:
39
+
40
+ - **Rails applications**: Railtie automatically inserts Labkit::Tracing::RackMiddleware into the middleware stack
41
+ - **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))
39
42
 
40
43
  Auto-initialization provides:
41
- - Automatic tracer creation with connection string settings
44
+ - Automatic tracer creation with connection string settings (sampler, exporter, service name)
42
45
  - Service name from `service_name` query parameter (defaults to `"labkit-service"`)
43
- - All available OpenTelemetry instrumentation enabled by default (`c.use_all()`)
44
- - LabKit-specific instrumentation enabled automatically:
46
+ - Selective OpenTelemetry instrumentation for components that don't conflict with LabKit:
47
+ - `ConcurrentRuby`, `Net::HTTP`, `ActionPack`, `ActionMailer`, `ActiveJob`
48
+ - LabKit's own instrumentation for components it handles directly:
49
+ - HTTP request tracing (RackMiddleware for Rails)
45
50
  - Rails components (ActiveRecord, ActionView, ActiveSupport) - if Rails is available
46
51
  - Redis instrumentation - if Redis gem is loaded
47
52
  - External HTTP instrumentation (Net::HTTP, Excon, HTTPClient)
@@ -73,6 +78,7 @@ The following connection string formats and query parameters are supported:
73
78
  - **Behavior**: Outputs spans immediately to stdout with no remote export
74
79
  - **Benefits**: No external collector required, instant feedback, simple debugging
75
80
 
81
+
76
82
  ### Sampling
77
83
 
78
84
  **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:
159
165
 
160
166
  ### Automatic Initialization (Default Behavior)
161
167
 
162
- When `GITLAB_TRACING` is set, LabKit automatically creates and configures a tracer when the gem loads:
168
+ When `GITLAB_TRACING` is set, LabKit automatically creates and configures a tracer:
163
169
 
164
170
  ```bash
165
171
  # Set environment variable with service name
@@ -169,14 +175,23 @@ export GITLAB_TRACING="otlp://localhost:4318?service_name=my-api&sampler=probabi
169
175
  export GITLAB_TRACING="otlp://localhost:4318"
170
176
  ```
171
177
 
178
+ **Rails Applications:**
179
+ - Initialization happens automatically during Rails boot via Railtie
180
+ - Runs after all other gem initializers (including `opentelemetry-instrumentation-rails`)
181
+ - LabKit's TracerProvider is the final configuration used by the application
182
+ - **No application code changes required** - just set the environment variable
183
+
184
+ **Non-Rails Applications (Sinatra, Grape, standalone Ruby):**
185
+ - Tracer auto-initialization happens automatically when `require 'gitlab-labkit'` is called.
186
+ - 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.
187
+
172
188
  Auto-initialization:
173
189
  - Creates tracer with connection string settings (sampler, exporter, endpoints)
174
190
  - Uses service name from `service_name` query parameter (defaults to `"labkit-service"`)
175
- - Enables all available OpenTelemetry instrumentation
191
+ - Enables selective OpenTelemetry instrumentation (non-conflicting with LabKit's own)
192
+ - Enables LabKit instrumentation for ActiveRecord, ActionView, ActiveSupport, Redis, and External HTTP
176
193
  - Sets up the global `OpenTelemetry.tracer_provider`
177
194
 
178
- **No application code changes required** - just set the environment variable.
179
-
180
195
  ### Automatic Rails Middleware Insertion
181
196
 
182
197
  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
214
229
  # config/initializers/tracing.rb
215
230
  # Override auto-initialization with custom configuration
216
231
  Labkit::Tracing::Factory.create_tracer("my-custom-service", ENV["GITLAB_TRACING"]) do |c|
217
- # Selective instrumentation instead of c.use_all()
218
- c.use 'OpenTelemetry::Instrumentation::Rails'
232
+ # Custom OTel instrumentation
233
+ c.use 'OpenTelemetry::Instrumentation::ConcurrentRuby'
219
234
  c.use 'OpenTelemetry::Instrumentation::Sidekiq'
220
235
 
221
236
  # Add custom span processors
@@ -232,7 +247,7 @@ end
232
247
  ```
233
248
 
234
249
  **When to use manual initialization:**
235
- - You need selective instrumentation (not `c.use_all()`)
250
+ - You need different OTel instrumentations than the defaults
236
251
  - You need custom span processors
237
252
  - You need to add resource attributes
238
253
  - You want to override the service name from code instead of the connection string
@@ -289,11 +304,16 @@ end
289
304
 
290
305
  ### Initialization Order and Precedence
291
306
 
292
- **Auto-initialization:**
293
- 1. Runs when the gem is loaded (`require 'gitlab-labkit'`)
307
+ **Auto-initialization (all applications):**
308
+ 1. Runs automatically when `require 'gitlab-labkit'` is called
294
309
  2. Only runs if `GITLAB_TRACING` environment variable is set
295
310
  3. Uses `service_name` query parameter or defaults to `"labkit-service"`
296
- 4. Attempts to enable all instrumentation with `c.use_all()`
311
+ 4. Enables selective OpenTelemetry instrumentation (ConcurrentRuby, Net::HTTP, ActionPack, ActionMailer, ActiveJob) to avoid conflicts with LabKit's own instrumentation
312
+ 5. Enables LabKit instrumentation for ActiveRecord, ActionView, ActiveSupport, Redis, and External HTTP
313
+
314
+ **Additional Rails behavior:**
315
+ 1. Railtie runs after user config initializers (via `initializer ... after: :load_config_initializers`)
316
+ 2. Automatically inserts `Labkit::Tracing::RackMiddleware` into the middleware stack
297
317
 
298
318
  **Manual initialization:**
299
319
  1. Runs in your application initializer (e.g., `config/initializers/tracing.rb`)
@@ -308,7 +328,7 @@ end
308
328
  - Span processors with OTLP exporter
309
329
 
310
330
  2. **Configuration block** runs second and can:
311
- - Add automatic instrumentation (`use`, `use_all`)
331
+ - Add automatic instrumentation (`use`)
312
332
  - Add additional span processors
313
333
  - Merge additional resource attributes
314
334
  - 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
570
590
  # config/initializers/tracing.rb
571
591
 
572
592
  Labkit::Tracing::Factory.create_tracer("my-rails-app", ENV["GITLAB_TRACING"]) do |c|
573
- # Enable all available OpenTelemetry instrumentation
574
- c.use_all()
593
+ # Selective instrumentation (avoid conflicting with LabKit's own instrumentation
594
+ # for ActiveRecord, ActionView, ActiveSupport, Redis, Rack, and Sidekiq)
595
+ c.use("OpenTelemetry::Instrumentation::ConcurrentRuby")
596
+ c.use("OpenTelemetry::Instrumentation::Net::HTTP")
597
+ c.use("OpenTelemetry::Instrumentation::ActionPack") if defined?(ActionPack)
598
+ c.use("OpenTelemetry::Instrumentation::ActionMailer") if defined?(ActionMailer)
599
+ c.use("OpenTelemetry::Instrumentation::ActiveJob") if defined?(ActiveJob)
575
600
 
576
601
  # Add deployment metadata
577
602
  c.resource = c.resource.merge(
@@ -588,18 +613,18 @@ end
588
613
 
589
614
  Note: Middleware is still automatically inserted even with custom tracer initialization.
590
615
 
591
- ### With Selective Instrumentation (Manual Override)
616
+ ### With Custom Instrumentation (Manual Override)
592
617
 
593
- If you want to control exactly which components are instrumented instead of using `c.use_all()`, override tracer initialization:
618
+ If you want to control exactly which OTel components are instrumented, override tracer initialization:
594
619
 
595
620
  **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.
596
621
 
597
622
  ```ruby
598
623
  # config/initializers/tracing.rb
599
- # Override auto-initialization for selective OpenTelemetry instrumentation
624
+ # Override auto-initialization for custom OpenTelemetry instrumentation
600
625
  Labkit::Tracing::Factory.create_tracer("my-rails-app", ENV["GITLAB_TRACING"]) do |c|
601
- # Selective OpenTelemetry instrumentation (instead of c.use_all())
602
- c.use 'OpenTelemetry::Instrumentation::Rails'
626
+ # Only enable the OTel instrumentations you need
627
+ c.use 'OpenTelemetry::Instrumentation::ConcurrentRuby'
603
628
  c.use 'OpenTelemetry::Instrumentation::Sidekiq'
604
629
  end
605
630
 
@@ -642,9 +667,9 @@ If you're not seeing HTTP request traces in Rails:
642
667
  ```
643
668
 
644
669
  2. Check Rails logs for auto-insertion message:
645
- ```
646
- Labkit::Tracing: Automatically inserted RackMiddleware after Rails::Rack::Logger
647
- ```
670
+ ```
671
+ Labkit::Tracing::RackMiddleware automatically inserted after Labkit::Middleware::Rack
672
+ ```
648
673
 
649
674
  3. If using custom middleware positioning, verify the order is correct:
650
675
  ```bash
@@ -7,27 +7,26 @@ module Labkit
7
7
  # https://edgeapi.rubyonrails.org/classes/ActiveSupport/Notifications/Instrumenter.html#method-c-new
8
8
  class AbstractInstrumenter
9
9
  def start(_name, _id, payload)
10
- scope = Labkit::Tracing::TracingUtils.tracer.start_active_span(span_name(payload))
10
+ span_wrapper = Labkit::Tracing::TracingUtils.tracer.start_active_span(span_name(payload))
11
11
 
12
- scope_stack.push scope
12
+ scope_stack.push span_wrapper
13
13
  end
14
14
 
15
15
  def finish(_name, _id, payload)
16
- scope = scope_stack.pop
17
- span = scope.span
16
+ span_wrapper = scope_stack.pop
18
17
 
19
- Labkit::Tracing::TracingUtils.log_common_fields_on_span(span, span_name(payload))
18
+ Labkit::Tracing::TracingUtils.log_common_fields_on_span(span_wrapper, span_name(payload))
20
19
 
21
20
  # exception_object is the standard exception payload from ActiveSupport::Notifications
22
21
  # https://github.com/rails/rails/blob/v6.0.3.1/activesupport/lib/active_support/notifications/instrumenter.rb#L26
23
22
  exception = payload[:exception_object].presence || payload[:exception].presence
24
- Labkit::Tracing::TracingUtils.log_exception_on_span(span, exception)
23
+ span_wrapper.set_error(exception)
25
24
 
26
25
  tags(payload).each do |k, v|
27
- span.set_tag(k, v)
26
+ span_wrapper.set_tag(k, v)
28
27
  end
29
28
 
30
- scope.close
29
+ span_wrapper.close
31
30
  end
32
31
 
33
32
  def scope_stack
@@ -40,6 +40,10 @@ module Labkit
40
40
  end
41
41
  end
42
42
 
43
+ def close(**opts)
44
+ finish(**opts)
45
+ end
46
+
43
47
  def context
44
48
  span.context
45
49
  end
@@ -91,6 +91,18 @@ module Labkit
91
91
  @token = token
92
92
  end
93
93
 
94
+ def set_tag(key, value)
95
+ span.set_tag(key, value)
96
+ end
97
+
98
+ def set_error(exception)
99
+ span.set_error(exception)
100
+ end
101
+
102
+ def log_event(name, **attributes)
103
+ span.log_event(name, **attributes)
104
+ end
105
+
94
106
  def close
95
107
  @raw_span.finish
96
108
  OpenTelemetry::Context.detach(@token)
@@ -41,6 +41,10 @@ module Labkit
41
41
  scope&.close
42
42
  end
43
43
 
44
+ def close
45
+ finish
46
+ end
47
+
44
48
  def context
45
49
  span.context
46
50
  end
@@ -34,15 +34,18 @@ module Labkit
34
34
  end
35
35
 
36
36
  def active_span
37
- OpenTracing.active_span
37
+ span = OpenTracing.active_span
38
+ OpentracingSpan.new(span) if span
38
39
  end
39
40
 
40
41
  def start_active_span(operation_name, tags: nil)
41
- if tags
42
- OpenTracing.start_active_span(operation_name, tags: tags)
43
- else
44
- OpenTracing.start_active_span(operation_name)
45
- end
42
+ scope = if tags
43
+ OpenTracing.start_active_span(operation_name, tags: tags)
44
+ else
45
+ OpenTracing.start_active_span(operation_name)
46
+ end
47
+
48
+ OpentracingSpan.new(scope)
46
49
  end
47
50
  end
48
51
  end