Skip to content

Commit 648066a

Browse files
popcornjohnnagrorafaeelaudibert
authored
Add posthog-rails gem for automatic Rails exception tracking (#81)
* Add posthog-rails gem for automatic Rails exception tracking This commit introduces the posthog-rails gem which provides automatic exception tracking for Rails applications, including: - Automatic capture of unhandled exceptions via Rails middleware - Automatic capture of rescued exceptions (configurable) - Automatic instrumentation of ActiveJob failures - Integration with Rails 7.0+ error reporter - Configurable exception exclusion list - User context capture from controllers Core library improvements: - Added comprehensive logging throughout exception capture flow - Added ExceptionCapture module for standardized exception parsing - Fixed field handling in Transport to strip internal-only fields (type, library, library_version, messageId) that are not part of PostHog's RawEvent struct - Fixed UUID field handling to avoid sending null values - Added capture_exception method to Client for explicit exception tracking The posthog-rails gem includes: - Railtie for automatic initialization and middleware insertion - Multiple middleware layers for capturing different exception types - ErrorSubscriber for Rails 7.0+ error reporter integration - ActiveJob extensions for job failure tracking - Comprehensive configuration options * fix rubocop errors * correct default URL Co-authored-by: Rafael Audibert <32079912+rafaeelaudibert@users.noreply.github.com> * test mode example config which includes development Co-authored-by: Rafael Audibert <32079912+rafaeelaudibert@users.noreply.github.com> * Add URLs to API key documentation in example config Added direct links to PostHog settings pages: - Project API key: https://app.posthog.com/settings/project-details#variables - Personal API key: https://app.posthog.com/settings/user-api-keys * Comment out on_error callback in example config Keep it as an example users can uncomment if needed, consistent with other optional callbacks like before_send. * Add docs explaining benefits of current_user_method config * refactor: move rails_config to PostHog::Rails.config namespace Move Rails-specific configuration from PostHog.rails_config to PostHog::Rails.config for cleaner namespacing. * feat(rails): Add user_id_method config and pk support for ID extraction Add configurable `user_id_method` option and support for `posthog_distinct_id`, `distinct_id`, and `pk` methods when extracting user IDs for exception tracking. * rescue on individual entries Co-authored-by: Rafael Audibert <32079912+rafaeelaudibert@users.noreply.github.com> * Separate Rails config from PostHog.init Move Rails-specific options (auto_capture_exceptions, etc.) to PostHog::Rails.configure, keeping core SDK options in PostHog.init. This follows the common Rails gem pattern of namespaced configuration. * remove LLM-y file * docs: clarify only ActiveJob is supported, other runners coming soon * Add Rails install generator for posthog:install command - Create generator at lib/generators/posthog/install_generator.rb that copies the initializer template to config/initializers/posthog.rb - Remove USAGE EXAMPLES section from examples/posthog.rb to keep it as a clean, focused initializer template for customers - Document the generator in README with usage instructions Users can now run `rails generate posthog:install` to automatically set up PostHog in their Rails application. * Abort loading posthog-rails if Rails is not defined * Add posthog_distinct_id DSL for ActiveJob distinct_id extraction * docs: clarify user_id/distinct_id detection in Rails error reporter context * refactor(rails): use metaprogramming for client method delegation Replace repetitive method definitions with define_method loop and add method_missing fallback for future client methods. * Fix session ID extraction in exception capture middleware - Use request.session.id instead of session_options.dig(:id) - session_options returns Session::Options, not a Hash * fix(rails): safely serialize exception context to prevent stack overflow Add safe_serialize to handle circular references and complex objects in exception context. Tracks visited objects, limits depth/size, and converts non-serializable objects to safe string representations. Prevents SystemStackError when Rails error reporter context contains ActiveRecord models or other objects with circular references. * Fix duplicate exception capture for web requests When auto_capture_exceptions was enabled, exceptions in web requests were being captured twice: 1. By CaptureExceptions middleware (after ShowExceptions) 2. By ErrorSubscriber (via Rails.error.subscribe in Rails 7.0+) The ErrorSubscriber fires from within DebugExceptions before the CaptureExceptions middleware catches the exception, causing both to report the same error to PostHog. Fix by tracking web request context via thread-local flag: - CaptureExceptions sets flag at request start, clears in ensure block - ErrorSubscriber checks flag and skips if in web request context This ensures web requests are captured only by the middleware (which has richer context: URL, params, controller, action), while ErrorSubscriber still handles non-web contexts (ActiveJob, manual Rails.error.record). * feat(rails): make automatic error tracking opt-in by default Change auto_capture_exceptions, report_rescued_exceptions, and auto_instrument_active_job to default to false, requiring explicit opt-in for automatic error tracking features. * ensuring the example config we copy over maintains the new defaults * fix: Fix posthog_distinct_id This must be prepended to make sure it works as expected * refactor: Small fixes Tiny bugfixes found by Claude * fix: Update and point default to us.i.posthog.com * chore: Bump to v3.5.0 * docs: Update release process * refactor: Simplify rubocop rules * chore: Improve release flow to use new approvals workflow * chore: Remove inherit call --------- Co-authored-by: John Nagro <john@noreastergroup.com> Co-authored-by: John Nagro <johnnagro@fastmail.com> Co-authored-by: Rafael Audibert <32079912+rafaeelaudibert@users.noreply.github.com> Co-authored-by: Rafa Audibert <rafaeelaudibert@gmail.com>
1 parent 4cf9e34 commit 648066a

20 files changed

Lines changed: 1574 additions & 38 deletions

.github/workflows/release.yml

Lines changed: 150 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,91 @@
1-
name: Publish Release
1+
name: "Release"
22

33
on:
4-
workflow_dispatch:
4+
pull_request:
5+
types: [closed]
6+
branches: [main]
7+
8+
permissions:
9+
contents: read
10+
11+
# Concurrency control: only one release process can run at a time
12+
# This prevents race conditions if multiple PRs with 'release' label merge simultaneously
13+
concurrency:
14+
group: release
15+
cancel-in-progress: false
516

617
jobs:
7-
publish:
18+
check-release-label:
19+
name: Check for release label
820
runs-on: ubuntu-latest
21+
# Run when PR with 'release' label is merged to main
22+
if: |
23+
github.event.pull_request.merged == true
24+
&& contains(github.event.pull_request.labels.*.name, 'release')
25+
outputs:
26+
should-release: ${{ steps.check.outputs.should-release }}
27+
version: ${{ steps.check.outputs.version }}
28+
steps:
29+
- name: Checkout repository
30+
uses: actions/checkout@v4
31+
with:
32+
ref: main
33+
34+
- name: Check version is new
35+
id: check
36+
run: |
37+
# Extract version from source
38+
version=$(grep "VERSION = '" lib/posthog/version.rb | grep -o "'[0-9]\+\.[0-9]\+\.[0-9]\+'" | tr -d "'")
39+
echo "version=$version" >> "$GITHUB_OUTPUT"
40+
41+
# Get currently published version from RubyGems
42+
published=$(gem info posthog-ruby --remote 2>/dev/null | grep -o 'posthog-ruby ([0-9.]*' | grep -o '[0-9].*' || echo "none")
43+
44+
echo "Local version: $version"
45+
echo "Published version: $published"
46+
47+
if [ "$version" = "$published" ]; then
48+
echo "Version $version is already published, skipping release"
49+
echo "should-release=false" >> "$GITHUB_OUTPUT"
50+
else
51+
echo "Ready to release version $version"
52+
echo "should-release=true" >> "$GITHUB_OUTPUT"
53+
fi
54+
55+
notify-approval-needed:
56+
name: Notify Slack - Approval Needed
57+
needs: check-release-label
58+
if: needs.check-release-label.outputs.should-release == 'true'
59+
uses: posthog/.github/.github/workflows/notify-approval-needed.yml@main
60+
with:
61+
slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }}
62+
slack_user_group_id: ${{ vars.GROUP_CLIENT_LIBRARIES_SLACK_GROUP_ID }}
63+
secrets:
64+
slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }}
65+
posthog_project_api_key: ${{ secrets.POSTHOG_PROJECT_API_KEY }}
66+
67+
release:
68+
name: Release and publish
69+
needs: [check-release-label, notify-approval-needed]
70+
runs-on: ubuntu-latest
71+
if: always() && needs.check-release-label.outputs.should-release == 'true'
72+
environment: "Release" # This will require an approval from a maintainer, they are notified in Slack above
973
permissions:
10-
contents: read
74+
contents: write
1175
id-token: write
1276
steps:
13-
- name: Checkout
14-
uses: actions/checkout@v5
77+
- name: Notify Slack - Approved
78+
if: needs.notify-approval-needed.outputs.slack_ts != ''
79+
uses: posthog/.github/.github/actions/slack-thread-reply@main
80+
with:
81+
slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }}
82+
slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }}
83+
thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }}
84+
message: "✅ Release approved! Publishing v${{ needs.check-release-label.outputs.version }}..."
85+
emoji_reaction: "white_check_mark"
86+
87+
- name: Checkout repository
88+
uses: actions/checkout@v4
1589
with:
1690
ref: main
1791
fetch-depth: 0
@@ -22,4 +96,73 @@ jobs:
2296
bundler-cache: true
2397
ruby-version: ruby
2498

