railwatch 0.3.1 → 0.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6efded747b3ad71d6a52964ab2d2f32b7a94a862a706d573ac983f013c720b75
4
- data.tar.gz: 7310f931fc2f1215acf83345a7469c554be1692185fd76fb5f7af1d9fe80637e
3
+ metadata.gz: 9c88b489c82195c106d414c397d391d8cf2e8d3e499dd67715d3db14fc4f7dae
4
+ data.tar.gz: 3a2bb00def3c244d0eb9630f1eb6b3d4fddb29ea17cf34e2c8023123c92bdaf3
5
5
  SHA512:
6
- metadata.gz: 74988c5b9f75e56774d8166c0c30fc4d87a9f23b22fed11e3b3d05b6e2b5044da1e069eaadce118ee7a84ec307d1f043bc1852b59e2b375a09f5ea6323368672
7
- data.tar.gz: 0d183de6bf27ce94efb729b5000d7e37225520e1fe5fd38574392893bf7e8b0f2a3b170cf98bab74394b83bd08976b8726b8d5a919cffd1a5c793e4141fc4e59
6
+ metadata.gz: f616d461f05f956503d2fa37d20dd238f98f1f77624665e6c352c675b9df12704b2a912e15b8bcf6d992192a86016b3ee39e8924b7b4a70d9bbe24f61d5c4845
7
+ data.tar.gz: 8d372cfaed4dd585b39a8bbd6f042b9af6bada233e92dc7e0021613507e6bc0d570cc120bce4f1f5ef163c223ea8855a730781c16bc5fd34321a9cff10020607
data/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.3 (2026-09-19)
4
+
5
+ - Fix `railwatch:install --local` writing test databases that parallel test
6
+ runners cannot share. The generated `test` entries named
7
+ `storage/test_railwatch*.sqlite3` with no `TEST_ENV_NUMBER`, so every worker
8
+ in a parallel run opened the same two SQLite files and raced through them:
9
+ `ActiveRecord::PendingMigrationError` and `SQLite3::IOException: disk I/O
10
+ error` out of `configure_connection`, on every shard. Rails' own test
11
+ database naming carries the number for this reason, and the generated names
12
+ now do too. An app with no parallel runner sets no `TEST_ENV_NUMBER`, so its
13
+ generated file is unchanged and no existing install needs migrating.
14
+
15
+ ## 0.3.2 (2026-09-19)
16
+
17
+ - Fix an embedded install's rake tasks reporting over HTTP instead of into
18
+ the app's own database. A rake process invokes its top-level task -- where
19
+ the command patch starts the execution, and sampling it builds the
20
+ reporter -- before that task's `:environment` prerequisite boots Rails and
21
+ runs `config/initializers`. A token in the environment is enough for
22
+ Railwatch to be enabled that early, so the reporter chose HTTP from a
23
+ config that had not yet been told the app is embedded, and kept it.
24
+ Every rake task's telemetry then went to the receiver instead of the
25
+ local database, skipping the export queue and the replay receipt it
26
+ earns. Affects embedded and hybrid installs with a token configured;
27
+ cloud-only installs were never affected, and neither was an embedded
28
+ install with no token.
29
+ - The reporter now re-decides what it derived from a not-yet-final config
30
+ once `config/initializers` has run: its transport, and its buffer size
31
+ while that buffer is still empty.
32
+ - Add a subprocess regression that reproduces rake's ordering and fails if
33
+ an embedded app's rake task reports over HTTP.
34
+
3
35
  ## 0.3.1 (2026-09-19)
4
36
 
5
37
  - Fix production boot for cloud-only installs with no Railwatch databases.
@@ -383,12 +383,26 @@ module Railwatch
383
383
  lines.insert(start + 1, " primary:\n")
384
384
  stop += 1
385
385
  end
386
- entries = format(RAILWATCH_DATABASES, env: env).lines.map { |line| " #{line}" }
386
+ entries = format(RAILWATCH_DATABASES, env: database_prefix(env)).lines.map { |line| " #{line}" }
387
387
  lines = insert_lines(lines, stop, entries.join).lines
388
388
  end
389
389
  lines.join
390
390
  end
391
391
 
