Module: ActiveCypher::Instrumentation

Included in:
Bolt::Session, Bolt::Transaction, ConnectionAdapters::AbstractBoltAdapter
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

Instance Method Details

#instrument(operation, payload = {}) { ... } ⇒ Object

Instruments an operation and publishes an event with timing information.

Parameters:

  • operation (String, Symbol)

    The operation name (prefixed with ‘active_cypher.’)

  • payload (Hash) (defaults to: {})

    Additional context for the event

Yields:

  • The operation to instrument

Returns:

  • (Object)

    The result of the block



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.

Parameters:

  • operation (Symbol)

    The connection operation (:connect, :disconnect, etc)

  • config (Hash) (defaults to: {})

    Connection configuration

  • metadata (Hash) (defaults to: {})

    Additional metadata

Yields:

  • The connection operation

Returns:

  • (Object)

    The result of the block



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.

Parameters:

  • cypher (String)

    The Cypher query

  • params (Hash) (defaults to: {})

    Query parameters

  • context (String) (defaults to: 'Query')

    Additional context (e.g., “Model.find”)

  • metadata (Hash) (defaults to: {})

    Additional metadata

Yields:

  • The query operation

Returns:

  • (Object)

    The result of the block



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.

Parameters:

  • operation (Symbol)

    The transaction operation (:begin, :commit, :rollback)

  • transaction_id (String, Integer) (defaults to: nil)

    Transaction identifier (if available)

  • metadata (Hash) (defaults to: {})

    Additional metadata

Yields:

  • The transaction operation

Returns:

  • (Object)

    The result of the block



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.

Parameters:

  • config (Hash)

    The configuration to sanitize

Returns:

  • (Hash)

    Sanitized configuration



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.

Parameters:

  • params (Hash, Object)

    The parameters to sanitize

Returns:

  • (Hash, Object)

    Sanitized parameters



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.

Parameters:

  • key (String, Symbol)

    The key to check

Returns:

  • (Boolean)

    True if the key contains sensitive information



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