require "time" require "socket" module PlainApm module EventAttributes SOURCES_WITH_EXTRA_ATTRIBUTES = %w[ action_controller action_mailer active_job deploy exception ].freeze # FIXME: This is duplicated for ErrorReporter IGNORED_EXCEPTIONS = [ "Sidekiq::JobRetry::Skip", # Sidekiq uses exceptions for control flow. "ActionController::RoutingError" # Rails unmapped route, raised before the request hits the app. ].freeze def attributes_from_deploy(tool, revision) source = "deploy" attrs = { "source" => source, "revision" => revision, "name" => tool } attrs.merge!(trace_attributes(source)) [tool, attrs] end def attributes_from_exception(e, context, error_source) source = "exception" return [source, nil] if IGNORED_EXCEPTIONS.include?(e.class.name) attrs = { "class" => e.class.name, "message" => e.message, "backtrace" => e.backtrace } if error_source attrs["event_source"] = error_source end if context[:env] attrs["params"] = context[:env]["action_dispatch.request.parameters"] end if context[:job]&.is_a?(ActiveJob::Base) attrs["job_class"] = context[:job].class.name attrs["queue_name"] = context[:job].queue_name end if e.cause attrs.merge!({ "cause_class" => e.cause.class.name, "cause_message" => e.cause.message, "cause_backtrace" => e.cause.backtrace }) end attrs.merge!(trace_attributes(source)) [source, attrs] end def attributes_from_notification(event) name, source = *event.name.split(".") loc = source_location attrs = { "source" => source, "name" => name, "allocations" => event.allocations, "event_time" => event.time, "duration" => event.duration } attrs["thread_allocations"] = event.thread_allocations if event.respond_to?(:thread_allocations) attrs["source_location"] = loc if !loc.nil? attrs.merge!(trace_attributes(source)) [name, attrs] end private def trace_attributes(source) process_attributes.merge(context_attributes) end def process_attributes { "thread_id" => Thread.current.object_id, "collected_at" => Time.now.iso8601(9), "version" => PlainApm::VERSION }.merge( host_attributes ) end def host_attributes @host_attributes ||= { "hostname" => Socket.gethostname, "pid" => Process.pid } end def context_attributes ## # Context contains the trace ID (which comes from either # HTTP_X_REQUEST_ID header, the deserialized job, # or is generated by the trace_id middleware). # It can also carry user inserted app data. if defined?(PlainApm::Extensions::Context) PlainApm::Extensions::Context.current.transform_keys(&:to_s) else {} end end def source_location filtered_backtrace&.first end def filtered_backtrace if defined?(Rails) && defined?(Rails::BacktraceCleaner) @cleaner ||= Rails::BacktraceCleaner.new @cleaner.clean(caller) end end end end