flare 0.1.1 → 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 -109
- 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 -408
- data/test/helper.rb +10 -0
- data/test/test_flare.rb +7 -0
- metadata +89 -228
- checksums.yaml +0 -7
- data/CHANGELOG.md +0 -5
- data/LICENSE.txt +0 -21
- data/README.md +0 -148
- data/app/controllers/flare/application_controller.rb +0 -22
- 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/engine.rb +0 -43
- data/lib/flare/http_metrics_config.rb +0 -101
- data/lib/flare/metric_counter.rb +0 -45
- data/lib/flare/metric_flusher.rb +0 -124
- data/lib/flare/metric_key.rb +0 -42
- data/lib/flare/metric_span_processor.rb +0 -470
- data/lib/flare/metric_storage.rb +0 -42
- data/lib/flare/metric_submitter.rb +0 -221
- data/lib/flare/source_location.rb +0 -113
- data/lib/flare/sqlite_exporter.rb +0 -279
- data/lib/flare/storage/sqlite.rb +0 -789
- data/lib/flare/storage.rb +0 -54
- data/lib/flare/version.rb +0 -5
- 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/engine.rb
DELETED
|
@@ -1,43 +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 the metrics flusher after all initializers have run
|
|
29
|
-
# so user config (metrics_enabled, flush_interval, etc.) is applied.
|
|
30
|
-
config.after_initialize do
|
|
31
|
-
Flare.start_metrics_flusher
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Auto-mount routes in development/test
|
|
35
|
-
initializer "flare.routes", before: :add_routing_paths do |app|
|
|
36
|
-
if Rails.env.development? || Rails.env.test?
|
|
37
|
-
app.routes.prepend do
|
|
38
|
-
mount Flare::Engine => "/flare"
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Flare
|
|
4
|
-
class HttpMetricsConfig
|
|
5
|
-
class HostConfig
|
|
6
|
-
attr_reader :rules
|
|
7
|
-
|
|
8
|
-
def initialize
|
|
9
|
-
@rules = []
|
|
10
|
-
@all = false
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def initialize_copy(source)
|
|
14
|
-
@rules = source.rules.dup
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def all?
|
|
18
|
-
@all
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Track all paths for this host (normalized via normalize_path)
|
|
22
|
-
def all
|
|
23
|
-
@all = true
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Track paths matching this regex (normalized via normalize_path)
|
|
27
|
-
def allow(pattern)
|
|
28
|
-
@rules << {pattern: pattern, replacement: nil}
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Track paths matching this regex with a custom replacement string
|
|
32
|
-
def map(pattern, replacement)
|
|
33
|
-
@rules << {pattern: pattern, replacement: replacement}
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Returns the resolved path for a given raw path, or "*" if no match.
|
|
37
|
-
# If :all, returns nil to signal "use normalize_path".
|
|
38
|
-
# If a rule matches with a replacement, returns the replacement.
|
|
39
|
-
# If a rule matches without a replacement, returns nil to signal "use normalize_path".
|
|
40
|
-
# If no rules match, returns "*".
|
|
41
|
-
def resolve(path)
|
|
42
|
-
return nil if @all
|
|
43
|
-
|
|
44
|
-
@rules.each do |rule|
|
|
45
|
-
if rule[:pattern].match?(path)
|
|
46
|
-
return rule[:replacement] # nil means use normalize_path
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
"*"
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def initialize
|
|
55
|
-
@hosts = {}
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def initialize_copy(source)
|
|
59
|
-
@hosts = source.instance_variable_get(:@hosts).transform_values(&:dup)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def host(hostname, mode = nil, &block)
|
|
63
|
-
config = @hosts[hostname] ||= HostConfig.new
|
|
64
|
-
|
|
65
|
-
if mode == :all
|
|
66
|
-
config.all
|
|
67
|
-
elsif block
|
|
68
|
-
yield config
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Resolve a host+path to the target path for metrics.
|
|
73
|
-
# Returns "*" for unknown hosts or unmatched paths.
|
|
74
|
-
# Returns nil to signal "use normalize_path".
|
|
75
|
-
# Returns a string for custom replacements.
|
|
76
|
-
def resolve(hostname, path)
|
|
77
|
-
host_config = @hosts[hostname]
|
|
78
|
-
return "*" unless host_config
|
|
79
|
-
|
|
80
|
-
host_config.resolve(path)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
DEFAULT = new.tap do |config|
|
|
84
|
-
config.host "flare.am" do |h|
|
|
85
|
-
h.allow %r{/api/metrics}
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
config.host "www.flippercloud.io" do |h|
|
|
89
|
-
h.map %r{/adapter/features/[^/]+/(boolean|actors|groups|percentage_of_actors|percentage_of_time|expression|clear)}, "/adapter/features/:name/:gate"
|
|
90
|
-
h.map %r{/adapter/features/[^/]+}, "/adapter/features/:name"
|
|
91
|
-
h.map %r{/adapter/actors/[^/]+}, "/adapter/actors/:id"
|
|
92
|
-
h.allow %r{/adapter/features}
|
|
93
|
-
h.allow %r{/adapter/import}
|
|
94
|
-
h.allow %r{/adapter/telemetry/summary}
|
|
95
|
-
h.allow %r{/adapter/telemetry}
|
|
96
|
-
h.allow %r{/adapter/events}
|
|
97
|
-
h.allow %r{/adapter/audits}
|
|
98
|
-
end
|
|
99
|
-
end.freeze
|
|
100
|
-
end
|
|
101
|
-
end
|