25-
- uses: rubygems/release-gem@v1
99+
- name: Configure trusted publishing credentials
100+
uses: rubygems/configure-rubygems-credentials@v1.0.0
101+
102+
# Build and publish posthog-ruby first (posthog-rails depends on it)
103+
- name: Build posthog-ruby
104+
run: gem build posthog-ruby.gemspec
105+
106+
- name: Publish posthog-ruby
107+
run: gem push posthog-ruby-*.gem
108+
109+
- name: Wait for posthog-ruby to be available
110+
run: gem exec rubygems-await posthog-ruby-*.gem
111+
112+
# Build and publish posthog-rails
113+
- name: Build posthog-rails
114+
run: gem build posthog-rails/posthog-rails.gemspec
115+
116+
- name: Publish posthog-rails
117+
run: gem push posthog-rails-*.gem
118+
119+
- name: Wait for posthog-rails to be available
120+
run: gem exec rubygems-await posthog-rails-*.gem
121+
122+
# Create and push git tag
123+
- name: Create git tag
124+
run: |
125+
git config user.name "github-actions[bot]"
126+
git config user.email "github-actions[bot]@users.noreply.github.com"
127+
git tag -a "v${{ needs.check-release-label.outputs.version }}" -m "Release v${{ needs.check-release-label.outputs.version }}"
128+
git push origin "v${{ needs.check-release-label.outputs.version }}"
129+
130+
# Notify in case of a failure
131+
- name: Send failure event to PostHog
132+
if: ${{ failure() }}
133+
uses: PostHog/posthog-github-action@v0.1
134+
with:
135+
posthog-token: "${{ secrets.POSTHOG_PROJECT_API_KEY }}"
136+
event: "posthog-ruby-github-release-workflow-failure"
137+
properties: >-
138+
{
139+
"commitSha": "${{ github.sha }}",
140+
"jobStatus": "${{ job.status }}",
141+
"ref": "${{ github.ref }}",
142+
"version": "v${{ needs.check-release-label.outputs.version }}"
143+
}
144+
145+
- name: Notify Slack - Failed
146+
if: ${{ failure() && needs.notify-approval-needed.outputs.slack_ts != '' }}
147+
uses: posthog/.github/.github/actions/slack-thread-reply@main
148+
with:
149+
slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }}
150+
slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }}
151+
thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }}
152+
message: "❌ Failed to release `posthog-ruby@v${{ needs.check-release-label.outputs.version }}`! <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
153+
emoji_reaction: "x"
154+
155+
notify-released:
156+
name: Notify Slack - Released
157+
needs: [check-release-label, notify-approval-needed, release]
158+
runs-on: ubuntu-latest
159+
if: always() && needs.release.result == 'success' && needs.notify-approval-needed.outputs.slack_ts != ''
160+
steps:
161+
- name: Notify Slack - Released
162+
uses: posthog/.github/.github/actions/slack-thread-reply@main
163+
with:
164+
slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }}
165+
slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }}
166+
thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }}
167+
message: "🚀 posthog-ruby and posthog-rails v${{ needs.check-release-label.outputs.version }} released successfully!"
168+
emoji_reaction: "rocket"

