logtail-rails 0.2.12 → 0.2.13

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: 84163f336f6b5a4a1ba19dd718853f056c84f8aa37833a1e627eac0807c029a9
4
- data.tar.gz: d4449b68f74462513787e701cbadc68e9db7951844f6422d25a26e2d3dcada8b
3
+ metadata.gz: 199f2f2bf589ef69bbe057b16224aa71999e45e991e30eeda9533f11ce233c79
4
+ data.tar.gz: 26603038e5aa56d21112ce259084999f4cf29df2eab4b1b411f13d0cef6ae246
5
5
  SHA512:
6
- metadata.gz: ceeefbafaf65054cb6cdc66438f456d5f0b69d82f89c0103ea4550445540785e1ca8b3791ac972ee96352bd210c9a3bd03d88575c93746a14e707b834653341a
7
- data.tar.gz: 338066840792cbf30875b7e355ef31c93cfdb87d4090d24e03d8230b56b898c83d743c5ea45475c161111c334621c5d23d0ff2344457cc376175df92c010df2e
6
+ metadata.gz: fb36a1279648ec39acebf782e59b26441824b1222152ba76c44cc7454ae0593eaece3beca230d3e305ae803e4215c958a3f51e5cadb962ee3592060b45ed8357
7
+ data.tar.gz: e267ac9f6488ec482c1b84035cb119e6062f3f335e03bedb8eff3cae3fd318a5a5e61dacc560acca8eb53870080a89add3006b7299454b4b2025d1b738d2a4a4
@@ -1,22 +1,24 @@
1
1
  name: build
2
2
 
3
3
  on:
4
- pull_request:
5
4
  push:
6
5
  branches: [main]
6
+ pull_request:
7
7
  schedule:
8
8
  - cron: '20 5 * * 1'
9
9
  workflow_dispatch:
10
10
 
11
11
  jobs:
12
12
  test:
13
- runs-on: ubuntu-20.04
13
+
14
+ runs-on: ubuntu-24.04
14
15
 
15
16
  strategy:
16
17
  fail-fast: false
17
18
 
18
19
  matrix:
19
20
  ruby-version:
21
+ - "4.0"
20
22
  - "3"
21
23
  - "3.3"
22
24
  - "3.2"
@@ -25,12 +27,10 @@ jobs:
25
27
  - "2.7"
26
28
  - "2.6"
27
29
  - "2.5"
28
- - "jruby-9.4.3.0"
29
- - "jruby-9.2.14.0"
30
- - "truffleruby-23.0.0"
31
- - "truffleruby-22.1.0"
30
+ - "truffleruby"
32
31
  gemfile:
33
32
  - "rails-edge"
33
+ - "rails-8.1"
34
34
  - "rails-8.0"
35
35
  - "rails-7.2"
36
36
  - "rails-7.1"
@@ -41,6 +41,8 @@ jobs:
41
41
  - "rails-5.1"
42
42
  - "rails-5.0"
43
43
  exclude:
44
+ - gemfile: "rails-edge"
45
+ ruby-version: "3.2"
44
46
  - gemfile: "rails-edge"
45
47
  ruby-version: "3.1"
46
48
  - gemfile: "rails-edge"
@@ -51,14 +53,17 @@ jobs:
51
53
  ruby-version: "2.6"
52
54
  - gemfile: "rails-edge"
53
55
  ruby-version: "2.5"
54
- - gemfile: "rails-edge"
55
- ruby-version: "jruby-9.4.3.0"
56
- - gemfile: "rails-edge"
57
- ruby-version: "jruby-9.2.14.0"
58
- - gemfile: "rails-edge"
59
- ruby-version: "truffleruby-22.1.0"
60
- - gemfile: "rails-edge"
61
- ruby-version: "truffleruby-23.0.0"
56
+
57
+ - gemfile: "rails-8.1"
58
+ ruby-version: "3.1"
59
+ - gemfile: "rails-8.1"
60
+ ruby-version: "3.0"
61
+ - gemfile: "rails-8.1"
62
+ ruby-version: "2.7"
63
+ - gemfile: "rails-8.1"
64
+ ruby-version: "2.6"
65
+ - gemfile: "rails-8.1"
66
+ ruby-version: "2.5"
62
67
 
63
68
  - gemfile: "rails-8.0"
64
69
  ruby-version: "3.1"
@@ -70,14 +75,6 @@ jobs:
70
75
  ruby-version: "2.6"
71
76
  - gemfile: "rails-8.0"
72
77
  ruby-version: "2.5"
73
- - gemfile: "rails-8.0"
74
- ruby-version: "jruby-9.4.3.0"
75
- - gemfile: "rails-8.0"
76
- ruby-version: "jruby-9.2.14.0"
77
- - gemfile: "rails-8.0"
78
- ruby-version: "truffleruby-22.1.0"
79
- - gemfile: "rails-8.0"
80
- ruby-version: "truffleruby-23.0.0"
81
78
 
82
79
  - gemfile: "rails-7.2"
83
80
  ruby-version: "3.0"
@@ -87,29 +84,19 @@ jobs:
87
84
  ruby-version: "2.6"
88
85
  - gemfile: "rails-7.2"
