Module: BetterService::Concerns::Instrumentation
- Extended by:
- ActiveSupport::Concern
- Included in:
- Services::Base
- Defined in:
- lib/better_service/concerns/instrumentation.rb
Class Method Summary collapse
-
.prepended(base) ⇒ Object
Hook into prepend to wrap call method.
Instance Method Summary collapse
-
#build_completion_payload(service_name, user_id, result, duration) ⇒ Hash
Build payload for service.completed event.
-
#build_failure_payload(service_name, user_id, error, duration) ⇒ Hash
Build payload for service.failed event.
-
#build_result_failure_payload(service_name, user_id, result, duration) ⇒ Hash
Build payload for service.failed event from Result object.
-
#build_start_payload(service_name, user_id) ⇒ Hash
Build payload for service.started event.
-
#extract_user_id_from_instance ⇒ Integer, ...
Extract user ID from service instance.
-
#instrumentation_enabled? ⇒ Boolean
Check if instrumentation is enabled for this service.
-
#publish_cache_hit(cache_key, context = nil) ⇒ void
Publish cache hit event.
-
#publish_cache_miss(cache_key, context = nil) ⇒ void
Publish cache miss event.
Class Method Details
.prepended(base) ⇒ Object
Hook into prepend to wrap call method
This is called when the concern is prepended to a class.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/better_service/concerns/instrumentation.rb', line 34 def self.prepended(base) # Always wrap call method base.class_eval do # Save original call method alias_method :call_without_instrumentation, :call # Define new call method with instrumentation define_method(:call) do return call_without_instrumentation unless instrumentation_enabled? service_name = self.class.name user_id = extract_user_id_from_instance # Publish service.started event payload = build_start_payload(service_name, user_id) ActiveSupport::Notifications.instrument("service.started", payload) # Execute the service start_time = Time.current begin result = call_without_instrumentation duration = ((Time.current - start_time) * 1000).round(2) # milliseconds # Validate that result is a BetterService::Result object unless result.is_a?(BetterService::Result) raise BetterService::Errors::Runtime::InvalidResultError.new( "Service #{service_name} must return BetterService::Result, got #{result.class}", context: { service: service_name, result_class: result.class.name } ) end if result.failure? # Publish service.failed event for Result failures failure_payload = build_result_failure_payload( service_name, user_id, result, duration ) ActiveSupport::Notifications.instrument("service.failed", failure_payload) else # Publish service.completed event completion_payload = build_completion_payload( service_name, user_id, result, duration ) ActiveSupport::Notifications.instrument("service.completed", completion_payload) end result rescue => error duration = ((Time.current - start_time) * 1000).round(2) # Extract original error if wrapped in ExecutionError original_error = error.respond_to?(:original_error) && error.original_error ? error.original_error : error # Publish service.failed event failure_payload = build_failure_payload( service_name, user_id, original_error, duration ) ActiveSupport::Notifications.instrument("service.failed", failure_payload) # Re-raise the error (don't swallow it) raise end end end end |
Instance Method Details
#build_completion_payload(service_name, user_id, result, duration) ⇒ Hash
Build payload for service.completed event
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/better_service/concerns/instrumentation.rb', line 152 def build_completion_payload(service_name, user_id, result, duration) payload = { service_name: service_name, user_id: user_id, duration: duration, timestamp: Time.current.iso8601, success: true } # Include params if configured and available if BetterService.configuration.instrumentation_include_args && respond_to?(:params, true) payload[:params] = send(:params) end # Include result if configured if BetterService.configuration.instrumentation_include_result payload[:result] = result end # Include cache metadata if available from Result meta if result.[:cache_hit] payload[:cache_hit] = result.[:cache_hit] end if result.[:cache_key] payload[:cache_key] = result.[:cache_key] end payload end |
#build_failure_payload(service_name, user_id, error, duration) ⇒ Hash
Build payload for service.failed event
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/better_service/concerns/instrumentation.rb', line 220 def build_failure_payload(service_name, user_id, error, duration) payload = { service_name: service_name, user_id: user_id, duration: duration, timestamp: Time.current.iso8601, success: false, error_class: error.class.name, error_message: error. } # Include params if configured and available if BetterService.configuration.instrumentation_include_args && respond_to?(:params, true) payload[:params] = send(:params) end # Include backtrace (first 5 lines) for debugging if error.backtrace payload[:error_backtrace] = error.backtrace.first(5) end payload end |
#build_result_failure_payload(service_name, user_id, result, duration) ⇒ Hash
Build payload for service.failed event from Result object
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/better_service/concerns/instrumentation.rb', line 189 def build_result_failure_payload(service_name, user_id, result, duration) payload = { service_name: service_name, user_id: user_id, duration: duration, timestamp: Time.current.iso8601, success: false, error_class: result.[:error_code]&.to_s || "UnknownError", error_message: result. || "Service failed" } # Include params if configured and available if BetterService.configuration.instrumentation_include_args && respond_to?(:params, true) payload[:params] = send(:params) end # Include validation errors if present if result.validation_errors payload[:validation_errors] = result.validation_errors end payload end |
#build_start_payload(service_name, user_id) ⇒ Hash
Build payload for service.started event
130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/better_service/concerns/instrumentation.rb', line 130 def build_start_payload(service_name, user_id) payload = { service_name: service_name, user_id: user_id, timestamp: Time.current.iso8601 } # Include params if configured and available if BetterService.configuration.instrumentation_include_args && respond_to?(:params, true) payload[:params] = send(:params) end payload end |
#extract_user_id_from_instance ⇒ Integer, ...
Extract user ID from service instance
116 117 118 119 120 121 122 123 |
# File 'lib/better_service/concerns/instrumentation.rb', line 116 def extract_user_id_from_instance return nil unless respond_to?(:user, true) user = send(:user) return nil unless user user.respond_to?(:id) ? user.id : user end |
#instrumentation_enabled? ⇒ Boolean
Check if instrumentation is enabled for this service
103 104 105 106 107 108 109 110 111 |
# File 'lib/better_service/concerns/instrumentation.rb', line 103 def instrumentation_enabled? return false unless BetterService.configuration.instrumentation_enabled excluded = BetterService.configuration.instrumentation_excluded_services full_name = self.class.name # Check exact match or if excluded name matches the end of full name !excluded.any? { |excluded_name| full_name == excluded_name || full_name.end_with?("::#{excluded_name}") } end |
#publish_cache_hit(cache_key, context = nil) ⇒ void
This method returns an undefined value.
Publish cache hit event
Called from Cacheable concern when cache lookup succeeds.
251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/better_service/concerns/instrumentation.rb', line 251 def publish_cache_hit(cache_key, context = nil) return unless instrumentation_enabled? payload = { service_name: self.class.name, event_type: "cache_hit", cache_key: cache_key, context: context, timestamp: Time.current.iso8601 } ActiveSupport::Notifications.instrument("cache.hit", payload) end |
#publish_cache_miss(cache_key, context = nil) ⇒ void
This method returns an undefined value.
Publish cache miss event
Called from Cacheable concern when cache lookup fails.
272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/better_service/concerns/instrumentation.rb', line 272 def publish_cache_miss(cache_key, context = nil) return unless instrumentation_enabled? payload = { service_name: self.class.name, event_type: "cache_miss", cache_key: cache_key, context: context, timestamp: Time.current.iso8601 } ActiveSupport::Notifications.instrument("cache.miss", payload) end |