flare 0.4.0 → 1.0.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.
Potentially problematic release.
This version of flare might be problematic. Click here for more details.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/flare.gemspec +66 -0
- data/lib/flare/active_record.rb +102 -0
- data/lib/flare/collection.rb +47 -0
- data/lib/flare/configuration.rb +64 -123
- data/lib/flare/index_builder.rb +24 -0
- data/lib/flare/session.rb +142 -0
- data/lib/flare/tasks.rb +18 -0
- data/lib/flare.rb +19 -537
- data/test/helper.rb +10 -0
- data/test/test_flare.rb +7 -0
- metadata +89 -242
- checksums.yaml +0 -7
- data/CHANGELOG.md +0 -3
- data/LICENSE.txt +0 -21
- data/README.md +0 -148
- data/app/controllers/flare/application_controller.rb +0 -30
- data/app/controllers/flare/jobs_controller.rb +0 -55
- data/app/controllers/flare/requests_controller.rb +0 -73
- data/app/controllers/flare/spans_controller.rb +0 -101
- data/app/helpers/flare/application_helper.rb +0 -168
- data/app/views/flare/jobs/index.html.erb +0 -69
- data/app/views/flare/jobs/show.html.erb +0 -323
- data/app/views/flare/requests/index.html.erb +0 -120
- data/app/views/flare/requests/show.html.erb +0 -498
- data/app/views/flare/spans/index.html.erb +0 -112
- data/app/views/flare/spans/show.html.erb +0 -184
- data/app/views/layouts/flare/application.html.erb +0 -126
- data/config/routes.rb +0 -20
- data/exe/flare +0 -9
- data/lib/flare/backoff_policy.rb +0 -73
- data/lib/flare/cli/doctor_command.rb +0 -129
- data/lib/flare/cli/output.rb +0 -45
- data/lib/flare/cli/setup_command.rb +0 -404
- data/lib/flare/cli/status_command.rb +0 -47
- data/lib/flare/cli.rb +0 -50
- data/lib/flare/client_headers.rb +0 -39
- data/lib/flare/deadline.rb +0 -27
- data/lib/flare/engine.rb +0 -45
- data/lib/flare/filtering_span_processor.rb +0 -429
- data/lib/flare/http_metrics_config.rb +0 -101
- data/lib/flare/http_transport.rb +0 -76
- data/lib/flare/lifecycle.rb +0 -39
- data/lib/flare/marker.rb +0 -106
- data/lib/flare/metric_counter.rb +0 -51
- data/lib/flare/metric_flusher.rb +0 -279
- data/lib/flare/metric_key.rb +0 -42
- data/lib/flare/metric_span_processor.rb +0 -470
- data/lib/flare/metric_storage.rb +0 -67
- data/lib/flare/metric_submitter.rb +0 -239
- data/lib/flare/recording_batch_span_processor.rb +0 -238
- data/lib/flare/rule_manager.rb +0 -141
- data/lib/flare/sampler.rb +0 -130
- data/lib/flare/source_location.rb +0 -113
- data/lib/flare/sqlite_exporter.rb +0 -334
- data/lib/flare/storage/sqlite.rb +0 -794
- data/lib/flare/storage.rb +0 -54
- data/lib/flare/trace_blob.rb +0 -116
- data/lib/flare/trace_exporter.rb +0 -182
- data/lib/flare/trace_health_reporter.rb +0 -74
- data/lib/flare/upload_url_pool.rb +0 -108
- data/lib/flare/version.rb +0 -5
- data/lib/flare/web_marker_subscriber.rb +0 -76
- data/public/flare-assets/flare.css +0 -1245
- data/public/flare-assets/images/flipper.png +0 -0
data/lib/flare/cli/output.rb
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Flare
|
|
4
|
-
module CLI
|
|
5
|
-
module Output
|
|
6
|
-
private
|
|
7
|
-
|
|
8
|
-
def color?
|
|
9
|
-
$stdout.tty?
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def green(text)
|
|
13
|
-
color? ? "\e[32m#{text}\e[0m" : text
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def yellow(text)
|
|
17
|
-
color? ? "\e[33m#{text}\e[0m" : text
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def red(text)
|
|
21
|
-
color? ? "\e[31m#{text}\e[0m" : text
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def bold(text)
|
|
25
|
-
color? ? "\e[1m#{text}\e[0m" : text
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def dim(text)
|
|
29
|
-
color? ? "\e[2m#{text}\e[0m" : text
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def checkmark
|
|
33
|
-
green("✓")
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def xmark
|
|
37
|
-
red("✗")
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def warn_mark
|
|
41
|
-
yellow("!")
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "securerandom"
|
|
4
|
-
require "digest"
|
|
5
|
-
require "base64"
|
|
6
|
-
require "socket"
|
|
7
|
-
require "net/http"
|
|
8
|
-
require "uri"
|
|
9
|
-
require "json"
|
|
10
|
-
require "fileutils"
|
|
11
|
-
require_relative "output"
|
|
12
|
-
|
|
13
|
-
module Flare
|
|
14
|
-
class SetupCommand
|
|
15
|
-
include CLI::Output
|
|
16
|
-
|
|
17
|
-
DEFAULT_HOST = "https://flare.am"
|
|
18
|
-
TIMEOUT_SECONDS = 300 # 5 minutes
|
|
19
|
-
|
|
20
|
-
INITIALIZER_CONTENT = <<~RUBY
|
|
21
|
-
# frozen_string_literal: true
|
|
22
|
-
|
|
23
|
-
Flare.configure do |config|
|
|
24
|
-
# ── Spans (local development dashboard) ────────────────────────────
|
|
25
|
-
# Spans capture detailed trace data and are stored in a local SQLite
|
|
26
|
-
# database. Enabled by default in development only. Visit /flare in
|
|
27
|
-
# your browser to see the dashboard.
|
|
28
|
-
|
|
29
|
-
# Enable or disable spans (default: true in development)
|
|
30
|
-
# config.spans_enabled = true
|
|
31
|
-
|
|
32
|
-
# How long to keep spans in hours (default: 24)
|
|
33
|
-
# config.retention_hours = 24
|
|
34
|
-
|
|
35
|
-
# Maximum number of spans to store (default: 10000)
|
|
36
|
-
# config.max_spans = 10000
|
|
37
|
-
|
|
38
|
-
# Path to the SQLite database (default: db/flare.sqlite3)
|
|
39
|
-
# config.database_path = Rails.root.join("db", "flare.sqlite3").to_s
|
|
40
|
-
|
|
41
|
-
# Ignore specific requests (receives a Rack::Request, return true to ignore)
|
|
42
|
-
# config.ignore_request = ->(request) {
|
|
43
|
-
# request.path.start_with?("/health")
|
|
44
|
-
# }
|
|
45
|
-
|
|
46
|
-
# ── Metrics (remote monitoring) ────────────────────────────────────
|
|
47
|
-
# Metrics aggregate span data into counts, durations, and error rates.
|
|
48
|
-
# Enabled by default in development and production (disabled in test).
|
|
49
|
-
# Sent to flare.am when FLARE_KEY is configured.
|
|
50
|
-
|
|
51
|
-
# Enable or disable metrics (default: true except in test)
|
|
52
|
-
# config.metrics_enabled = true
|
|
53
|
-
|
|
54
|
-
# How often to flush metrics in seconds (default: 60)
|
|
55
|
-
# config.metrics_flush_interval = 60
|
|
56
|
-
|
|
57
|
-
# ── Custom Instrumentation ─────────────────────────────────────────
|
|
58
|
-
# Subscribe to additional notification prefixes (default: ["app."])
|
|
59
|
-
# config.subscribe_patterns << "mycompany."
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# ════════════════════════════════════════════════════════════════════════
|
|
63
|
-
# Custom Instrumentation
|
|
64
|
-
# ════════════════════════════════════════════════════════════════════════
|
|
65
|
-
#
|
|
66
|
-
# Use ActiveSupport::Notifications.instrument with an "app." prefix
|
|
67
|
-
# anywhere in your code. Flare captures these in development and
|
|
68
|
-
# displays them in the dashboard.
|
|
69
|
-
#
|
|
70
|
-
# ActiveSupport::Notifications.instrument("app.geocoding", address: address) do
|
|
71
|
-
# geocoder.lookup(address)
|
|
72
|
-
# end
|
|
73
|
-
#
|
|
74
|
-
# ActiveSupport::Notifications.instrument("app.stripe.charge", amount: 1000) do
|
|
75
|
-
# Stripe::Charge.create(amount: 1000, currency: "usd")
|
|
76
|
-
# end
|
|
77
|
-
RUBY
|
|
78
|
-
|
|
79
|
-
def initialize(force: false)
|
|
80
|
-
@force = force
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def run
|
|
84
|
-
authenticate
|
|
85
|
-
create_initializer
|
|
86
|
-
add_gitignore_entries
|
|
87
|
-
|
|
88
|
-
puts
|
|
89
|
-
puts "#{green("Setup complete!")}"
|
|
90
|
-
puts
|
|
91
|
-
puts bold("What's next:")
|
|
92
|
-
puts " 1. Start your Rails server (#{dim("bin/rails server")})"
|
|
93
|
-
puts " 2. Make a few requests to your app"
|
|
94
|
-
puts " 3. Visit #{bold("/flare")} to see the dashboard"
|
|
95
|
-
puts
|
|
96
|
-
puts dim(" The dashboard auto-mounts at /flare in development.")
|
|
97
|
-
puts dim(" Metrics are sent to flare.am when FLARE_KEY is configured.")
|
|
98
|
-
puts
|
|
99
|
-
puts " Run #{bold("flare doctor")} to verify your setup."
|
|
100
|
-
puts " Run #{bold("flare status")} to see your configuration."
|
|
101
|
-
rescue Interrupt
|
|
102
|
-
puts
|
|
103
|
-
puts "Setup cancelled."
|
|
104
|
-
exit 1
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
private
|
|
108
|
-
|
|
109
|
-
# --- Auth ---
|
|
110
|
-
|
|
111
|
-
def authenticate
|
|
112
|
-
env_path = File.join(Dir.pwd, ".env")
|
|
113
|
-
|
|
114
|
-
if !@force && File.exist?(env_path) && File.read(env_path).match?(/^FLARE_KEY=.+/)
|
|
115
|
-
puts "#{checkmark} FLARE_KEY already set in .env, skipping auth."
|
|
116
|
-
puts " Run with --force to re-authenticate."
|
|
117
|
-
return
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
server = nil
|
|
121
|
-
state = SecureRandom.hex(32)
|
|
122
|
-
code_verifier = SecureRandom.urlsafe_base64(32)
|
|
123
|
-
code_challenge = Base64.urlsafe_encode64(
|
|
124
|
-
Digest::SHA256.digest(code_verifier),
|
|
125
|
-
padding: false
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
server = TCPServer.new("127.0.0.1", 0)
|
|
129
|
-
port = server.addr[1]
|
|
130
|
-
|
|
131
|
-
host = ENV.fetch("FLARE_HOST", DEFAULT_HOST)
|
|
132
|
-
authorize_url = "#{host}/cli/authorize?state=#{state}&port=#{port}&code_challenge=#{code_challenge}"
|
|
133
|
-
|
|
134
|
-
puts "Opening browser to authorize Flare..."
|
|
135
|
-
open_browser(authorize_url)
|
|
136
|
-
puts
|
|
137
|
-
puts "If the browser didn't open, visit:"
|
|
138
|
-
puts " #{authorize_url}"
|
|
139
|
-
puts
|
|
140
|
-
puts "Waiting for authorization (up to 5 minutes)..."
|
|
141
|
-
|
|
142
|
-
auth_code = wait_for_callback(server, state, TIMEOUT_SECONDS)
|
|
143
|
-
|
|
144
|
-
unless auth_code
|
|
145
|
-
puts "Timed out waiting for authorization."
|
|
146
|
-
exit 1
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
token = exchange_code(auth_code, code_verifier)
|
|
150
|
-
save_token(token)
|
|
151
|
-
ensure
|
|
152
|
-
server&.close
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def wait_for_callback(server, expected_state, timeout)
|
|
156
|
-
deadline = Time.now + timeout
|
|
157
|
-
|
|
158
|
-
while Time.now < deadline
|
|
159
|
-
readable = IO.select([server], nil, nil, 1)
|
|
160
|
-
next unless readable
|
|
161
|
-
|
|
162
|
-
client = server.accept
|
|
163
|
-
request_line = client.gets
|
|
164
|
-
|
|
165
|
-
unless request_line&.start_with?("GET /callback")
|
|
166
|
-
client.print "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n"
|
|
167
|
-
client.close
|
|
168
|
-
next
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# Read remaining headers (required by HTTP spec)
|
|
172
|
-
while (line = client.gets) && line != "\r\n"; end
|
|
173
|
-
|
|
174
|
-
params = parse_query_string(request_line)
|
|
175
|
-
returned_state = params["state"]
|
|
176
|
-
code = params["code"]
|
|
177
|
-
error = params["error"]
|
|
178
|
-
|
|
179
|
-
if error
|
|
180
|
-
client.print http_response(error_page(error))
|
|
181
|
-
client.close
|
|
182
|
-
return nil
|
|
183
|
-
elsif returned_state != expected_state
|
|
184
|
-
client.print http_response(error_page("State mismatch. Please try again."))
|
|
185
|
-
client.close
|
|
186
|
-
return nil
|
|
187
|
-
elsif code.nil? || code.empty?
|
|
188
|
-
client.print http_response(error_page("No authorization code received."))
|
|
189
|
-
client.close
|
|
190
|
-
return nil
|
|
191
|
-
else
|
|
192
|
-
client.print http_response(success_page)
|
|
193
|
-
client.close
|
|
194
|
-
return code
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
nil
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def parse_query_string(request_line)
|
|
202
|
-
return {} unless request_line
|
|
203
|
-
path = request_line.split(" ")[1]
|
|
204
|
-
return {} unless path
|
|
205
|
-
query = URI(path).query
|
|
206
|
-
return {} unless query
|
|
207
|
-
URI.decode_www_form(query).to_h
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def http_response(body)
|
|
211
|
-
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: #{body.bytesize}\r\nConnection: close\r\n\r\n#{body}"
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def exchange_code(auth_code, code_verifier)
|
|
215
|
-
host = ENV.fetch("FLARE_HOST", DEFAULT_HOST)
|
|
216
|
-
uri = URI("#{host}/api/cli/exchange")
|
|
217
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
218
|
-
http.use_ssl = uri.scheme == "https"
|
|
219
|
-
|
|
220
|
-
request = Net::HTTP::Post.new(uri.path)
|
|
221
|
-
request["Content-Type"] = "application/x-www-form-urlencoded"
|
|
222
|
-
request.set_form_data(code: auth_code, code_verifier: code_verifier)
|
|
223
|
-
|
|
224
|
-
response = http.request(request)
|
|
225
|
-
|
|
226
|
-
unless response.is_a?(Net::HTTPSuccess)
|
|
227
|
-
$stderr.puts "Failed to exchange code for token: #{response.code} #{response.message}"
|
|
228
|
-
$stderr.puts response.body if response.body
|
|
229
|
-
exit 1
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
data = JSON.parse(response.body)
|
|
233
|
-
token = data["token"]
|
|
234
|
-
|
|
235
|
-
if token.nil? || token.empty?
|
|
236
|
-
$stderr.puts "No token received from server."
|
|
237
|
-
exit 1
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
token
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
# --- Token storage ---
|
|
244
|
-
|
|
245
|
-
def save_token(token)
|
|
246
|
-
puts
|
|
247
|
-
puts "Where would you like to save the token?"
|
|
248
|
-
puts " 1. .env file"
|
|
249
|
-
puts " 2. Rails credentials"
|
|
250
|
-
puts " 3. Print token"
|
|
251
|
-
print "Choose (1/2/3): "
|
|
252
|
-
|
|
253
|
-
choice = $stdin.gets&.strip
|
|
254
|
-
|
|
255
|
-
case choice
|
|
256
|
-
when "1"
|
|
257
|
-
@saved_to_dotenv = true
|
|
258
|
-
save_to_dotenv(token)
|
|
259
|
-
when "2"
|
|
260
|
-
print_credentials_instructions(token)
|
|
261
|
-
when "3"
|
|
262
|
-
print_token(token)
|
|
263
|
-
else
|
|
264
|
-
puts "Invalid choice."
|
|
265
|
-
save_token(token)
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
def save_to_dotenv(token)
|
|
270
|
-
env_path = File.join(Dir.pwd, ".env")
|
|
271
|
-
|
|
272
|
-
if File.exist?(env_path)
|
|
273
|
-
contents = File.read(env_path)
|
|
274
|
-
if contents.match?(/^FLARE_KEY=/)
|
|
275
|
-
contents.gsub!(/^FLARE_KEY=.*$/, "FLARE_KEY=#{token}")
|
|
276
|
-
else
|
|
277
|
-
contents = contents.chomp + "\nFLARE_KEY=#{token}\n"
|
|
278
|
-
end
|
|
279
|
-
File.write(env_path, contents)
|
|
280
|
-
puts " Token saved to .env"
|
|
281
|
-
else
|
|
282
|
-
print " .env file not found. Create it? (y/n): "
|
|
283
|
-
answer = $stdin.gets&.strip&.downcase
|
|
284
|
-
if answer == "y" || answer == "yes"
|
|
285
|
-
File.write(env_path, "FLARE_KEY=#{token}\n")
|
|
286
|
-
puts " Created .env with FLARE_KEY"
|
|
287
|
-
else
|
|
288
|
-
print_dotenv_instructions(token)
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
def print_dotenv_instructions(token)
|
|
294
|
-
puts
|
|
295
|
-
puts "Add the following to your .env file:"
|
|
296
|
-
puts
|
|
297
|
-
puts " FLARE_KEY=#{token}"
|
|
298
|
-
puts
|
|
299
|
-
puts "Make sure .env is loaded in your app, for example with the dotenv gem:"
|
|
300
|
-
puts
|
|
301
|
-
puts " # Gemfile"
|
|
302
|
-
puts " gem \"dotenv-rails\", groups: [:development, :test]"
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
def print_credentials_instructions(token)
|
|
306
|
-
puts
|
|
307
|
-
puts "Add the following to your Rails credentials:"
|
|
308
|
-
puts
|
|
309
|
-
puts " bin/rails credentials:edit"
|
|
310
|
-
puts
|
|
311
|
-
puts " flare:"
|
|
312
|
-
puts " key: #{token}"
|
|
313
|
-
puts
|
|
314
|
-
puts "Or for a specific environment:"
|
|
315
|
-
puts
|
|
316
|
-
puts " bin/rails credentials:edit --environment production"
|
|
317
|
-
puts
|
|
318
|
-
puts " flare:"
|
|
319
|
-
puts " key: #{token}"
|
|
320
|
-
puts
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
def print_token(token)
|
|
324
|
-
puts
|
|
325
|
-
puts " FLARE_KEY=#{token}"
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# --- Project setup ---
|
|
329
|
-
|
|
330
|
-
def create_initializer
|
|
331
|
-
path = File.join(Dir.pwd, "config/initializers/flare.rb")
|
|
332
|
-
existed = File.exist?(path)
|
|
333
|
-
|
|
334
|
-
if existed && !@force
|
|
335
|
-
puts "#{checkmark} config/initializers/flare.rb already exists"
|
|
336
|
-
puts " Run with --force to overwrite."
|
|
337
|
-
return
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
FileUtils.mkdir_p(File.dirname(path))
|
|
341
|
-
File.write(path, INITIALIZER_CONTENT)
|
|
342
|
-
|
|
343
|
-
if existed
|
|
344
|
-
puts "#{checkmark} Overwrote config/initializers/flare.rb"
|
|
345
|
-
else
|
|
346
|
-
puts "#{checkmark} Created config/initializers/flare.rb"
|
|
347
|
-
end
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
def add_gitignore_entries
|
|
351
|
-
gitignore_path = File.join(Dir.pwd, ".gitignore")
|
|
352
|
-
return unless File.exist?(gitignore_path)
|
|
353
|
-
|
|
354
|
-
contents = File.read(gitignore_path)
|
|
355
|
-
entries_to_add = []
|
|
356
|
-
|
|
357
|
-
entries_to_add << ".env" if @saved_to_dotenv && !contents.match?(/^\.env$/)
|
|
358
|
-
entries_to_add << "flare.sqlite3*" unless contents.include?("flare.sqlite3*")
|
|
359
|
-
|
|
360
|
-
return if entries_to_add.empty?
|
|
361
|
-
|
|
362
|
-
File.open(gitignore_path, "a") do |f|
|
|
363
|
-
f.puts "" unless contents.end_with?("\n")
|
|
364
|
-
entries_to_add.each { |entry| f.puts entry }
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
puts "#{checkmark} Added #{entries_to_add.join(", ")} to .gitignore"
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
# --- Browser ---
|
|
371
|
-
|
|
372
|
-
def open_browser(url)
|
|
373
|
-
case RUBY_PLATFORM
|
|
374
|
-
when /darwin/ then system("open", url)
|
|
375
|
-
when /linux/ then system("xdg-open", url)
|
|
376
|
-
when /mingw|mswin/ then system("start", url)
|
|
377
|
-
end
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
def success_page
|
|
381
|
-
page_shell("Authorized!", "You can close this window.")
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
def error_page(message)
|
|
385
|
-
escaped = message.gsub("&", "&").gsub("<", "<").gsub(">", ">").gsub('"', """)
|
|
386
|
-
page_shell("Error", escaped)
|
|
387
|
-
end
|
|
388
|
-
|
|
389
|
-
def page_shell(title, subtitle)
|
|
390
|
-
<<~HTML
|
|
391
|
-
<!DOCTYPE html>
|
|
392
|
-
<html>
|
|
393
|
-
<head><title>Flare</title></head>
|
|
394
|
-
<body style="font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f5f3ef;">
|
|
395
|
-
<div style="text-align: center; background: #fff; border-radius: 16px; padding: 48px 56px; box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.04);">
|
|
396
|
-
<h1 style="color: #3d3529; font-size: 28px; font-weight: 700; margin: 0 0 8px;">#{title}</h1>
|
|
397
|
-
<p style="color: #8a8078; font-size: 15px; margin: 0;">#{subtitle}</p>
|
|
398
|
-
</div>
|
|
399
|
-
</body>
|
|
400
|
-
</html>
|
|
401
|
-
HTML
|
|
402
|
-
end
|
|
403
|
-
end
|
|
404
|
-
end
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "output"
|
|
4
|
-
require_relative "../version"
|
|
5
|
-
|
|
6
|
-
module Flare
|
|
7
|
-
class StatusCommand
|
|
8
|
-
include CLI::Output
|
|
9
|
-
|
|
10
|
-
def run
|
|
11
|
-
puts bold("Flare v#{VERSION}")
|
|
12
|
-
puts
|
|
13
|
-
|
|
14
|
-
puts bold("Environment")
|
|
15
|
-
puts " RAILS_ENV: #{ENV.fetch("RAILS_ENV", dim("not set"))}"
|
|
16
|
-
puts " FLARE_KEY: #{key_status}"
|
|
17
|
-
puts " FLARE_URL: #{ENV.fetch("FLARE_URL", dim("https://flare.am (default)"))}"
|
|
18
|
-
puts
|
|
19
|
-
|
|
20
|
-
puts bold("Files")
|
|
21
|
-
puts " Initializer: #{file_status("config/initializers/flare.rb")}"
|
|
22
|
-
puts " .env: #{file_status(".env")}"
|
|
23
|
-
puts " .gitignore: #{file_status(".gitignore")}"
|
|
24
|
-
puts " Database: #{file_status("db/flare.sqlite3")}"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def key_status
|
|
30
|
-
if ENV["FLARE_KEY"] && !ENV["FLARE_KEY"].empty?
|
|
31
|
-
green("set via ENV")
|
|
32
|
-
else
|
|
33
|
-
env_path = File.join(Dir.pwd, ".env")
|
|
34
|
-
if File.exist?(env_path) && File.read(env_path).match?(/^FLARE_KEY=.+/)
|
|
35
|
-
green("set in .env")
|
|
36
|
-
else
|
|
37
|
-
red("not configured")
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def file_status(relative_path)
|
|
43
|
-
path = File.join(Dir.pwd, relative_path)
|
|
44
|
-
File.exist?(path) ? green("exists") : dim("not found")
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
data/lib/flare/cli.rb
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "version"
|
|
4
|
-
|
|
5
|
-
module Flare
|
|
6
|
-
module CLI
|
|
7
|
-
COMMANDS = {
|
|
8
|
-
"setup" => "Authenticate and configure Flare for this project",
|
|
9
|
-
"doctor" => "Check your Flare setup for issues",
|
|
10
|
-
"status" => "Show current Flare configuration",
|
|
11
|
-
"version" => "Print the Flare version",
|
|
12
|
-
"help" => "Show this help message",
|
|
13
|
-
}.freeze
|
|
14
|
-
|
|
15
|
-
def self.start(argv)
|
|
16
|
-
command = argv.first
|
|
17
|
-
|
|
18
|
-
case command
|
|
19
|
-
when "setup"
|
|
20
|
-
require_relative "cli/setup_command"
|
|
21
|
-
force = argv.include?("--force")
|
|
22
|
-
SetupCommand.new(force: force).run
|
|
23
|
-
when "doctor"
|
|
24
|
-
require_relative "cli/doctor_command"
|
|
25
|
-
DoctorCommand.new.run
|
|
26
|
-
when "status"
|
|
27
|
-
require_relative "cli/status_command"
|
|
28
|
-
StatusCommand.new.run
|
|
29
|
-
when "version", "-v", "--version"
|
|
30
|
-
puts "flare #{Flare::VERSION}"
|
|
31
|
-
when "help", nil, "-h", "--help"
|
|
32
|
-
print_help
|
|
33
|
-
else
|
|
34
|
-
$stderr.puts "Unknown command: #{command}"
|
|
35
|
-
$stderr.puts
|
|
36
|
-
print_help
|
|
37
|
-
exit 1
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def self.print_help
|
|
42
|
-
puts "Usage: flare <command>"
|
|
43
|
-
puts
|
|
44
|
-
puts "Commands:"
|
|
45
|
-
COMMANDS.each do |name, description|
|
|
46
|
-
puts " %-12s %s" % [name, description]
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
data/lib/flare/client_headers.rb
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "socket"
|
|
4
|
-
|
|
5
|
-
require_relative "version"
|
|
6
|
-
|
|
7
|
-
module Flare
|
|
8
|
-
# Single source of truth for the headers that identify this client and its
|
|
9
|
-
# version to the Flare API. The server parses the gem version out of the
|
|
10
|
-
# User-Agent ("Flare Ruby/X.Y.Z") to gate version-dependent features, and
|
|
11
|
-
# uses the X-Client-* headers to build a per-client picture.
|
|
12
|
-
#
|
|
13
|
-
# Safe to send on any request that targets the Flare API (metrics POST,
|
|
14
|
-
# rules GET, trace-notify POST). Do NOT send these on the presigned R2 blob
|
|
15
|
-
# PUT -- they're meaningless to R2 and can invalidate the signed-header set.
|
|
16
|
-
#
|
|
17
|
-
# Excludes Authorization / Flare-Project / Flare-Environment -- callers add
|
|
18
|
-
# those since they're request- or context-specific.
|
|
19
|
-
module ClientHeaders
|
|
20
|
-
USER_AGENT = "Flare Ruby/#{Flare::VERSION}"
|
|
21
|
-
|
|
22
|
-
def self.to_h
|
|
23
|
-
{
|
|
24
|
-
"User-Agent" => USER_AGENT,
|
|
25
|
-
"X-Client-Language" => "ruby",
|
|
26
|
-
"X-Client-Language-Version" => RUBY_VERSION,
|
|
27
|
-
"X-Client-Platform" => RUBY_PLATFORM,
|
|
28
|
-
"X-Client-Pid" => Process.pid.to_s,
|
|
29
|
-
"X-Client-Hostname" => hostname
|
|
30
|
-
}
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def self.hostname
|
|
34
|
-
Socket.gethostname
|
|
35
|
-
rescue StandardError
|
|
36
|
-
"unknown"
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
data/lib/flare/deadline.rb
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Flare
|
|
4
|
-
# A small monotonic deadline shared by lifecycle operations. A nil timeout
|
|
5
|
-
# represents an unbounded operation.
|
|
6
|
-
class Deadline
|
|
7
|
-
def initialize(timeout)
|
|
8
|
-
@expires_at = monotonic_now + [timeout.to_f, 0].max unless timeout.nil?
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def remaining
|
|
12
|
-
return nil unless @expires_at
|
|
13
|
-
|
|
14
|
-
[@expires_at - monotonic_now, 0].max
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def expired?
|
|
18
|
-
remaining == 0
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def monotonic_now
|
|
24
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
data/lib/flare/engine.rb
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Flare
|
|
4
|
-
class Engine < ::Rails::Engine
|
|
5
|
-
isolate_namespace Flare
|
|
6
|
-
|
|
7
|
-
# Load secrets from Rails credentials if not already set via ENV
|
|
8
|
-
initializer "flare.defaults", before: :load_config_initializers do |app|
|
|
9
|
-
ENV["FLARE_KEY"] ||= app.credentials.dig(:flare, :key)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Serve static assets from the engine's public directory
|
|
13
|
-
initializer "flare.static_assets" do |app|
|
|
14
|
-
app.middleware.use(
|
|
15
|
-
Rack::Static,
|
|
16
|
-
urls: ["/flare-assets"],
|
|
17
|
-
root: root.join("public"),
|
|
18
|
-
cascade: true
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Phase 1: Configure OTel SDK and instrumentations before middleware is
|
|
23
|
-
# built so Rack/ActionPack can insert their middleware.
|
|
24
|
-
initializer "flare.opentelemetry", before: :build_middleware_stack do
|
|
25
|
-
Flare.configure_opentelemetry
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Phase 2: Start background threads after all initializers have run
|
|
29
|
-
# so user config (metrics_enabled, tracing_enabled, flush_interval,
|
|
30
|
-
# tracing_poll_interval, etc.) is applied.
|
|
31
|
-
config.after_initialize do
|
|
32
|
-
Flare.start_rule_manager
|
|
33
|
-
Flare.start_metrics_flusher
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Auto-mount routes in development/test
|
|
37
|
-
initializer "flare.routes", before: :add_routing_paths do |app|
|
|
38
|
-
if Rails.env.development? || Rails.env.test?
|
|
39
|
-
app.routes.prepend do
|
|
40
|
-
mount Flare::Engine => "/flare"
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|