89
86
  ruby-version: "2.5"
90
- - gemfile: "rails-7.2"
91
- ruby-version: "jruby-9.4.3.0"
92
- - gemfile: "rails-7.2"
93
- ruby-version: "jruby-9.2.14.0"
94
- - gemfile: "rails-7.2"
95
- ruby-version: "truffleruby-22.1.0"
96
87
 
97
88
  - gemfile: "rails-7.1"
98
89
  ruby-version: "2.6"
99
90
  - gemfile: "rails-7.1"
100
91
  ruby-version: "2.5"
101
- - gemfile: "rails-7.1"
102
- ruby-version: "jruby-9.4.3.0"
103
- - gemfile: "rails-7.1"
104
- ruby-version: "jruby-9.2.14.0"
105
92
 
106
93
  - gemfile: "rails-7.0"
107
94
  ruby-version: "2.6"
108
95
  - gemfile: "rails-7.0"
109
96
  ruby-version: "2.5"
110
- - gemfile: "rails-7.0"
111
- ruby-version: "jruby-9.2.14.0"
112
97
 
98
+ - gemfile: "rails-5.2"
99
+ ruby-version: "4.0"
113
100
  - gemfile: "rails-5.2"
114
101
  ruby-version: "3"
115
102
  - gemfile: "rails-5.2"
@@ -121,12 +108,10 @@ jobs:
121
108
  - gemfile: "rails-5.2"
122
109
  ruby-version: "3.0"
123
110
  - gemfile: "rails-5.2"
124
- ruby-version: "jruby-9.4.3.0"
125
- - gemfile: "rails-5.2"
126
- ruby-version: "truffleruby-22.1.0"
127
- - gemfile: "rails-5.2"
128
- ruby-version: "truffleruby-23.0.0"
111
+ ruby-version: "truffleruby"
129
112
 
113
+ - gemfile: "rails-5.1"
114
+ ruby-version: "4.0"
130
115
  - gemfile: "rails-5.1"
131
116
  ruby-version: "3"
132
117
  - gemfile: "rails-5.1"
@@ -138,12 +123,10 @@ jobs:
138
123
  - gemfile: "rails-5.1"
139
124
  ruby-version: "3.0"
140
125
  - gemfile: "rails-5.1"
141
- ruby-version: "jruby-9.4.3.0"
142
- - gemfile: "rails-5.1"
143
- ruby-version: "truffleruby-23.0.0"
144
- - gemfile: "rails-5.1"
145
- ruby-version: "truffleruby-22.1.0"
126
+ ruby-version: "truffleruby"
146
127
 
128
+ - gemfile: "rails-5.0"
129
+ ruby-version: "4.0"
147
130
  - gemfile: "rails-5.0"
148
131
  ruby-version: "3"
149
132
  - gemfile: "rails-5.0"
@@ -155,18 +138,14 @@ jobs:
155
138
  - gemfile: "rails-5.0"
156
139
  ruby-version: "3.0"
157
140
  - gemfile: "rails-5.0"
158
- ruby-version: "jruby-9.4.3.0"
159
- - gemfile: "rails-5.0"
160
- ruby-version: "truffleruby-23.0.0"
161
- - gemfile: "rails-5.0"
162
- ruby-version: "truffleruby-22.1.0"
141
+ ruby-version: "truffleruby"
163
142
 
164
143
  env:
165
144
  BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
166
145
  RAILS_ENV: test
167
146
 
168
147
  steps:
169
- - uses: actions/checkout@v2
148
+ - uses: actions/checkout@v7
170
149
 
171
150
  - name: Selected Gemfile
172
151
  run: echo $BUNDLE_GEMFILE
