flare 0.1.1 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0fb9866f1a34be35ebd27efe1cd0b1eb6c9ea7109e21b18a6aa34d003ac32220
4
- data.tar.gz: 772b2542a8d1a35026dd11db71116b9f7a7d492cc2b36ece8920ceea5f30cac6
3
+ metadata.gz: c659b175126da90baf0738ea221b54c25d9939ffbca3883b7756b2972574d0b6
4
+ data.tar.gz: 1b5d1f9f146a91ac1e7fc7a605fa2a3e3f22d28b906b92939362f99d9c63361d
5
5
  SHA512:
6
- metadata.gz: 6f990ff07a6013e217574d08b0af5a7deb519bfb18b0104cbeac5c9e765b40e053b62bd3fe018854c8741e123acf885a07584ee21a410da4754d88e6ba052e6a
7
- data.tar.gz: c6903cc313cbb726cdc373a2307a53e8ef7cde7180e5b4c38fee9eac0667924ce1c3089ddabffac7e373da94f92b3bfbabbb9f996ff862f4bd1475f096bd18df
6
+ metadata.gz: 2662255ed5b8c4b77315e348c6c334f02df3a0879fdba62b64bd9d20ead9c78add8d88847dd945c9a276138ebdaa222352e64b2f2d1b054acb91bb23e3c67da4
7
+ data.tar.gz: 6374272263432f9f3a75d7d291fb7099bc7bcd8c8bfe87b8d3eb1b6c1a7ff79d39d226e49d20f88018f17f0f1411df6849c7ae65faa73f15350612fe1a03880c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-04-23
4
+
5
+ - Auto-detect OTel instrumentation gems via `use_all`
6
+ - Name Sidekiq job spans by worker class (via `job_class` attribute)
7
+ - Support both old and new OTel semantic convention property keys in dashboard queries
8
+ - Guard dashboard routes when sqlite3 is missing
9
+
10
+ ## [0.1.1] - 2025-12-17
11
+
12
+ - Rename gem from caboose to flare
13
+ - Warn instead of raising when sqlite3 gem is missing
14
+ - Make sqlite3 an optional dependency for production compatibility
15
+
3
16
  ## [0.1.0] - 2025-12-17
4
17
 
5
18
  - Initial release
@@ -6,10 +6,18 @@ module Flare
6
6
 
7
7
  layout "flare/application"
8
8
 
9
+ before_action :require_storage
10
+
9
11
  helper_method :show_redis_tab?
10
12
 
11
13
  private
12
14
 
15
+ def require_storage
16
+ return if Flare.storage
17
+
18
+ render plain: "Flare dashboard requires the sqlite3 gem. Add `gem 'sqlite3'` to your Gemfile.", status: :service_unavailable
19
+ end
20
+
13
21
  # Only show the Redis tab if:
14
22
  # 1. The Redis client library is loaded
15
23
  # 2. There are Redis spans in the database
@@ -53,8 +53,8 @@ module Flare
53
53
  { primary: primary, secondary: nil }
54
54
  when "http"
55
55
  full_url = props["http.url"] || ""
56
- target = props["http.target"] || ""
57
- host = props["http.host"] || props["net.peer.name"] || props["peer.service"]
56
+ target = props["url.path"] || props["http.target"] || ""
57
+ host = props["server.address"] || props["http.host"] || props["net.peer.name"] || props["peer.service"]
58
58
  uri = URI.parse(full_url) rescue nil
59
59
  if uri && uri.host
60
60
  domain = uri.host
@@ -64,8 +64,8 @@ module Flare
64
64
  domain = host
65
65
  path = target.presence || full_url
66
66
  end
67
- method = props["http.method"]
68
- status = props["http.status_code"]
67
+ method = props["http.request.method"] || props["http.method"]
68
+ status = props["http.response.status_code"] || props["http.status_code"]
69
69
  { primary: path.to_s.truncate(100), secondary: domain, http_method: method, http_status: status }
70
70
  when "mail"
71
71
  mailer = props["mailer"]
@@ -220,8 +220,8 @@
220
220
  span[:name]
221
221
  end
222
222
  elsif category == "http"
223
- url = props["http.url"] || props["http.target"]
224
- method = props["http.method"]
223
+ url = props["http.url"] || props["url.path"]
224
+ method = props["http.request.method"]
225
225
  "#{method} #{url}".strip.presence || span[:name]
