Skip to content

Allow unsubscribing from events #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: ac-structured-events
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions actionpack/lib/action_dispatch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class MissingController < NameError
autoload :HostAuthorization
autoload :RequestId
autoload :Callbacks
autoload :ClearEventReporterContext
autoload :Cookies
autoload :ActionableExceptions
autoload :DebugExceptions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module ActionDispatch
# Middleware that sets up a callback on rack.response_finished to clear
# the EventReporter context when the response is finished. This ensures that
# context is cleared as late as possible in the request lifecycle.
class ClearEventReporterContext # :nodoc:
def initialize(app)
@app = app
end

def call(env)
response = @app.call(env)

env["rack.response_finished"] ||= []
env["rack.response_finished"] << -> do
ActiveSupport.event_reporter.clear_context
end

response
rescue Exception => e
ActiveSupport.event_reporter.clear_context
raise e
end
end
end
39 changes: 39 additions & 0 deletions actionpack/test/dispatch/clear_event_reporter_context_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

require "abstract_unit"

class ClearEventReporterContextTest < ActiveSupport::TestCase
def setup
@app = ->(env) { [200, {}, ["Hello"]] }
@middleware = ActionDispatch::ClearEventReporterContext.new(@app)
@reporter = ActiveSupport.event_reporter
end

test "clears event reporter context in response finished callback" do
@reporter.set_context(shop_id: 123)

env = {}
@middleware.call(env)

assert env["rack.response_finished"]
assert_equal 1, env["rack.response_finished"].length

env["rack.response_finished"].each(&:call)

assert_equal({}, @reporter.context)
end

test "clears event reporter context when exception is raised" do
@reporter.set_context(shop_id: 123)

exception_app = ->(env) { raise StandardError, "Test exception" }
exception_middleware = ActionDispatch::ClearEventReporterContext.new(exception_app)

env = {}
assert_raises(StandardError) do
exception_middleware.call(env)
end

assert_equal({}, @reporter.context)
end
end
10 changes: 10 additions & 0 deletions activejob/lib/active_job/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ class Railtie < Rails::Railtie # :nodoc:
end
end

initializer "active_job.clear_event_reporter_context" do
ActiveSupport.on_load(:active_job) do
ActiveJob::Callbacks.singleton_class.set_callback(:execute, :around) do |_, inner|
inner.call
ensure
ActiveSupport.event_reporter.clear_context
end
end
end

initializer "active_job.query_log_tags" do |app|
query_logs_tags_enabled = app.config.respond_to?(:active_record) &&
app.config.active_record.query_log_tags_enabled &&
Expand Down
41 changes: 41 additions & 0 deletions activesupport/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
* Add Structured Event Reporter, accessible via `Rails.event`.

The Event Reporter provides a unified interface for producing structured events in Rails
applications:

```ruby
Rails.event.notify("user.signup", user_id: 123, email: "[email protected]")
```

It supports adding tags to events:

```ruby
Rails.event.tagged("graphql") do
# Event includes tags: { graphql: true }
Rails.event.notify("user.signup", user_id: 123, email: "[email protected]")
end
```

As well as context:
```ruby
# All events will contain context: {request_id: "abc123", shop_id: 456}
Rails.event.set_context(request_id: "abc123", shop_id: 456)
```

Events are emitted to subscribers. Applications register subscribers to
control how events are serialized and emitted. Rails provides several default
encoders that can be used to serialize events to common formats:

```ruby
class MySubscriber
def emit(event)
encoded_event = ActiveSupport::EventReporter.encoder(:json).encode(event)
StructuredLogExporter.export(encoded_event)
end
end

Rails.event.subscribe(MySubscriber.new)
```

*Adrianna Chang*

* Make `ActiveSupport::Gzip.compress` deterministic based on input.

`ActiveSupport::Gzip.compress` used to include a timestamp in the output,
Expand Down
4 changes: 4 additions & 0 deletions activesupport/lib/active_support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ module ActiveSupport
autoload :ExecutionWrapper
autoload :Executor
autoload :ErrorReporter
autoload :EventReporter
autoload :FileUpdateChecker
autoload :EventedFileUpdateChecker
autoload :ForkTracker
Expand Down Expand Up @@ -110,6 +111,9 @@ def self.eager_load!
@error_reporter = ActiveSupport::ErrorReporter.new
singleton_class.attr_accessor :error_reporter # :nodoc:

@event_reporter = ActiveSupport::EventReporter.new
singleton_class.attr_accessor :event_reporter # :nodoc:

def self.cache_format_version
Cache.format_version
end
Expand Down
Loading