@@ -0,0 +1,28 @@
1
+ # Issues in this repo are synced with Linear. A GitHub closing keyword ("Closes #123",
2
+ # "Fixes owner/repo#123") closes the issue the moment the PR merges, while Linear's PR
3
+ # automation moves the linked Linear issue to Self QA at the same time. The two automations
4
+ # then fight: the GitHub issue is closed, reopened, and the Linear issue lands in Triage.
5
+ # Reference the issue with "Refs #123" instead and close it from Linear once the fix is
6
+ # released.
7
+ name: no closing keywords
8
+ on:
9
+ pull_request:
10
+ types: [opened, edited, reopened, synchronize]
11
+ permissions:
12
+ pull-requests: read
13
+ jobs:
14
+ check:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Reject GitHub closing keywords in the PR description
18
+ env:
19
+ BODY: ${{ github.event.pull_request.body }}
20
+ run: |
21
+ pattern='\b(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\b:?[[:space:]]+(#[0-9]+|[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#[0-9]+|https?://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/issues/[0-9]+)'
22
+ if matches=$(printf '%s' "$BODY" | grep -inoE "$pattern"); then
23
+ echo "::error::The PR description contains a GitHub closing keyword, which would close the issue on merge and desync it from Linear:"
24
+ echo "$matches"
25
+ echo "Use 'Refs #123' instead and close the issue from Linear once the fix is released."
26
+ exit 1
27
+ fi
28
+ echo "No closing keywords found."
@@ -0,0 +1,219 @@
1
+ # Publishes the gem to RubyGems.org from GitHub Actions with trusted publishing (OIDC), the way
2
+ # logtail-js releases the @logtail/* packages to npm. No RubyGems.org API key exists anywhere:
3
+ # the gem on rubygems.org trusts exactly this workflow file, run from this repository in the
4
+ # "rubygems" environment, and the shared account's multi-factor authentication stays as it is.
5
+ #
6
+ # One-time setup on rubygems.org, signed in as the gem's owner: the gem's page → Trusted publishers
7
+ # → GitHub Actions, with the repository owner "logtail", this repository's name, the workflow
8
+ # "release.yml" and the environment "rubygems".
9
+ #
10
+ # Repository settings this relies on: the "rubygems" environment with deployment branches limited
11
+ # to main and no required reviewers (add reviewers there if releases should need an approval; a
12
+ # job that references a missing environment would create it WITHOUT protection), and the GitHub
13
+ # Actions app among the actors allowed to bypass the pull request requirement of main, which the
14
+ # version commit needs.
15
+ #
16
+ # Release: Actions → Release → Run workflow from main, pick patch or minor. The workflow runs the
17
+ # tests, bumps the version in lib/*/version.rb, builds the gem, commits "vX.Y.Z", tags it, pushes
18
+ # both, pushes the gem to RubyGems.org and creates a GitHub release with auto-generated notes for
19
+ # the tag; edit the notes afterwards if needed.
20
+ #
21
+ # Retry: if a release failed after the version commit was pushed, run "retry" from main. It bumps
22
+ # nothing, checks out the tag of the version in lib/*/version.rb and rebuilds that commit, pushes
23
+ # the gem if RubyGems.org is still missing it and creates the GitHub release if it is still
24
+ # missing. Running patch or minor again would release the next version instead.
25
+ #
26
+ # Dry run: bumps in place without committing, builds the gem and, on main, exchanges the
27
+ # workflow's OIDC token with RubyGems.org, which proves that the trusted publisher matches this
28
+ # workflow. Nothing is pushed. Every push that touches this file is a dry run, so a change to the
29
+ # workflow proves itself on its pull request before it reaches main, and the push that merges it
30
+ # performs the token exchange as well, because the "rubygems" environment only admits main. A dry
31
+ # run can also be dispatched from any branch.
32
+ name: Release
33
+
34
+ on:
35
+ workflow_dispatch:
36
+ inputs:
37
+ release:
38
+ description: "patch or minor: bump, tag and publish. retry: finish a release that failed halfway."
39
+ type: choice
40
+ options: [patch, minor, retry]
41
+ required: true
42
+ dry_run:
43
+ description: "Dry run: bump and build, verify RubyGems.org accepts this workflow, publish nothing"
44
+ type: boolean
45
+ default: false
46
+ push:
47
+ paths:
48
+ - .github/workflows/release.yml
49
+
50
+ permissions:
51
+ contents: read
52
+
53
+ concurrency:
54
+ group: release
55
+ cancel-in-progress: false
56
+
57
+ env:
58
+ DRY_RUN: ${{ github.event_name == 'push' || inputs.dry_run }}
59
+ RELEASE: ${{ inputs.release || 'patch' }}
60
+
61
+ jobs:
62
+ verify:
63
+ name: Test
64
+ runs-on: ubuntu-24.04
65
+ env:
66
+ # The root Gemfile cannot load the suite, so the tests run against the newest released Rails.
67
+ BUNDLE_GEMFILE: gemfiles/rails-8.1.gemfile
68
+ RAILS_ENV: test
69
+ steps:
70
+ - uses: actions/checkout@v7
71
+
72
+ - name: Set up Ruby
73
+ uses: ruby/setup-ruby@v1
74
+ with:
75
+ ruby-version: "3"
76
+ bundler-cache: true
77
+
78
+ - name: Run tests
79
+ run: bundle exec rspec
80
+
81
+ build:
82
+ name: Bump and build
83
+ needs: verify
84
+ runs-on: ubuntu-24.04
85
+ permissions:
86
+ contents: write # pushes the version commit and the tag
87
+ outputs:
88
+ version: ${{ steps.version.outputs.version }}
89
+ gem: ${{ steps.build.outputs.gem }}
90
+
91
+ steps:
92
+ - name: Releases run from main only
93
+ if: ${{ env.DRY_RUN != 'true' && github.ref != 'refs/heads/main' }}
94
+ run: |
95
+ echo "::error::Dispatch the release from main, not from $GITHUB_REF_NAME."
96
+ exit 1
97
+
98
+ - uses: actions/checkout@v7
99
+ with:
100
+ fetch-depth: 0 # the version check looks at the tags on HEAD
101
+
102
+ - name: Set up Ruby
103
+ uses: ruby/setup-ruby@v1
104
+ with:
105
+ ruby-version: "3"
106
+
107
+ - name: Bump version
108
+ id: version
109
+ run: |
110
+ ruby - <<'EOF'
111
+ release, dry_run = ENV.fetch("RELEASE"), ENV["DRY_RUN"] == "true"
112
+ path = Dir["lib/**/version.rb"].fetch(0)
113
+ source = File.read(path)
114
+ current = source[/VERSION = "(\d+\.\d+\.\d+)"/, 1] || abort("no VERSION constant in #{path}")
115
+ tagged = `git tag --points-at HEAD`.split.include?("v#{current}")
116
+
117
+ if release == "retry"
118
+ # main may have moved on since the version commit, so build what the tag points at
119
+ abort("retry only finishes a release whose version v#{current} is tagged") unless system("git", "rev-parse", "-q", "--verify", "refs/tags/v#{current}", out: File::NULL)
120
+ system("git", "checkout", "-q", "v#{current}") || abort("could not check out v#{current}")
121
+ version = current
122
+ else
123
+ abort("HEAD is already released as v#{current}, there is nothing new to release") if tagged && !dry_run
124
+ major, minor, patch = current.split(".").map(&:to_i)
125
+ version = release == "minor" ? "#{major}.#{minor + 1}.0" : "#{major}.#{minor}.#{patch + 1}"
126
+ File.write(path, source.sub(%("#{current}"), %("#{version}")))
127
+ end
128
+
129
+ puts "#{current} -> #{version}"
130
+ File.open(ENV.fetch("GITHUB_OUTPUT"), "a") { |output| output.puts "version=#{version}" }
131
+ EOF
132
+
133
+ - name: Build the gem
134
+ id: build
135
+ env:
136
+ VERSION: ${{ steps.version.outputs.version }}
137
+ run: |
138
+ git diff --stat
139
+ gem build *.gemspec
140
+ gem=$(ls *.gem)
141
+ case "$gem" in
142
+ *-"$VERSION".gem) ;;
143
+ *) echo "::error::Built $gem, expected version $VERSION."; exit 1 ;;
144
+ esac
145
+ echo "gem=$gem" >> "$GITHUB_OUTPUT"
146
+
147
+ - name: Commit and tag
148
+ if: ${{ env.DRY_RUN != 'true' && env.RELEASE != 'retry' }}
149
+ env:
150
+ VERSION: ${{ steps.version.outputs.version }}
151
+ run: |
152
+ git config user.name "github-actions[bot]"
153
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
154
+ git commit -am "v$VERSION"
155
+ git tag -a "v$VERSION" -m "v$VERSION"
156
+ git push origin main "v$VERSION"
157
+
158
+ - uses: actions/upload-artifact@v7
159
+ with:
160
+ name: gem
161
+ path: "*.gem"
162
+ if-no-files-found: error
163
+
164
+ release:
165
+ name: Publish
166
+ needs: build
167
+ # The rubygems environment only admits main, so dry runs from other branches stop after the build
168
+ if: ${{ github.ref == 'refs/heads/main' }}
169
+ runs-on: ubuntu-24.04
170
+ environment: rubygems
171
+ permissions:
172
+ contents: write # creates the GitHub release
173
+ id-token: write # OIDC token exchange with RubyGems.org
174
+ env:
175
+ VERSION: ${{ needs.build.outputs.version }}
176
+ GEM: ${{ needs.build.outputs.gem }}
177
+
178
+ steps:
179
+ - uses: actions/download-artifact@v8
180
+ with:
181
+ name: gem
182
+
183
+ - name: Set up Ruby
184
+ uses: ruby/setup-ruby@v1
185
+ with:
186
+ ruby-version: "3"
187
+
188
+ # Exchanges the job's OIDC token for a short-lived RubyGems.org API key and hands it to
189
+ # `gem push`. Fails when no trusted publisher on rubygems.org matches this workflow.
190
+ - uses: rubygems/configure-rubygems-credentials@v2.1.0
191
+
192
+ - name: Dry run
193
+ if: ${{ env.DRY_RUN == 'true' }}
194
+ run: |
195
+ ls -l "$GEM"
196
+ echo "RubyGems.org accepted the OIDC token: the trusted publisher matches this workflow. Nothing is pushed."
197
+
198
+ - name: Push to RubyGems.org
199
+ if: ${{ env.DRY_RUN != 'true' }}
200
+ run: |
201
+ name="${GEM%-$VERSION.gem}"
202
+ if gem specification --remote "$name" --version "$VERSION" > /dev/null 2>&1; then
203
+ echo "$name $VERSION is on rubygems.org already, finishing the release."
204
+ else
205
+ gem push "$GEM"
206
+ fi
207
+
208
+ - name: Create GitHub release
209
+ if: ${{ env.DRY_RUN != 'true' }}
210
+ env:
211
+ GH_TOKEN: ${{ github.token }}
212
+ GH_REPO: ${{ github.repository }}
213
+ run: |
214
+ if gh release view "v$VERSION" > /dev/null 2>&1; then
215
+ echo "Release v$VERSION already exists."
216
+ else
217
+ # --verify-tag: only ever attach to the tag the build job pushed, never create one here.
218
+ gh release create "v$VERSION" --verify-tag --generate-notes
219
+ fi
@@ -5,6 +5,8 @@ gem 'sidekiq', '>= 7.3.0', require: false if RUBY_VERSION >= '2.7.0'
5
5
 