226
226
  elsif category == "controller"
227
227
  ns = props["code.namespace"]
@@ -1,9 +1,9 @@
1
1
  <%
2
2
  # Get root span properties
3
3
  root_props = @root_span ? @root_span[:properties] : {}
4
- http_method = root_props["http.method"]
5
- http_status = root_props["http.status_code"]
6
- http_target = root_props["http.target"]
4
+ http_method = root_props["http.request.method"]
5
+ http_status = root_props["http.response.status_code"]
6
+ http_target = root_props["url.path"]
7
7
  controller = root_props["code.namespace"]
8
8
  action = root_props["code.function"]
9
9
 
@@ -51,8 +51,8 @@
51
51
 
52
52
  # Properties to exclude from "other" section (already shown elsewhere)
53
53
  shown_keys = %w[
54
- http.method http.status_code http.target http.scheme http.host
55
- code.namespace code.function http.route http.user_agent
54
+ http.request.method http.response.status_code url.path url.scheme server.address
55
+ code.namespace code.function http.route user_agent.original
56
56
  ]
57
57
  other_props = root_props.reject { |k, v| shown_keys.include?(k) || v.is_a?(Hash) || v.is_a?(Array) }
58
58
  %>
@@ -260,8 +260,8 @@
260
260
  span[:name]
261
261
  end
262
262
  elsif category == "http"
263
- url = props["http.url"] || props["http.target"]
264
- method = props["http.method"]
263
+ url = props["http.url"] || props["url.path"]
264
+ method = props["http.request.method"]
265
265
  "#{method} #{url}".strip.presence || span[:name]
266
266
  elsif category == "controller"
267
267
  ns = props["code.namespace"]
@@ -387,7 +387,7 @@
387
387
  extra = props['identifier'].replace(/.*\/app\/views\//, '');
388
388
  } else if (category === 'http') {
389
389
  name = 'HTTP';
390
- extra = ((props['http.method'] || '') + ' ' + (props['http.url'] || props['http.target'] || '')).trim();
390
+ extra = ((props['http.request.method'] || '') + ' ' + (props['http.url'] || props['url.path'] || '')).trim();
391
391
  }
392
392
 
393
393
  let line = offset.padStart(8) + ' | ' + duration.padStart(8) + ' | ' + name;
@@ -156,7 +156,7 @@ module Flare
156
156
  bucket: bucket_time(span),
157
157
  namespace: "job",
158
158
  service: extract_job_system(span),
159
- target: transaction_name || span.attributes["code.namespace"] || span.attributes["messaging.destination"] || "unknown",
159
+ target: transaction_name || span.attributes["code.namespace"] || span.attributes["messaging.sidekiq.job_class"] || span.attributes["messaging.destination"] || "unknown",
160
160
  operation: transaction_name ? "perform" : (span.attributes["code.function"] || span.name)
161
161
  )
162
162
 
@@ -34,25 +34,25 @@ module Flare
34
34
  if status
35
35
  case status
36
36
  when "2xx"
37
- conditions << "status_prop.value LIKE ?"
37
+ conditions << "COALESCE(status_prop.value, status_prop_old.value) LIKE ?"
38
38
  values << "2%"
39
39
  when "3xx"
40
- conditions << "status_prop.value LIKE ?"
40
+ conditions << "COALESCE(status_prop.value, status_prop_old.value) LIKE ?"
41
41
  values << "3%"
42
42
  when "4xx"
43
- conditions << "status_prop.value LIKE ?"
43
+ conditions << "COALESCE(status_prop.value, status_prop_old.value) LIKE ?"
44
44
  values << "4%"
45
45
  when "5xx"
46
- conditions << "status_prop.value LIKE ?"
46
+ conditions << "COALESCE(status_prop.value, status_prop_old.value) LIKE ?"
47
47
  values << "5%"
48
48
  else
49
- conditions << "status_prop.value = ?"
49
+ conditions << "COALESCE(status_prop.value, status_prop_old.value) = ?"
50
50
  values << status.to_s
51
51
  end
52
52
  end
53
53
 
54
54
  if method
55
- conditions << "method_prop.value = ?"
55
+ conditions << "COALESCE(method_prop.value, method_prop_old.value) = ?"
56
56
  values << "\"#{method}\""
57
57
  end
58
58
 
