posthog-rails 3.12.3 → 3.13.1

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: 499457f4cdb3d65001ca2052dcce6231b7f56879333a5dd5c957376460a70992
4
- data.tar.gz: e06c59907254de2d57373a167b20c21d5e54df9aca89b59bda2f138cd81e731b
3
+ metadata.gz: bc1787c8bae17ac624fd59a956284a92e996082c0774a83e27cf8b7554ae5393
4
+ data.tar.gz: d13ef6ae2d87f8820050f4fd86e9588b09d5cfea1591548d9a70a0b0d38810d3
5
5
  SHA512:
6
- metadata.gz: 6c51cd064c094fc7df538a348a3faf842a873597e527eadb65e341850649beb09e18159d191b32bce744077ed54afa6c791479c74045ed018007cb647b666a05
7
- data.tar.gz: c61468861d957d4a5cc41276fdae05ce4f00d621af51320dbb05c8f9571df28aa1d89524600f25ce71e2bfe1b9864678b5340583888e7682a9da7b1cc514a847
6
+ metadata.gz: 8cf28fe0b562b377d411bc259c3f72a8e0ea9a28051714f2fa933f8082c1a58e1fa1f24f58759413fb4d0f1ad83b684ba74930183836da1f5c3e3bacfe07842d
7
+ data.tar.gz: 897e312969d310aaf1559bd5e9c02f9684faaa755375dc2f1dc36759d35db7f1a8766981474ecb5c13e431cb1e0ed34d265e42762c41f54f2ce99ee169ec3313
@@ -37,6 +37,12 @@ PostHog::Rails.configure do |config|
37
37
  # making it easier to identify affected users and debug user-specific issues.
38
38
  # config.current_user_method = :current_user
39
39
 
40
+ # Or provide a resolver for apps that store user context outside controllers,
41
+ # such as ActiveSupport::CurrentAttributes. The resolver takes precedence over
42
+ # current_user_method and may accept the controller as an argument.
43
+ # config.current_user_resolver = proc { Current.user }
44
+ # config.current_user_resolver = proc { |controller| controller.current_user }
45
+
40
46
  # Additional exception classes to exclude from reporting
41
47
  # These are added to the default excluded exceptions
42
48
  # config.excluded_exceptions = [
@@ -69,20 +69,40 @@ module PostHog
69
69
  def extract_distinct_id(env)
70
70
  # Prefer authenticated Rails user context. Request/tracing context is
71
71
  # applied later by the core capture path if this returns nil.
72
- if PostHog::Rails.config&.capture_user_context && env['action_controller.instance']
73
- controller = env['action_controller.instance']
74
- method_name = PostHog::Rails.config&.current_user_method || :current_user
72
+ return nil unless PostHog::Rails.config&.capture_user_context
75
73
 
76
- if controller.respond_to?(method_name, true)
77
- user = controller.send(method_name)
78
- user_id = extract_user_id(user) if user
79
- return user_id if present?(user_id)
80
- end
81
- end
74
+ user = extract_current_user(env['action_controller.instance'])
75
+ user_id = extract_user_id(user) if user
76
+ return user_id if present?(user_id)
82
77
 
83
78
  nil
84
79
  end
85
80
 
81
+ def extract_current_user(controller)
82
+ resolver = PostHog::Rails.config&.current_user_resolver
83
+ return resolve_current_user(resolver, controller) if resolver
84
+
85
+ method_name = PostHog::Rails.config&.current_user_method || :current_user
86
+ return unless controller.respond_to?(method_name, true)
87
+
88
+ controller.send(method_name)
89
+ end
90
+
91
+ def resolve_current_user(resolver, controller)
92
+ call_current_user_resolver(resolver, controller)
93
+ rescue StandardError => e
94
+ PostHog::Logging.logger.warn("current_user_resolver failed: #{e.message}")
95
+ nil
96
+ end
97
+
98
+ def call_current_user_resolver(resolver, controller)
99
+ if resolver.arity.zero?
100
+ controller ? controller.instance_exec(&resolver) : resolver.call
101
+ elsif controller
102
+ resolver.call(controller)
103
+ end
104
+ end
105
+
86
106
  def extract_user_id(user)
