devbench 0.5.0 → 0.7.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/README.md +107 -20
- data/lib/devbench/config.rb +109 -27
- data/lib/devbench/direct.rb +172 -23
- data/lib/devbench/egress_policy.rb +101 -0
- data/lib/devbench/logs.rb +413 -0
- data/lib/devbench/railtie.rb +21 -0
- data/lib/devbench/scrub.rb +46 -8
- data/lib/devbench/sidekiq_hooks.rb +4 -0
- data/lib/devbench/transport.rb +65 -10
- data/lib/devbench/version.rb +1 -1
- data/lib/devbench/view_helper.rb +62 -0
- data/lib/devbench.rb +7 -2
- data/lib/generators/devbench/devbench_generator.rb +210 -0
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd716a7741353f88f381775e58d7d613d7a5d50ff69287feb65f86f25c38e13d
|
|
4
|
+
data.tar.gz: '03358a6c9d097c8b4adc3daacc8c032699aba2a81814284caa0287254134f95c'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bdc43a37a987bcc745a17bc957d704bbf93835a6ef475c66e7228acb9c64edcad1ee2ab4acd9b68020f21435d16f6cf263e378db615cd49242e20407115e6d9a
|
|
7
|
+
data.tar.gz: b0c9a3d9884106853d587c2944cb8436bd333ed197fd4630ae17a713e4f959068e96faaf40ea54bfe1cc87f21532f6b482686dd6451ee39d022d9d683e6229d5
|
data/README.md
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
# Dev Bench for Ruby and Rails
|
|
2
2
|
|
|
3
3
|
Everything Sentry's Rails SDK captures by default, plus who was affected,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
the server log lines of a failing request, the browser sensor, trace
|
|
5
|
+
propagation and the handled-failure header — so an app can turn Sentry
|
|
6
|
+
off. Zero runtime dependencies; loads in plain Ruby, hooks itself
|
|
6
7
|
into Rails when Rails is there.
|
|
7
8
|
|
|
8
9
|
## Install (Rails)
|
|
9
10
|
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
```sh
|
|
12
|
+
bundle add devbench
|
|
13
|
+
bin/rails generate devbench
|
|
13
14
|
```
|
|
14
15
|
|
|
16
|
+
Then set **one** value in the app's environment — the DSN `adt dsn create`
|
|
17
|
+
printed for this environment:
|
|
18
|
+
|
|
15
19
|
```sh
|
|
16
|
-
|
|
17
|
-
DEVBENCH_DSN=https://<key>@adt-ingest.onrender.com
|
|
20
|
+
DEVBENCH_DSN=https://<public>:<secret>@adt-ingest.onrender.com
|
|
18
21
|
```
|
|
19
22
|
|
|
20
|
-
That is the whole install
|
|
21
|
-
|
|
23
|
+
That is the whole install: no browser key, no sidecar, no service names,
|
|
24
|
+
no initializer, no middleware line. Check it:
|
|
22
25
|
|
|
23
26
|
```sh
|
|
24
27
|
bin/rails devbench:test
|
|
@@ -30,16 +33,51 @@ It prints the HTTP result, or says plainly what is wrong (`DEVBENCH_DSN is
|
|
|
30
33
|
not set`, `HTTP 401: the key in DEVBENCH_DSN was rejected`, `could not reach
|
|
31
34
|
…`) and exits non-zero. Outside Rake: `Devbench.test!`.
|
|
32
35
|
|
|
36
|
+
What the generator does, and nothing else (run it again and nothing
|
|
37
|
+
changes):
|
|
38
|
+
|
|
39
|
+
- puts `<%= devbench_script_tag %>` in `app/views/layouts/application.html.erb`
|
|
40
|
+
just before `</head>` (`.haml` / `.slim`: `= devbench_script_tag` as the
|
|
41
|
+
last line of `head`). Without that layout it prints what to add where.
|
|
42
|
+
- if `config/initializers/content_security_policy.rb` defines a policy,
|
|
43
|
+
adds `https://unpkg.com` to `script_src`, and the ingest host and
|
|
44
|
+
`https://*.storage.supabase.co` (evidence uploads) to `connect_src`.
|
|
45
|
+
|
|
46
|
+
### The browser tag
|
|
47
|
+
|
|
48
|
+
`devbench_script_tag` renders the browser sensor, version-pinned to this
|
|
49
|
+
gem (upgrading the gem upgrades the sensor), with **only the public part**
|
|
50
|
+
of `DEVBENCH_DSN`:
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<script src="https://unpkg.com/devbench@0.7.0/dist/devbench.min.js"
|
|
54
|
+
data-dsn="https://<public>@adt-ingest.onrender.com" data-release="<release>" defer></script>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The secret never reaches a page. Without a DSN (or with a 0.5 single-key
|
|
58
|
+
DSN, or `DEVBENCH_ENABLED=false`) it renders nothing at all, so a
|
|
59
|
+
development or test environment without a DSN serves no tag. If the app
|
|
60
|
+
uses CSP nonces (`content_security_policy_nonce_generator`), the tag
|
|
61
|
+
carries the request's nonce.
|
|
62
|
+
|
|
63
|
+
**CSP:** if your policy is defined somewhere other than the standard
|
|
64
|
+
initializer, add these yourself:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
policy.script_src ..., "https://unpkg.com"
|
|
68
|
+
policy.connect_src ..., "https://adt-ingest.onrender.com", "https://*.storage.supabase.co"
|
|
69
|
+
```
|
|
70
|
+
|
|
33
71
|
## Configuration
|
|
34
72
|
|
|
35
73
|
All optional except the DSN.
|
|
36
74
|
|
|
37
75
|
| Variable | Default | |
|
|
38
76
|
|---|---|---|
|
|
39
|
-
| `DEVBENCH_DSN` | — (falls back to `ADT_DSN`) | `https://<
|
|
40
|
-
| `DEVBENCH_SERVICE` | your app's module, underscored (`AcmeShop` → `acme_shop`); `app` outside Rails | Groups this app's issues. |
|
|
41
|
-
| `DEVBENCH_RELEASE` | `GIT_SHA`, `SOURCE_VERSION`, `RENDER_GIT_COMMIT
|
|
42
|
-
| `DEVBENCH_ENABLED` | on | `false` turns everything off: no middleware, no hooks, nothing sent. |
|
|
77
|
+
| `DEVBENCH_DSN` | — (falls back to `ADT_DSN`) | `https://<public>:<secret>@<host>[:port]` from `adt dsn create`. The server authenticates with the secret; the page gets the public part. A 0.5 `https://<key>@<host>` still works (no browser tag, see below). Plain `http://` is accepted only for localhost. |
|
|
78
|
+
| `DEVBENCH_SERVICE` | your app's module, underscored (`AcmeShop` → `acme_shop`); `app` outside Rails; **`<that>-sidekiq` in a Sidekiq process** | Groups this app's issues. Set it and it wins everywhere. |
|
|
79
|
+
| `DEVBENCH_RELEASE` | the first found of: a `REVISION` file in `Rails.root` (Capistrano writes one), `HEROKU_SLUG_COMMIT`, `KAMAL_VERSION`, `GITHUB_SHA`, `GIT_SHA`, `SOURCE_VERSION`, `RENDER_GIT_COMMIT`; else empty | Which deploy an occurrence came from. `c.release` in code wins over all of these. |
|
|
80
|
+
| `DEVBENCH_ENABLED` | on | `false` turns everything off: no middleware, no hooks, no tag, nothing sent. |
|
|
43
81
|
|
|
44
82
|
Or from code, e.g. `config/initializers/devbench.rb`:
|
|
45
83
|
|
|
@@ -51,7 +89,7 @@ end
|
|
|
51
89
|
```
|
|
52
90
|
|
|
53
91
|
A DSN that does not parse logs one `[devbench]` warning and reports
|
|
54
|
-
nothing; it never raises into the app.
|
|
92
|
+
nothing; it never raises into the app. Neither key is ever printed.
|
|
55
93
|
|
|
56
94
|
## What is captured automatically
|
|
57
95
|
|
|
@@ -94,8 +132,9 @@ Like the browser sensor, the gem does not send one request per error:
|
|
|
94
132
|
Dev Bench sensor). Repeats cost a counter increment.
|
|
95
133
|
- **Once a minute** a background thread sends the counts — fingerprint,
|
|
96
134
|
how many, first/last seen, and up to 20 affected users each — in one
|
|
97
|
-
small request. Nothing is sent for a quiet minute
|
|
98
|
-
|
|
135
|
+
small request. Nothing is sent for a quiet minute (except a small poll
|
|
136
|
+
while the process holds log lines, below), and what is left is sent
|
|
137
|
+
when the process exits (waiting at most 2 seconds).
|
|
99
138
|
- **Detail only on request.** When Dev Bench sees a fingerprint for the
|
|
100
139
|
first time it asks for evidence, and the gem uploads the stack and one
|
|
101
140
|
example message — **templated and scrubbed first** (emails, numbers,
|
|
@@ -107,6 +146,33 @@ Like the browser sensor, the gem does not send one request per error:
|
|
|
107
146
|
counted). Safe under forking servers (Puma, Unicorn, Sidekiq): each
|
|
108
147
|
worker reports on its own.
|
|
109
148
|
|
|
149
|
+
## Server log lines
|
|
150
|
+
|
|
151
|
+
Triage reads the server's log lines for the user action that failed. With
|
|
152
|
+
`DEVBENCH_DSN` set, the gem keeps them itself — no sidecar:
|
|
153
|
+
|
|
154
|
+
- **What:** every line written to `Rails.logger` and `Sidekiq.logger`
|
|
155
|
+
while a request or job carries a trace (`x-adt-trace` from the browser
|
|
156
|
+
sensor; a job inherits the trace of the request that enqueued it).
|
|
157
|
+
Untraced lines are not kept. Rails 7.1+: a capturing logger joins
|
|
158
|
+
`Rails.logger`'s broadcast (it follows the app's level and `silence`,
|
|
159
|
+
and writes nothing); older Rails and `Sidekiq.logger`: a tee after the
|
|
160
|
+
logger's own write. **What your logger writes is unchanged.**
|
|
161
|
+
- **Bounded:** per process, the newest 10,000 lines or 4 MiB, nothing older
|
|
162
|
+
than 15 minutes, 4 KiB per line. A logging call is never blocked on I/O
|
|
163
|
+
and never raises because of this.
|
|
164
|
+
- **Sent only when asked:** when Dev Bench asks for a trace, the flush
|
|
165
|
+
thread sends that trace's lines from this process — the newest ≤ 200
|
|
166
|
+
lines / 64 KiB — **redacted first** with the same strict rules as
|
|
167
|
+
evidence (quoted values, numbers, emails, cards, tokens, credentials,
|
|
168
|
+
IPs and two-word names removed), plus any redaction rules Dev Bench has
|
|
169
|
+
learned for your app. A process holding lines it was not asked about
|
|
170
|
+
sends nothing but a small poll once a minute while it holds them.
|
|
171
|
+
- **Forking servers:** each Puma worker and Sidekiq process keeps and
|
|
172
|
+
answers for its own lines; Dev Bench assembles them.
|
|
173
|
+
|
|
174
|
+
A Rack app without Rails: `Devbench.capture_logs(logger)`.
|
|
175
|
+
|
|
110
176
|
## Who was affected
|
|
111
177
|
|
|
112
178
|
Set it once per request, typically in `ApplicationController`:
|
|
@@ -198,11 +264,12 @@ browser cannot read `x-adt-handled` unless the server lists it in
|
|
|
198
264
|
than overwrites. **A CORS layer that replaces the response header set will
|
|
199
265
|
strip it** and detection will silently never fire; check the ordering.
|
|
200
266
|
|
|
201
|
-
## Optional: the sidecar
|
|
267
|
+
## Optional: the sidecar
|
|
202
268
|
|
|
203
269
|
The Dev Bench sidecar is a separate process that reads your app's log
|
|
204
270
|
stream on the host and answers triage's requests for the log lines of one
|
|
205
|
-
user action. It is optional
|
|
271
|
+
user action. It is optional: with a DSN the gem keeps those lines itself
|
|
272
|
+
(above). It remains for setups that cannot load the gem.
|
|
206
273
|
|
|
207
274
|
Without a DSN, the gem reports to the sidecar's unix socket instead
|
|
208
275
|
(`$ADT_SIDECAR_SOCKET`, default `/tmp/adt-sidecar.sock`), exactly as 0.4
|
|
@@ -212,6 +279,26 @@ gem does not also write to its socket, so nothing is counted twice.
|
|
|
212
279
|
Fingerprints are identical in both modes, so moving between them keeps
|
|
213
280
|
every issue.
|
|
214
281
|
|
|
282
|
+
## Upgrading from 0.5
|
|
283
|
+
|
|
284
|
+
Nothing is required: a 0.5 single-key `DEVBENCH_DSN` keeps reporting
|
|
285
|
+
exactly as before. To get server log lines and the browser tag from the
|
|
286
|
+
same value, mint a pair and replace the DSN:
|
|
287
|
+
|
|
288
|
+
```sh
|
|
289
|
+
adt dsn create --tenant <tenant> --environment production
|
|
290
|
+
# https://<public>:<secret>@adt-ingest.onrender.com
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
then run `bin/rails generate devbench` for the tag and CSP. A page that
|
|
294
|
+
loaded the browser sensor with its own DSN can drop that and use the tag.
|
|
295
|
+
|
|
296
|
+
One default changed: a Sidekiq process without `DEVBENCH_SERVICE` now
|
|
297
|
+
reports as `<app>-sidekiq` (it was `<app>`), so job failures are grouped
|
|
298
|
+
apart from web failures — and an existing job issue reappears once under
|
|
299
|
+
the new name. Set `DEVBENCH_SERVICE` in the Sidekiq process to keep the
|
|
300
|
+
old name.
|
|
301
|
+
|
|
215
302
|
## Upgrading from `adt` (0.4)
|
|
216
303
|
|
|
217
304
|
- Change the Gemfile line to `gem 'devbench'`. `require 'adt'` and every
|
|
@@ -221,5 +308,5 @@ every issue.
|
|
|
221
308
|
- `config.middleware.insert_before 0, ADT::Middleware` can stay or go: the
|
|
222
309
|
Railtie sees it and does not insert a second one.
|
|
223
310
|
- Running the sidecar and want to keep it that way? Change nothing else.
|
|
224
|
-
To report directly instead, set `DEVBENCH_DSN` (
|
|
225
|
-
|
|
311
|
+
To report directly instead, set `DEVBENCH_DSN` (from `adt dsn create`);
|
|
312
|
+
see "Upgrading from 0.5".
|
data/lib/devbench/config.rb
CHANGED
|
@@ -4,23 +4,31 @@ require 'uri'
|
|
|
4
4
|
|
|
5
5
|
module Devbench
|
|
6
6
|
# Where reports go, parsed from DEVBENCH_DSN (docs/SERVER_SDK_SPEC.md, "Dev
|
|
7
|
-
# Bench naming and configuration"):
|
|
7
|
+
# Bench naming and configuration"; DECISIONS #161):
|
|
8
8
|
#
|
|
9
|
-
# https://<
|
|
9
|
+
# https://<public>:<secret>@<host>[:port] from `adt dsn create`
|
|
10
|
+
# https://<key>@<host>[:port] 0.5 and earlier: one key
|
|
10
11
|
#
|
|
11
|
-
# scheme://host[:port] is the ingest base
|
|
12
|
-
#
|
|
13
|
-
#
|
|
12
|
+
# scheme://host[:port] is the ingest base. The secret (a server key) is
|
|
13
|
+
# what this process authenticates with; the public part (a browser key) is
|
|
14
|
+
# only ever handed to the page, via #browser_dsn. A single-key DSN keeps
|
|
15
|
+
# working with its one key and has no browser part — that key is a server
|
|
16
|
+
# secret and must never reach a page. A path or query, if present, is
|
|
17
|
+
# ignored: the keys alone identify tenant, environment and source.
|
|
14
18
|
class DSN
|
|
15
19
|
class Invalid < StandardError; end
|
|
16
20
|
|
|
17
21
|
LOOPBACK = /\A(?:localhost|127(?:\.\d{1,3}){3}|::1|\[::1\])\z|\.localhost\z/i
|
|
22
|
+
SHAPE = 'https://<public>:<secret>@<host>'
|
|
18
23
|
|
|
19
|
-
attr_reader :base, :key
|
|
24
|
+
attr_reader :base, :key, :public_key
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
# key: what the server sends as X-ADT-Key (the secret, or a 0.5 DSN's
|
|
27
|
+
# single key). public_key: the browser key, or nil.
|
|
28
|
+
def initialize(base, key, public_key = nil)
|
|
22
29
|
@base = base.freeze
|
|
23
30
|
@key = key.freeze
|
|
31
|
+
@public_key = public_key&.freeze
|
|
24
32
|
freeze
|
|
25
33
|
end
|
|
26
34
|
|
|
@@ -28,8 +36,26 @@ module Devbench
|
|
|
28
36
|
"#{@base}/v1/flush"
|
|
29
37
|
end
|
|
30
38
|
|
|
31
|
-
# The
|
|
32
|
-
|
|
39
|
+
# The self-test endpoint: validates the key, stores nothing.
|
|
40
|
+
def check_url
|
|
41
|
+
"#{@base}/v1/check"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def logs_url
|
|
45
|
+
"#{@base}/v1/logs"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The DSN a browser may hold: https://<public>@host. Nil when there is
|
|
49
|
+
# no public part.
|
|
50
|
+
def browser_dsn
|
|
51
|
+
return nil if @public_key.nil?
|
|
52
|
+
|
|
53
|
+
scheme, rest = @base.split('://', 2)
|
|
54
|
+
"#{scheme}://#{URI.encode_www_form_component(@public_key)}@#{rest}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The DSN without its keys, for messages and logs. The secret must never
|
|
58
|
+
# be printed.
|
|
33
59
|
def to_s
|
|
34
60
|
@base
|
|
35
61
|
end
|
|
@@ -37,7 +63,7 @@ module Devbench
|
|
|
37
63
|
alias inspect to_s
|
|
38
64
|
|
|
39
65
|
# Raises DSN::Invalid with a message that says what to fix. Never echoes
|
|
40
|
-
#
|
|
66
|
+
# either key back.
|
|
41
67
|
def self.parse(raw)
|
|
42
68
|
text = raw.to_s.strip
|
|
43
69
|
raise Invalid, 'is empty' if text.empty?
|
|
@@ -45,28 +71,32 @@ module Devbench
|
|
|
45
71
|
uri = begin
|
|
46
72
|
URI.parse(text)
|
|
47
73
|
rescue URI::Error
|
|
48
|
-
raise Invalid,
|
|
74
|
+
raise Invalid, "is not a URL (expected #{SHAPE})"
|
|
49
75
|
end
|
|
50
76
|
|
|
51
77
|
scheme = uri.scheme.to_s.downcase
|
|
52
|
-
raise Invalid,
|
|
78
|
+
raise Invalid, "must start with https:// (expected #{SHAPE})" unless %w[http https].include?(scheme)
|
|
53
79
|
|
|
54
80
|
host = uri.host.to_s
|
|
55
|
-
raise Invalid,
|
|
81
|
+
raise Invalid, "has no host (expected #{SHAPE})" if host.empty?
|
|
56
82
|
|
|
57
|
-
|
|
58
|
-
|
|
83
|
+
user = URI.decode_www_form_component(uri.user.to_s).strip
|
|
84
|
+
secret = URI.decode_www_form_component(uri.password.to_s).strip
|
|
85
|
+
raise Invalid, "has no key (expected #{SHAPE})" if user.empty? && secret.empty?
|
|
59
86
|
|
|
60
|
-
# The
|
|
61
|
-
# the network readable by anything on the path, so http is
|
|
62
|
-
# only for a loopback ingest (local development and tests).
|
|
87
|
+
# The secret is a real secret on a server. Over plain http it would
|
|
88
|
+
# cross the network readable by anything on the path, so http is
|
|
89
|
+
# accepted only for a loopback ingest (local development and tests).
|
|
63
90
|
if scheme == 'http' && !LOOPBACK.match?(host)
|
|
64
91
|
raise Invalid, "uses http:// for #{host}: the key would cross the network in plaintext; use https://"
|
|
65
92
|
end
|
|
66
93
|
|
|
67
94
|
port = uri.port && uri.port != uri.default_port ? ":#{uri.port}" : ''
|
|
68
95
|
host = "[#{host}]" if host.include?(':') && !host.start_with?('[')
|
|
69
|
-
|
|
96
|
+
base = "#{scheme}://#{host}#{port}"
|
|
97
|
+
return new(base, user) if secret.empty?
|
|
98
|
+
|
|
99
|
+
new(base, secret, user.empty? ? nil : user)
|
|
70
100
|
end
|
|
71
101
|
end
|
|
72
102
|
|
|
@@ -77,12 +107,22 @@ module Devbench
|
|
|
77
107
|
# sidecar's socket, as before 0.5.
|
|
78
108
|
# DEVBENCH_SERVICE this app's name. Default: the Rails application's
|
|
79
109
|
# module, underscored (AcmeShop -> acme_shop);
|
|
80
|
-
# else "app"
|
|
81
|
-
#
|
|
82
|
-
#
|
|
110
|
+
# else "app"; with "-sidekiq" appended in a
|
|
111
|
+
# Sidekiq process.
|
|
112
|
+
# DEVBENCH_RELEASE the deployed version. Default, first found: the
|
|
113
|
+
# REVISION file in the app root (Capistrano),
|
|
114
|
+
# HEROKU_SLUG_COMMIT, KAMAL_VERSION, GITHUB_SHA,
|
|
115
|
+
# GIT_SHA, SOURCE_VERSION, RENDER_GIT_COMMIT,
|
|
116
|
+
# else "".
|
|
83
117
|
# DEVBENCH_ENABLED "false" turns everything off.
|
|
84
118
|
class Configuration
|
|
85
|
-
|
|
119
|
+
# Tried in order after the REVISION file when no release is configured.
|
|
120
|
+
# Common deploy tools and platforms only; never an app's own variable.
|
|
121
|
+
RELEASE_FALLBACKS = %w[HEROKU_SLUG_COMMIT KAMAL_VERSION GITHUB_SHA GIT_SHA SOURCE_VERSION RENDER_GIT_COMMIT].freeze
|
|
122
|
+
# Capistrano writes the deployed commit here, in the release's root.
|
|
123
|
+
REVISION_FILE = 'REVISION'
|
|
124
|
+
# A commit or tag, not a document: read no more than this.
|
|
125
|
+
REVISION_MAX_BYTES = 255
|
|
86
126
|
OFF = %w[false 0 no off].freeze
|
|
87
127
|
|
|
88
128
|
# Strings, or nil for "not set".
|
|
@@ -94,8 +134,8 @@ module Devbench
|
|
|
94
134
|
def initialize(env = ENV)
|
|
95
135
|
@dsn = presence(env['DEVBENCH_DSN']) || presence(env['ADT_DSN'])
|
|
96
136
|
@service = presence(env['DEVBENCH_SERVICE'])
|
|
97
|
-
@release = presence(env['DEVBENCH_RELEASE'])
|
|
98
|
-
|
|
137
|
+
@release = presence(env['DEVBENCH_RELEASE'])
|
|
138
|
+
@release_env = RELEASE_FALLBACKS.lazy.map { |name| presence(env[name]) }.find(&:itself)
|
|
99
139
|
@enabled = !OFF.include?(env['DEVBENCH_ENABLED'].to_s.strip.downcase)
|
|
100
140
|
@flush_interval = 60
|
|
101
141
|
end
|
|
@@ -106,16 +146,58 @@ module Devbench
|
|
|
106
146
|
|
|
107
147
|
# The service name sent with every flush and folded into every
|
|
108
148
|
# fingerprint. Resolved when reporting starts, after Rails has booted.
|
|
149
|
+
#
|
|
150
|
+
# A Sidekiq process (Sidekiq.server?) defaults to "<app>-sidekiq", so a
|
|
151
|
+
# typical app's web and job processes are told apart with no
|
|
152
|
+
# DEVBENCH_SERVICE at all. An explicit service always wins.
|
|
109
153
|
def resolved_service
|
|
110
|
-
presence(@service)
|
|
154
|
+
explicit = presence(@service)
|
|
155
|
+
return explicit if explicit
|
|
156
|
+
|
|
157
|
+
base = rails_service || 'app'
|
|
158
|
+
sidekiq_server? ? "#{base}-sidekiq" : base
|
|
111
159
|
end
|
|
112
160
|
|
|
161
|
+
# The release sent with every flush and on the browser tag. Explicit
|
|
162
|
+
# (configure or DEVBENCH_RELEASE) wins; else the REVISION file, read
|
|
163
|
+
# once reporting starts (after Rails has booted, so Rails.root is
|
|
164
|
+
# known); else the deploy variables in RELEASE_FALLBACKS order; else "".
|
|
113
165
|
def resolved_release
|
|
114
|
-
@release
|
|
166
|
+
explicit = presence(@release)
|
|
167
|
+
return explicit if explicit
|
|
168
|
+
|
|
169
|
+
@detected_release ||= (revision_file_release || @release_env).to_s
|
|
115
170
|
end
|
|
116
171
|
|
|
117
172
|
private
|
|
118
173
|
|
|
174
|
+
# The first line of <app root>/REVISION, or nil when there is none or it
|
|
175
|
+
# cannot be read. The app root is Rails.root, else the working directory.
|
|
176
|
+
def revision_file_release
|
|
177
|
+
path = File.join(app_root, REVISION_FILE)
|
|
178
|
+
return nil unless File.file?(path)
|
|
179
|
+
|
|
180
|
+
presence(File.open(path, 'rb') { |f| f.read(REVISION_MAX_BYTES) }.to_s.lines.first)
|
|
181
|
+
rescue StandardError
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def app_root
|
|
186
|
+
if defined?(::Rails) && ::Rails.respond_to?(:root) && ::Rails.root
|
|
187
|
+
::Rails.root.to_s
|
|
188
|
+
else
|
|
189
|
+
Dir.pwd
|
|
190
|
+
end
|
|
191
|
+
rescue StandardError
|
|
192
|
+
Dir.pwd
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def sidekiq_server?
|
|
196
|
+
defined?(::Sidekiq) && ::Sidekiq.respond_to?(:server?) && ::Sidekiq.server? ? true : false
|
|
197
|
+
rescue StandardError, SystemStackError
|
|
198
|
+
false
|
|
199
|
+
end
|
|
200
|
+
|
|
119
201
|
def presence(value)
|
|
120
202
|
text = value.to_s.strip
|
|
121
203
|
text.empty? ? nil : text
|