posthog-rails 3.8.1 → 3.9.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: 5f7082d07840d3127b46409cfaa97b2d169cb0520d1589c9c5fdf7cf875a403b
4
- data.tar.gz: 2371da2d51b6977fd8a2544270fae72c8c6e5e9465783ca9fd32bf118b85ae8c
3
+ metadata.gz: 792f6e0ca6b259928ec0704d585e4887fbd4839837b7d49c08fc47b7eeeeeaaf
4
+ data.tar.gz: bc72fab471755cd32695bfdc919aa5cc99559c626504af061e89b80ad98dfd33
5
5
  SHA512:
6
- metadata.gz: 1eb75d92349a07684187e06367675d7e4a844f7c9fb04616e9c85a0c88e94f33a8adce88f092c1a476813523ebfb0b5ee317cb5013b17b9eb15b31209218fed1
7
- data.tar.gz: '0338af574847d6c9f52e1c3f43b7f7b7401ee9966a1d0816d3b1a5967b3a4689d584e1801cfa50101e0106f4692239eb0ac2a9be5e7f7533099bca7266730705'
6
+ metadata.gz: f860543aeca28abfd50a110fa76fd2eed17a86e8c933cbbb0a0fdcdf4002d11bf400d2fb41d5680fa7587de749cad9514e71ad2a0855f1050827fb3da5d7a71b
7
+ data.tar.gz: afb7cb589db961516dea558424cdbe71ff9e20a004a0b944eba6f0aa54fdcb5a4be92acd0dfe6ce88abc84e35a63ca6567ecea4b5fd662018940b05b595aa27b
@@ -22,7 +22,13 @@ PostHog::Rails.configure do |config|
22
22
  # Set to true to enable automatic ActiveJob exception tracking
23
23
  # config.auto_instrument_active_job = true
24
24
 
25
- # Capture user context with exceptions (default: true)
25
+ # Use PostHog tracing headers for request-scoped identity/session context (default: true)
26
+ # Request metadata (current URL, method, path, user agent, and IP) is always captured during Rails requests
27
+ # Set to false to ignore client-supplied X-PostHog-Distinct-Id and X-PostHog-Session-Id headers
28
+ # config.use_tracing_headers = true
29
+
30
+ # Capture authenticated user context with exceptions (default: true)
31
+ # Authenticated Rails user context takes precedence over client-supplied tracing headers for exception identity
26
32
  # config.capture_user_context = true
27
33
 
28
34
  # Controller method name to get current user (default: :current_user)
@@ -13,7 +13,12 @@ module PostHog
13
13
  end
14
14
 
15
15
  module ClassMethods
16
- # DSL for defining how to extract distinct_id from job arguments
16
+ # DSL for defining how to extract distinct_id from job arguments.
17
+ #
18
+ # @param proc [Proc, nil] Callable that receives the job's perform arguments.
19
+ # @yield The block receives the job's perform arguments.
20
+ # @return [Proc, nil] The configured extractor.
21
+ #
17
22
  # Example:
18
23
  # class MyJob < ApplicationJob
19
24
  # posthog_distinct_id ->(user, arg1, arg2) { user.id }
@@ -25,6 +30,7 @@ module PostHog
25
30
  @posthog_distinct_id_proc = proc || block
26
31
  end
27
32
 
33
+ # @return [Proc, nil] The configured distinct_id extractor.
28
34
  def posthog_distinct_id_proc
29
35
  @posthog_distinct_id_proc
30
36
  end
@@ -4,20 +4,26 @@ require 'posthog/rails/parameter_filter'
4
4
 
5
5
  module PostHog
6
6
  module Rails
7
- # Middleware that captures exceptions and sends them to PostHog
7
+ # Middleware that captures exceptions and sends them to PostHog.
8
+ #
9
+ # @api private
8
10
  class CaptureExceptions
9
11
  include ParameterFilter
10
12
 
13
+ # @param app [#call] Rack application.
11
14
  def initialize(app)
12
15
  @app = app
13
16
  end
14
17
 
18
+ # @param env [Hash] Rack environment.
19
+ # @return [Array] Rack response.
15
20
  def call(env)
16
21
  # Signal that we're in a web request context
17
22
  # ErrorSubscriber will skip capture for web requests to avoid duplicates
