require "active_support/operation_logger/version" require "active_support/notifications" require "active_support/log_subscriber" module ActiveSupport module OperationLogger # Provides methods that should probably be part of Ruby's stdlib module MethodHelper def self.public_owned_instance_methods(klass) klass.public_instance_methods.select do |m| klass.instance_method(m).owner == klass end end end # A generic factory responsible for creating modules that can be used to # decorate classes with ActiveSupport::Notifications module EventInstrumenterFactory # @param [Class] klass # @param [Array<Symbol>] methods # @param [Symbol] event_namespace # @param [#call] filter # @return [Module] a module for prepending def self.instrumenter_for(klass, methods, event_namespace, filter = nil) Module.new do methods.each do |m| define_method m do |*args, &block| if !filter || filter.call(m, *args, &block) command_str = if args.size == 1 && args.first.is_a?(Array) args.first.map(&:inspect).join(', ') else args.map(&:inspect).join(' ') end event_id = "call.#{event_namespace}" name = "#{event_namespace.to_s.camelize} #{m}" ActiveSupport::Notifications.instrument(event_id, name: name, command: command_str) do super *args, &block end else super *args, &block end end end end end end # A generic factory responsible for creating classes that can be used to # create LogSubscribers that integrate with ActiveSupport::Notifications # and mirror the functionality of ActiveRecord::LogSubscriber module EventSubscriberFactory def self.subscriber_for(klass) Class.new(ActiveSupport::LogSubscriber) do def initialize super @odd = false end def odd? @odd = !@odd end def call(event) name = "#{event.payload[:name]} (#{event.duration.round(1)}ms)" command = event.payload[:command] if odd? name = color(name, ActiveSupport::LogSubscriber::CYAN, true) command = color(command, nil, true) else name = color(name, ActiveSupport::LogSubscriber::MAGENTA, true) end debug " #{name} #{command}" end end end end # Encapsulates the act of decorating classes with ActiveSupport::Notifications # instrumenters and ActiveSupport::LogSubscribers # # TODO: @jbodah 2016-04-30: just use one shared event subscriber def self.log_calls_on!(klass, only: nil, event_namespace: nil, filter: nil) methods = only || MethodHelper.public_owned_instance_methods(klass) event_namespace ||= klass.name.demodulize.underscore event_namespace = event_namespace.to_sym klass.prepend EventInstrumenterFactory.instrumenter_for(klass, methods, event_namespace, filter) EventSubscriberFactory.subscriber_for(klass).attach_to(event_namespace) end end end