.rubocop.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
inherit_from: .rubocop_todo.yml
2-
31
AllCops:
42
NewCops: enable
53
SuggestExtensions: false
64

75
Style/Documentation:
86
Enabled: false
97

8+
Naming/FileName:
9+
Exclude:
10+
- "posthog-rails/lib/posthog-rails.rb"
11+
1012
# Modern Ruby 3.0+ specific cops
1113
Style/HashTransformKeys:
1214
Enabled: true
@@ -75,7 +77,8 @@ Metrics/MethodLength:
7577
Metrics/ParameterLists:
7678
Enabled: false
7779

78-
# Allow longer modules in spec files since they contain many test cases
80+
# Ideally need to bring this down
7981
Metrics/ModuleLength:
82+
Max: 4055
8083
Exclude:
81-
- 'spec/**/*_spec.rb'
84+
- "spec/**/*_spec.rb"

.rubocop_todo.yml

Lines changed: 0 additions & 12 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 3.5.0 - 2026-02-05
2+
3+
1. feat: Add posthog-rails gem for automatic Rails exception tracking
4+
- Automatic capture of unhandled exceptions via Rails middleware
5+
- Automatic capture of rescued exceptions (configurable)
6+
- Automatic instrumentation of ActiveJob failures
7+
- Integration with Rails 7.0+ error reporter
8+
- Configurable exception exclusion list
9+
- User context capture from controllers
10+
111
## 3.4.0 - 2025-12-04
212