18
23
  PostHog::Rails.enter_web_request
19
24
 
20
25
  response = @app.call(env)
26
+ env['posthog.response_status_code'] = response_status(response)
21
27
 
22
28
  # Check if there was an exception that Rails handled
23
29
  exception = collect_exception(env)
@@ -51,7 +57,7 @@ module PostHog
51
57
 
52
58
  def capture_exception(exception, env)
53
59
  request = ActionDispatch::Request.new(env)
54
- distinct_id = extract_distinct_id(env, request)
60
+ distinct_id = extract_distinct_id(env)
55
61
  additional_properties = build_properties(request, env)
56
62
 
57
63
  PostHog.capture_exception(exception, distinct_id, additional_properties)
@@ -60,20 +66,21 @@ module PostHog
60
66
  PostHog::Logging.logger.error("Backtrace: #{e.backtrace&.first(5)&.join("\n")}")
61
67
  end
62
68
 
63
- def extract_distinct_id(env, request)
64
- # Try to get user from controller if capture_user_context is enabled
69
+ def extract_distinct_id(env)
70
+ # Prefer authenticated Rails user context. Request/tracing context is
71
+ # applied later by the core capture path if this returns nil.
65
72
  if PostHog::Rails.config&.capture_user_context && env['action_controller.instance']
66
73
  controller = env['action_controller.instance']
67
74
  method_name = PostHog::Rails.config&.current_user_method || :current_user
68
75
 
69
76
  if controller.respond_to?(method_name, true)
70
77
  user = controller.send(method_name)
71
- return extract_user_id(user) if user
78
+ user_id = extract_user_id(user) if user
79
+ return user_id if present?(user_id)
72
80
  end
73
81
  end
74
82
 
75
- # Fallback to session ID or nil
76
- request.session&.id&.to_s
83
+ nil
77
84
  end
78
85
 
79
86
  def extract_user_id(user)
@@ -98,10 +105,7 @@ module PostHog
98
105
 
99
106
  def build_properties(request, env)
100
107
  properties = {
101
- '$exception_source' => 'rails',
102
- '$current_url' => safe_serialize(request.url),
103
- '$request_method' => safe_serialize(request.method),
104
- '$request_path' => safe_serialize(request.path)
108
+ '$exception_source' => 'rails'
105
109
  }
106
110
 
107
111
  # Add controller and action if available
@@ -118,14 +122,23 @@ module PostHog
118
122
  properties['$request_params'] = safe_serialize(filtered_params) unless filtered_params.empty?
119
123
  end
120
124
 
121
- # Add user agent
122
- properties['$user_agent'] = safe_serialize(request.user_agent) if request.user_agent
125
+ response_status_code = env['posthog.response_status_code']
126
+ properties['$response_status_code'] = response_status_code if response_status_code
123
127
 
124
128
  # Add referrer
125
129
  properties['$referrer'] = safe_serialize(request.referrer) if request.referrer
126
130
 
127
131
  properties
128
132
  end
133
+
134
+ def response_status(response)
135
+ status = response.respond_to?(:[]) ? response[0] : nil
136
+ status if status.is_a?(Integer)
137
+ end
138
+
139
+ def present?(value)
140
+ !(value.nil? || (value.respond_to?(:empty?) && value.empty?))
141
+ end
129
142
  end
130
143
  end
131
144
  end
@@ -3,39 +3,47 @@
3
3
  module PostHog
4
4
  module Rails
5
5
  class Configuration
6
- # Whether to automatically capture exceptions from Rails
6
+ # @return [Boolean] Whether to automatically capture exceptions from Rails. Defaults to false.
7
7
  attr_accessor :auto_capture_exceptions
8
8
 
9
- # Whether to capture exceptions that Rails rescues (e.g., with rescue_from)
9
+ # @return [Boolean] Whether to capture exceptions that Rails rescues (e.g., with rescue_from). Defaults to false.
10
10
  attr_accessor :report_rescued_exceptions
11
11
 
12
- # Whether to automatically instrument ActiveJob
12
+ # @return [Boolean] Whether to automatically instrument ActiveJob. Defaults to false.
13
13
  attr_accessor :auto_instrument_active_job
14
14
 
15
- # List of exception classes to ignore (in addition to default)
15
+ # @return [Array<String>] Exception class names to ignore in addition to the defaults.
16
16
  attr_accessor :excluded_exceptions
