Skip to content

Commit

Permalink
Add support for dry-monitor events (#996)
Browse files Browse the repository at this point in the history
New hook that adds transaction events every time a dry-monitor
notifications instrumentation is called.

There's also specific support for `rom-sql` events emitted through
`dry-monitor`.

Co-authored-by: Luismi Ramírez <[email protected]>
  • Loading branch information
tombruijn and luismiramirez authored Oct 3, 2023
1 parent 6932bb3 commit 29970d9
Show file tree
Hide file tree
Showing 12 changed files with 263 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changesets/add-support-for-dry-monitor-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
bump: "patch"
type: "add"
---

Events from `dry-monitor` are now supported. There's also native support for `rom-sql` instrumentation events if they're configured.
54 changes: 54 additions & 0 deletions .semaphore/semaphore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,24 @@ blocks:
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.0.5 for dry-monitor
env_vars:
- *2
- *3
- *4
- *5
- name: RUBY_VERSION
value: 3.0.5
- name: GEMSET
value: dry-monitor
- name: BUNDLE_GEMFILE
value: gemfiles/dry-monitor.gemfile
- name: _RUBYGEMS_VERSION
value: latest
- name: _BUNDLER_VERSION
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.0.5 for grape
env_vars:
- *2
Expand Down Expand Up @@ -940,6 +958,24 @@ blocks:
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.1.3 for dry-monitor
env_vars:
- *2
- *3
- *4
- *5
- name: RUBY_VERSION
value: 3.1.3
- name: GEMSET
value: dry-monitor
- name: BUNDLE_GEMFILE
value: gemfiles/dry-monitor.gemfile
- name: _RUBYGEMS_VERSION
value: latest
- name: _BUNDLER_VERSION
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.1.3 for grape
env_vars:
- *2
Expand Down Expand Up @@ -1279,6 +1315,24 @@ blocks:
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.2.1 for dry-monitor
env_vars:
- *2
- *3
- *4
- *5
- name: RUBY_VERSION
value: 3.2.1
- name: GEMSET
value: dry-monitor
- name: BUNDLE_GEMFILE
value: gemfiles/dry-monitor.gemfile
- name: _RUBYGEMS_VERSION
value: latest
- name: _BUNDLER_VERSION
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.2.1 for grape
env_vars:
- *2
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ configurations you need to run the spec suite with a specific Gemfile.
```
BUNDLE_GEMFILE=gemfiles/capistrano2.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/capistrano3.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/dry-monitor.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/grape.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/hanami.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/http5.gemfile bundle exec rspec
Expand Down
6 changes: 6 additions & 0 deletions build_matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ matrix:
- gem: "no_dependencies"
- gem: "capistrano2"
- gem: "capistrano3"
- gem: "dry-monitor"
only:
ruby:
- "3.0.5"
- "3.1.3"
- "3.2.1"
- gem: "grape"
- gem: "hanami"
only:
Expand Down
5 changes: 5 additions & 0 deletions gemfiles/dry-monitor.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
source 'https://rubygems.org'

gem "dry-monitor", "~> 1.0.1"

gemspec :path => '../'
18 changes: 18 additions & 0 deletions lib/appsignal/event_formatter/rom/sql_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Appsignal
class EventFormatter
module Rom
class SqlFormatter
def format(payload)
["query.#{payload[:name]}", payload[:query], SQL_BODY_FORMAT]
end
end
end
end
end

Appsignal::EventFormatter.register(
"sql.dry",
Appsignal::EventFormatter::Rom::SqlFormatter
)
1 change: 1 addition & 0 deletions lib/appsignal/hooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def self.const_missing(name)
require "appsignal/hooks/celluloid"
require "appsignal/hooks/delayed_job"
require "appsignal/hooks/gvl"
require "appsignal/hooks/dry_monitor"
require "appsignal/hooks/http"
require "appsignal/hooks/mri"
require "appsignal/hooks/net_http"
Expand Down
20 changes: 20 additions & 0 deletions lib/appsignal/hooks/dry_monitor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module Appsignal
class Hooks
# @api private
class DryMonitorHook < Appsignal::Hooks::Hook
register :dry_monitor

def dependencies_present?
defined?(::Dry::Monitor::Notifications)
end

def install
require "appsignal/integrations/dry_monitor"

::Dry::Monitor::Notifications.prepend(Appsignal::Integrations::DryMonitorIntegration)
end
end
end
end
22 changes: 22 additions & 0 deletions lib/appsignal/integrations/dry_monitor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module Appsignal
module Integrations
module DryMonitorIntegration
def instrument(event_id, payload = {}, &block)
Appsignal::Transaction.current.start_event

super
ensure
title, body, body_format = Appsignal::EventFormatter.format("#{event_id}.dry", payload)

Appsignal::Transaction.current.finish_event(
title || event_id.to_s,
title,
body,
body_format
)
end
end
end
end
22 changes: 22 additions & 0 deletions spec/lib/appsignal/event_formatter/rom/sql_formatter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

describe Appsignal::EventFormatter::Rom::SqlFormatter do
let(:klass) { described_class }
let(:formatter) { klass.new }

it "registers the sql event formatter" do
expect(Appsignal::EventFormatter.registered?("sql.dry", klass)).to be_truthy
end

describe "#format" do
let(:payload) do
{
:name => "postgres",
:query => "SELECT * FROM users"
}
end
subject { formatter.format(payload) }

it { is_expected.to eq ["query.postgres", "SELECT * FROM users", 1] }
end
end
104 changes: 104 additions & 0 deletions spec/lib/appsignal/hooks/dry_monitor_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# frozen_string_literal: true

if DependencyHelper.dry_monitor_present?
require "dry-monitor"

describe Appsignal::Hooks::DryMonitorHook do
describe "#dependencies_present?" do
subject { described_class.new.dependencies_present? }

context "when Dry::Monitor::Notifications constant is found" do
before { stub_const "Dry::Monitor::Notifications", Class.new }

it { is_expected.to be_truthy }
end

context "when Dry::Monitor::Notifications constant is not found" do
before { hide_const "Dry::Monitor::Notifications" }

it { is_expected.to be_falsy }
end
end
end

describe "#install" do
it "installs the dry-monitor hook" do
start_agent

expect(Dry::Monitor::Notifications.included_modules).to include(
Appsignal::Integrations::DryMonitorIntegration
)
end
end

describe "Dry Monitor Integration" do
before :context do
start_agent
end

let!(:transaction) do
Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST, "test")
end

let(:notifications) { Dry::Monitor::Notifications.new(:test) }

context "when is a dry-sql event" do
let(:event_id) { :sql }
let(:payload) do
{
:name => "postgres",
:query => "SELECT * FROM users"
}
end

it "creates an sql event" do
notifications.instrument(event_id, payload)
expect(transaction.to_h["events"]).to match([
{
"allocation_count" => kind_of(Integer),
"body" => "SELECT * FROM users",
"body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT,
"child_allocation_count" => kind_of(Integer),
"child_duration" => kind_of(Float),
"child_gc_duration" => kind_of(Float),
"count" => 1,
"duration" => kind_of(Float),
"gc_duration" => kind_of(Float),
"name" => "query.postgres",
"start" => kind_of(Float),
"title" => "query.postgres"
}
])
end
end

context "when is an unregistered formatter event" do
let(:event_id) { :foo }
let(:payload) do
{
:name => "foo"
}
end

it "creates a generic event" do
notifications.instrument(event_id, payload)
expect(transaction.to_h["events"]).to match([
{
"allocation_count" => kind_of(Integer),
"body" => "",
"body_format" => Appsignal::EventFormatter::DEFAULT,
"child_allocation_count" => kind_of(Integer),
"child_duration" => kind_of(Float),
"child_gc_duration" => kind_of(Float),
"count" => 1,
"duration" => kind_of(Float),
"gc_duration" => kind_of(Float),
"name" => "foo",
"start" => kind_of(Float),
"title" => ""
}
])
end
end
end
end
4 changes: 4 additions & 0 deletions spec/support/helpers/dependency_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ def hanami_present?
dependency_present? "hanami"
end

def dry_monitor_present?
dependency_present? "dry-monitor"
end

def hanami2_present?
hanami_present? && Gem.loaded_specs["hanami"].version >= Gem::Version.new("2.0")
end
Expand Down

0 comments on commit 29970d9

Please sign in to comment.