@@ -79,19 +79,22 @@ module Flare
79
79
 
80
80
  rows = query_all(<<~SQL, values)
81
81
  SELECT s.*,
82
- method_prop.value as http_method,
83
- status_prop.value as http_status,
84
- target_prop.value as http_target,
82
+ COALESCE(method_prop.value, method_prop_old.value) as http_method,
83
+ COALESCE(status_prop.value, status_prop_old.value) as http_status,
84
+ COALESCE(target_prop.value, target_prop_old.value) as http_target,
85
85
  controller_prop.value as controller,
86
86
  action_prop.value as action
87
87
  FROM flare_spans s
88
- LEFT JOIN flare_properties method_prop ON method_prop.owner_type = 'Flare::Span' AND method_prop.owner_id = s.id AND method_prop.key = 'http.method'
89
- LEFT JOIN flare_properties status_prop ON status_prop.owner_type = 'Flare::Span' AND status_prop.owner_id = s.id AND status_prop.key = 'http.status_code'
90
- LEFT JOIN flare_properties target_prop ON target_prop.owner_type = 'Flare::Span' AND target_prop.owner_id = s.id AND target_prop.key = 'http.target'
88
+ LEFT JOIN flare_properties method_prop ON method_prop.owner_type = 'Flare::Span' AND method_prop.owner_id = s.id AND method_prop.key = 'http.request.method'
89
+ LEFT JOIN flare_properties method_prop_old ON method_prop_old.owner_type = 'Flare::Span' AND method_prop_old.owner_id = s.id AND method_prop_old.key = 'http.method'
90
+ LEFT JOIN flare_properties status_prop ON status_prop.owner_type = 'Flare::Span' AND status_prop.owner_id = s.id AND status_prop.key = 'http.response.status_code'
91
+ LEFT JOIN flare_properties status_prop_old ON status_prop_old.owner_type = 'Flare::Span' AND status_prop_old.owner_id = s.id AND status_prop_old.key = 'http.status_code'
92
+ LEFT JOIN flare_properties target_prop ON target_prop.owner_type = 'Flare::Span' AND target_prop.owner_id = s.id AND target_prop.key = 'url.path'
93
+ LEFT JOIN flare_properties target_prop_old ON target_prop_old.owner_type = 'Flare::Span' AND target_prop_old.owner_id = s.id AND target_prop_old.key = 'http.target'
91
94
  LEFT JOIN flare_properties controller_prop ON controller_prop.owner_type = 'Flare::Span' AND controller_prop.owner_id = s.id AND controller_prop.key = 'code.namespace'
92
95
  LEFT JOIN flare_properties action_prop ON action_prop.owner_type = 'Flare::Span' AND action_prop.owner_id = s.id AND action_prop.key = 'code.function'
93
96
  #{where_clause}
94
- AND method_prop.value IS NOT NULL
97
+ AND COALESCE(method_prop.value, method_prop_old.value) IS NOT NULL
95
98
  ORDER BY s.created_at DESC
96
99
  LIMIT ? OFFSET ?
97
100
  SQL
@@ -352,25 +355,25 @@ module Flare
352
355
  if status
353
356
  case status
354
357
  when "2xx"
355
- conditions << "status_prop.value LIKE ?"
358
+ conditions << "COALESCE(status_prop.value, status_prop_old.value) LIKE ?"
356
359
  values << "2%"
357
360
  when "3xx"
358
- conditions << "status_prop.value LIKE ?"
361
+ conditions << "COALESCE(status_prop.value, status_prop_old.value) LIKE ?"
359
362
  values << "3%"
360
363
  when "4xx"
361
- conditions << "status_prop.value LIKE ?"
364
+ conditions << "COALESCE(status_prop.value, status_prop_old.value) LIKE ?"
362
365
  values << "4%"
363
366
  when "5xx"
364
- conditions << "status_prop.value LIKE ?"
367
+ conditions << "COALESCE(status_prop.value, status_prop_old.value) LIKE ?"
365
368
  values << "5%"
366
369
  else
367
- conditions << "status_prop.value = ?"
370
+ conditions << "COALESCE(status_prop.value, status_prop_old.value) = ?"
368
371
  values << status.to_s
369
372
  end
370
373
  end
371
374
 
372
375
  if method
