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 +4 -4
- data/CHANGELOG.md +13 -0
- data/app/controllers/flare/application_controller.rb +8 -0
- data/app/helpers/flare/application_helper.rb +4 -4
- data/app/views/flare/jobs/show.html.erb +2 -2
- data/app/views/flare/requests/show.html.erb +8 -8
- data/lib/flare/metric_span_processor.rb +1 -1
- data/lib/flare/storage/sqlite.rb +27 -22
- data/lib/flare/version.rb +1 -1
- data/lib/flare.rb +19 -13
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c659b175126da90baf0738ea221b54c25d9939ffbca3883b7756b2972574d0b6
|
|
4
|
+
data.tar.gz: 1b5d1f9f146a91ac1e7fc7a605fa2a3e3f22d28b906b92939362f99d9c63361d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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["
|
|
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["
|
|
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
|
|
55
|
-
code.namespace code.function http.route
|
|
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["
|
|
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['
|
|
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
|
|
data/lib/flare/storage/sqlite.rb
CHANGED
|
@@ -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
|
|
90
|
-
LEFT JOIN flare_properties
|
|
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
|
|
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
data/lib/flare.rb
CHANGED
|
@@ -141,7 +141,7 @@ module Flare
|
|
|
141
141
|
"app"
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
-
# Require
|
|
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
|
-
#
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|