posthog-rails 3.5.0 → 3.5.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 +4 -4
- data/lib/generators/posthog/install_generator.rb +31 -0
- data/lib/posthog/rails/active_job.rb +106 -0
- data/lib/posthog/rails/capture_exceptions.rb +131 -0
- data/lib/posthog/rails/configuration.rb +69 -0
- data/lib/posthog/rails/error_subscriber.rb +43 -0
- data/lib/posthog/rails/parameter_filter.rb +128 -0
- data/lib/posthog/rails/railtie.rb +186 -0
- data/lib/posthog/rails/rescued_exception_interceptor.rb +28 -0
- data/lib/posthog/rails.rb +46 -0
- data/lib/posthog-rails.rb +11 -0
- metadata +13 -25
- data/lib/posthog/backoff_policy.rb +0 -46
- data/lib/posthog/client.rb +0 -545
- data/lib/posthog/defaults.rb +0 -44
- data/lib/posthog/exception_capture.rb +0 -116
- data/lib/posthog/feature_flag.rb +0 -66
- data/lib/posthog/feature_flag_error.rb +0 -36
- data/lib/posthog/feature_flag_result.rb +0 -56
- data/lib/posthog/feature_flags.rb +0 -1004
- data/lib/posthog/field_parser.rb +0 -194
- data/lib/posthog/logging.rb +0 -70
- data/lib/posthog/message_batch.rb +0 -73
- data/lib/posthog/noop_worker.rb +0 -19
- data/lib/posthog/response.rb +0 -15
- data/lib/posthog/send_feature_flags_options.rb +0 -34
- data/lib/posthog/send_worker.rb +0 -70
- data/lib/posthog/transport.rb +0 -144
- data/lib/posthog/utils.rb +0 -145
- data/lib/posthog/version.rb +0 -5
- data/lib/posthog.rb +0 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea6edcb493fd06b49850a250f1c4c4b41ececd9c875918472419a8c7ccf7cca0
|
|
4
|
+
data.tar.gz: f2ef41e5646688bbd593b6f002b0a7b6514cce97a65d60013c6a4664cebf854d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3941f7f40575f422511be6326f421bafa4d4b24b5996d84aa0c7a24b4e30b79770449ce6064248a1af6a9b61121990fd838394666a0fed16ae5cb661af53c608
|
|
7
|
+
data.tar.gz: 0edf54ddca0d8f08d674dd8bf0102a6bfd77378ce4ce92027256f7331178d20c56bc3787d30b1b9610c9b74ef5b08bcb0e3f4d66b040d10300ecb8584044b1bc
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
|
|
5
|
+
module Posthog
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
8
|
+
desc 'Creates a PostHog initializer file at config/initializers/posthog.rb'
|
|
9
|
+
|
|
10
|
+
source_root File.expand_path('../../..', __dir__)
|
|
11
|
+
|
|
12
|
+
def copy_initializer
|
|
13
|
+
copy_file 'examples/posthog.rb', 'config/initializers/posthog.rb'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def show_readme
|
|
17
|
+
say ''
|
|
18
|
+
say 'PostHog Rails has been installed!', :green
|
|
19
|
+
say ''
|
|
20
|
+
say 'Next steps:', :yellow
|
|
21
|
+
say ' 1. Edit config/initializers/posthog.rb with your PostHog API key'
|
|
22
|
+
say ' 2. Set environment variables:'
|
|
23
|
+
say ' - POSTHOG_API_KEY (required)'
|
|
24
|
+
say ' - POSTHOG_PERSONAL_API_KEY (optional, for feature flags)'
|
|
25
|
+
say ''
|
|
26
|
+
say 'For more information, see: https://posthog.com/docs/libraries/ruby'
|
|
27
|
+
say ''
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'posthog/rails/parameter_filter'
|
|
4
|
+
|
|
5
|
+
module PostHog
|
|
6
|
+
module Rails
|
|
7
|
+
# ActiveJob integration to capture exceptions from background jobs
|
|
8
|
+
module ActiveJobExtensions
|
|
9
|
+
include ParameterFilter
|
|
10
|
+
|
|
11
|
+
def self.prepended(base)
|
|
12
|
+
base.extend(ClassMethods)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module ClassMethods
|
|
16
|
+
# DSL for defining how to extract distinct_id from job arguments
|
|
17
|
+
# Example:
|
|
18
|
+
# class MyJob < ApplicationJob
|
|
19
|
+
# posthog_distinct_id ->(user, arg1, arg2) { user.id }
|
|
20
|
+
# def perform(user, arg1, arg2)
|
|
21
|
+
# # ...
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
def posthog_distinct_id(proc = nil, &block)
|
|
25
|
+
@posthog_distinct_id_proc = proc || block
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def posthog_distinct_id_proc
|
|
29
|
+
@posthog_distinct_id_proc
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def perform_now
|
|
34
|
+
super
|
|
35
|
+
rescue StandardError => e
|
|
36
|
+
# Capture the exception with job context
|
|
37
|
+
capture_job_exception(e)
|
|
38
|
+
raise
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def capture_job_exception(exception)
|
|
44
|
+
return unless PostHog::Rails.config&.auto_instrument_active_job
|
|
45
|
+
|
|
46
|
+
# Build distinct_id from job arguments if possible
|
|
47
|
+
distinct_id = extract_distinct_id_from_job
|
|
48
|
+
|
|
49
|
+
properties = {
|
|
50
|
+
'$exception_source' => 'active_job',
|
|
51
|
+
'$job_class' => self.class.name,
|
|
52
|
+
'$job_id' => job_id,
|
|
53
|
+
'$queue_name' => queue_name,
|
|
54
|
+
'$job_priority' => priority,
|
|
55
|
+
'$job_executions' => executions
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Add serialized job arguments (be careful with sensitive data)
|
|
59
|
+
properties['$job_arguments'] = sanitize_job_arguments(arguments) if arguments.present?
|
|
60
|
+
|
|
61
|
+
PostHog.capture_exception(exception, distinct_id, properties)
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
# Don't let PostHog errors break job processing
|
|
64
|
+
PostHog::Logging.logger.error("Failed to capture job exception: #{e.message}")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def extract_distinct_id_from_job
|
|
68
|
+
# First, check if the job class defines a custom extractor
|
|
69
|
+
return self.class.posthog_distinct_id_proc.call(*arguments) if self.class.posthog_distinct_id_proc
|
|
70
|
+
|
|
71
|
+
# Fallback: look for explicit user_id in hash arguments only
|
|
72
|
+
arguments.each do |arg|
|
|
73
|
+
if arg.is_a?(Hash) && arg['user_id']
|
|
74
|
+
return arg['user_id']
|
|
75
|
+
elsif arg.is_a?(Hash) && arg[:user_id]
|
|
76
|
+
return arg[:user_id]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
nil # No user context found
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def sanitize_job_arguments(args)
|
|
84
|
+
# Convert arguments to a safe format
|
|
85
|
+
args.map do |arg|
|
|
86
|
+
case arg
|
|
87
|
+
when String
|
|
88
|
+
# Truncate long strings to prevent huge payloads
|
|
89
|
+
arg.length > 100 ? "[FILTERED: #{arg.length} chars]" : arg
|
|
90
|
+
when Integer, Float, TrueClass, FalseClass, NilClass
|
|
91
|
+
arg
|
|
92
|
+
when Hash
|
|
93
|
+
# Use Rails' filter_parameters to filter sensitive data
|
|
94
|
+
filter_sensitive_params(arg)
|
|
95
|
+
when defined?(ActiveRecord::Base) && ActiveRecord::Base
|
|
96
|
+
{ class: arg.class.name, id: arg.id }
|
|
97
|
+
else
|
|
98
|
+
arg.class.name
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
rescue StandardError
|
|
102
|
+
['<serialization error>']
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'posthog/rails/parameter_filter'
|
|
4
|
+
|
|
5
|
+
module PostHog
|
|
6
|
+
module Rails
|
|
7
|
+
# Middleware that captures exceptions and sends them to PostHog
|
|
8
|
+
class CaptureExceptions
|
|
9
|
+
include ParameterFilter
|
|
10
|
+
|
|
11
|
+
def initialize(app)
|
|
12
|
+
@app = app
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(env)
|
|
16
|
+
# Signal that we're in a web request context
|
|
17
|
+
# ErrorSubscriber will skip capture for web requests to avoid duplicates
|
|
18
|
+
PostHog::Rails.enter_web_request
|
|
19
|
+
|
|
20
|
+
response = @app.call(env)
|
|
21
|
+
|
|
22
|
+
# Check if there was an exception that Rails handled
|
|
23
|
+
exception = collect_exception(env)
|
|
24
|
+
|
|
25
|
+
capture_exception(exception, env) if exception && should_capture?(exception)
|
|
26
|
+
|
|
27
|
+
response
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
# Capture unhandled exceptions
|
|
30
|
+
capture_exception(e, env) if should_capture?(e)
|
|
31
|
+
raise
|
|
32
|
+
ensure
|
|
33
|
+
PostHog::Rails.exit_web_request
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def collect_exception(env)
|
|
39
|
+
# Rails stores exceptions in these env keys
|
|
40
|
+
env['action_dispatch.exception'] ||
|
|
41
|
+
env['rack.exception'] ||
|
|
42
|
+
env['posthog.rescued_exception']
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def should_capture?(exception)
|
|
46
|
+
return false unless PostHog::Rails.config&.auto_capture_exceptions
|
|
47
|
+
return false unless PostHog::Rails.config&.should_capture_exception?(exception)
|
|
48
|
+
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def capture_exception(exception, env)
|
|
53
|
+
request = ActionDispatch::Request.new(env)
|
|
54
|
+
distinct_id = extract_distinct_id(env, request)
|
|
55
|
+
additional_properties = build_properties(request, env)
|
|
56
|
+
|
|
57
|
+
PostHog.capture_exception(exception, distinct_id, additional_properties)
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
PostHog::Logging.logger.error("Failed to capture exception: #{e.message}")
|
|
60
|
+
PostHog::Logging.logger.error("Backtrace: #{e.backtrace&.first(5)&.join("\n")}")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def extract_distinct_id(env, request)
|
|
64
|
+
# Try to get user from controller if capture_user_context is enabled
|
|
65
|
+
if PostHog::Rails.config&.capture_user_context && env['action_controller.instance']
|
|
66
|
+
controller = env['action_controller.instance']
|
|
67
|
+
method_name = PostHog::Rails.config&.current_user_method || :current_user
|
|
68
|
+
|
|
69
|
+
if controller.respond_to?(method_name, true)
|
|
70
|
+
user = controller.send(method_name)
|
|
71
|
+
return extract_user_id(user) if user
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Fallback to session ID or nil
|
|
76
|
+
request.session&.id&.to_s
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def extract_user_id(user)
|
|
80
|
+
# Use configured method if specified
|
|
81
|
+
method_name = PostHog::Rails.config&.user_id_method
|
|
82
|
+
return user.send(method_name) if method_name && user.respond_to?(method_name)
|
|
83
|
+
|
|
84
|
+
# Try explicit PostHog method (allows users to customize without config)
|
|
85
|
+
return user.posthog_distinct_id if user.respond_to?(:posthog_distinct_id)
|
|
86
|
+
return user.distinct_id if user.respond_to?(:distinct_id)
|
|
87
|
+
|
|
88
|
+
# Try common ID methods
|
|
89
|
+
return user.id if user.respond_to?(:id)
|
|
90
|
+
return user['id'] if user.respond_to?(:[]) && user['id']
|
|
91
|
+
return user.pk if user.respond_to?(:pk)
|
|
92
|
+
return user['pk'] if user.respond_to?(:[]) && user['pk']
|
|
93
|
+
return user.uuid if user.respond_to?(:uuid)
|
|
94
|
+
return user['uuid'] if user.respond_to?(:[]) && user['uuid']
|
|
95
|
+
|
|
96
|
+
user.to_s
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def build_properties(request, env)
|
|
100
|
+
properties = {
|
|
101
|
+
'$exception_source' => 'rails',
|
|
102
|
+
'$request_url' => safe_serialize(request.url),
|
|
103
|
+
'$request_method' => safe_serialize(request.method),
|
|
104
|
+
'$request_path' => safe_serialize(request.path)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Add controller and action if available
|
|
108
|
+
if env['action_controller.instance']
|
|
109
|
+
controller = env['action_controller.instance']
|
|
110
|
+
properties['$controller'] = safe_serialize(controller.controller_name)
|
|
111
|
+
properties['$action'] = safe_serialize(controller.action_name)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Add request parameters (be careful with sensitive data)
|
|
115
|
+
if request.params.present?
|
|
116
|
+
filtered_params = filter_sensitive_params(request.params)
|
|
117
|
+
# Safe serialize to handle any complex objects in params
|
|
118
|
+
properties['$request_params'] = safe_serialize(filtered_params) unless filtered_params.empty?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Add user agent
|
|
122
|
+
properties['$user_agent'] = safe_serialize(request.user_agent) if request.user_agent
|
|
123
|
+
|
|
124
|
+
# Add referrer
|
|
125
|
+
properties['$referrer'] = safe_serialize(request.referrer) if request.referrer
|
|
126
|
+
|
|
127
|
+
properties
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PostHog
|
|
4
|
+
module Rails
|
|
5
|
+
class Configuration
|
|
6
|
+
# Whether to automatically capture exceptions from Rails
|
|
7
|
+
attr_accessor :auto_capture_exceptions
|
|
8
|
+
|
|
9
|
+
# Whether to capture exceptions that Rails rescues (e.g., with rescue_from)
|
|
10
|
+
attr_accessor :report_rescued_exceptions
|
|
11
|
+
|
|
12
|
+
# Whether to automatically instrument ActiveJob
|
|
13
|
+
attr_accessor :auto_instrument_active_job
|
|
14
|
+
|
|
15
|
+
# List of exception classes to ignore (in addition to default)
|
|
16
|
+
attr_accessor :excluded_exceptions
|
|
17
|
+
|
|
18
|
+
# Whether to capture the current user context in exceptions
|
|
19
|
+
attr_accessor :capture_user_context
|
|
20
|
+
|
|
21
|
+
# Method name to call on controller to get user ID (default: :current_user)
|
|
22
|
+
attr_accessor :current_user_method
|
|
23
|
+
|
|
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
|
|
26
|
+
attr_accessor :user_id_method
|
|
27
|
+
|
|
28
|
+
def initialize
|
|
29
|
+
@auto_capture_exceptions = false
|
|
30
|
+
@report_rescued_exceptions = false
|
|
31
|
+
@auto_instrument_active_job = false
|
|
32
|
+
@excluded_exceptions = []
|
|
33
|
+
@capture_user_context = true
|
|
34
|
+
@current_user_method = :current_user
|
|
35
|
+
@user_id_method = nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Default exceptions that Rails apps typically don't want to track
|
|
39
|
+
def default_excluded_exceptions
|
|
40
|
+
[
|
|
41
|
+
'AbstractController::ActionNotFound',
|
|
42
|
+
'ActionController::BadRequest',
|
|
43
|
+
'ActionController::InvalidAuthenticityToken',
|
|
44
|
+
'ActionController::InvalidCrossOriginRequest',
|
|
45
|
+
'ActionController::MethodNotAllowed',
|
|
46
|
+
'ActionController::NotImplemented',
|
|
47
|
+
'ActionController::ParameterMissing',
|
|
48
|
+
'ActionController::RoutingError',
|
|
49
|
+
'ActionController::UnknownFormat',
|
|
50
|
+
'ActionController::UnknownHttpMethod',
|
|
51
|
+
'ActionDispatch::Http::Parameters::ParseError',
|
|
52
|
+
'ActiveRecord::RecordNotFound',
|
|
53
|
+
'ActiveRecord::RecordNotUnique'
|
|
54
|
+
]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def should_capture_exception?(exception)
|
|
58
|
+
exception_name = exception.class.name
|
|
59
|
+
!all_excluded_exceptions.include?(exception_name)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def all_excluded_exceptions
|
|
65
|
+
default_excluded_exceptions + excluded_exceptions
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'posthog/rails/parameter_filter'
|
|
4
|
+
|
|
5
|
+
module PostHog
|
|
6
|
+
module Rails
|
|
7
|
+
# Rails 7.0+ error reporter integration
|
|
8
|
+
# This integrates with Rails.error.handle and Rails.error.record
|
|
9
|
+
class ErrorSubscriber
|
|
10
|
+
include ParameterFilter
|
|
11
|
+
|
|
12
|
+
def report(error, handled:, severity:, context:, source: nil)
|
|
13
|
+
return unless PostHog::Rails.config&.auto_capture_exceptions
|
|
14
|
+
return unless PostHog::Rails.config&.should_capture_exception?(error)
|
|
15
|
+
# Skip if in a web request - CaptureExceptions middleware will handle it
|
|
16
|
+
# with richer context (URL, params, controller, etc.)
|
|
17
|
+
return if PostHog::Rails.in_web_request?
|
|
18
|
+
|
|
19
|
+
distinct_id = context[:user_id] || context[:distinct_id]
|
|
20
|
+
|
|
21
|
+
properties = {
|
|
22
|
+
'$exception_source' => source || 'rails_error_reporter',
|
|
23
|
+
'$exception_handled' => handled,
|
|
24
|
+
'$exception_severity' => severity.to_s
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Add context information (safely serialized to avoid circular references)
|
|
28
|
+
if context.present?
|
|
29
|
+
context.each do |key, value|
|
|
30
|
+
next if key.in?(%i[user_id distinct_id])
|
|
31
|
+
|
|
32
|
+
properties["$context_#{key}"] = safe_serialize(value)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
PostHog.capture_exception(error, distinct_id, properties)
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
PostHog::Logging.logger.error("Failed to report error via subscriber: #{e.message}")
|
|
39
|
+
PostHog::Logging.logger.error("Backtrace: #{e.backtrace&.first(5)&.join("\n")}")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PostHog
|
|
4
|
+
module Rails
|
|
5
|
+
# Shared utility module for filtering sensitive parameters
|
|
6
|
+
#
|
|
7
|
+
# This module provides consistent parameter filtering across all PostHog Rails
|
|
8
|
+
# components, leveraging Rails' built-in parameter filtering when available.
|
|
9
|
+
# It automatically detects the correct Rails parameter filtering API based on
|
|
10
|
+
# the Rails version.
|
|
11
|
+
#
|
|
12
|
+
# @example Usage in a class
|
|
13
|
+
# class MyClass
|
|
14
|
+
# include PostHog::Rails::ParameterFilter
|
|
15
|
+
#
|
|
16
|
+
# def my_method(params)
|
|
17
|
+
# filtered = filter_sensitive_params(params)
|
|
18
|
+
# PostHog.capture(event: 'something', properties: filtered)
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
module ParameterFilter
|
|
22
|
+
EMPTY_HASH = {}.freeze
|
|
23
|
+
MAX_STRING_LENGTH = 10_000
|
|
24
|
+
MAX_DEPTH = 10
|
|
25
|
+
|
|
26
|
+
if ::Rails.version.to_f >= 6.0
|
|
27
|
+
def self.backend
|
|
28
|
+
ActiveSupport::ParameterFilter
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
def self.backend
|
|
32
|
+
ActionDispatch::Http::ParameterFilter
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Filter sensitive parameters from a hash, respecting Rails configuration.
|
|
37
|
+
#
|
|
38
|
+
# Uses Rails' configured filter_parameters (e.g., :password, :token, :api_key)
|
|
39
|
+
# to automatically filter sensitive data that the Rails app has configured.
|
|
40
|
+
#
|
|
41
|
+
# @param params [Hash] The parameters to filter
|
|
42
|
+
# @return [Hash] Filtered parameters with sensitive data masked
|
|
43
|
+
def filter_sensitive_params(params)
|
|
44
|
+
return EMPTY_HASH unless params.is_a?(Hash)
|
|
45
|
+
return params unless ::Rails.application
|
|
46
|
+
|
|
47
|
+
filter_parameters = ::Rails.application.config.filter_parameters
|
|
48
|
+
parameter_filter = ParameterFilter.backend.new(filter_parameters)
|
|
49
|
+
|
|
50
|
+
parameter_filter.filter(params)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Safely serialize a value to a JSON-compatible format.
|
|
54
|
+
#
|
|
55
|
+
# Handles circular references and complex objects by converting them to
|
|
56
|
+
# simple primitives or string representations. This prevents SystemStackError
|
|
57
|
+
# when serializing objects with circular references (like ActiveRecord models).
|
|
58
|
+
#
|
|
59
|
+
# @param value [Object] The value to serialize
|
|
60
|
+
# @param seen [Set] Set of object_ids already visited (for cycle detection)
|
|
61
|
+
# @param depth [Integer] Current recursion depth
|
|
62
|
+
# @return [Object] A JSON-safe value (String, Numeric, Boolean, nil, Array, or Hash)
|
|
63
|
+
def safe_serialize(value, seen = Set.new, depth = 0)
|
|
64
|
+
return '[max depth exceeded]' if depth > MAX_DEPTH
|
|
65
|
+
|
|
66
|
+
case value
|
|
67
|
+
when nil, true, false, Integer, Float
|
|
68
|
+
value
|
|
69
|
+
when String
|
|
70
|
+
truncate_string(value)
|
|
71
|
+
when Symbol
|
|
72
|
+
value.to_s
|
|
73
|
+
when Time, DateTime
|
|
74
|
+
value.iso8601(3)
|
|
75
|
+
when Date
|
|
76
|
+
value.iso8601
|
|
77
|
+
when Array
|
|
78
|
+
serialize_array(value, seen, depth)
|
|
79
|
+
when Hash
|
|
80
|
+
serialize_hash(value, seen, depth)
|
|
81
|
+
else
|
|
82
|
+
serialize_object(value, seen)
|
|
83
|
+
end
|
|
84
|
+
rescue StandardError => e
|
|
85
|
+
"[serialization error: #{e.class}]"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def truncate_string(str)
|
|
91
|
+
return str if str.length <= MAX_STRING_LENGTH
|
|
92
|
+
|
|
93
|
+
"#{str[0...MAX_STRING_LENGTH]}... (truncated)"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def serialize_array(array, seen, depth)
|
|
97
|
+
return '[circular reference]' if seen.include?(array.object_id)
|
|
98
|
+
|
|
99
|
+
seen = seen.dup.add(array.object_id)
|
|
100
|
+
array.first(100).map { |item| safe_serialize(item, seen, depth + 1) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def serialize_hash(hash, seen, depth)
|
|
104
|
+
return '[circular reference]' if seen.include?(hash.object_id)
|
|
105
|
+
|
|
106
|
+
seen = seen.dup.add(hash.object_id)
|
|
107
|
+
result = {}
|
|
108
|
+
hash.first(100).each do |key, val|
|
|
109
|
+
result[key.to_s] = safe_serialize(val, seen, depth + 1)
|
|
110
|
+
end
|
|
111
|
+
result
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def serialize_object(obj, seen)
|
|
115
|
+
return '[circular reference]' if seen.include?(obj.object_id)
|
|
116
|
+
|
|
117
|
+
# For ActiveRecord and similar objects, use id if available
|
|
118
|
+
return "#{obj.class.name}##{obj.id}" if obj.respond_to?(:id) && obj.respond_to?(:class)
|
|
119
|
+
|
|
120
|
+
# Try to_s as fallback, but limit length
|
|
121
|
+
str = obj.to_s
|
|
122
|
+
truncate_string(str)
|
|
123
|
+
rescue StandardError
|
|
124
|
+
"[#{obj.class.name}]"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|