posthog-rails 3.8.1 → 3.9.0
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 +4 -4
- data/lib/generators/posthog/templates/posthog.rb +7 -1
- data/lib/posthog/rails/capture_exceptions.rb +20 -12
- data/lib/posthog/rails/configuration.rb +4 -0
- data/lib/posthog/rails/railtie.rb +15 -1
- data/lib/posthog/rails/request_context.rb +58 -0
- data/lib/posthog/rails/request_metadata.rb +76 -0
- data/lib/posthog/rails/tracing_headers.rb +77 -0
- data/lib/posthog/rails.rb +3 -0
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9d0502771ef899181fbcf4acbbaf7b3c72000e5082ef69e98163bf43ae57002
|
|
4
|
+
data.tar.gz: 6002687536b139e97f4e4afc361f17ec519fc0fa54b16416bf888ba55e9b37f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 339d21bdceb568ff493da11539db72fb0ca32d61cc50e18d12facc6a7920761554cc15a2cc85a45adf60a9b8fa324ad6129fb09ca176e180b3c7a4002f08e2ec
|
|
7
|
+
data.tar.gz: b6f2417f98317d42a5cdad3f15e7b2ae1d00254fd63571768a01956ffbb4cc0e398892409f65eb69167941a19392fc549c33406723b3cc36cef38b8f0f49017c
|
|
@@ -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
|
-
#
|
|
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)
|
|
@@ -18,6 +18,7 @@ module PostHog
|
|
|
18
18
|
PostHog::Rails.enter_web_request
|
|
19
19
|
|
|
20
20
|
response = @app.call(env)
|
|
21
|
+
env['posthog.response_status_code'] = response_status(response)
|
|
21
22
|
|
|
22
23
|
# Check if there was an exception that Rails handled
|
|
23
24
|
exception = collect_exception(env)
|
|
@@ -51,7 +52,7 @@ module PostHog
|
|
|
51
52
|
|
|
52
53
|
def capture_exception(exception, env)
|
|
53
54
|
request = ActionDispatch::Request.new(env)
|
|
54
|
-
distinct_id = extract_distinct_id(env
|
|
55
|
+
distinct_id = extract_distinct_id(env)
|
|
55
56
|
additional_properties = build_properties(request, env)
|
|
56
57
|
|
|
57
58
|
PostHog.capture_exception(exception, distinct_id, additional_properties)
|
|
@@ -60,20 +61,21 @@ module PostHog
|
|
|
60
61
|
PostHog::Logging.logger.error("Backtrace: #{e.backtrace&.first(5)&.join("\n")}")
|
|
61
62
|
end
|
|
62
63
|
|
|
63
|
-
def extract_distinct_id(env
|
|
64
|
-
#
|
|
64
|
+
def extract_distinct_id(env)
|
|
65
|
+
# Prefer authenticated Rails user context. Request/tracing context is
|
|
66
|
+
# applied later by the core capture path if this returns nil.
|
|
65
67
|
if PostHog::Rails.config&.capture_user_context && env['action_controller.instance']
|
|
66
68
|
controller = env['action_controller.instance']
|
|
67
69
|
method_name = PostHog::Rails.config&.current_user_method || :current_user
|
|
68
70
|
|
|
69
71
|
if controller.respond_to?(method_name, true)
|
|
70
72
|
user = controller.send(method_name)
|
|
71
|
-
|
|
73
|
+
user_id = extract_user_id(user) if user
|
|
74
|
+
return user_id if present?(user_id)
|
|
72
75
|
end
|
|
73
76
|
end
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
request.session&.id&.to_s
|
|
78
|
+
nil
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
def extract_user_id(user)
|
|
@@ -98,10 +100,7 @@ module PostHog
|
|
|
98
100
|
|
|
99
101
|
def build_properties(request, env)
|
|
100
102
|
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)
|
|
103
|
+
'$exception_source' => 'rails'
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
# Add controller and action if available
|
|
@@ -118,14 +117,23 @@ module PostHog
|
|
|
118
117
|
properties['$request_params'] = safe_serialize(filtered_params) unless filtered_params.empty?
|
|
119
118
|
end
|
|
120
119
|
|
|
121
|
-
|
|
122
|
-
properties['$
|
|
120
|
+
response_status_code = env['posthog.response_status_code']
|
|
121
|
+
properties['$response_status_code'] = response_status_code if response_status_code
|
|
123
122
|
|
|
124
123
|
# Add referrer
|
|
125
124
|
properties['$referrer'] = safe_serialize(request.referrer) if request.referrer
|
|
126
125
|
|
|
127
126
|
properties
|
|
128
127
|
end
|
|
128
|
+
|
|
129
|
+
def response_status(response)
|
|
130
|
+
status = response.respond_to?(:[]) ? response[0] : nil
|
|
131
|
+
status if status.is_a?(Integer)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def present?(value)
|
|
135
|
+
!(value.nil? || (value.respond_to?(:empty?) && value.empty?))
|
|
136
|
+
end
|
|
129
137
|
end
|
|
130
138
|
end
|
|
131
139
|
end
|
|
@@ -15,6 +15,9 @@ module PostHog
|
|
|
15
15
|
# List of exception classes to ignore (in addition to default)
|
|
16
16
|
attr_accessor :excluded_exceptions
|
|
17
17
|
|
|
18
|
+
# Whether to use PostHog tracing headers for request-scoped identity/session context
|
|
19
|
+
attr_accessor :use_tracing_headers
|
|
20
|
+
|
|
18
21
|
# Whether to capture the current user context in exceptions
|
|
19
22
|
attr_accessor :capture_user_context
|
|
20
23
|
|
|
@@ -30,6 +33,7 @@ module PostHog
|
|
|
30
33
|
@report_rescued_exceptions = false
|
|
31
34
|
@auto_instrument_active_job = false
|
|
32
35
|
@excluded_exceptions = []
|
|
36
|
+
@use_tracing_headers = true
|
|
33
37
|
@capture_user_context = true
|
|
34
38
|
@current_user_method = :current_user
|
|
35
39
|
@user_id_method = nil
|
|
@@ -73,8 +73,15 @@ module PostHog
|
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
# Insert middleware for exception capturing
|
|
76
|
+
# Insert middleware for request context and exception capturing
|
|
77
77
|
initializer 'posthog.insert_middlewares' do |app|
|
|
78
|
+
# Wrap the Rails exception middleware so request context is active for
|
|
79
|
+
# downstream handlers and exception capture.
|
|
80
|
+
insert_middleware_before(
|
|
81
|
+
app, ActionDispatch::ShowExceptions,
|
|
82
|
+
PostHog::Rails::RequestContext
|
|
83
|
+
)
|
|
84
|
+
|
|
78
85
|
# Insert after DebugExceptions to catch rescued exceptions
|
|
79
86
|
insert_middleware_after(
|
|
80
87
|
app, ActionDispatch::DebugExceptions,
|
|
@@ -118,6 +125,13 @@ module PostHog
|
|
|
118
125
|
app.config.middleware.insert_after(target, middleware)
|
|
119
126
|
end
|
|
120
127
|
|
|
128
|
+
def insert_middleware_before(app, target, middleware)
|
|
129
|
+
# During initialization, app.config.middleware is a MiddlewareStackProxy
|
|
130
|
+
# which only supports recording operations (insert_before, use, etc.)
|
|
131
|
+
# and does NOT support query methods like include?.
|
|
132
|
+
app.config.middleware.insert_before(target, middleware)
|
|
133
|
+
end
|
|
134
|
+
|
|
121
135
|
def self.register_error_subscriber
|
|
122
136
|
return unless PostHog::Rails.config&.auto_capture_exceptions
|
|
123
137
|
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
class RequestContext
|
|
11
|
+
def initialize(app)
|
|
12
|
+
@app = app
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(env)
|
|
16
|
+
request = build_request(env)
|
|
17
|
+
|
|
18
|
+
Internal::Context.with_context(context_data(request), fresh: true) do
|
|
19
|
+
@app.call(env)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def context_data(request)
|
|
26
|
+
data = { properties: request_properties(request) }
|
|
27
|
+
return data unless use_tracing_headers?
|
|
28
|
+
|
|
29
|
+
data.merge(
|
|
30
|
+
distinct_id: tracing_header(request, 'X-POSTHOG-DISTINCT-ID'),
|
|
31
|
+
session_id: tracing_header(request, 'X-POSTHOG-SESSION-ID')
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def use_tracing_headers?
|
|
36
|
+
PostHog::Rails.config&.use_tracing_headers != false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def request_properties(request)
|
|
40
|
+
RequestMetadata.extract(request)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def tracing_header(request, header_name)
|
|
44
|
+
TracingHeaders.extract_header(request, header_name)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build_request(env)
|
|
48
|
+
if defined?(ActionDispatch::Request)
|
|
49
|
+
ActionDispatch::Request.new(env)
|
|
50
|
+
elsif defined?(Rack::Request)
|
|
51
|
+
Rack::Request.new(env)
|
|
52
|
+
else
|
|
53
|
+
env
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
module RequestMetadata
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def extract(request)
|
|
12
|
+
properties = {}
|
|
13
|
+
add_property(properties, '$current_url', current_url(request))
|
|
14
|
+
request_method = request_value(request, :request_method) || request_value(request, :method)
|
|
15
|
+
add_property(properties, '$request_method', request_method)
|
|
16
|
+
add_property(properties, '$request_path', request_value(request, :path) || request_value(request, :path_info))
|
|
17
|
+
add_property(properties, '$user_agent', TracingHeaders.extract_header(request, 'User-Agent'))
|
|
18
|
+
add_property(properties, '$ip', client_ip(request))
|
|
19
|
+
properties
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def current_url(request)
|
|
23
|
+
url = request_value(request, :url)
|
|
24
|
+
return if url.nil?
|
|
25
|
+
|
|
26
|
+
url.to_s.split('?', 2).first
|
|
27
|
+
end
|
|
28
|
+
private_class_method :current_url
|
|
29
|
+
|
|
30
|
+
def client_ip(request)
|
|
31
|
+
trusted_ip = request_value(request, :remote_ip) || request_value(request, :ip)
|
|
32
|
+
return trusted_ip if present?(trusted_ip)
|
|
33
|
+
|
|
34
|
+
forwarded_for = TracingHeaders.extract_header(request, 'X-Forwarded-For')
|
|
35
|
+
forwarded_ip = forwarded_for.split(',').first&.strip if forwarded_for
|
|
36
|
+
return forwarded_ip if present?(forwarded_ip)
|
|
37
|
+
|
|
38
|
+
env_value(request, 'REMOTE_ADDR')
|
|
39
|
+
end
|
|
40
|
+
private_class_method :client_ip
|
|
41
|
+
|
|
42
|
+
def present?(value)
|
|
43
|
+
!(value.nil? || (value.respond_to?(:empty?) && value.empty?))
|
|
44
|
+
end
|
|
45
|
+
private_class_method :present?
|
|
46
|
+
|
|
47
|
+
def add_property(properties, key, value)
|
|
48
|
+
return if value.nil?
|
|
49
|
+
|
|
50
|
+
serialized = value.to_s
|
|
51
|
+
return if serialized.empty?
|
|
52
|
+
|
|
53
|
+
properties[key] = serialized
|
|
54
|
+
end
|
|
55
|
+
private_class_method :add_property
|
|
56
|
+
|
|
57
|
+
def request_value(request, method_name)
|
|
58
|
+
return unless request.respond_to?(method_name)
|
|
59
|
+
|
|
60
|
+
request.public_send(method_name)
|
|
61
|
+
rescue StandardError
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
private_class_method :request_value
|
|
65
|
+
|
|
66
|
+
def env_value(request, key)
|
|
67
|
+
request.respond_to?(:get_header) ? request.get_header(key) : request.env[key]
|
|
68
|
+
rescue StandardError
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
private_class_method :env_value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private_constant :RequestMetadata
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
module TracingHeaders
|
|
7
|
+
MAX_HEADER_VALUE_LENGTH = 1000
|
|
8
|
+
CONTROL_CHARACTERS = /[[:cntrl:]]/
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def sanitize_header_value(value)
|
|
13
|
+
return nil unless value.is_a?(String)
|
|
14
|
+
|
|
15
|
+
sanitized = value.strip.gsub(CONTROL_CHARACTERS, '').strip
|
|
16
|
+
return nil if sanitized.empty?
|
|
17
|
+
|
|
18
|
+
sanitized[0, MAX_HEADER_VALUE_LENGTH]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def extract_header(request_or_env, header_name)
|
|
22
|
+
candidates = header_candidates(header_name)
|
|
23
|
+
|
|
24
|
+
candidates.each do |candidate|
|
|
25
|
+
value = header_value(request_or_env, candidate)
|
|
26
|
+
sanitized = sanitize_header_value(value)
|
|
27
|
+
return sanitized if sanitized
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
env = request_env(request_or_env)
|
|
31
|
+
return nil unless env.respond_to?(:each)
|
|
32
|
+
|
|
33
|
+
target_names = candidates.map { |candidate| normalize_header_name(candidate) }
|
|
34
|
+
env.each do |key, value|
|
|
35
|
+
next unless target_names.include?(normalize_header_name(key))
|
|
36
|
+
|
|
37
|
+
sanitized = sanitize_header_value(value)
|
|
38
|
+
return sanitized if sanitized
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def header_candidates(header_name)
|
|
45
|
+
canonical = header_name.to_s
|
|
46
|
+
rack = "HTTP_#{canonical.upcase.tr('-', '_')}"
|
|
47
|
+
[canonical, canonical.downcase, rack]
|
|
48
|
+
end
|
|
49
|
+
private_class_method :header_candidates
|
|
50
|
+
|
|
51
|
+
def header_value(request_or_env, header_name)
|
|
52
|
+
if request_or_env.respond_to?(:headers)
|
|
53
|
+
value = request_or_env.headers[header_name]
|
|
54
|
+
return value unless value.nil?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
env = request_env(request_or_env)
|
|
58
|
+
return nil unless env.respond_to?(:[])
|
|
59
|
+
|
|
60
|
+
env[header_name]
|
|
61
|
+
end
|
|
62
|
+
private_class_method :header_value
|
|
63
|
+
|
|
64
|
+
def request_env(request_or_env)
|
|
65
|
+
request_or_env.respond_to?(:env) ? request_or_env.env : request_or_env
|
|
66
|
+
end
|
|
67
|
+
private_class_method :request_env
|
|
68
|
+
|
|
69
|
+
def normalize_header_name(header_name)
|
|
70
|
+
header_name.to_s.upcase.tr('-', '_')
|
|
71
|
+
end
|
|
72
|
+
private_class_method :normalize_header_name
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private_constant :TracingHeaders
|
|
76
|
+
end
|
|
77
|
+
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'
|
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.
|
|
4
|
+
version: 3.9.0
|
|
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.
|
|
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.
|
|
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
|