17
17
 
18
- # Whether to capture the current user context in exceptions
18
+ # @return [Boolean] Whether to use PostHog tracing headers for request-scoped identity/session context.
19
+ # Defaults to true.
20
+ attr_accessor :use_tracing_headers
21
+
22
+ # @return [Boolean] Whether to capture the current user context in exceptions. Defaults to true.
19
23
  attr_accessor :capture_user_context
20
24
 
21
- # Method name to call on controller to get user ID (default: :current_user)
25
+ # @return [Symbol] Method name to call on controller to get the current user. Defaults to :current_user.
22
26
  attr_accessor :current_user_method
23
27
 
24
- # Method name to call on user object to get distinct_id (default: auto-detect)
25
- # When nil, tries: posthog_distinct_id, distinct_id, id, pk, uuid in order
28
+ # @return [Symbol, nil] Method name to call on the user object to get distinct_id. When nil, tries:
29
+ # posthog_distinct_id, distinct_id, id, pk, uuid in order.
26
30
  attr_accessor :user_id_method
27
31
 
32
+ # @return [PostHog::Rails::Configuration]
28
33
  def initialize
29
34
  @auto_capture_exceptions = false
30
35
  @report_rescued_exceptions = false
31
36
  @auto_instrument_active_job = false
32
37
  @excluded_exceptions = []
38
+ @use_tracing_headers = true
33
39
  @capture_user_context = true
34
40
  @current_user_method = :current_user
35
41
  @user_id_method = nil
36
42
  end
37
43
 
38
- # Default exceptions that Rails apps typically don't want to track
44
+ # Default exceptions that Rails apps typically don't want to track.
45
+ #
46
+ # @return [Array<String>]
39
47
  def default_excluded_exceptions
40
48
  [
41
49
  'AbstractController::ActionNotFound',
@@ -54,6 +62,8 @@ module PostHog
54
62
  ]
55
63
  end
56
64
 
65
+ # @param exception [Exception] The exception to check.
66
+ # @return [Boolean] Whether the exception should be captured.
57
67
  def should_capture_exception?(exception)
58
68
  exception_name = exception.class.name
59
69
  !all_excluded_exceptions.include?(exception_name)
@@ -4,11 +4,19 @@ require 'posthog/rails/parameter_filter'
4
4
 
5
5
  module PostHog
6
6
  module Rails
7
- # Rails 7.0+ error reporter integration
8
- # This integrates with Rails.error.handle and Rails.error.record
7
+ # Rails 7.0+ error reporter integration.
8
+ # This integrates with Rails.error.handle and Rails.error.record.
9
+ #
10
+ # @api private
9
11
  class ErrorSubscriber
10
12
  include ParameterFilter
11
13
 
14
+ # @param error [Exception] Error reported by Rails.
15
+ # @param handled [Boolean]
16
+ # @param severity [Symbol, String]
17
+ # @param context [Hash]
18
+ # @param source [String, nil]
19
+ # @return [void]
12
20
  def report(error, handled:, severity:, context:, source: nil)
13
21
  return unless PostHog::Rails.config&.auto_capture_exceptions
14
22
  return unless PostHog::Rails.config&.should_capture_exception?(error)
@@ -9,6 +9,8 @@ module PostHog
9
9
  # It automatically detects the correct Rails parameter filtering API based on
10
10
  # the Rails version.
11
11
  #
12
+ # @api private
13
+ #
12
14
  # @example Usage in a class
13
15
  # class MyClass
14
16
  # include PostHog::Rails::ParameterFilter
@@ -24,10 +26,12 @@ module PostHog
24
26
  MAX_DEPTH = 10
25
27
 
26
28
  if ::Rails.version.to_f >= 6.0
29
+ # @return [Class] Rails parameter filter backend.
27
30
  def self.backend
28
31
  ActiveSupport::ParameterFilter
29
32
  end
30
33
  else
34
+ # @return [Class] Rails parameter filter backend.
31
35
  def self.backend
32
36
  ActionDispatch::Http::ParameterFilter
33
37
  end
@@ -21,7 +21,11 @@ module PostHog
21
21
  get_all_flags
22
22
  ].freeze
23
23
 
