txlemetry-rails 4.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f59e5f965cb9ed894ce85ddfda78b7d932fff6d1879d930dba5851371d7e30e0
4
+ data.tar.gz: f672ae2ae805ca8dbe721cc233e82b7e9e7a6dd581796f3597267c2673badc03
5
+ SHA512:
6
+ metadata.gz: 8d75c19096f1526aa77a4dfed383ee7aaac3b679e4328c154dd93cb27e13a2737a4af80f381441cb31fb9973a46e18df698f23ae4a0c9e48698374b5a6e21df4
7
+ data.tar.gz: 0b6ef6fc63481e2cba86c8dc2e98e219aa972f869a2a1b774c097c814c35478df7b64583238455a9f8d5094f545479126020ebdd17a5ddfa2ae2369fd472f2c8
data/LICENSE ADDED
@@ -0,0 +1,48 @@
1
+ Copyright (c) 2025 PostHog (part of Hiberly Inc)
2
+
3
+ Copyright (c) 2013 Segment Inc. friends@segment.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ Some files in this codebase contain code from getsentry/sentry-ruby.
26
+ In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases.
27
+
28
+ The MIT License (MIT)
29
+
30
+ Copyright (c) 2020 Sentry
31
+
32
+ Permission is hereby granted, free of charge, to any person obtaining a copy
33
+ of this software and associated documentation files (the "Software"), to deal
34
+ in the Software without restriction, including without limitation the rights
35
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
36
+ copies of the Software, and to permit persons to whom the Software is
37
+ furnished to do so, subject to the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be included in
40
+ all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
45
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
47
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
48
+ THE SOFTWARE.
data/NOTICE ADDED
@@ -0,0 +1,5 @@
1
+ Txlemetry ruby SDK sources
2
+ Derived from https://github.com/PostHog/posthog-ruby, tag posthog-rails-v3.18.9, commit 820d4bfe8aa3fb3284c4fdf3fb356530340e1c7a.
3
+ Original license files and author notices are retained.
4
+ Public names, package identity, default hosts and distribution are maintained for Txlemetry.
5
+ Candidate source migration and validation are in progress; no release has been published.
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module Txlemetry
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ desc 'Creates a Txlemetry initializer file at config/initializers/txlemetry.rb'
9
+
10
+ source_root File.expand_path('templates', __dir__)
11
+
12
+ def copy_initializer
13
+ copy_file 'txlemetry.rb', 'config/initializers/txlemetry.rb'
14
+ end
15
+
16
+ def show_readme
17
+ say ''
18
+ say 'Txlemetry Rails has been installed!', :green
19
+ say ''
20
+ say 'Next steps:', :yellow
21
+ say ' 1. Edit config/initializers/txlemetry.rb with your Txlemetry API key'
22
+ say ' 2. Set environment variables:'
23
+ say ' - TXLEMETRY_API_KEY (required)'
24
+ say ' - TXLEMETRY_SECRET_KEY (optional, for feature flags)'
25
+ say ''
26
+ say 'Optional: forward Rails.logger to Txlemetry Logs', :yellow
27
+ say ' - Add to your Gemfile (requires Ruby 3.3+):'
28
+ say " gem 'opentelemetry-sdk', require: false"
29
+ say " gem 'opentelemetry-logs-sdk', '>= 0.6.0', require: false"
30
+ say " gem 'opentelemetry-exporter-otlp-logs', require: false"
31
+ say ' - Set config.logs_enabled = true in the initializer'
32
+ say ' - Docs: https://txlemetry.com/docs/logs'
33
+ say ''
34
+ say 'For more information, see: https://txlemetry.com/docs/libraries/ruby'
35
+ say ''
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Txlemetry Rails Initializer
4
+ # Place this file in config/initializers/txlemetry.rb
5
+
6
+ # ============================================================================
7
+ # RAILS-SPECIFIC CONFIGURATION
8
+ # ============================================================================
9
+ # Configure Rails-specific options via Txlemetry::Rails.configure
10
+ # These settings control how Txlemetry integrates with Rails features.
11
+
12
+ Txlemetry::Rails.configure do |config|
13
+ # Automatically capture exceptions (default: false)
14
+ # Set to true to enable automatic exception tracking
15
+ # config.auto_capture_exceptions = true
16
+
17
+ # Report exceptions that Rails rescues (e.g., with rescue_from) (default: false)
18
+ # Set to true to capture rescued exceptions
19
+ # config.report_rescued_exceptions = true
20
+
21
+ # Automatically instrument ActiveJob background jobs (default: false)
22
+ # Set to true to enable automatic ActiveJob exception tracking
23
+ # config.auto_instrument_active_job = true
24
+
25
+ # Use Txlemetry 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
32
+ # config.capture_user_context = true
33
+
34
+ # Controller method name to get current user (default: :current_user)
35
+ # Change this if your app uses a different method name (e.g., :authenticated_user)
36
+ # When configured, exceptions will include user context (distinct_id, email, name),
37
+ # making it easier to identify affected users and debug user-specific issues.
38
+ # config.current_user_method = :current_user
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
+
46
+ # Additional exception classes to exclude from reporting
47
+ # These are added to the default excluded exceptions
48
+ # config.excluded_exceptions = [
49
+ # # 'MyCustom404Error',
50
+ # # 'MyCustomValidationError'
51
+ # ]
52
+
53
+ # --------------------------------------------------------------------------
54
+ # TXLEMETRY LOGS (OpenTelemetry) - opt-in
55
+ # --------------------------------------------------------------------------
56
+ # Forward Rails.logger output to Txlemetry Logs over OTLP, automatically
57
+ # correlated with the request's distinct_id and session_id.
58
+ #
59
+ # Requires the OpenTelemetry gems (Ruby 3.3+) in your Gemfile. Use
60
+ # require: false — txlemetry-rails loads them only when logs are enabled.
61
+ # logs-sdk must be >= 0.6.0 (older versions lack LogRecordData#event_name,
62
+ # which the OTLP exporter needs, raising NoMethodError on export):
63
+ # gem 'opentelemetry-sdk', require: false
64
+ # gem 'opentelemetry-logs-sdk', '>= 0.6.0', require: false
65
+ # gem 'opentelemetry-exporter-otlp-logs', require: false
66
+ #
67
+ # Enable log forwarding (default: false)
68
+ # config.logs_enabled = true
69
+
70
+ # Broadcast Rails.logger into Txlemetry Logs (default: true when logs enabled)
71
+ # config.logs_forward_rails_logger = true
72
+
73
+ # Minimum severity to forward; nil inherits Rails.logger's level (default: nil)
74
+ # config.logs_level = :info
75
+
76
+ # Maximum records forwarded per minute, protecting your ingestion quota from
77
+ # runaway log volume. Numeric strings (e.g. from ENV) are coerced.
78
+ # (default: 6000; set to nil or 0 to disable the cap)
79
+ # config.logs_max_records_per_minute = 6_000
80
+
81
+ # Modify or drop log records before they are sent, e.g. to scrub PII.
82
+ # Receives a hash (:timestamp, :severity, :body, :attributes — :severity is
83
+ # a symbol such as :warn); return the (modified) hash to send or nil to
84
+ # drop. Records are dropped if the callback raises. (default: nil)
85
+ # config.logs_before_send = proc { |record|
86
+ # next nil if record[:severity] == :debug
87
+ #
88
+ # record[:body] = record[:body].gsub(/\b[\w.+-]+@[\w-]+\.[\w.]+\b/, '[redacted email]')
89
+ # record
90
+ # }
91
+
92
+ # Logs reuse the same project token (api_key) and host configured below, so
93
+ # there is nothing extra to set. Logs are sent to <host>/i/v1/logs.
94
+ end
95
+
96
+ # You can also configure Rails options directly:
97
+ # Txlemetry::Rails.config.auto_capture_exceptions = true
98
+
99
+ # ============================================================================
100
+ # CORE TXLEMETRY CONFIGURATION
101
+ # ============================================================================
102
+ # Initialize the Txlemetry client with core SDK options.
103
+ # IMPORTANT: Use Txlemetry.init once — creating multiple clients can cause dropped events.
104
+ # Txlemetry.init provides a singleton-like pattern; use Txlemetry.capture, Txlemetry.identify, etc.
105
+
106
+ Txlemetry.init do |config|
107
+ # ============================================================================
108
+ # REQUIRED CONFIGURATION
109
+ # ============================================================================
110
+
111
+ # Your Txlemetry project API key (required)
112
+ # Get this from: Txlemetry Project Settings > API Keys
113
+ # https://app.txlemetry.com/settings/project-details#variables
114
+ config.api_key = ENV.fetch('TXLEMETRY_API_KEY', nil)
115
+
116
+ # ============================================================================
117
+ # OPTIONAL CONFIGURATION
118
+ # ============================================================================
119
+
120
+ # For Txlemetry Cloud, use: https://app.txlemetry.com or https://app.txlemetry.com
121
+ config.host = ENV.fetch('TXLEMETRY_HOST', 'https://app.txlemetry.com')
122
+
123
+ # Secret key (optional, but required for local feature flag evaluation).
124
+ # Accepts a Personal API Key (phx_...) or a Project Secret API Key (phs_...).
125
+ # Get this from: Txlemetry Settings > Personal API Keys / Project API Keys
126
+ config.secret_key = ENV.fetch('TXLEMETRY_SECRET_KEY', nil)
127
+
128
+ # Maximum number of events to queue before dropping (default: 10000)
129
+ config.max_queue_size = 10_000
130
+
131
+ # Feature flags polling interval in seconds (default: 30)
132
+ config.feature_flags_polling_interval = 30
133
+
134
+ # Feature flag request timeout in seconds (default: 3)
135
+ config.feature_flag_request_timeout_seconds = 3
136
+
137
+ # Error callback - called when Txlemetry encounters an error
138
+ # config.on_error = proc { |status, message|
139
+ # Rails.logger.error("[Txlemetry] Error #{status}: #{message}")
140
+ # }
141
+
142
+ # Before send callback - modify or filter events before sending
143
+ # Return nil to prevent the event from being sent
144
+ # config.before_send = proc { |event|
145
+ # # Filter out test users
146
+ # return nil if event[:properties]&.dig('$user_email')&.end_with?('@test.com')
147
+ #
148
+ # # Add custom properties to all events
149
+ # event[:properties] ||= {}
150
+ # event[:properties]['environment'] = Rails.env
151
+ #
152
+ # event
153
+ # }
154
+
155
+ # ============================================================================
156
+ # ENVIRONMENT-SPECIFIC CONFIGURATION
157
+ # ============================================================================
158
+
159
+ # Disable in test environment
160
+ config.test_mode = true if Rails.env.test?
161
+
162
+ # Optional: Disable in development
163
+ # config.test_mode = true if Rails.env.test? || Rails.env.development?
164
+ end
165
+
166
+ # ============================================================================
167
+ # DEFAULT EXCLUDED EXCEPTIONS
168
+ # ============================================================================
169
+ # The following exceptions are excluded by default:
170
+ #
171
+ # - AbstractController::ActionNotFound
172
+ # - ActionController::BadRequest
173
+ # - ActionController::InvalidAuthenticityToken
174
+ # - ActionController::InvalidCrossOriginRequest
175
+ # - ActionController::MethodNotAllowed
176
+ # - ActionController::NotImplemented
177
+ # - ActionController::ParameterMissing
178
+ # - ActionController::RoutingError
179
+ # - ActionController::UnknownFormat
180
+ # - ActionController::UnknownHttpMethod
181
+ # - ActionDispatch::Http::MimeNegotiation::InvalidType
182
+ # - ActionDispatch::Http::Parameters::ParseError
183
+ # - ActiveRecord::RecordNotFound
184
+ # - ActiveRecord::RecordNotUnique
185
+ #
186
+ # These can be re-enabled by removing them from the exclusion list if needed.
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'txlemetry/rails/parameter_filter'
4
+
5
+ module Txlemetry
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
+ #
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
+ #
22
+ # Example:
23
+ # class MyJob < ApplicationJob
24
+ # txlemetry_distinct_id ->(user, arg1, arg2) { user.id }
25
+ # def perform(user, arg1, arg2)
26
+ # # ...
27
+ # end
28
+ # end
29
+ def txlemetry_distinct_id(proc = nil, &block)
30
+ @txlemetry_distinct_id_proc = proc || block
31
+ end
32
+
33
+ # @return [Proc, nil] The configured distinct_id extractor.
34
+ def txlemetry_distinct_id_proc
35
+ @txlemetry_distinct_id_proc
36
+ end
37
+ end
38
+
39
+ def perform_now
40
+ super
41
+ rescue StandardError => e
42
+ # Capture the exception with job context
43
+ capture_job_exception(e)
44
+ raise
45
+ end
46
+
47
+ private
48
+
49
+ def capture_job_exception(exception)
50
+ return unless Txlemetry::Rails.config&.auto_instrument_active_job
51
+
52
+ # Build distinct_id from job arguments if possible
53
+ distinct_id = extract_distinct_id_from_job
54
+
55
+ properties = {
56
+ '$exception_source' => 'active_job',
57
+ '$job_class' => self.class.name,
58
+ '$job_id' => job_id,
59
+ '$queue_name' => queue_name,
60
+ '$job_priority' => priority,
61
+ '$job_executions' => executions
62
+ }
63
+
64
+ # Add serialized job arguments (be careful with sensitive data)
65
+ properties['$job_arguments'] = sanitize_job_arguments(arguments) if arguments.present?
66
+
67
+ Txlemetry.capture_exception(
68
+ exception,
69
+ distinct_id,
70
+ properties,
71
+ mechanism: { 'type' => 'active_job', 'handled' => false }
72
+ )
73
+ Txlemetry::Rails.mark_active_job_exception_captured(exception)
74
+ rescue StandardError => e
75
+ # Don't let Txlemetry errors break job processing
76
+ Txlemetry::Logging.logger.error("Failed to capture job exception: #{e.message}")
77
+ end
78
+
79
+ def extract_distinct_id_from_job
80
+ # First, check if the job class defines a custom extractor
81
+ return self.class.txlemetry_distinct_id_proc.call(*arguments) if self.class.txlemetry_distinct_id_proc
82
+
83
+ # Fallback: look for explicit user_id in hash arguments only
84
+ arguments.each do |arg|
85
+ if arg.is_a?(Hash) && arg['user_id']
86
+ return arg['user_id']
87
+ elsif arg.is_a?(Hash) && arg[:user_id]
88
+ return arg[:user_id]
89
+ end
90
+ end
91
+
92
+ nil # No user context found
93
+ end
94
+
95
+ def sanitize_job_arguments(args)
96
+ # Convert arguments to a safe format
97
+ args.map do |arg|
98
+ case arg
99
+ when String
100
+ # Truncate long strings to prevent huge payloads
101
+ arg.length > 100 ? "[FILTERED: #{arg.length} chars]" : arg
102
+ when Integer, Float, TrueClass, FalseClass, NilClass
103
+ arg
104
+ when Hash
105
+ # Use Rails' filter_parameters to filter sensitive data
106
+ filter_sensitive_params(arg)
107
+ when defined?(ActiveRecord::Base) && ActiveRecord::Base
108
+ { class: arg.class.name, id: arg.id }
109
+ else
110
+ arg.class.name
111
+ end
112
+ end
113
+ rescue StandardError
114
+ ['<serialization error>']
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'txlemetry/rails/parameter_filter'
4
+
5
+ module Txlemetry
6
+ module Rails
7
+ # Middleware that captures exceptions and sends them to Txlemetry.
8
+ #
9
+ # @api private
10
+ class CaptureExceptions
11
+ include ParameterFilter
12
+
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
+ # Signal that we're in a web request context
22
+ # ErrorSubscriber will skip capture for web requests to avoid duplicates
23
+ Txlemetry::Rails.enter_web_request
24
+
25
+ response = @app.call(env)
26
+ env['txlemetry.response_status_code'] = response_status(response)
27
+
28
+ # Check if there was an exception that Rails handled
29
+ exception = collect_exception(env)
30
+
31
+ capture_exception(exception, env) if exception && should_capture?(exception)
32
+
33
+ response
34
+ rescue StandardError => e
35
+ # Capture unhandled exceptions
36
+ capture_exception(e, env) if should_capture?(e)
37
+ raise
38
+ ensure
39
+ Txlemetry::Rails.exit_web_request
40
+ end
41
+
42
+ private
43
+
44
+ def collect_exception(env)
45
+ # Rails stores exceptions in these env keys
46
+ env['action_dispatch.exception'] ||
47
+ env['rack.exception'] ||
48
+ env['txlemetry.rescued_exception']
49
+ end
50
+
51
+ def should_capture?(exception)
52
+ return false unless Txlemetry::Rails.config&.auto_capture_exceptions
53
+ return false unless Txlemetry::Rails.config&.should_capture_exception?(exception)
54
+
55
+ true
56
+ end
57
+
58
+ def capture_exception(exception, env)
59
+ request = ActionDispatch::Request.new(env)
60
+ distinct_id = extract_distinct_id(env)
61
+ additional_properties = build_properties(request, env)
62
+
63
+ captured = Txlemetry.capture_exception(
64
+ exception,
65
+ distinct_id,
66
+ additional_properties,
67
+ mechanism: { 'type' => 'rails', 'handled' => false }
68
+ )
69
+ Txlemetry::Rails.mark_web_exception_captured(exception) if captured
70
+ rescue StandardError => e
71
+ Txlemetry::Logging.logger.error("Failed to capture exception: #{e.message}")
72
+ Txlemetry::Logging.logger.error("Backtrace: #{e.backtrace&.first(5)&.join("\n")}")
73
+ end
74
+
75
+ def extract_distinct_id(env)
76
+ # Prefer authenticated Rails user context. Request/tracing context is
77
+ # applied later by the core capture path if this returns nil.
78
+ return nil unless Txlemetry::Rails.config&.capture_user_context
79
+
80
+ user = extract_current_user(env['action_controller.instance'])
81
+ user_id = extract_user_id(user) if user
82
+ return user_id if present?(user_id)
83
+
84
+ nil
85
+ end
86
+
87
+ def extract_current_user(controller)
88
+ resolver = Txlemetry::Rails.config&.current_user_resolver
89
+ return resolve_current_user(resolver, controller) if resolver
90
+
91
+ method_name = Txlemetry::Rails.config&.current_user_method || :current_user
92
+ return unless controller.respond_to?(method_name, true)
93
+
94
+ controller.send(method_name)
95
+ end
96
+
97
+ def resolve_current_user(resolver, controller)
98
+ call_current_user_resolver(resolver, controller)
99
+ rescue StandardError => e
100
+ Txlemetry::Logging.logger.warn("current_user_resolver failed: #{e.message}")
101
+ nil
102
+ end
103
+
104
+ def call_current_user_resolver(resolver, controller)
105
+ if resolver.arity.zero?
106
+ controller ? controller.instance_exec(&resolver) : resolver.call
107
+ elsif controller
108
+ resolver.call(controller)
109
+ end
110
+ end
111
+
112
+ def extract_user_id(user)
113
+ # Use configured method if specified
114
+ method_name = Txlemetry::Rails.config&.user_id_method
115
+ return user.send(method_name) if method_name && user.respond_to?(method_name)
116
+
117
+ # Try explicit Txlemetry method (allows users to customize without config)
118
+ return user.txlemetry_distinct_id if user.respond_to?(:txlemetry_distinct_id)
119
+ return user.distinct_id if user.respond_to?(:distinct_id)
120
+
121
+ # Try common ID methods
122
+ return user.id if user.respond_to?(:id)
123
+ return user['id'] if user.respond_to?(:[]) && user['id']
124
+ return user.pk if user.respond_to?(:pk)
125
+ return user['pk'] if user.respond_to?(:[]) && user['pk']
126
+ return user.uuid if user.respond_to?(:uuid)
127
+ return user['uuid'] if user.respond_to?(:[]) && user['uuid']
128
+
129
+ user.to_s
130
+ end
131
+
132
+ def build_properties(request, env)
133
+ properties = {
134
+ '$exception_source' => 'rails'
135
+ }
136
+
137
+ # Add controller and action if available
138
+ if env['action_controller.instance']
139
+ controller = env['action_controller.instance']
140
+ properties['$controller'] = safe_serialize(controller.controller_name)
141
+ properties['$action'] = safe_serialize(controller.action_name)
142
+ end
143
+
144
+ # Add request parameters (be careful with sensitive data)
145
+ if request.params.present?
146
+ filtered_params = filter_sensitive_params(request.params)
147
+ # Safe serialize to handle any complex objects in params
148
+ properties['$request_params'] = safe_serialize(filtered_params) unless filtered_params.empty?
149
+ end
150
+
151
+ response_status_code = env['txlemetry.response_status_code']
152
+ properties['$response_status_code'] = response_status_code if response_status_code
153
+
154
+ # Add referrer
155
+ properties['$referrer'] = safe_serialize(request.referrer) if request.referrer
156
+
157
+ properties
158
+ end
159
+
160
+ def response_status(response)
161
+ status = response.respond_to?(:[]) ? response[0] : nil
162
+ status if status.is_a?(Integer)
163
+ end
164
+
165
+ def present?(value)
166
+ !(value.nil? || (value.respond_to?(:empty?) && value.empty?))
167
+ end
168
+ end
169
+ end
170
+ end