313
1. feat: Add ETag support for feature flag definitions polling ([#84](https://github.com/PostHog/posthog-ruby/pull/84))

README.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ Specifically, the [Ruby integration](https://posthog.com/docs/integrations/ruby-
1111
>
1212
> All 2.x versions of the PostHog Ruby library are compatible with Ruby 2.0 and above if you need Ruby 2.0 support.
1313
14+
## Rails Integration
15+
16+
**Using Rails?** Check out [posthog-rails](posthog-rails/README.md) for automatic exception tracking, ActiveJob instrumentation, and Rails-specific features.
17+
1418
## Developing Locally
1519

1620
1. Install `asdf` to manage your Ruby version: `brew install asdf`
@@ -31,18 +35,22 @@ Specifically, the [Ruby integration](https://posthog.com/docs/integrations/ruby-
3135

3236
## How to release
3337

34-
1. Get access to RubyGems from @dmarticus, @daibhin or @mariusandra
35-
2. Install [`gh`](https://cli.github.com/) and authenticate with `gh auth login`
36-
3. Update `lib/posthog/version.rb` with the new version & add to `CHANGELOG.md` making sure to add the current date. Commit the changes:
38+
Both `posthog-ruby` and `posthog-rails` are released together with the same version number.
39+
40+
1. Create a PR that:
41+
- Updates `lib/posthog/version.rb` with the new version
42+
- Updates `CHANGELOG.md` with the changes and current date
43+
44+
2. Add the `release` label to the PR
45+
46+
3. Merge the PR to `main`
3747

38-
```shell
39-
VERSION=1.2.3 # Replace with the new version here
40-
git commit -am "Version $VERSION"
41-
git tag -a $VERSION -m "Version $VERSION"
42-
git push && git push --tags
43-
gh release create $VERSION --generate-notes --fail-on-no-commits
44-
```
48+
4. The release workflow will:
49+
- Notify the Client Libraries team in Slack
50+
- Wait for approval via the GitHub `Release` environment
51+
- Publish both gems to RubyGems (via trusted publishing)
52+
- Create and push a git tag
4553

46-
4. [Trigger](https://github.com/PostHog/posthog-ruby/actions) "Publish Release" GitHub Action
54+
5. Approve the release in GitHub when prompted
4755

48-
5. Authenticate with your RubyGems account and approve the publish!
56+
The workflow handles publishing both `posthog-ruby` and `posthog-rails` in the correct order (since `posthog-rails` depends on `posthog-ruby`).

lib/posthog/logging.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ def logger
4141
return @logger if @logger
4242

4343
base_logger =
44-
if defined?(Rails)
45-
Rails.logger
44+
if defined?(::Rails)
45+
::Rails.logger
4646
else
4747
logger = Logger.new $stdout
4848
logger.progname = 'PostHog'

lib/posthog/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module PostHog
4-
VERSION = '3.4.0'
4+
VERSION = '3.5.0'
55
end

0 commit comments

Comments
 (0)