24
- # Initialize PostHog client with a block configuration
24
+ # Initialize the singleton PostHog client used by Rails delegators.
25
+ #
26
+ # @param options [Hash] Core {PostHog::Client} options.
27
+ # @yieldparam config [PostHog::Rails::InitConfig] Block-based core SDK configuration.
28
+ # @return [PostHog::Client]
25
29
  def init(options = {})
26
30
  # If block given, yield to configuration
27
31
  if block_given?
@@ -42,11 +46,14 @@ module PostHog
42
46
  end
43
47
  end
44
48
 
49
+ # @return [Boolean] Whether {PostHog.init} has created a client.
45
50
  def initialized?
46
51
  !@client.nil?
47
52
  end
48
53
 
49
- # Fallback for any client methods not explicitly defined
54
+ # Fallback for any client methods not explicitly defined.
55
+ #
56
+ # @api private
50
57
  # rubocop:disable Lint/RedundantSafeNavigation
51
58
  def method_missing(method_name, ...)
52
59
  if client&.respond_to?(method_name)
@@ -57,6 +64,7 @@ module PostHog
57
64
  end
58
65
  end
59
66
 
67
+ # @api private
60
68
  def respond_to_missing?(method_name, include_private = false)
61
69
  client&.respond_to?(method_name) || super
62
70
  end
@@ -73,8 +81,15 @@ module PostHog
73
81
  end
74
82
  end
75
83
 
76
- # Insert middleware for exception capturing
84
+ # Insert middleware for request context and exception capturing
77
85
  initializer 'posthog.insert_middlewares' do |app|
86
+ # Wrap the Rails exception middleware so request context is active for
87
+ # downstream handlers and exception capture.
88
+ insert_middleware_before(
89
+ app, ActionDispatch::ShowExceptions,
90
+ PostHog::Rails::RequestContext
91
+ )
92
+
78
93
  # Insert after DebugExceptions to catch rescued exceptions