87
107
  # Use configured method if specified
88
108
  method_name = PostHog::Rails.config&.user_id_method
@@ -32,6 +32,10 @@ module PostHog
32
32
  # @return [Symbol] Method name to call on controller to get the current user. Defaults to :current_user.
33
33
  attr_accessor :current_user_method
34
34
 
35
+ # @return [Proc, nil] Callable used to resolve the current user. When set, this takes precedence over
36
+ # current_user_method. The callable may accept the controller instance or no arguments.
37
+ attr_accessor :current_user_resolver
38
+
35
39
  # @return [Symbol, nil] Method name to call on the user object to get distinct_id. When nil, tries:
36
40
  # posthog_distinct_id, distinct_id, id, pk, uuid in order.
37
41
  attr_accessor :user_id_method
@@ -68,6 +72,7 @@ module PostHog
68
72
  @use_tracing_headers = true
69
73
  @capture_user_context = true
70
74
  @current_user_method = :current_user
75
+ @current_user_resolver = nil
71
76
  @user_id_method = nil
72
77
  @logs_enabled = false
73
78
  @logs_forward_rails_logger = true
@@ -64,24 +64,137 @@ module PostHog
64
64
  end
65
65
  end
66
66
 
67
+ MISSING_MIDDLEWARE_MESSAGE = 'No such middleware to insert'
68
+ MIDDLEWARE_FALLBACK_OPERATION = :posthog_insert_middleware_with_fallback
69
+
70
+ module MiddlewareStackFallback
71
+ def posthog_insert_middleware_with_fallback(location, target, middleware)
72
+ PostHog::Rails::Railtie.instance.send(
73
+ :insert_middleware_with_fallback,
74
+ self,
75
+ location,
76
+ target,
77
+ middleware
78
+ )
79
+ end
80
+
81
+ private :posthog_insert_middleware_with_fallback
82
+ end
83
+
84
+ private_constant :MISSING_MIDDLEWARE_MESSAGE,
85
+ :MIDDLEWARE_FALLBACK_OPERATION,
86
+ :MiddlewareStackFallback
87
+
67
88
  # @api private
68
89
  # @return [void]
69
90
  def insert_middleware_after(app, target, middleware)
70
- # During initialization, app.config.middleware is a MiddlewareStackProxy
71
- # which only supports recording operations (insert_after, use, etc.)
72
- # and does NOT support query methods like include?.
73
- app.config.middleware.insert_after(target, middleware)
91
+ insert_middleware(app.config.middleware, :after, target, middleware)
74
92
  end
75
93
 
76
94
  # @api private
77
95
  # @return [void]
78
96
  def insert_middleware_before(app, target, middleware)
79
- # During initialization, app.config.middleware is a MiddlewareStackProxy
80
- # which only supports recording operations (insert_before, use, etc.)
81
- # and does NOT support query methods like include?.
82
- app.config.middleware.insert_before(target, middleware)
97
+ insert_middleware(app.config.middleware, :before, target, middleware)
98
+ end
99
+
100
+ # During initialization, app.config.middleware is usually a
101
+ # Rails::Configuration::MiddlewareStackProxy. The proxy only records
102
+ # operations, so missing-target errors happen later when Rails builds the
103
+ # real ActionDispatch::MiddlewareStack. Add our own deferred operation so
104
+ # we can fall back at build time instead of crashing or silently skipping.
105
+ def insert_middleware(middleware_stack, location, target, middleware)
106
+ if middleware_stack_proxy?(middleware_stack)
107
+ append_middleware_operation(middleware_stack, location, target, middleware)
108
+ else
109
+ insert_middleware_with_fallback(middleware_stack, location, target, middleware)
110
+ end
83
111
  end
84
112
 
