feat: add Prism, Instrumentation and Jobs
This commit is contained in:
1 parent
069b348302
commit
6825941c0b
3 files changed
+203
No files matched your search
@@ -0,0 +1,121 @@
|
||||
module Prism
|
||||
# ActiveSupport::Notifications-lite: a process-wide publish/subscribe bus
|
||||
# for cross-cutting events (queries, request dispatch, template renders).
|
||||
# Instrumented code owns naming and payload shape; subscribers (loggers,
|
||||
# metrics collectors, tests) opt into a subset by exact name or Regexp.
|
||||
module Instrumentation
|
||||
Event = Struct.new(:name, :payload, :duration, :time) do
|
||||
def duration_ms
|
||||
duration * 1000.0
|
||||
end
|
||||
end
|
||||
|
||||
class Subscriber
|
||||
def initialize(pattern, block)
|
||||
@pattern = pattern
|
||||
@block = block
|
||||
end
|
||||
|
||||
def match?(name)
|
||||
case @pattern
|
||||
when nil then true
|
||||
when Regexp then @pattern.match?(name)
|
||||
else @pattern.to_s == name
|
||||
end
|
||||
end
|
||||
|
||||
def call(event)
|
||||
@block.call(event)
|
||||
end
|
||||
end
|
||||
|
||||
class Bus
|
||||
def initialize
|
||||
@subscribers = []
|
||||
@mutex = Mutex.new
|
||||
end
|
||||
|
||||
def subscribe(pattern = nil, &block)
|
||||
raise ArgumentError, 'block required' unless block
|
||||
subscriber = Subscriber.new(pattern, block)
|
||||
@mutex.synchronize { @subscribers = @subscribers + [subscriber] }
|
||||
subscriber
|
||||
end
|
||||
|
||||
def unsubscribe(subscriber)
|
||||
@mutex.synchronize { @subscribers = @subscribers - [subscriber] }
|
||||
end
|
||||
|
||||
def subscribers
|
||||
@mutex.synchronize { @subscribers.dup }
|
||||
end
|
||||
|
||||
# Yields a mutable payload hash so instrumented code (and nested
|
||||
# instrument calls further down the stack) can enrich it before the
|
||||
# event is published. Always publishes, even when the block raises -
|
||||
# the exception is recorded on the payload and re-raised afterward.
|
||||
def instrument(name, payload = {})
|
||||
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
time = Time.now
|
||||
result = nil
|
||||
begin
|
||||
result = block_given? ? yield(payload) : nil
|
||||
result
|
||||
rescue StandardError => e
|
||||
payload[:exception] = [e.class.name, e.message]
|
||||
payload[:exception_object] = e
|
||||
raise
|
||||
ensure
|
||||
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
||||
publish(Event.new(name, payload, duration, time))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def publish(event)
|
||||
subscribers.each do |subscriber|
|
||||
next unless subscriber.match?(event.name)
|
||||
subscriber.call(event)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def bus
|
||||
@bus ||= Bus.new
|
||||
end
|
||||
|
||||
def instrument(name, payload = {}, &block)
|
||||
bus.instrument(name, payload, &block)
|
||||
end
|
||||
|
||||
def subscribe(pattern = nil, &block)
|
||||
bus.subscribe(pattern, &block)
|
||||
end
|
||||
|
||||
def unsubscribe(subscriber)
|
||||
bus.unsubscribe(subscriber)
|
||||
end
|
||||
end
|
||||
|
||||
# Rails-style one-line-per-event logger: " sql.prism (0.3ms) SELECT posts".
|
||||
# Attach once (typically in an initializer / Application setup); returns
|
||||
# the underlying Subscriber so callers can #detach it later.
|
||||
class LogSubscriber
|
||||
def self.attach(io = $stdout, patterns: [nil])
|
||||
patterns.map do |pattern|
|
||||
Instrumentation.subscribe(pattern) { |event| io.puts(format_event(event)) }
|
||||
end
|
||||
end
|
||||
|
||||
def self.format_event(event)
|
||||
ms = format('%.1f', event.duration_ms)
|
||||
details = event.payload.reject { |key, _| key == :exception_object }
|
||||
.map { |key, value| "#{key}=#{value.inspect}" }.join(' ')
|
||||
line = " #{event.name} (#{ms}ms)"
|
||||
details.empty? ? line : "#{line} #{details}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
require_relative 'jobs/job'
|
||||
require_relative 'jobs/queue'
|
||||
require_relative 'jobs/worker'
|
||||
|
||||
module Prism
|
||||
module Jobs
|
||||
class << self
|
||||
attr_writer :default_queue
|
||||
|
||||
def default_queue
|
||||
@default_queue ||= Queue.new('db/jobs.pstore')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
require_relative 'mailer/message'
|
||||
require_relative 'mailer/delivery'
|
||||
require_relative 'mailer/delivery_job'
|
||||
require_relative 'mailer/base'
|
||||
require_relative 'mailer/preview'
|
||||
require_relative 'mailer/preview_app'
|
||||
|
||||
module Prism
|
||||
module Mailer
|
||||
class << self
|
||||
attr_accessor :smtp_settings
|
||||
|
||||
# Router used by Mailer::Base#url_for to build absolute links inside
|
||||
# mail bodies, where there is no incoming request to pull a router off
|
||||
# of. Set once, normally via Application#enable_mailer, e.g.
|
||||
# `Prism::Mailer.router = BlogApp.router`.
|
||||
attr_accessor :router
|
||||
attr_writer :delivery_method, :default_url_options
|
||||
|
||||
def delivery_method
|
||||
@delivery_method ||= :test
|
||||
end
|
||||
|
||||
# App-wide fallback for Mailer::Base.default_url_options (host,
|
||||
# protocol, port); a mailer class's own default_url_options take
|
||||
# precedence when both are set.
|
||||
def default_url_options
|
||||
@default_url_options ||= {}
|
||||
end
|
||||
|
||||
# Messages handed to Mailer.deliver! while delivery_method == :test
|
||||
# pile up here instead of going out over the network, mirroring
|
||||
# ActionMailer::Base.deliveries for assertions in tests.
|
||||
def deliveries
|
||||
@deliveries ||= []
|
||||
end
|
||||
|
||||
def deliver!(message)
|
||||
Instrumentation.instrument('deliver_mail.prism', subject: message.subject,
|
||||
to: message.to,
|
||||
delivery_method: delivery_method) do
|
||||
case delivery_method
|
||||
when :test
|
||||
deliveries << message
|
||||
when :smtp
|
||||
deliver_via_smtp(message)
|
||||
else
|
||||
raise ArgumentError, "unknown Prism::Mailer.delivery_method #{delivery_method.inspect}"
|
||||
end
|
||||
end
|
||||
message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def deliver_via_smtp(message)
|
||||
require 'net/smtp'
|
||||
settings = smtp_settings || {}
|
||||
Net::SMTP.start(settings[:address] || 'localhost', settings[:port] || 25,
|
||||
settings[:domain] || 'localhost', settings[:user_name],
|
||||
settings[:password], settings[:authentication]) do |smtp|
|
||||
smtp.send_message(message.to_s, message.from, message.all_recipients)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user