392
+ # What the database filenames are built from. For test that has to carry
393
+ # TEST_ENV_NUMBER, the way Rails' own test database naming does: runners
394
+ # like parallel_tests give each worker its own number and expect a
395
+ # database per worker. Without it every worker in a run opens the same
396
+ # two SQLite files and they fight over them -- observed as
397
+ # PendingMigrationError and "disk I/O error" from configure_connection,
398
+ # on an app running four workers. An app with no such runner sets no
399
+ # TEST_ENV_NUMBER, so the suffix is empty and the name is unchanged.
400
+ def self.database_prefix(env)
401
+ return env unless env == "test"
402
+
403
+ %(test<%= ENV["TEST_ENV_NUMBER"] %>)
404
+ end
405
+
392
406
  # Unconditional: `bundle exec puma` evaluates this file before it loads
393
407
  # the Rack app, so `if defined?(Railwatch)` would be false there and the
394
408
  # writer would never start. The plugin itself is inert when Railwatch is
@@ -112,6 +112,15 @@ module Railwatch
112
112
  end
113
113
  end
114
114
 
115
+ # A rake process starts its command execution before this point -- the
116
+ # top-level task is invoked before its `:environment` prerequisite boots
117
+ # Rails -- so a reporter can already exist, built from a config that had
118
+ # not yet been told the app is embedded. This is the first moment the
119
+ # app's own configuration is final.
120
+ initializer "railwatch.adopt_final_config", after: :load_config_initializers do
121
+ Railwatch.adopt_final_config!
122
+ end
123
+
115
124
  initializer "railwatch.transport_security", after: :load_config_initializers do
116
125
  next if Railwatch.config.local? || Railwatch.config.ingest_url_allowed?
117
126
 
@@ -143,6 +143,32 @@ module Railwatch
143
143
  self
144
144
  end
145
145
 
146
+ # The reporter can be built before the application's own configuration
147
+ # has had its say. A rake process invokes its top-level task -- which is
148
+ # where the command patch starts the execution, and sampling that
149
+ # execution builds the reporter -- before the task's `:environment`
150
+ # prerequisite boots Rails and runs config/initializers. A token in the
151
+ # environment is enough for Railwatch to be enabled that early, so an
152
+ # embedded app gets a reporter aimed at HTTP, chosen from a config that
153
+ # had not yet been told the app is embedded. Left alone it stays aimed
154
+ # there: the memo outlives the configuration that produced it, and every
155
+ # record from that process goes to the receiver instead of the app's own
156
+ # database, skipping the export queue and the replay receipts it earns.
157
+ #
158
+ # Nothing has been recorded at that point -- starting an execution
159
+ # decides sampling and pushes nothing -- so deciding again here is free.
160
+ def adopt_final_config!
161
+ # The swap forked_transport already makes, for the same reason: the
162
+ # situation this transport was chosen for is not the situation now.
163
+ reselected = Railwatch.local_transport if @config.local?
164
+ @transport = reselected if reselected && reselected.class != @transport.class
165
+ # Sized from the config as it stood, which was the default one. Safe
166
+ # only while the buffer is still empty: resizing means a new buffer,
167
+ # and anything already in the old one would go with it.
168
+ @buffer = build_buffer if @thread.nil? && @buffer.size.zero?
169
+ self
170
+ end
171
+
146
172
  def shutdown
147
173
  ensure_process!
148
174
  ensure_thread if pending_records? && !@thread&.alive?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Railwatch
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.3"
5
5
  end
data/lib/railwatch.rb CHANGED
@@ -72,6 +72,21 @@ module Railwatch
72
72
  @reporter ||= Reporter.new(config, transport: local_transport)
73
73
  end
74
74
 
75
+ # Called once config/initializers has run, so a reporter built before
76
+ # that can re-decide what it derived from a configuration that was not
77
+ # final. Deliberately does not build one: a process that has not
78
+ # reported anything yet has nothing to correct, and the reporter is
79
+ # built on first use for a reason.
80
+ def adopt_final_config!
81
+ # The redactor snapshots redact_headers when it is built. Today nothing
82
+ # redacts before this point, so it is built later and already correct --
83
+ # but an early one would quietly stop hiding the headers an app asked it
84
+ # to hide, which is not a failure worth leaving to ordering. Dropping the
85
+ # memo costs a rebuild on next use and nothing else; it holds only caches.
86
+ @redactor = nil
87
+ @reporter&.adopt_final_config!
88
+ end
89
+
75
90
  # Embedded mode: a Puma worker hands its batches to the writer process
76
91
  # over the socket; the writer itself, and any process when no socket is
77
92
  # configured, writes them straight into SQLite. nil means HTTP.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: railwatch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cole Robertson