6
6
  gem 'logtail'
7
7
  gem 'logtail-rack'
8
+ # ActiveSupport passes quirks_mode to JSON.generate until 7.2.4 and 8.1, json 3 no longer accepts it
9
+ gem 'json', '< 3'
8
10
 
9
11
  if RUBY_PLATFORM == "java"
10
12
  gem 'mime-types', '2.6.2'
@@ -1,6 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'rails', '~> 7.2.0'
3
+ # 7.2.4 is the first release that works with json 3. Without the floor, Bundler prefers
4
+ # minitest 6 over it and settles for Rails 7.2.3.
5
+ gem 'rails', '~> 7.2.4'
4
6
  gem 'sidekiq', '>= 7.3.0', require: false
5
7
 
6
8
  gem 'logtail'
@@ -1,10 +1,11 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'rails', '~> 8.0.0.pre'
3
+ gem 'rails', '~> 8.0.0'
4
4
  gem 'sidekiq', '>= 7.3.0', require: false
5
5
 
6
6
  gem 'logtail'
7
7
  gem 'logtail-rack'
8
- gem "sqlite3", ">= 2.0"
8
+ # ActiveSupport passes quirks_mode to JSON.generate until 7.2.4 and 8.1, json 3 no longer accepts it
9
+ gem 'json', '< 3'
9
10
 