373
- conditions << "method_prop.value = ?"
376
+ conditions << "COALESCE(method_prop.value, method_prop_old.value) = ?"
374
377
  values << "\"#{method}\""
375
378
  end
376
379
 
@@ -396,11 +399,13 @@ module Flare
396
399
  row = query_one(<<~SQL, values)
397
400
  SELECT COUNT(*) as count
398
401
  FROM flare_spans s
399
- LEFT JOIN flare_properties method_prop ON method_prop.owner_type = 'Flare::Span' AND method_prop.owner_id = s.id AND method_prop.key = 'http.method'
400
- LEFT JOIN flare_properties status_prop ON status_prop.owner_type = 'Flare::Span' AND status_prop.owner_id = s.id AND status_prop.key = 'http.status_code'
402
+ LEFT JOIN flare_properties method_prop ON method_prop.owner_type = 'Flare::Span' AND method_prop.owner_id = s.id AND method_prop.key = 'http.request.method'
403
+ LEFT JOIN flare_properties method_prop_old ON method_prop_old.owner_type = 'Flare::Span' AND method_prop_old.owner_id = s.id AND method_prop_old.key = 'http.method'
404
+ LEFT JOIN flare_properties status_prop ON status_prop.owner_type = 'Flare::Span' AND status_prop.owner_id = s.id AND status_prop.key = 'http.response.status_code'
405
+ LEFT JOIN flare_properties status_prop_old ON status_prop_old.owner_type = 'Flare::Span' AND status_prop_old.owner_id = s.id AND status_prop_old.key = 'http.status_code'
401
406
  LEFT JOIN flare_properties controller_prop ON controller_prop.owner_type = 'Flare::Span' AND controller_prop.owner_id = s.id AND controller_prop.key = 'code.namespace'
402
407
  #{where_clause}
403
- AND method_prop.value IS NOT NULL
408
+ AND COALESCE(method_prop.value, method_prop_old.value) IS NOT NULL
404
409
  SQL
405
410
 
406
411
  row ? row["count"] : 0
data/lib/flare/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Flare
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/flare.rb CHANGED
@@ -141,7 +141,7 @@ module Flare
141
141
  "app"
142
142
  end
143
143
 
144
- # Require only the instrumentations we want
144
+ # Require flare's bundled instrumentations
145
145
  require "opentelemetry-instrumentation-rack"
146
146
  require "opentelemetry-instrumentation-net_http"
147
147
  require "opentelemetry-instrumentation-active_support"
@@ -165,19 +165,25 @@ module Flare
165
165
  log "Spans enabled (database=#{configuration.database_path})"
166
166
  end
167
167
 
168
- # Configure specific instrumentations
169
- c.use "OpenTelemetry::Instrumentation::Rack",
170
- untraced_requests: ->(env) {
171
- request = Rack::Request.new(env)
172
- return true if request.path.start_with?("/flare")
173
-
174
- configuration.ignore_request.call(request)
168
+ # Auto-detect and install all OTel instrumentation gems in the bundle.
169
+ # Apps can add gems like opentelemetry-instrumentation-sidekiq to their
170
+ # Gemfile and they'll be picked up automatically.
171
+ c.use_all(
172
+ "OpenTelemetry::Instrumentation::Rack" => {
173
+ untraced_requests: ->(env) {
174
+ request = Rack::Request.new(env)
175
+ return true if request.path.start_with?("/flare")
176
+
177
+ configuration.ignore_request.call(request)
178
+ }
179
+ },
180
+ # Name Sidekiq job spans after the worker class (e.g. "MyWorker
181
+ # process") instead of the upstream default of the queue name
182
+ # ("default process"), matching how ActiveJob spans are named.
183
+ "OpenTelemetry::Instrumentation::Sidekiq" => {
184
+ span_naming: :job_class,
175
185
  }
176
- c.use "OpenTelemetry::Instrumentation::Net::HTTP"
177
- c.use "OpenTelemetry::Instrumentation::ActiveSupport"
178
- c.use "OpenTelemetry::Instrumentation::ActionPack" if defined?(ActionController)
179
- c.use "OpenTelemetry::Instrumentation::ActionView" if defined?(ActionView)
180
- c.use "OpenTelemetry::Instrumentation::ActiveJob" if defined?(ActiveJob)
186
+ )
181
187
  end
182
188
 
183
189
  # Subscribe to common ActiveSupport notification patterns
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flare
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker