# frozen_string_literal: true require "singleton" require "json" module PlainApm class Agent include Singleton def self.collect(event) instance.collect(event) end def self.start instance.start end def collect(event) return unless @config.enabled ## # 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) event.merge!(PlainApm::Extensions::Context.current) end event.merge!( "version" => PlainApm::VERSION, "collected_at" => Time.now.utc.to_f, "pid" => Process.pid, "thread_id" => Thread.current.object_id.to_s, ) @events << event end def start return unless @publisher.nil? return unless @config.enabled # TODO: sized queue w/ a timeout. @events = Thread::Queue.new # TODO: Multiple threads @publisher = Thread.new { publisher_loop } install_hooks # TODO: add a cleaner shutdown. at_exit { shutdown } end private def initialize # TODO: validate config @config = Config.new super end def install_hooks [ Hooks::Deploy, Hooks::ActionMailer, Hooks::ActionPack, Hooks::ActionView, Hooks::ActiveJob, Hooks::ActiveRecord, Hooks::ErrorReporter ].map(&:new).each(&:install) end def shutdown return if @publisher.nil? @events << nil @publisher.join end ## # Run a background thread that pops events from the queue and posts them to # the target server. def publisher_loop # Have the thread keep it's own connection. transport = Transport.new( endpoint: @config.endpoint, app_key: @config.app_key ) loop do event = @events.pop break if event.nil? # TODO: event serialization _response, _error, _retriable = transport.deliver(JSON.generate(event)) # TODO: retries / drops end end end end