flare 0.1.0 → 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 +27 -17
- 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
|
@@ -67,7 +67,9 @@ module Flare
|
|
|
67
67
|
require_relative "flare/sqlite_exporter"
|
|
68
68
|
SQLiteExporter.new(configuration.database_path)
|
|
69
69
|
rescue LoadError
|
|
70
|
-
|
|
70
|
+
warn "[Flare] sqlite3 gem not found. Spans are disabled. Add `gem 'sqlite3'` to your Gemfile to enable the development dashboard."
|
|
71
|
+
configuration.spans_enabled = false
|
|
72
|
+
nil
|
|
71
73
|
end
|
|
72
74
|
end
|
|
73
75
|
|
|
@@ -139,7 +141,7 @@ module Flare
|
|
|
139
141
|
"app"
|
|
140
142
|
end
|
|
141
143
|
|
|
142
|
-
# Require
|
|
144
|
+
# Require flare's bundled instrumentations
|
|
143
145
|
require "opentelemetry-instrumentation-rack"
|
|
144
146
|
require "opentelemetry-instrumentation-net_http"
|
|
145
147
|
require "opentelemetry-instrumentation-active_support"
|
|
@@ -158,24 +160,30 @@ module Flare
|
|
|
158
160
|
c.service_name = service_name
|
|
159
161
|
|
|
160
162
|
# Spans: detailed trace data stored in SQLite
|
|
161
|
-
if configuration.spans_enabled
|
|
163
|
+
if configuration.spans_enabled && exporter
|
|
162
164
|
c.add_span_processor(span_processor)
|
|
163
165
|
log "Spans enabled (database=#{configuration.database_path})"
|
|
164
166
|
end
|
|
165
167
|
|
|
166
|
-
#
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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,
|
|
173
185
|
}
|
|
174
|
-
|
|
175
|
-
c.use "OpenTelemetry::Instrumentation::ActiveSupport"
|
|
176
|
-
c.use "OpenTelemetry::Instrumentation::ActionPack" if defined?(ActionController)
|
|
177
|
-
c.use "OpenTelemetry::Instrumentation::ActionView" if defined?(ActionView)
|
|
178
|
-
c.use "OpenTelemetry::Instrumentation::ActiveJob" if defined?(ActiveJob)
|
|
186
|
+
)
|
|
179
187
|
end
|
|
180
188
|
|
|
181
189
|
# Subscribe to common ActiveSupport notification patterns
|
|
@@ -188,7 +196,7 @@ module Flare
|
|
|
188
196
|
|
|
189
197
|
at_exit do
|
|
190
198
|
log "Shutting down..."
|
|
191
|
-
if configuration.spans_enabled
|
|
199
|
+
if configuration.spans_enabled && @span_processor
|
|
192
200
|
span_processor.force_flush
|
|
193
201
|
span_processor.shutdown
|
|
194
202
|
log "Span processor flushed and stopped"
|
|
@@ -386,7 +394,9 @@ module Flare
|
|
|
386
394
|
require_relative "flare/storage/sqlite"
|
|
387
395
|
Storage::SQLite.new(configuration.database_path)
|
|
388
396
|
rescue LoadError
|
|
389
|
-
|
|
397
|
+
warn "[Flare] sqlite3 gem not found. Dashboard is disabled. Add `gem 'sqlite3'` to your Gemfile to enable it."
|
|
398
|
+
configuration.spans_enabled = false
|
|
399
|
+
nil
|
|
390
400
|
end
|
|
391
401
|
end
|
|
392
402
|
|