113
+ def middleware_stack_proxy?(middleware_stack)
114
+ defined?(::Rails::Configuration::MiddlewareStackProxy) &&
115
+ middleware_stack.instance_of?(::Rails::Configuration::MiddlewareStackProxy) &&
116
+ middleware_stack.instance_variable_defined?(:@operations)
117
+ end
118
+
119
+ def append_middleware_operation(middleware_stack, location, target, middleware)
120
+ operations = middleware_stack.instance_variable_get(:@operations)
121
+
122
+ if callable_middleware_operations?(middleware_stack, operations)
123
+ operations << lambda do |resolved_stack|
124
+ insert_middleware_with_fallback(resolved_stack, location, target, middleware)
125
+ end
126
+ else
127
+ ensure_middleware_stack_fallback_operation!
128
+ operations << [MIDDLEWARE_FALLBACK_OPERATION, [location, target, middleware], nil]
129
+ end
130
+ end
131
+
132
+ def callable_middleware_operations?(middleware_stack, operations)
133
+ operation = operations.first || probe_middleware_operation(middleware_stack)
134
+
135
+ operation.respond_to?(:call)
136
+ end
137
+
138
+ def probe_middleware_operation(middleware_stack)
139
+ probe = middleware_stack.class.new
140
+ probe.use(Object)
141
+ probe.instance_variable_get(:@operations).first
142
+ end
143
+
144
+ def ensure_middleware_stack_fallback_operation!
145
+ require 'action_dispatch/middleware/stack' unless defined?(::ActionDispatch::MiddlewareStack)
146
+ return if ::ActionDispatch::MiddlewareStack < MiddlewareStackFallback
147
+
148
+ ::ActionDispatch::MiddlewareStack.include(MiddlewareStackFallback)
149
+ end
150
+
151
+ def insert_middleware_with_fallback(middleware_stack, location, target, middleware)
152
+ perform_middleware_insert(middleware_stack, location, target, middleware)
153
+ rescue RuntimeError => e
154
+ raise unless missing_middleware_error?(e)
155
+
156
+ fallback_insert_middleware(middleware_stack, location, target, middleware)
157
+ end
158
+
159
+ def perform_middleware_insert(middleware_stack, location, target, middleware)
160
+ if location == :after
161
+ middleware_stack.insert_after(target, middleware)
162
+ else
163
+ middleware_stack.insert_before(target, middleware)
164
+ end
165
+ end
166
+
167
+ def fallback_insert_middleware(middleware_stack, location, target, middleware)
168
+ fallback_position = if location == :before && middleware_stack.respond_to?(:unshift)
169
+ middleware_stack.unshift(middleware)
170
+ 'beginning'
171
+ else
172
+ middleware_stack.use(middleware)
173
+ 'end'
174
+ end
175
+
176
+ PostHog::Logging.logger.warn(
177
+ "Could not find #{target.inspect} in the Rails middleware stack; " \
178
+ "inserted #{middleware.inspect} at the #{fallback_position} " \
179
+ 'of the stack instead.'
180
+ )
181
+ end
182
+
183
+ def missing_middleware_error?(error)
184
+ error.message.start_with?(MISSING_MIDDLEWARE_MESSAGE)
185
+ end
186
+
187
+ private :insert_middleware,
188
+ :middleware_stack_proxy?,
189
+ :append_middleware_operation,
190
+ :callable_middleware_operations?,
191
+ :probe_middleware_operation,
192
+ :ensure_middleware_stack_fallback_operation!,
193
+ :insert_middleware_with_fallback,
194
+ :perform_middleware_insert,
195
+ :fallback_insert_middleware,
196
+ :missing_middleware_error?
197
+
85
198
  # Build the PostHog Logs pipeline and broadcast Rails.logger into it.
86
199
  #
87
200
  # @api private
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: posthog-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.12.3
4
+ version: 3.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - PostHog
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '3.12'
32
+ version: '3.13'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '3.12'
39
+ version: '3.13'
40
40
  description: Automatic exception tracking and instrumentation for Ruby on Rails applications
41
41
  using PostHog
42
42
  email: engineering@posthog.com