Module: ActiveCypher::Instrumentation
- Defined in:
- lib/active_cypher/instrumentation.rb
Overview
Instrumentation for ActiveCypher operations. Because every database operation needs a stopwatch and an audience.
Instance Method Summary collapse
-
#instrument(operation, payload = {}) { ... } ⇒ Object
Instruments an operation and publishes an event with timing information.
-
#instrument_connection(operation, config = {}, metadata: {}) { ... } ⇒ Object
Instruments a connection operation.
-
#instrument_query(cypher, params = {}, context: 'Query', metadata: {}) { ... } ⇒ Object
Instruments a database query.
-
#instrument_transaction(operation, transaction_id = nil, metadata: {}) { ... } ⇒ Object
Instruments a transaction operation.
-
#sanitize_config(config) ⇒ Hash
Sanitizes connection configuration to remove sensitive values.
-
#sanitize_params(params) ⇒ Hash, Object
Sanitizes query parameters to remove sensitive values.
-
#sensitive_key?(key) ⇒ Boolean
Determines if a key contains sensitive information that should be filtered.
Instance Method Details
#instrument(operation, payload = {}) { ... } ⇒ Object
Instruments an operation and publishes an event with timing information.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/active_cypher/instrumentation.rb', line 18 def instrument(operation, payload = {}) # Start timing with monotonic clock for accuracy (because wall time is for amateurs) start = Process.clock_gettime(Process::CLOCK_MONOTONIC) # Run the actual operation result = yield # Calculate duration in milliseconds (because counting seconds is so 1990s) duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1_000).round(2) # Add duration to payload payload[:duration_ms] = duration_ms # Publish event via ActiveSupport::Notifications event_name = operation.to_s.start_with?('active_cypher.') ? operation.to_s : "active_cypher.#{operation}" ActiveSupport::Notifications.instrument(event_name, payload) # Also log if we have logging capabilities log_instrumented_event(operation, payload) if respond_to?(:logger) # Return the original result result end |
#instrument_connection(operation, config = {}, metadata: {}) { ... } ⇒ Object
Instruments a connection operation.
72 73 74 75 76 77 78 |
# File 'lib/active_cypher/instrumentation.rb', line 72 def instrument_connection(operation, config = {}, metadata: {}, &) payload = .merge( config: sanitize_config(config) ) instrument("connection.#{operation}", payload, &) end |
#instrument_query(cypher, params = {}, context: 'Query', metadata: {}) { ... } ⇒ Object
Instruments a database query.
53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/active_cypher/instrumentation.rb', line 53 def instrument_query(cypher, params = {}, context: 'Query', metadata: {}, &) truncated_cypher = cypher.to_s.gsub(/\s+/, ' ').strip truncated_cypher = "#{truncated_cypher[0...97]}..." if truncated_cypher.length > 100 payload = .merge( cypher: truncated_cypher, params: sanitize_params(params), context: context ) instrument('query', payload, &) end |
#instrument_transaction(operation, transaction_id = nil, metadata: {}) { ... } ⇒ Object
Instruments a transaction operation.
86 87 88 89 90 91 |
# File 'lib/active_cypher/instrumentation.rb', line 86 def instrument_transaction(operation, transaction_id = nil, metadata: {}, &) payload = .dup payload[:transaction_id] = transaction_id if transaction_id instrument("transaction.#{operation}", payload, &) end |
#sanitize_config(config) ⇒ Hash
Sanitizes connection configuration to remove sensitive values.
117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/active_cypher/instrumentation.rb', line 117 def sanitize_config(config) return {} unless config.is_a?(Hash) config.each_with_object({}) do |(key, value), result| result[key] = if sensitive_key?(key) '[FILTERED]' elsif value.is_a?(Hash) sanitize_config(value) else value end end end |
#sanitize_params(params) ⇒ Hash, Object
Sanitizes query parameters to remove sensitive values.
100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/active_cypher/instrumentation.rb', line 100 def sanitize_params(params) return params unless params.is_a?(Hash) params.each_with_object({}) do |(key, value), sanitized| sanitized[key] = if sensitive_key?(key) '[FILTERED]' elsif value.is_a?(Hash) sanitize_params(value) else value end end end |
#sensitive_key?(key) ⇒ Boolean
Determines if a key contains sensitive information that should be filtered.
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/active_cypher/instrumentation.rb', line 134 def sensitive_key?(key) return true if key.to_s.match?(/(^|[-_])(?:password|token|secret|credential|key)($|[-_])/i) # Check against Rails filter parameters if available if defined?(Rails) && Rails.application Rails.application.config.filter_parameters.any? do |pattern| case pattern when Regexp key.to_s =~ pattern when Symbol, String key.to_s == pattern.to_s else false end end else false end end |