10
11
  gemspec :path => '../'
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rails', '~> 8.1.0'
4
+ gem 'sidekiq', '>= 7.3.0', require: false
5
+
6
+ gem 'logtail'
7
+ gem 'logtail-rack'
8
+
9
+ gemspec :path => '../'
@@ -5,7 +5,6 @@ gem 'sidekiq', '>= 7.3.0', require: false if RUBY_VERSION >= '2.7.0'
5
5
 
6
6
  gem 'logtail'
7
7
  gem 'logtail-rack'
8
- gem "sqlite3", ">= 2.0"
9
8
 
10
9
  if RUBY_PLATFORM == "java"
11
10
  gem 'mime-types', '2.6.2'
@@ -9,21 +9,42 @@ module Logtail
9
9
  #
10
10
  # @private
11
11
  class LogtailLogSubscriber < ::ActionController::LogSubscriber
12
- def start_processing(event)
13
- return true if silence?
12
+ if ::ActionController::LogSubscriber < ::ActiveSupport::LogSubscriber
13
+ def start_processing(event)
14
+ return true if silence?
14
15
 
15
- info do
16
- payload = event.payload
17
- params = payload[:params].except(*INTERNAL_PARAMS)
18
- format = extract_format(payload)
19
- format = format.to_s.upcase if format.is_a?(Symbol)
16
+ info do
17
+ payload = event.payload
18
+ params = payload[:params].except(*INTERNAL_PARAMS)
19
+ format = extract_format(payload)
20
+ format = format.to_s.upcase if format.is_a?(Symbol)
20
21
 
21
- Events::ControllerCall.new(
22
- controller: payload[:controller],
23
- action: payload[:action],
24
- format: format,
25
- params: params
26
- )
22
+ Events::ControllerCall.new(
23
+ controller: payload[:controller],
24
+ action: payload[:action],
25
+ format: format,
26
+ params: params
27
+ )
28
+ end
29
+ end
30
+ else
31
+ # Rails 8.2+ feeds this subscriber from Rails.event: the event is a hash whose
32
+ # payload already comes without the internal params and with the format upcased.
33
+ self.namespace = "action_controller"
34
+
35
+ def request_started(event)
36
+ return true if silence?
37
+
38
+ info do
39
+ payload = event[:payload]
40
+
41
+ Events::ControllerCall.new(
42
+ controller: payload[:controller],
43
+ action: payload[:action],
44
+ format: payload[:format],
45
+ params: payload[:params]
46
+ )
47
+ end
27
48
  end
28
49
  end
29
50
 
@@ -18,7 +18,7 @@ module Logtail
18
18
  return true if Logtail::Integrations::Rails::ActiveSupportLogSubscriber.subscribed?(:action_controller, LogtailLogSubscriber)
19
19
 
20
20
  Logtail::Integrations::Rails::ActiveSupportLogSubscriber.unsubscribe!(:action_controller, ::ActionController::LogSubscriber)
21
- LogtailLogSubscriber.attach_to(:action_controller)
21
+ Logtail::Integrations::Rails::ActiveSupportLogSubscriber.subscribe!(:action_controller, LogtailLogSubscriber)
22
22
  end
23
23
  end
24
24
  end
@@ -7,8 +7,12 @@ module Logtail
7
7
  # The intent of this subscriber is to, as transparently as possible, properly
8
8
  # track events that are being logged here.
9
9
  #
10
+ # Until Rails 8.1 it extends the default subscriber. Rails 8.2 turned that one into a
11
+ # subscriber of Rails.event, which receives the rendering events in debug mode only, so
12
+ # there this subscriber listens to the notifications on its own.
13
+ #
10
14
  # @private
11
- class LogtailLogSubscriber < ::ActionView::LogSubscriber
15
+ class LogtailLogSubscriber < (::ActionView::LogSubscriber < ::ActiveSupport::LogSubscriber ? ::ActionView::LogSubscriber : ::ActiveSupport::LogSubscriber)
12
16
  def render_template(event)