79
94
  insert_middleware_after(
80
95
  app, ActionDispatch::DebugExceptions,
@@ -111,6 +126,8 @@ module PostHog
111
126
  at_exit { PostHog.client&.shutdown if PostHog.initialized? }
112
127
  end
113
128
 
129
+ # @api private
130
+ # @return [void]
114
131
  def insert_middleware_after(app, target, middleware)
115
132
  # During initialization, app.config.middleware is a MiddlewareStackProxy
116
133
  # which only supports recording operations (insert_after, use, etc.)
@@ -118,6 +135,17 @@ module PostHog
118
135
  app.config.middleware.insert_after(target, middleware)
119
136
  end
120
137
 
138
+ # @api private
139
+ # @return [void]
140
+ def insert_middleware_before(app, target, middleware)
141
+ # During initialization, app.config.middleware is a MiddlewareStackProxy
142
+ # which only supports recording operations (insert_before, use, etc.)
143
+ # and does NOT support query methods like include?.
144
+ app.config.middleware.insert_before(target, middleware)
145
+ end
146
+
147
+ # @api private
148
+ # @return [void]
121
149
  def self.register_error_subscriber
122
150
  return unless PostHog::Rails.config&.auto_capture_exceptions
123
151
 
@@ -128,6 +156,8 @@ module PostHog
128
156
  PostHog::Logging.logger.warn("Backtrace: #{e.backtrace&.first(5)&.join("\n")}")
129
157
  end
130
158
 
159
+ # @api private
160
+ # @return [Boolean]
131
161
  def self.rails_version_above_7?
132
162
  ::Rails.version.to_f >= 7.0
133
163
  end
@@ -135,51 +165,74 @@ module PostHog
135
165
 
136
166
  # Configuration wrapper for the init block
137
167
  class InitConfig
168
+ # @param base_options [Hash] Initial core SDK options.
138
169
  def initialize(base_options = {})
139
170
  @base_options = base_options
140
171
  end
141
172
 
142
173
  # Core PostHog options
174
+ #
175
+ # @param value [String]
176
+ # @return [String]
143
177
  def api_key=(value)
144
178
  @base_options[:api_key] = value
145
179
  end
146
180
 
181
+ # @param value [String, nil]
182
+ # @return [String, nil]
147
183
  def personal_api_key=(value)
148
184
  @base_options[:personal_api_key] = value
149
185
  end
150
186
 
187
+ # @param value [String]
188
+ # @return [String]
151
189
  def host=(value)
152
190
  @base_options[:host] = value
153
191
  end
154
192
 
193
+ # @param value [Integer]
194
+ # @return [Integer]
155
195
  def max_queue_size=(value)
156
196
  @base_options[:max_queue_size] = value
157
197
  end
158
198
 
199
+ # @param value [Boolean]
200
+ # @return [Boolean]
159
201
  def test_mode=(value)
160
202
  @base_options[:test_mode] = value
161
203
  end
162
204
 
205
+ # @param value [Boolean]
206
+ # @return [Boolean]
163
207
  def sync_mode=(value)
164
208
  @base_options[:sync_mode] = value
165
209
  end
166
210
 
211
+ # @param value [Proc]
212
+ # @return [Proc]
167
213
  def on_error=(value)
168
214
  @base_options[:on_error] = value
169
215
  end
170
216
 
217
+ # @param value [Integer]
218
+ # @return [Integer]
171
219
  def feature_flags_polling_interval=(value)
172
220
  @base_options[:feature_flags_polling_interval] = value
173
221
  end
174
222
 
223
+ # @param value [Integer]
224
+ # @return [Integer]
175
225
  def feature_flag_request_timeout_seconds=(value)
176
226
  @base_options[:feature_flag_request_timeout_seconds] = value
177
227
  end
178
228
 
229
+ # @param value [Proc]
230
+ # @return [Proc]
179
231
  def before_send=(value)
180
232
  @base_options[:before_send] = value
181
233
  end
182
234
 
235
+ # @return [Hash] Core SDK options suitable for {PostHog::Client.new}.
183
236
  def to_client_options
184
237
  @base_options
185
238
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'posthog/internal/context'
4
+ require 'posthog/rails/tracing_headers'
5
+ require 'posthog/rails/request_metadata'
6
+
7
+ module PostHog
8
+ module Rails
9
+ # Rack middleware that creates a request-local PostHog context from tracing headers.
10
+ #
11
+ # @api private
12
+ class RequestContext
13
+ # @param app [#call] Rack application.
14
+ def initialize(app)
15
+ @app = app
16
+ end
17
+
18
+ # @param env [Hash] Rack environment.
19
+ # @return [Array] Rack response.
20
+ def call(env)
21
+ request = build_request(env)
22
+
23
+ Internal::Context.with_context(context_data(request), fresh: true) do
24
+ @app.call(env)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def context_data(request)
31
+ data = { properties: request_properties(request) }
32
+ return data unless use_tracing_headers?
33
+
34
+ data.merge(
35
+ distinct_id: tracing_header(request, 'X-POSTHOG-DISTINCT-ID'),
36
+ session_id: tracing_header(request, 'X-POSTHOG-SESSION-ID')
37
+ )
38
+ end
39
+
40
+ def use_tracing_headers?
41
+ PostHog::Rails.config&.use_tracing_headers != false
42
+ end
43
+
44
+ def request_properties(request)
45
+ RequestMetadata.extract(request)
46
+ end
47
+
48
+ def tracing_header(request, header_name)
49
+ TracingHeaders.extract_header(request, header_name)
50
+ end
51
+
52
+ def build_request(env)
53
+ if defined?(ActionDispatch::Request)
54
+ ActionDispatch::Request.new(env)
55
+ elsif defined?(Rack::Request)
56
+ Rack::Request.new(env)
57
+ else
58
+ env
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'posthog/rails/tracing_headers'
4
+
5
+ module PostHog
6
+ module Rails
7
+ # Internal helpers for extracting request metadata owned by RequestContext.
8
+ #
9
+ # @api private
10
+ module RequestMetadata
11
+ module_function
12
+
13
+ # @param request [Object] Rack or Rails request object.
14
+ # @return [Hash] Event properties extracted from the request.
15
+ def extract(request)
16
+ properties = {}
17
+ add_property(properties, '$current_url', current_url(request))
18
+ request_method = request_value(request, :request_method) || request_value(request, :method)
19
+ add_property(properties, '$request_method', request_method)
20
+ add_property(properties, '$request_path', request_value(request, :path) || request_value(request, :path_info))
21
+ add_property(properties, '$user_agent', TracingHeaders.extract_header(request, 'User-Agent'))
22
+ add_property(properties, '$ip', client_ip(request))
23
+ properties
24
+ end
25
+
26
+ def current_url(request)
27
+ url = request_value(request, :url)
28
+ return if url.nil?
29
+
30
+ url.to_s.split('?', 2).first
31
+ end
32
+ private_class_method :current_url
33
+
34
+ def client_ip(request)
35
+ trusted_ip = request_value(request, :remote_ip) || request_value(request, :ip)
36
+ return trusted_ip if present?(trusted_ip)
37
+
38
+ forwarded_for = TracingHeaders.extract_header(request, 'X-Forwarded-For')
39
+ forwarded_ip = forwarded_for.split(',').first&.strip if forwarded_for
40
+ return forwarded_ip if present?(forwarded_ip)
41
+
42
+ env_value(request, 'REMOTE_ADDR')
43
+ end
44
+ private_class_method :client_ip
45
+
46
+ def present?(value)
47
+ !(value.nil? || (value.respond_to?(:empty?) && value.empty?))
48
+ end
49
+ private_class_method :present?
50
+
51
+ def add_property(properties, key, value)
52
+ return if value.nil?
53
+
54
+ serialized = value.to_s
55
+ return if serialized.empty?
56
+
57
+ properties[key] = serialized
58
+ end
59
+ private_class_method :add_property
60
+
61
+ def request_value(request, method_name)
62
+ return unless request.respond_to?(method_name)
63
+
64
+ request.public_send(method_name)
65
+ rescue StandardError
66
+ nil
67
+ end
68
+ private_class_method :request_value
69
+
70
+ def env_value(request, key)
71
+ request.respond_to?(:get_header) ? request.get_header(key) : request.env[key]
72
+ rescue StandardError
73
+ nil
74
+ end
75
+ private_class_method :env_value
76
+ end
77
+
78
+ private_constant :RequestMetadata
79
+ end
80
+ end
@@ -2,14 +2,19 @@
2
2
 
3
3
  module PostHog
4
4
  module Rails
5
- # Middleware that intercepts exceptions that are rescued by Rails
5
+ # Middleware that intercepts exceptions that are rescued by Rails.
6
6
  # This middleware runs before ShowExceptions and captures the exception
7
- # so we can report it even if Rails rescues it
7
+ # so we can report it even if Rails rescues it.
8
+ #
9
+ # @api private
8
10
  class RescuedExceptionInterceptor
11
+ # @param app [#call] Rack application.
9
12
  def initialize(app)
10
13
  @app = app
11
14
  end
12
15
 
16
+ # @param env [Hash] Rack environment.
17
+ # @return [Array] Rack response.
13
18
  def call(env)
14
19
  @app.call(env)
15
20
  rescue StandardError => e
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PostHog
4
+ module Rails
5
+ # Helpers for extracting and sanitizing PostHog tracing headers from Rack/Rails requests.
6
+ #
7
+ # @api private
8
+ module TracingHeaders
9
+ MAX_HEADER_VALUE_LENGTH = 1000
10
+ CONTROL_CHARACTERS = /[[:cntrl:]]/
11
+
12
+ module_function
13
+
14
+ # @param value [Object]
15
+ # @return [String, nil]
16
+ def sanitize_header_value(value)
17
+ return nil unless value.is_a?(String)
18
+
19
+ sanitized = value.strip.gsub(CONTROL_CHARACTERS, '').strip
20
+ return nil if sanitized.empty?
21
+
22
+ sanitized[0, MAX_HEADER_VALUE_LENGTH]
23
+ end
24
+
25
+ # @param request_or_env [Object, Hash] Rack request, Rails request, or Rack env hash.
26
+ # @param header_name [String]
27
+ # @return [String, nil]
28
+ def extract_header(request_or_env, header_name)
29
+ candidates = header_candidates(header_name)
30
+
31
+ candidates.each do |candidate|
32
+ value = header_value(request_or_env, candidate)
33
+ sanitized = sanitize_header_value(value)
34
+ return sanitized if sanitized
35
+ end
36
+
37
+ env = request_env(request_or_env)
38
+ return nil unless env.respond_to?(:each)
39
+
40
+ target_names = candidates.map { |candidate| normalize_header_name(candidate) }
41
+ env.each do |key, value|
42
+ next unless target_names.include?(normalize_header_name(key))
43
+
44
+ sanitized = sanitize_header_value(value)
45
+ return sanitized if sanitized
46
+ end
47
+
48
+ nil
49
+ end
50
+
51
+ def header_candidates(header_name)
52
+ canonical = header_name.to_s
53
+ rack = "HTTP_#{canonical.upcase.tr('-', '_')}"
54
+ [canonical, canonical.downcase, rack]
55
+ end
56
+ private_class_method :header_candidates
57
+
58
+ def header_value(request_or_env, header_name)
59
+ if request_or_env.respond_to?(:headers)
60
+ value = request_or_env.headers[header_name]
61
+ return value unless value.nil?
62
+ end
63
+
64
+ env = request_env(request_or_env)
65
+ return nil unless env.respond_to?(:[])
66
+
67
+ env[header_name]
68
+ end
69
+ private_class_method :header_value
70
+
71
+ def request_env(request_or_env)
72
+ request_or_env.respond_to?(:env) ? request_or_env.env : request_or_env
73
+ end
74
+ private_class_method :request_env
75
+
76
+ def normalize_header_name(header_name)
77
+ header_name.to_s.upcase.tr('-', '_')
78
+ end
79
+ private_class_method :normalize_header_name
80
+ end
81
+
82
+ private_constant :TracingHeaders
83
+ end
84
+ end
data/lib/posthog/rails.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'posthog/rails/configuration'
4
+ require 'posthog/rails/tracing_headers'
5
+ require 'posthog/rails/request_metadata'
6
+ require 'posthog/rails/request_context'
4
7
  require 'posthog/rails/capture_exceptions'
5
8
  require 'posthog/rails/rescued_exception_interceptor'
6
9
  require 'posthog/rails/active_job'
@@ -15,29 +18,41 @@ module PostHog
15
18
  IN_WEB_REQUEST_KEY = :posthog_in_web_request
16
19
 
17
20
  class << self
21
+ # @return [PostHog::Rails::Configuration] Rails integration configuration.
18
22
  def config
19
23
  @config ||= Configuration.new
20
24
  end
21
25
 
26
+ # @param config [PostHog::Rails::Configuration] Rails integration configuration.
22
27
  attr_writer :config
23
28
 
29
+ # Configure Rails integration options.
30
+ #
31
+ # @yieldparam config [PostHog::Rails::Configuration]
32
+ # @return [void]
24
33
  def configure
25
34
  yield config if block_given?
26
35
  end
27
36
 
28
37
  # Mark that we're in a web request context
29
38
  # CaptureExceptions middleware will handle exception capture
39
+ # @api private
40
+ # @return [void]
30
41
  def enter_web_request
31
42
  Thread.current[IN_WEB_REQUEST_KEY] = true
32
43
  end
33
44
 
34
45
  # Clear web request context (called at end of request)
46
+ # @api private
47
+ # @return [void]
35
48
  def exit_web_request
36
49
  Thread.current[IN_WEB_REQUEST_KEY] = false
37
50
  end
38
51
 
39
52
  # Check if we're currently in a web request context
40
53
  # Used by ErrorSubscriber to avoid duplicate captures
54
+ # @api private
55
+ # @return [Boolean]
41
56
  def in_web_request?
42
57
  Thread.current[IN_WEB_REQUEST_KEY] == true
43
58
  end
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.8.1
4
+ version: 3.9.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.8'
32
+ version: '3.9'
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.8'
39
+ version: '3.9'
40
40
  description: Automatic exception tracking and instrumentation for Ruby on Rails applications
41
41
  using PostHog
42
42
  email: engineering@posthog.com
@@ -54,7 +54,10 @@ files:
54
54
  - lib/posthog/rails/error_subscriber.rb
55
55
  - lib/posthog/rails/parameter_filter.rb
56
56
  - lib/posthog/rails/railtie.rb
57
+ - lib/posthog/rails/request_context.rb
58
+ - lib/posthog/rails/request_metadata.rb
57
59
  - lib/posthog/rails/rescued_exception_interceptor.rb
60
+ - lib/posthog/rails/tracing_headers.rb
58
61
  homepage: https://github.com/PostHog/posthog-ruby
59
62
  licenses:
60
63
  - MIT