diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8aa243c1..25ad2baf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ Airbrake Ruby Changelog
* Added the `versions` option
([#327](https://github.com/airbrake/airbrake-ruby/pull/327))
+* Added `DependencyFilter` (optional)
+ ([#328](https://github.com/airbrake/airbrake-ruby/pull/328))
### [v2.9.0][v2.9.0] (April 26, 2018)
diff --git a/README.md b/README.md
index ca83bd29..d3276ad1 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,7 @@ Key features
* Severity support[[link](#setting-severity)]
* Support for code hunks (lines of code surrounding each backtrace frame)[[link](#code_hunks)]
* Ability to add context to reported exceptions[[link](#airbrakemerge_context)]
+* Dependency tracking[[link](#airbrakefiltersdependencyfilter)]
* Last but not least, we follow semantic versioning 2.0.0[[link][semver2]]
Installation
@@ -568,14 +569,23 @@ every type of application, so there's no need to include them only to clutter
the notice object. You have to include such filters manually, if you think you
need them. The list of optional filters include:
-* Airbrake::Filters::ThreadFilter
+###### Airbrake::Filters::ThreadFilter
-Adding them is as easy as:
+Attaches thread & fiber local variables along with general thread information.
```ruby
Airbrake.add_filter(Airbrake::Filters::ThreadFilter.new)
```
+###### Airbrake::Filters::DependencyFilter
+
+Attaches loaded dependencies to the notice object (under
+`context/versions/dependencies`).
+
+```ruby
+Airbrake.add_filter(Airbrake::Filters::DependencyFilter.new)
+```
+
##### Using classes for building filters
For more complex filters you can use the special API. Simply pass an object that
diff --git a/lib/airbrake-ruby.rb b/lib/airbrake-ruby.rb
index 4add34f1..96ccc213 100644
--- a/lib/airbrake-ruby.rb
+++ b/lib/airbrake-ruby.rb
@@ -25,6 +25,7 @@
require 'airbrake-ruby/filters/thread_filter'
require 'airbrake-ruby/filters/context_filter'
require 'airbrake-ruby/filters/exception_attributes_filter'
+require 'airbrake-ruby/filters/dependency_filter'
require 'airbrake-ruby/filter_chain'
require 'airbrake-ruby/notifier'
require 'airbrake-ruby/code_hunk'
diff --git a/lib/airbrake-ruby/filters/dependency_filter.rb b/lib/airbrake-ruby/filters/dependency_filter.rb
new file mode 100644
index 00000000..d86bdeca
--- /dev/null
+++ b/lib/airbrake-ruby/filters/dependency_filter.rb
@@ -0,0 +1,27 @@
+module Airbrake
+ module Filters
+ # Attaches loaded dependencies to the notice object.
+ class DependencyFilter
+ def initialize
+ @weight = 117
+ end
+
+ def call(notice)
+ deps = {}
+ Gem.loaded_specs.map.with_object(deps) do |(name, spec), h|
+ h[name] = "#{spec.version}#{git_version(spec)}"
+ end
+
+ notice[:context][:versions] = {} unless notice[:context].key?(:versions)
+ notice[:context][:versions][:dependencies] = deps
+ end
+
+ private
+
+ def git_version(spec)
+ return unless spec.respond_to?(:git_version) || spec.git_version
+ spec.git_version.to_s
+ end
+ end
+ end
+end
diff --git a/spec/filters/dependency_filter_spec.rb b/spec/filters/dependency_filter_spec.rb
new file mode 100644
index 00000000..4030b41d
--- /dev/null
+++ b/spec/filters/dependency_filter_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+RSpec.describe Airbrake::Filters::DependencyFilter do
+ let(:notice) do
+ Airbrake::Notice.new(Airbrake::Config.new, AirbrakeTestError.new)
+ end
+
+ describe "#call" do
+ it "attaches loaded dependencies to context/versions/dependencies" do
+ subject.call(notice)
+ expect(notice[:context][:versions][:dependencies]).to include(
+ 'airbrake-ruby' => Airbrake::AIRBRAKE_RUBY_VERSION
+ )
+ end
+ end
+end