13
17
  return true if silence?
14
18
 
@@ -67,15 +71,25 @@ module Logtail
67
71
  end
68
72
  end
69
73
 
70
- def self.attach_to(*)
71
- super
74
+ if superclass == ::ActionView::LogSubscriber
75
+ def self.attach_to(*)
76
+ super
77
+
78
+ if ::Rails::VERSION::MAJOR > 7 || ::Rails::VERSION::MAJOR == 7 && ::Rails::VERSION::MINOR >= 1
79
+ # Clean extra listeners subscribed in parent's attach_to method
80
+ ::ActiveSupport::Notifications.notifier.listeners_for("render_template.action_view")
81
+ .concat(::ActiveSupport::Notifications.notifier.listeners_for("render_layout.action_view")).flatten
82
+ .filter { |listener| listener.delegate.class == ::ActionView::LogSubscriber::Start }
83
+ .each { |listener| ActiveSupport::Notifications.unsubscribe(listener) }
84
+ end
85
+ end
86
+ else
87
+ # The default subscriber used to bring these log levels, the logger and the helpers below
88
+ subscribe_log_level :render_partial, :debug
89
+ subscribe_log_level :render_collection, :debug
72
90
 
73
- if ::Rails::VERSION::MAJOR > 7 || ::Rails::VERSION::MAJOR == 7 && ::Rails::VERSION::MINOR >= 1
74
- # Clean extra listeners subscribed in parent's attach_to method
75
- ::ActiveSupport::Notifications.notifier.listeners_for("render_template.action_view")
76
- .concat(::ActiveSupport::Notifications.notifier.listeners_for("render_layout.action_view")).flatten
77
- .filter { |listener| listener.delegate.class == ::ActionView::LogSubscriber::Start }
78
- .each { |listener| ActiveSupport::Notifications.unsubscribe(listener) }
91
+ def logger
92
+ ::ActionView::Base.logger
79
93
  end
80
94
  end
81
95
 
@@ -90,6 +104,35 @@ module Logtail
90
104
  def silence?
91
105
  ActionView.silence?
92
106
  end
107
+
108
+ unless superclass == ::ActionView::LogSubscriber
109
+ def from_rails_root(string)
110
+ string = string.sub(rails_root, "")
111
+ string.sub!(/^app\/views\//, "")
112
+ string
113
+ end
114
+
115
+ def rails_root
116
+ @rails_root ||= "#{::Rails.root}/"
117
+ end
118
+
119
+ def render_count(payload)
120
+ if payload[:cache_hits]
121
+ "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]"
122
+ else
123
+ "[#{payload[:count]} times]"
124
+ end
125
+ end
126
+
127
+ def cache_message(payload)
128
+ case payload[:cache_hit]
129
+ when :hit
130
+ "[cache hit]"
131
+ when :miss
132
+ "[cache miss]"
133
+ end
134
+ end
135
+ end
93
136
  end
94
137
  end
95
138
  end
@@ -17,7 +17,7 @@ module Logtail
17
17
  return true if Logtail::Integrations::Rails::ActiveSupportLogSubscriber.subscribed?(:action_view, LogtailLogSubscriber)
18
18
 
19
19
  Logtail::Integrations::Rails::ActiveSupportLogSubscriber.unsubscribe!(:action_view, ::ActionView::LogSubscriber)
20
- LogtailLogSubscriber.attach_to(:action_view)
20
+ Logtail::Integrations::Rails::ActiveSupportLogSubscriber.subscribe!(:action_view, LogtailLogSubscriber)
21
21
  end
22
22
  end
23
23
  end
@@ -13,35 +13,88 @@ module Logtail
13
13
  # track events that are being logged here. This LogSubscriber will never change
14
14
  # default behavior / log messages.
15
15
  #
16
+ # Until Rails 8.1 it extends the default subscriber. Rails 8.2 turned that one into a
17
+ # subscriber of Rails.event, which receives the SQL events in debug mode only, so there
18
+ # this subscriber listens to the notifications on its own.
19
+ #
16
20
  # @private
17
- class LogtailLogSubscriber < ::ActiveRecord::LogSubscriber
18
- def sql(event)
19
- return true if silence?
21
+ class LogtailLogSubscriber < (::ActiveRecord::LogSubscriber < ::ActiveSupport::LogSubscriber ? ::ActiveRecord::LogSubscriber : ::ActiveSupport::LogSubscriber)
22
+ if superclass == ::ActiveRecord::LogSubscriber
23
+ def sql(event)
24
+ return true if silence?
20
25
 
21
- r = super(event)
26
+ r = super(event)
22
27
 
23
- if @message
24
- payload = event.payload
28
+ if @message
29
+ payload = event.payload
25
30
 
26
- sql_event = Events::SQLQuery.new(
27
- sql: payload[:sql],
28
- duration_ms: event.duration,
29
- message: @message,
30
- )
31
+ sql_event = Events::SQLQuery.new(
32
+ sql: payload[:sql],
33
+ duration_ms: event.duration,
34
+ message: @message,
35
+ )
31
36
 
32
- logger.debug sql_event
37
+ logger.debug sql_event
33
38
 
34
- @message = nil
39
+ @message = nil
40
+ end
41
+
42
+ r
35
43
  end
36
44
 
37
- r
38
- end
45
+ private
46
+ def debug(message)
47
+ @message = message
48
+ end
49
+ else
50
+ require "active_record/structured_event_subscriber"
39
51
 
40
- private
41
- def debug(message)
42
- @message = message
52
+ # Rails 8.2 split ActiveRecord::LogSubscriber#sql in two: a subscriber that turns the
53
+ # notification into an event for Rails.event, rendering and filtering the binds, and
54
+ # a log subscriber that formats the event. Both are used here as they are, without
55
+ # Rails.event between them.
56
+ class Translation < ::ActiveRecord::StructuredEventSubscriber
57
+ attr_reader :payload
58
+
59
+ def emit_debug_event(name, payload = nil, caller_depth: 1, **kwargs)
60
+ @payload = payload || kwargs
61
+ end
62
+ end
63
+
64
+ class Formatting < ::ActiveRecord::LogSubscriber
65
+ attr_reader :message
66
+
67
+ private
68
+ def debug(message)
69
+ @message = message
70
+ end
71
+ end
72
+
73
+ def sql(event)
74
+ return true if silence?
75
+
76
+ translation = Translation.new
77
+ translation.sql(event)
78
+ # Schema and explain queries are not reported
79
+ return unless translation.payload
80
+
81
+ formatting = Formatting.new
82
+ formatting.sql({ name: "active_record.sql", payload: translation.payload })
83
+
84
+ logger.debug Events::SQLQuery.new(
85
+ sql: event.payload[:sql],
86
+ duration_ms: event.duration,
87
+ message: formatting.message,
88
+ )
89
+ end
90
+ subscribe_log_level :sql, :debug
91
+
92
+ def logger
93
+ ::ActiveRecord::Base.logger
94
+ end
43
95
  end
44
96
 
97
+ private
45
98
  def silence?
46
99
  ActiveRecord.silence?
47
100
  end
@@ -12,7 +12,7 @@ module Logtail
12
12
  return true if Logtail::Integrations::Rails::ActiveSupportLogSubscriber.subscribed?(:active_record, LogtailLogSubscriber)
13
13
 
14
14
  Logtail::Integrations::Rails::ActiveSupportLogSubscriber.unsubscribe!(:active_record, ::ActiveRecord::LogSubscriber)
15
- LogtailLogSubscriber.attach_to(:active_record)
15
+ Logtail::Integrations::Rails::ActiveSupportLogSubscriber.subscribe!(:active_record, LogtailLogSubscriber)
16
16
  end
17
17
  end
18
18
  end
@@ -6,6 +6,12 @@ module Logtail
6
6
  extend self
7
7
 
8
8
  def find(component, type)
9
+ if event_reporter_subscriber?(type)
10
+ return ::ActiveSupport.event_reporter.subscribers.map { |entry| entry[:subscriber] }.find do |subscriber|
11
+ subscriber.class == type
12
+ end
13
+ end
14
+
9
15
  ::ActiveSupport::LogSubscriber.log_subscribers.find do |subscriber|
10
16
  subscriber.class == type
11
17
  end
@@ -15,9 +21,25 @@ module Logtail
15
21
  !find(component, type).nil?
16
22
  end
17
23
 
24
+ def subscribe!(component, type)
25
+ if event_reporter_subscriber?(type)
26
+ # Like attach_to below, only deliver the events for the methods the subscriber defines itself
27
+ events = type.public_instance_methods(false).map { |method| "#{component}.#{method}" }
28
+ ::ActiveSupport.event_reporter.subscribe(type.new) { |event| events.include?(event[:name]) }
29
+ return
30
+ end
31
+
32
+ type.attach_to(component)
33
+ end
34
+
18
35
  # I don't know why this has to be so complicated, but it is. This code was taken from
19
36
  # lograge :/
20
37
  def unsubscribe!(component, type)
38
+ if event_reporter_subscriber?(type)
39
+ ::ActiveSupport.event_reporter.unsubscribe(type)
40
+ return
41
+ end
42
+
21
43
  if defined?(type.detach_from)
22
44
  type.detach_from(component)
23
45
  return
@@ -38,6 +60,12 @@ module Logtail
38
60
  end
39
61
  end
40
62
  end
63
+
64
+ # Rails 8.2+ feeds the framework log subscribers from Rails.event instead of
65
+ # ActiveSupport::Notifications.
66
+ def event_reporter_subscriber?(type)
67
+ defined?(::ActiveSupport::EventReporter::LogSubscriber) && type < ::ActiveSupport::EventReporter::LogSubscriber
68
+ end
41
69
  end
42
70
  end
43
71
  end
@@ -22,6 +22,7 @@ module Logtail
22
22
  # Rails 8.1 event emission method - called when events are emitted
23
23
  def emit(event)
24
24
  return unless self.class.enabled
25
+ return if framework_event?(event)
25
26
 
26
27
  # Log the event with all its data to Rails.logger
27
28
  # Create a structured log entry with the event data
@@ -42,6 +43,17 @@ module Logtail
42
43
 
43
44
  private
44
45
 
46
+ # Rails 8.2+ routes the framework's own instrumentation (controller calls, SQL queries,
47
+ # template renders, ...) through Rails.event as well. The Rails log subscribers, and ours
48
+ # replacing them, already log those, so only the application's own events are forwarded.
49
+ def framework_event?(event)
50
+ return false unless defined?(::ActiveSupport::EventReporter::LogSubscriber)
51
+
52
+ ::ActiveSupport::EventReporter::LogSubscriber.descendants.any? do |log_subscriber|
53
+ event[:name].start_with?("#{log_subscriber.namespace}.")
54
+ end
55
+ end
56
+
45
57
  def build_log_message(event)
46
58
  payload = event[:payload] || {}
47
59
  payload_str = payload.map { |key, value| "#{key}=#{value}" }.join(" ")
@@ -1,7 +1,7 @@
1
1
  module Logtail
2
2
  module Integrations
3
3
  module Rails
4
- VERSION = "0.2.12"
4
+ VERSION = "0.2.13"
5
5
  end
6
6
  end
7
7
  end
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec|
39
39
  spec.add_development_dependency "rake", ">= 0.8"
40
40
  spec.add_development_dependency "rspec", "~> 3.0"
41
41
 
42
+ spec.add_development_dependency "benchmark", ">= 0"
42
43
  spec.add_development_dependency "bundler-audit", ">= 0"
43
44
  spec.add_development_dependency "rails_stdout_logging", ">= 0"
44
45
  spec.add_development_dependency "rspec-its", ">= 0"
@@ -57,7 +58,10 @@ Gem::Specification.new do |spec|
57
58
  spec.add_development_dependency('activerecord-jdbcsqlite3-adapter', '>= 0')
58
59
  elsif rails_version >= 3 && rails_version < 6
59
60
  spec.add_development_dependency('sqlite3', '1.3.13')
60
- else
61
+ elsif rails_version >= 6 && rails_version < 8
61
62
  spec.add_development_dependency('sqlite3', '~> 1.5.0')
63
+ else
64
+ # Rails 8 and newer, which is also what rails-edge and the root Gemfile resolve to
65
+ spec.add_development_dependency('sqlite3', '>= 2.0')
62
66
  end
63
67
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logtail-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.12
4
+ version: 0.2.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Better Stack
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-11-24 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: logtail
@@ -128,6 +127,20 @@ dependencies:
128
127
  - - "~>"
129
128
  - !ruby/object:Gem::Version
130
129
  version: '3.0'
130
+ - !ruby/object:Gem::Dependency
131
+ name: benchmark
132
+ requirement: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ type: :development
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
131
144
  - !ruby/object:Gem::Dependency
132
145
  name: bundler-audit
133
146
  requirement: !ruby/object:Gem::Requirement
@@ -202,17 +215,16 @@ dependencies:
202
215
  name: sqlite3
203
216
  requirement: !ruby/object:Gem::Requirement
204
217
  requirements:
205
- - - "~>"
218
+ - - ">="
206
219
  - !ruby/object:Gem::Version
207
- version: 1.5.0
220
+ version: '2.0'
208
221
  type: :development
209
222
  prerelease: false
210
223
  version_requirements: !ruby/object:Gem::Requirement
211
224
  requirements:
212
- - - "~>"
225
+ - - ">="
213
226
  - !ruby/object:Gem::Version
214
- version: 1.5.0
215
- description:
227
+ version: '2.0'
216
228
  email:
217
229
  - hello@betterstack.com
218
230
  executables: []
@@ -220,6 +232,8 @@ extensions: []
220
232
  extra_rdoc_files: []
221
233
  files:
222
234
  - ".github/workflows/main.yml"
235
+ - ".github/workflows/no-closing-keywords.yml"
236
+ - ".github/workflows/release.yml"
223
237
  - ".gitignore"
224
238
  - ".rspec"
225
239
  - Gemfile
@@ -315,6 +329,7 @@ files:
315
329
  - gemfiles/rails-7.1.gemfile
316
330
  - gemfiles/rails-7.2.gemfile
317
331
  - gemfiles/rails-8.0.gemfile
332
+ - gemfiles/rails-8.1.gemfile
318
333
  - gemfiles/rails-edge.gemfile
319
334
  - lib/logtail-rails.rb
320
335
  - lib/logtail-rails/action_controller.rb
@@ -355,7 +370,6 @@ metadata:
355
370
  homepage_uri: https://github.com/logtail/logtail-ruby-rails
356
371
  source_code_uri: https://github.com/logtail/logtail-ruby-rails
357
372
  changelog_uri: https://github.com/logtail/logtail-ruby-rails/blob/main/README.md
358
- post_install_message:
359
373
  rdoc_options: []
360
374
  require_paths:
361
375
  - lib
@@ -370,8 +384,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
370
384
  - !ruby/object:Gem::Version
371
385
  version: '0'
372
386
  requirements: []
373
- rubygems_version: 3.4.10
374
- signing_key:
387
+ rubygems_version: 3.6.9
375
388
  specification_version: 4
376
389
  summary: Better Stack Rails integration
377
390
  test_files: []