-
Notifications
You must be signed in to change notification settings - Fork 22k
Structured event subscribers #55690
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
Structured event subscribers #55690
Conversation
233c317
to
c3a1b91
Compare
end | ||
|
||
def rails_root # :doc: | ||
@root ||= "#{Rails.try(:root)}/" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be a bit undesirable. The log subscriber waits until a logger is set, but the structured event subscriber doesn't need to so things get triggered early enough that Rails sometimes doesn't have root defined yet. Generally, we should not be referencing Rails at all here, and I want to work on injecting the root here in a cleaner way when I consolidate the notification subscribers. But, I can try to to do that earlier if we feel strongly about this being bad.
Looks like there's some CI failures I need to look at in Action View and the base class tests. Will do that ASAP. |
3ad8ba5
to
f8b663a
Compare
exception_message: exception.message, | ||
mail: event.payload[:mail], | ||
) | ||
elsif event.payload[:perform_deliveries] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have separate events for what was the same notficiation?
https://edgeguides.rubyonrails.org/active_support_instrumentation.html#deliver-action-mailer
Are we going to publicly document these events, and their payloads (as stable) as their AS::Notification counterparts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- We should document these somewhere, yes. I'll look into that. There's no guides page yet. Given that, I think we should add it in a followup patch.
- We do this in the job subscriber too. The notification is too general, and I think it makes more sense to emit specific events in these cases. I'll think about consolidating them to one event, but I don't want to include an error keys in the payload unless there's an actual error, for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a new guide next to the AS instrumentation one "Active Support Structured Events" could make sense.
But once we document them, making changes are more difficult, so good to get feedback on the events being added here first. Maybe they could stay experimental and undocumented for now?
The other thing, now we have essentially two places to maintain the same basic "event" from Rails. So if someone wants to add a new field to the payload to sql.active_record
(for example) -- they now have to do it in multiple places, which can be a source for confusion and bugs.
How we have CI to tell us if a new config flag is missing from the configuring guide, I always thought it'd be nice to have for notifications as well, that might help.
There is some benefit to keeping the event names and payloads consistent between the two, but I also can appreciate the chance to make them better before people are depending on them. I don't have a strong opinion on this type of change, just wanted to call it out as a question.
Keep up the good work! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing events in two places is unfortunate, but because notification events can have non-serializable data, and structured events should be serializable, there's likely going to be a translation layer needed for all of these regardless. In terms of the original discussion about the multiple IFs, I will refactor these instances to a singular event being emitted with variable payload content to mirror related notification events.
Thanks again for all your feedback!
What would be the preferred way to turn these off if you don't need them? They seem to be like other LogSubscribers so AS::Subscriber.subscibers. 🤔 Are there any concern of overhead by essentially doubling all the notification events? Sorry just random thoughts popped into my head looking at this, I think it will take some time to review the events for each framework but it's nice they are all in one PR. Thanks for working on this! |
I tried doing this already by disabling method calls for debug mode events with |
e629953
to
0a3763a
Compare
0a3763a
to
f4153e7
Compare
f4153e7
to
74b39ec
Compare
For those who want to help review, I thought it might be useful to to know what events are included, how that compares to the existing notifications API, also for my own curiousity. ✍️ Here is the full list of events for the framework added in this PRAction Controller
|
Key | Value |
---|---|
:controller |
The controller name |
:action |
The action |
:format |
html/js/json/xml etc |
:params |
Hash of request parameters without any filtered parameter |
process_action.action_controller
-> action_controller.request_completed
Key | Value |
---|---|
:controller |
The controller name |
:action |
The action |
:status |
HTTP status code |
:status_name |
Name of HTTP status using Rack::Utils::HTTP_STATUS_CODES |
:duration_ms |
Total duration of the request in ms |
:gc_time_ms |
Amount spent in garbage collection in ms |
send_file.action_controller
-> action_controller.file_sent
Key | Value |
---|---|
:path |
Complete path to the file |
:duration_ms |
Total duration of the request in ms |
send_data.action_controller
-> action_controller.data_sent
Key | Value |
---|---|
:filename |
Name of the file being sent |
:duration_ms |
Total duration of the request in ms |
redirect_to.action_controller
-> action_controller.redirected
Key | Value |
---|---|
:location |
URL to redirect to |
halted_callback.action_controller
-> action_controller.callback_halted
Key | Value |
---|---|
:filter |
Filter that halted the action |
unpermitted_parameters.action_controller
-> action_controller.unpermitted_parameters
Key | Value |
---|---|
:controller |
The controller name |
:action |
The action |
:unpermitted_keys |
The unpermitted keys |
New: action_controller.rescue_from_handler
Key | Value |
---|---|
:exception_class |
The class of the exception |
:exception_message |
The message of the exception |
:exception_backtrace |
The backtrace of the exception |
Action Controller: Caching
action_controller.fragment_cache
Key | Value |
---|---|
:method |
Cache method (e.g. write_fragment , read_fragment , exist_fragment? , expire_fragment |
:key |
The complete key |
:duration_ms |
Duration in ms |
Action Dispatch
redirect.action_dispatch
-> action_dispatch.redirect
Key | Value |
---|---|
:location |
URL to redirect to |
:status |
HTTP response code |
:status_name |
HTTP response code |
:duration_ms |
Total duration of the request in ms |
Action Mailer
action_mailer.delivery_error
Key | Value |
---|---|
:message_id |
ID of the message, generated by the Mail gem |
:exception_class |
The class of the exception |
:exception_message |
The message of the exception |
:mail |
The encoded form of the mail |
action_mailer.delivered
Key | Value |
---|---|
:message_id |
ID of the message, generated by the Mail gem |
:duration |
Total duration of the request in ms |
:mail |
The encoded form of the mail |
action_mailer.delivery_skipped
Key | Value |
---|---|
:message_id |
ID of the message, generated by the Mail gem |
:mail |
The encoded form of the mail |
process.action_mailer
-> action_mailer.processed
Key | Value |
---|---|
:mailer |
Name of the mailer class |
:action |
The action |
:duration |
Duration in ms |
Action View
render_template.action_view
-> action_view.render_template
Key | Value |
---|---|
:identifier |
Full path to template |
:layout |
Applicable layout |
:duration |
Duration in ms |
:gc |
Amount spent in garbage collection in ms |
render_partial.action_view
-> action_view.render_partial
Key | Value |
---|---|
:identifier |
Full path to template |
:layout |
Applicable layout |
:duration |
Duration in ms |
:gc |
Amount spent in garbage collection in ms |
:cache_hit |
true if the partial was fetched from cache |
render_collection.action_view
-> action_view.render_collection
Key | Value |
---|---|
:identifier |
Full path to template |
:layout |
Applicable layout |
:duration |
Duration in ms |
:gc |
Amount spent in garbage collection in ms |
:cache_hits |
Number of partials fetched from cache |
:count |
Size of collection |
render_layout.action_view
-> action_view.render_layout
Key | Value |
---|---|
:identifier |
Full path to template |
:duration |
Duration in ms |
:gc |
Amount spent in garbage collection in ms |
New: action_view.render_start
Key | Value |
---|---|
:identifier |
Full path to template |
:layout |
Applicable layout |
Active Job
enqueue.active_job
-> active_job.enqueued
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:queue |
Queue name |
:scheduled_at |
Time the job was scheduled |
active_job.enqueue_failed
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:queue |
Queue name |
:exception_class |
The class of the exception |
:exception_message |
The message of the exception |
:scheduled_at |
Time the job was scheduled |
active_job.enqueue_aborted
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:queue |
Queue name |
:scheduled_at |
Time the job was scheduled |
enqueue_retry.active_job
-> active_job.retry_scheduled
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:executions |
Number of attempts |
:wait_seconds |
Seconds to wait before retry |
:exception_class |
The class of the exception |
:exception_message |
The message of the exception |
retry_stopped.active_job
-> active_job.retry_stopped
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:executions |
Number of attempts |
:exception_class |
The class of the exception |
:exception_message |
The message of the exception |
enqueue_all.active_job
-> active_job.bulk_enqueued
Key | Value |
---|---|
:adapter |
QueueAdapter object processing the job |
:total_jobs |
Total number of jobs enqueued |
:enqueued_count |
Count of successfully enqueued jobs |
:failed_count |
Count of jobs that failed to enqueue |
:job_classes |
Array of job class names |
perform_start.active_job
-> active_job.started
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:queue |
Queue name |
:enqueued_at |
Time the job was enqueued |
perform.active_job
-> active_job.completed
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:queue |
Queue name |
:duration_ms |
Duration in ms |
active_job.failed
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:queue |
Queue name |
:duration_ms |
Duration in ms |
:exception_class |
The class of the exception |
:exception_message |
The message of the exception |
active_job.aborted
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:queue |
Queue name |
:duration_ms |
Duration in ms |
discard.active_job
-> active_job.discarded
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:exception_class |
The class of the exception |
:exception_message |
The message of the exception |
New: active_job.interrupt
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:description |
Description of the interrupt |
:reason |
Reason for the interrupt |
New: active_job.resume
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:description |
Description of the interrupt |
New: active_job.step_skipped
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:step |
Name of the step being skipped |
New: active_job.step_resumed
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:step |
Name of the step being skipped |
:cursor |
Cursor at which the step resumes |
New: active_job.step_started
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:step |
Name of the step being skipped |
New: active_job.step_interrupted
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:step |
Name of the step being skipped |
:cursor |
Cursor at which the step resumes |
:duration |
Duration in ms |
New: active_job.step_errored
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:step |
Name of the step being skipped |
:cursor |
Cursor at which the step resumes |
:duration |
Duration in ms |
:exception_class |
The class of the exception |
:exception_message |
The message of the exception |
New: active_job.step
Key | Value |
---|---|
:job_class |
Job class name |
:job_id |
Job ID |
:step |
Name of the step being skipped |
:duration |
Duration in ms |
Active Record
sql.active_record
-> active_record.sql
Key | Value |
---|---|
:async |
true if query is loaded asynchronously |
:name |
Name of the operation |
:sql |
SQL statement |
:cached |
true is added when result comes from the query cache |
:lock_wait |
How long the query waited to perform asynchronously |
:binds |
Bind parameters |
strict_loading_violation.active_record
-> active_record.strict_loading_violation
Key | Value |
---|---|
:owner |
Model with strict_loading enabled |
:class |
Name of the class for the reflection of the association |
:name |
Name of the reflection of the association |
Active Storage
preview.active_storage
-> active_storage.preview
Key | Value |
---|---|
:key |
Secure token |
Active Storage: Storage Service
service_upload.active_storage
-> active_storage.service_upload
Key | Value |
---|---|
:key |
Secure token |
:checksum |
Checksum to ensure integrity |
service_streaming_download.active_storage
-> active_storage.service_streaming_download
Key | Value |
---|---|
:key |
Secure token |
service_download.active_storage
-> active_storage.service_download
Key | Value |
---|---|
:key |
Secure token |
service_delete.active_storage
-> active_storage.service_delete
Key | Value |
---|---|
:key |
Secure token |
service_delete_prefixed.active_storage
-> active_storage.service_delete_prefixed
Key | Value |
---|---|
:prefix |
Key prefix |
service_exist.active_storage
-> active_storage.service_exist
Key | Value |
---|---|
:key |
Secure token |
:exist |
File or blob exists or not |
service_url.active_storage
-> active_storage.service_url
Key | Value |
---|---|
:key |
Secure token |
:url |
Generated URL |
New: active_storage.service_mirror
Key | Value |
---|---|
:key |
Secure token |
:checksum |
Checksum to ensure integrity |
Maybe that can be useful for when we decide to put the guide together for this feature. 🤔
edit: I didn't label events if they were debug
or not but maybe a nice addition when we get there.
🔍 Here is a diff generated with the base markdown from the notifications guide
--- notifications.md 2025-09-21 16:50:37.888179044 +0900
+++ events.md 2025-09-21 18:50:03.136135966 +0900
@@ -1,597 +1,439 @@
-### Action Cable
-
-#### `perform_action.action_cable`
-
-| Key | Value |
-| ---------------- | ------------------------- |
-| `:channel_class` | Name of the channel class |
-| `:action` | The action |
-| `:data` | A hash of data |
-
-#### `transmit.action_cable`
-
-| Key | Value |
-| ---------------- | ------------------------- |
-| `:channel_class` | Name of the channel class |
-| `:data` | A hash of data |
-| `:via` | Via |
-
-#### `transmit_subscription_confirmation.action_cable`
-
-| Key | Value |
-| ---------------- | ------------------------- |
-| `:channel_class` | Name of the channel class |
-
-#### `transmit_subscription_rejection.action_cable`
-
-| Key | Value |
-| ---------------- | ------------------------- |
-| `:channel_class` | Name of the channel class |
-
-#### `broadcast.action_cable`
-
-| Key | Value |
-| --------------- | -------------------- |
-| `:broadcasting` | A named broadcasting |
-| `:message` | A hash of message |
-| `:coder` | The coder |
-
-
### Action Controller
-#### `start_processing.action_controller`
+#### `start_processing.action_controller` -> `action_controller.request_started`
| Key | Value |
| ------------- | --------------------------------------------------------- |
| `:controller` | The controller name |
| `:action` | The action |
-| `:request` | The [`ActionDispatch::Request`][] object |
-| `:params` | Hash of request parameters without any filtered parameter |
-| `:headers` | Request headers |
| `:format` | html/js/json/xml etc |
-| `:method` | HTTP request verb |
-| `:path` | Request path |
-
-#### `process_action.action_controller`
+| `:params` | Hash of request parameters without any filtered parameter |
-| Key | Value |
-| --------------- | --------------------------------------------------------- |
-| `:controller` | The controller name |
-| `:action` | The action |
-| `:params` | Hash of request parameters without any filtered parameter |
-| `:headers` | Request headers |
-| `:format` | html/js/json/xml etc |
-| `:method` | HTTP request verb |
-| `:path` | Request path |
-| `:request` | The [`ActionDispatch::Request`][] object |
-| `:response` | The [`ActionDispatch::Response`][] object |
-| `:status` | HTTP status code |
-| `:view_runtime` | Amount spent in view in ms |
-| `:db_runtime` | Amount spent executing database queries in ms |
-
-#### `send_file.action_controller`
-
-| Key | Value |
-| ------- | ------------------------- |
-| `:path` | Complete path to the file |
-
-#### `send_data.action_controller`
-
-`{}`
-
-#### `redirect_to.action_controller`
-
-| Key | Value |
-| ----------- | ---------------------------------------- |
-| `:status` | HTTP response code |
-| `:location` | URL to redirect to |
-| `:request` | The [`ActionDispatch::Request`][] object |
+#### `process_action.action_controller` -> `action_controller.request_completed`
+| Key | Value |
+| ----------------- | ---------------------------------------------------------- |
+| `:controller` | The controller name |
+| `:action` | The action |
+| `:status` | HTTP status code |
+| `:status_name` | Name of HTTP status using `Rack::Utils::HTTP_STATUS_CODES` |
+| `:duration_ms` | Total duration of the request in ms |
+| `:gc_time_ms` | Amount spent in garbage collection in ms |
+
+#### `send_file.action_controller` -> `action_controller.file_sent`
+
+| Key | Value |
+| -------------- | ----------------------------------- |
+| `:path` | Complete path to the file |
+| `:duration_ms` | Total duration of the request in ms |
+
+#### `send_data.action_controller` -> `action_controller.data_sent`
+
+| Key | Value |
+| -------------- | ----------------------------------- |
+| `:filename` | Name of the file being sent |
+| `:duration_ms` | Total duration of the request in ms |
+
+#### `redirect_to.action_controller` -> `action_controller.redirected`
+
+| Key | Value |
+| ------------ | ---------------------------------------- |
+| `:location` | URL to redirect to |
-#### `halted_callback.action_controller`
+#### `halted_callback.action_controller` -> `action_controller.callback_halted`
| Key | Value |
| --------- | ----------------------------- |
| `:filter` | Filter that halted the action |
-#### `unpermitted_parameters.action_controller`
+#### `unpermitted_parameters.action_controller` -> `action_controller.unpermitted_parameters`
-| Key | Value |
-| ------------- | ----------------------------------------------------------------------------- |
-| `:keys` | The unpermitted keys |
-| `:context` | Hash with the following keys: `:controller`, `:action`, `:params`, `:request` |
+| Key | Value |
+| ------------------- | ----------------------------------------------------------------------- |
+| `:controller` | The controller name |
+| `:action` | The action |
+| `:unpermitted_keys` | The unpermitted keys |
+
+#### New: `action_controller.rescue_from_handler`
+
+| Key | Value |
+| ---------------------- | ------------------------------ |
+| `:exception_class` | The class of the exception |
+| `:exception_message` | The message of the exception |
+| `:exception_backtrace` | The backtrace of the exception |
-#### `send_stream.action_controller`
-
-| Key | Value |
-| -------------- | ---------------------------------------- |
-| `:filename` | The filename |
-| `:type` | HTTP content type |
-| `:disposition` | HTTP content disposition |
-
-#### `rate_limit.action_controller`
-
-| Key | Value |
-| ------------ | --------------------------------------------- |
-| `:request` | The [`ActionDispatch::Request`][] object |
-| `:count` | Number of requests made |
-| `:to` | Maximum number of requests allowed |
-| `:within` | Time window for the rate limit |
-| `:by` | Identifier for the rate limit (e.g. IP) |
-| `:name` | Name of the rate limit |
-| `:scope` | Scope of the rate limit |
-| `:cache_key` | The cache key used for storing the rate limit |
### Action Controller: Caching
-#### `write_fragment.action_controller`
-
-| Key | Value |
-| ------ | ---------------- |
-| `:key` | The complete key |
-
-#### `read_fragment.action_controller`
-
-| Key | Value |
-| ------ | ---------------- |
-| `:key` | The complete key |
-
-#### `expire_fragment.action_controller`
-
-| Key | Value |
-| ------ | ---------------- |
-| `:key` | The complete key |
+#### `action_controller.fragment_cache`
-#### `exist_fragment?.action_controller`
-
-| Key | Value |
-| ------ | ---------------- |
-| `:key` | The complete key |
+| Key | Value |
+| -------------- | ---------------- |
+| `:method` | Cache method (e.g. `write_fragment`, `read_fragment`, `exist_fragment?`, `expire_fragment` |
+| `:key` | The complete key |
+| `:duration_ms` | Duration in ms |
### Action Dispatch
-#### `process_middleware.action_dispatch`
-
-| Key | Value |
-| ------------- | ---------------------- |
-| `:middleware` | Name of the middleware |
-
-#### `redirect.action_dispatch`
+#### `redirect.action_dispatch` -> `action_dispatch.redirect`
-| Key | Value |
-| ----------- | ---------------------------------------- |
-| `:status` | HTTP response code |
-| `:location` | URL to redirect to |
-| `:request` | The [`ActionDispatch::Request`][] object |
-
-#### `request.action_dispatch`
-
-| Key | Value |
-| ----------- | ---------------------------------------- |
-| `:request` | The [`ActionDispatch::Request`][] object |
+| Key | Value |
+| -------------- | ---------------------------------------- |
+| `:location` | URL to redirect to |
+| `:status` | HTTP response code |
+| `:status_name` | HTTP response code |
+| `:duration_ms` | Total duration of the request in ms |
[`ActionDispatch::Request`]: https://api.rubyonrails.org/classes/ActionDispatch/Request.html
[`ActionDispatch::Response`]: https://api.rubyonrails.org/classes/ActionDispatch/Response.html
-### Action Mailbox
-
-#### `process.action_mailbox`
-
-| Key | Value |
-| -----------------| ------------------------------------------------------ |
-| `:mailbox` | Instance of the Mailbox class inheriting from [`ActionMailbox::Base`][] |
-| `:inbound_email` | Hash with data about the inbound email being processed |
+### Action Mailer
-[`ActionMailbox::Base`]: https://api.rubyonrails.org/classes/ActionMailbox/Base.html
+#### `action_mailer.delivery_error`
-### Action Mailer
+| Key | Value |
+| --------------------- | -------------------------------------------- |
+| `:message_id` | ID of the message, generated by the Mail gem |
+| `:exception_class` | The class of the exception |
+| `:exception_message` | The message of the exception |
+| `:mail` | The encoded form of the mail |
+
+#### `action_mailer.delivered`
+
+| Key | Value |
+| ------------- | -------------------------------------------- |
+| `:message_id` | ID of the message, generated by the Mail gem |
+| `:duration` | Total duration of the request in ms |
+| `:mail` | The encoded form of the mail |
-#### `deliver.action_mailer`
+#### `action_mailer.delivery_skipped`
| Key | Value |
| --------------------- | ---------------------------------------------------- |
-| `:mailer` | Name of the mailer class |
| `:message_id` | ID of the message, generated by the Mail gem |
-| `:subject` | Subject of the mail |
-| `:to` | To address(es) of the mail |
-| `:from` | From address of the mail |
-| `:bcc` | BCC addresses of the mail |
-| `:cc` | CC addresses of the mail |
-| `:date` | Date of the mail |
| `:mail` | The encoded form of the mail |
-| `:perform_deliveries` | Whether delivery of this message is performed or not |
-#### `process.action_mailer`
+#### `process.action_mailer` -> `action_mailer.processed`
| Key | Value |
| ------------- | ------------------------ |
| `:mailer` | Name of the mailer class |
| `:action` | The action |
-| `:args` | The arguments |
+| `:duration` | Duration in ms |
### Action View
-#### `render_template.action_view`
+#### `render_template.action_view` -> `action_view.render_template`
-| Key | Value |
-| ------------- | ---------------------------------- |
-| `:identifier` | Full path to template |
-| `:layout` | Applicable layout |
-| `:locals` | Local variables passed to template |
-
-#### `render_partial.action_view`
-
-| Key | Value |
-| ------------- | ---------------------------------- |
-| `:identifier` | Full path to template |
-| `:locals` | Local variables passed to template |
-
-#### `render_collection.action_view`
-
-| Key | Value |
-| ------------- | ------------------------------------- |
-| `:identifier` | Full path to template |
-| `:count` | Size of collection |
-| `:cache_hits` | Number of partials fetched from cache |
-
-#### `render_layout.action_view`
-
-| Key | Value |
-| ------------- | --------------------- |
-| `:identifier` | Full path to template |
+| Key | Value |
+| ------------- | ---------------------------------------- |
+| `:identifier` | Full path to template |
+| `:layout` | Applicable layout |
+| `:duration` | Duration in ms |
+| `:gc` | Amount spent in garbage collection in ms |
+
+#### `render_partial.action_view` -> `action_view.render_partial`
+
+| Key | Value |
+| ------------- | -------------------------------------------- |
+| `:identifier` | Full path to template |
+| `:layout` | Applicable layout |
+| `:duration` | Duration in ms |
+| `:gc` | Amount spent in garbage collection in ms |
+| `:cache_hit` | `true` if the partial was fetched from cache |
+
+#### `render_collection.action_view` -> `action_view.render_collection`
+
+| Key | Value |
+| --------------- | ---------------------------------------- |
+| `:identifier` | Full path to template |
+| `:layout` | Applicable layout |
+| `:duration` | Duration in ms |
+| `:gc` | Amount spent in garbage collection in ms |
+| `:cache_hits` | Number of partials fetched from cache |
+| `:count` | Size of collection |
+
+#### `render_layout.action_view` -> `action_view.render_layout`
+
+| Key | Value |
+| --------------- | ---------------------------------------- |
+| `:identifier` | Full path to template |
+| `:duration` | Duration in ms |
+| `:gc` | Amount spent in garbage collection in ms |
+
+#### New: `action_view.render_start`
+
+| Key | Value |
+| --------------- | ---------------------------------------- |
+| `:identifier` | Full path to template |
+| `:layout` | Applicable layout |
### Active Job
-#### `enqueue_at.active_job`
+#### `enqueue.active_job` -> `active_job.enqueued`
-| Key | Value |
-| ------------ | -------------------------------------- |
-| `:adapter` | QueueAdapter object processing the job |
-| `:job` | Job object |
-
-#### `enqueue.active_job`
-
-| Key | Value |
-| ------------ | -------------------------------------- |
-| `:adapter` | QueueAdapter object processing the job |
-| `:job` | Job object |
-
-#### `enqueue_retry.active_job`
-
-| Key | Value |
-| ------------ | -------------------------------------- |
-| `:job` | Job object |
-| `:adapter` | QueueAdapter object processing the job |
-| `:error` | The error that caused the retry |
-| `:wait` | The delay of the retry |
-
-#### `enqueue_all.active_job`
-
-| Key | Value |
-| ------------ | -------------------------------------- |
-| `:adapter` | QueueAdapter object processing the job |
-| `:jobs` | An array of Job objects |
-
-#### `perform_start.active_job`
-
-| Key | Value |
-| ------------ | -------------------------------------- |
-| `:adapter` | QueueAdapter object processing the job |
-| `:job` | Job object |
-
-#### `perform.active_job`
-
-| Key | Value |
-| ------------- | --------------------------------------------- |
-| `:adapter` | QueueAdapter object processing the job |
-| `:job` | Job object |
-| `:db_runtime` | Amount spent executing database queries in ms |
-
-#### `retry_stopped.active_job`
-
-| Key | Value |
-| ------------ | -------------------------------------- |
-| `:adapter` | QueueAdapter object processing the job |
-| `:job` | Job object |
-| `:error` | The error that caused the retry |
-
-#### `discard.active_job`
-
-| Key | Value |
-| ------------ | -------------------------------------- |
-| `:adapter` | QueueAdapter object processing the job |
-| `:job` | Job object |
-| `:error` | The error that caused the discard |
+| Key | Value |
+| --------------- | ---------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:queue` | Queue name |
+| `:scheduled_at` | Time the job was scheduled |
+
+#### `active_job.enqueue_failed`
+
+| Key | Value |
+| -------------------- | ---------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:queue` | Queue name |
+| `:exception_class` | The class of the exception |
+| `:exception_message` | The message of the exception |
+| `:scheduled_at` | Time the job was scheduled |
+
+#### `active_job.enqueue_aborted`
+
+| Key | Value |
+| -------------------- | ---------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:queue` | Queue name |
+| `:scheduled_at` | Time the job was scheduled |
+
+#### `enqueue_retry.active_job` -> `active_job.retry_scheduled`
+
+| Key | Value |
+| -------------------- | ---------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:executions` | Number of attempts |
+| `:wait_seconds` | Seconds to wait before retry |
+| `:exception_class` | The class of the exception |
+| `:exception_message` | The message of the exception |
+
+#### `retry_stopped.active_job` -> `active_job.retry_stopped`
+
+| Key | Value |
+| -------------------- | ---------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:executions` | Number of attempts |
+| `:exception_class` | The class of the exception |
+| `:exception_message` | The message of the exception |
+
+#### `enqueue_all.active_job` -> `active_job.bulk_enqueued`
+
+| Key | Value |
+| ----------------- | -------------------------------------- |
+| `:adapter` | QueueAdapter object processing the job |
+| `:total_jobs` | Total number of jobs enqueued |
+| `:enqueued_count` | Count of successfully enqueued jobs |
+| `:failed_count` | Count of jobs that failed to enqueue |
+| `:job_classes` | Array of job class names |
+
+#### `perform_start.active_job` -> `active_job.started`
+
+| Key | Value |
+| ------------- | ------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:queue` | Queue name |
+| `:enqueued_at` | Time the job was enqueued |
+
+#### `perform.active_job` -> `active_job.completed`
+
+| Key | Value |
+| -------------- | -------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:queue` | Queue name |
+| `:duration_ms` | Duration in ms |
+
+#### `active_job.failed`
+
+| Key | Value |
+| -------------------- | ---------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:queue` | Queue name |
+| `:duration_ms` | Duration in ms |
+| `:exception_class` | The class of the exception |
+| `:exception_message` | The message of the exception |
+
+#### `active_job.aborted`
+
+| Key | Value |
+| -------------- | -------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:queue` | Queue name |
+| `:duration_ms` | Duration in ms |
+
+#### `discard.active_job` -> `active_job.discarded`
+
+| Key | Value |
+| -------------------- | ---------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:exception_class` | The class of the exception |
+| `:exception_message` | The message of the exception |
+
+#### New: `active_job.interrupt`
+
+| Key | Value |
+| -------------- | ---------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:description` | Description of the interrupt |
+| `:reason` | Reason for the interrupt |
+
+#### New: `active_job.resume`
+
+| Key | Value |
+| -------------- | ---------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:description` | Description of the interrupt |
+
+#### New: `active_job.step_skipped`
+
+| Key | Value |
+| ------------ | ------------------------------ |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:step` | Name of the step being skipped |
+
+#### New: `active_job.step_resumed`
+
+| Key | Value |
+| ------------ | -------------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:step` | Name of the step being skipped |
+| `:cursor` | Cursor at which the step resumes |
+
+#### New: `active_job.step_started`
+
+| Key | Value |
+| ------------ | -------------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:step` | Name of the step being skipped |
+
+#### New: `active_job.step_interrupted`
+
+| Key | Value |
+| ------------ | -------------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:step` | Name of the step being skipped |
+| `:cursor` | Cursor at which the step resumes |
+| `:duration` | Duration in ms |
+
+#### New: `active_job.step_errored`
+
+| Key | Value |
+| -------------------- | -------------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:step` | Name of the step being skipped |
+| `:cursor` | Cursor at which the step resumes |
+| `:duration` | Duration in ms |
+| `:exception_class` | The class of the exception |
+| `:exception_message` | The message of the exception |
+
+#### New: `active_job.step`
+
+| Key | Value |
+| -------------------- | -------------------------------- |
+| `:job_class` | Job class name |
+| `:job_id` | Job ID |
+| `:step` | Name of the step being skipped |
+| `:duration` | Duration in ms |
### Active Record
-#### `sql.active_record`
+#### `sql.active_record` -> `active_record.sql`
-| Key | Value |
-| -------------------- | ------------------------------------------------------ |
-| `:sql` | SQL statement |
-| `:name` | Name of the operation |
-| `:binds` | Bind parameters |
-| `:type_casted_binds` | Typecasted bind parameters |
-| `:async` | `true` if query is loaded asynchronously |
-| `:allow_retry` | `true` if the query can be automatically retried |
-| `:connection` | Connection object |
-| `:transaction` | Current transaction, if any |
-| `:affected_rows` | Number of rows affected by the query |
-| `:row_count` | Number of rows returned by the query |
-| `:cached` | `true` is added when result comes from the query cache |
-| `:statement_name` | SQL Statement name (Postgres only) |
-
-#### `strict_loading_violation.active_record`
-
-| Key | Value |
-| ------------- | ------------------------------------------------ |
-| `:owner` | Model with `strict_loading` enabled |
-| `:reflection` | Reflection of the association that tried to load |
-
-#### `instantiation.active_record`
-
-| Key | Value |
-| ---------------- | ----------------------------------------- |
-| `:record_count` | Number of records that instantiated |
-| `:class_name` | Record's class |
-
-#### `start_transaction.active_record`
-
-| Key | Value |
-| -------------------- | ---------------------------------------------------- |
-| `:transaction` | Transaction object |
-| `:connection` | Connection object |
-
-#### `transaction.active_record`
-
-| Key | Value |
-| -------------------- | ---------------------------------------------------- |
-| `:transaction` | Transaction object |
-| `:outcome` | `:commit`, `:rollback`, `:restart`, or `:incomplete` |
-| `:connection` | Connection object |
-
-#### `deprecated_association.active_record`
-
-| Key | Value |
-| -------------------- | ---------------------------------------------------- |
-| `:reflection` | The reflection of the association |
-| `:message` | A descriptive message about the access |
-| `:location` | The application-level location of the access |
-| `:backtrace` | Only present if the option `:backtrace` is true |
+| Key | Value |
+| ------------ | ------------------------------------------------------ |
+| `:async` | `true` if query is loaded asynchronously |
+| `:name` | Name of the operation |
+| `:sql` | SQL statement |
+| `:cached` | `true` is added when result comes from the query cache |
+| `:lock_wait` | How long the query waited to perform asynchronously |
+| `:binds` | Bind parameters |
+
+#### `strict_loading_violation.active_record` -> `active_record.strict_loading_violation`
+
+| Key | Value |
+| -------- | ------------------------------------------------------- |
+| `:owner` | Model with `strict_loading` enabled |
+| `:class` | Name of the class for the reflection of the association |
+| `:name` | Name of the reflection of the association |
### Active Storage
-#### `preview.active_storage`
+#### `preview.active_storage` -> `active_storage.preview`
| Key | Value |
| ------------ | ------------------- |
| `:key` | Secure token |
-#### `transform.active_storage`
-
-#### `analyze.active_storage`
-
-| Key | Value |
-| ------------ | ------------------------------ |
-| `:analyzer` | Name of analyzer e.g., ffprobe |
-
### Active Storage: Storage Service
-#### `service_upload.active_storage`
+#### `service_upload.active_storage` -> `active_storage.service_upload`
| Key | Value |
| ------------ | ---------------------------- |
| `:key` | Secure token |
-| `:service` | Name of the service |
| `:checksum` | Checksum to ensure integrity |
-#### `service_streaming_download.active_storage`
+#### `service_streaming_download.active_storage` -> `active_storage.service_streaming_download`
| Key | Value |
| ------------ | ------------------- |
| `:key` | Secure token |
-| `:service` | Name of the service |
-
-#### `service_download_chunk.active_storage`
-
-| Key | Value |
-| ------------ | ------------------------------- |
-| `:key` | Secure token |
-| `:service` | Name of the service |
-| `:range` | Byte range attempted to be read |
-#### `service_download.active_storage`
+#### `service_download.active_storage` -> `active_storage.service_download`
| Key | Value |
| ------------ | ------------------- |
| `:key` | Secure token |
-| `:service` | Name of the service |
-#### `service_delete.active_storage`
+#### `service_delete.active_storage` -> `active_storage.service_delete`
| Key | Value |
| ------------ | ------------------- |
| `:key` | Secure token |
-| `:service` | Name of the service |
-#### `service_delete_prefixed.active_storage`
+#### `service_delete_prefixed.active_storage` -> `active_storage.service_delete_prefixed`
| Key | Value |
| ------------ | ------------------- |
| `:prefix` | Key prefix |
-| `:service` | Name of the service |
-#### `service_exist.active_storage`
+#### `service_exist.active_storage` -> `active_storage.service_exist`
| Key | Value |
| ------------ | --------------------------- |
| `:key` | Secure token |
-| `:service` | Name of the service |
| `:exist` | File or blob exists or not |
-#### `service_url.active_storage`
+#### `service_url.active_storage` -> `active_storage.service_url`
| Key | Value |
| ------------ | ------------------- |
| `:key` | Secure token |
-| `:service` | Name of the service |
| `:url` | Generated URL |
-#### `service_update_metadata.active_storage`
+#### New: `active_storage.service_mirror`
| Key | Value |
| --------------- | -------------------------------- |
| `:key` | Secure token |
-| `:service` | Name of the service |
-| `:content_type` | HTTP `Content-Type` field |
-| `:disposition` | HTTP `Content-Disposition` field |
-
-
-### Active Support: Caching
-
-#### `cache_read.active_support`
-
-| Key | Value |
-| ------------------ | ----------------------- |
-| `:key` | Key used in the store |
-| `:store` | Name of the store class |
-| `:hit` | If this read is a hit |
-| `:super_operation` | `:fetch` if a read is done with [`fetch`][ActiveSupport::Cache::Store#fetch] |
-
-#### `cache_read_multi.active_support`
-
-| Key | Value |
-| ------------------ | ----------------------- |
-| `:key` | Keys used in the store |
-| `:store` | Name of the store class |
-| `:hits` | Keys of cache hits |
-| `:super_operation` | `:fetch_multi` if a read is done with [`fetch_multi`][ActiveSupport::Cache::Store#fetch_multi] |
-
-#### `cache_generate.active_support`
-
-| Key | Value |
-| -------- | ----------------------- |
-| `:key` | Key used in the store |
-| `:store` | Name of the store class |
-
-#### `cache_fetch_hit.active_support`
-
-| Key | Value |
-| -------- | ----------------------- |
-| `:key` | Key used in the store |
-| `:store` | Name of the store class |
-
-#### `cache_write.active_support`
-
-| Key | Value |
-| -------- | ----------------------- |
-| `:key` | Key used in the store |
-| `:store` | Name of the store class |
-
-#### `cache_write_multi.active_support`
-
-| Key | Value |
-| -------- | ------------------------------------ |
-| `:key` | Keys and values written to the store |
-| `:store` | Name of the store class |
-
-#### `cache_increment.active_support`
-
-| Key | Value |
-| --------- | ----------------------- |
-| `:key` | Key used in the store |
-| `:store` | Name of the store class |
-| `:amount` | Increment amount |
-
-#### `cache_decrement.active_support`
-
-| Key | Value |
-| --------- | ----------------------- |
-| `:key` | Key used in the store |
-| `:store` | Name of the store class |
-| `:amount` | Decrement amount |
-
-#### `cache_delete.active_support`
-
-| Key | Value |
-| -------- | ----------------------- |
-| `:key` | Key used in the store |
-| `:store` | Name of the store class |
-
-#### `cache_delete_multi.active_support`
-
-| Key | Value |
-| -------- | ----------------------- |
-| `:key` | Keys used in the store |
-| `:store` | Name of the store class |
-
-#### `cache_delete_matched.active_support`
-
-| Key | Value |
-| -------- | ----------------------- |
-| `:key` | Key pattern used |
-| `:store` | Name of the store class |
-
-#### `cache_cleanup.active_support`
-
-| Key | Value |
-| -------- | --------------------------------------------- |
-| `:store` | Name of the store class |
-| `:size` | Number of entries in the cache before cleanup |
-
-#### `cache_prune.active_support`
-
-| Key | Value |
-| -------- | --------------------------------------------- |
-| `:store` | Name of the store class |
-| `:key` | Target size (in bytes) for the cache |
-| `:from` | Size (in bytes) of the cache before prune |
-
-#### `cache_exist?.active_support`
-
-| Key | Value |
-| -------- | ----------------------- |
-| `:key` | Key used in the store |
-| `:store` | Name of the store class |
-
-[ActiveSupport::Cache::Store#fetch]: https://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-fetch
-[ActiveSupport::Cache::Store#fetch_multi]: https://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-fetch_multi
-
-### Active Support: Messages
-
-#### `message_serializer_fallback.active_support`
-
-| Key | Value |
-| --------------- | ----------------------------- |
-| `:serializer` | Primary (intended) serializer |
-| `:fallback` | Fallback (actual) serializer |
-| `:serialized` | Serialized string |
-| `:deserialized` | Deserialized value |
-
-
-### Rails
-
-#### `deprecation.rails`
-
-| Key | Value |
-| ---------------------- | ------------------------------------------------------|
-| `:message` | The deprecation warning |
-| `:callstack` | Where the deprecation came from |
-| `:gem_name` | Name of the gem reporting the deprecation |
-| `:deprecation_horizon` | Version where the deprecated behavior will be removed |
-
-
-
-### Railties
-
-#### `load_config_initializer.railties`
-
-| Key | Value |
-| -------------- | --------------------------------------------------- |
-| `:initializer` | Path of loaded initializer in `config/initializers` |
+| `:checksum` | Checksum to ensure integrity |
There maybe some omissions or mistakes, it was a lot of text to scroll through while reading the diff. 🙇
🚧 Here are the events that are missing from this PR:
Action Cable
perform_action.action_cable
Key | Value |
---|---|
:channel_class |
Name of the channel class |
:action |
The action |
:data |
A hash of data |
transmit.action_cable
Key | Value |
---|---|
:channel_class |
Name of the channel class |
:data |
A hash of data |
:via |
Via |
transmit_subscription_confirmation.action_cable
Key | Value |
---|---|
:channel_class |
Name of the channel class |
transmit_subscription_rejection.action_cable
Key | Value |
---|---|
:channel_class |
Name of the channel class |
broadcast.action_cable
Key | Value |
---|---|
:broadcasting |
A named broadcasting |
:message |
A hash of message |
:coder |
The coder |
Action Controller
send_stream.action_controller
Key | Value |
---|---|
:filename |
The filename |
:type |
HTTP content type |
:disposition |
HTTP content disposition |
rate_limit.action_controller
Key | Value |
---|---|
:request |
The ActionDispatch::Request object |
:count |
Number of requests made |
:to |
Maximum number of requests allowed |
:within |
Time window for the rate limit |
:by |
Identifier for the rate limit (e.g. IP) |
:name |
Name of the rate limit |
:scope |
Scope of the rate limit |
:cache_key |
The cache key used for storing the rate limit |
Action Dispatch
process_middleware.action_dispatch
Key | Value |
---|---|
:middleware |
Name of the middleware |
request.action_dispatch
Key | Value |
---|---|
:request |
The ActionDispatch::Request object |
Action Mailbox
process.action_mailbox
Key | Value |
---|---|
:mailbox |
Instance of the Mailbox class inheriting from ActionMailbox::Base |
:inbound_email |
Hash with data about the inbound email being processed |
Active Record
instantiation.active_record
Key | Value |
---|---|
:record_count |
Number of records that instantiated |
:class_name |
Record's class |
start_transaction.active_record
Key | Value |
---|---|
:transaction |
Transaction object |
:connection |
Connection object |
transaction.active_record
Key | Value |
---|---|
:transaction |
Transaction object |
:outcome |
:commit , :rollback , :restart , or :incomplete |
:connection |
Connection object |
deprecated_association.active_record
Key | Value |
---|---|
:reflection |
The reflection of the association |
:message |
A descriptive message about the access |
:location |
The application-level location of the access |
:backtrace |
Only present if the option :backtrace is true |
Active Storage
transform.active_storage
analyze.active_storage
Key | Value |
---|---|
:analyzer |
Name of analyzer e.g., ffprobe |
Active Storage: Storage Service
service_download_chunk.active_storage
Key | Value |
---|---|
:key |
Secure token |
:service |
Name of the service |
:range |
Byte range attempted to be read |
service_update_metadata.active_storage
Key | Value |
---|---|
:key |
Secure token |
:service |
Name of the service |
:content_type |
HTTP Content-Type field |
:disposition |
HTTP Content-Disposition field |
Active Support: Caching
cache_read.active_support
Key | Value |
---|---|
:key |
Key used in the store |
:store |
Name of the store class |
:hit |
If this read is a hit |
:super_operation |
:fetch if a read is done with fetch |
cache_read_multi.active_support
Key | Value |
---|---|
:key |
Keys used in the store |
:store |
Name of the store class |
:hits |
Keys of cache hits |
:super_operation |
:fetch_multi if a read is done with fetch_multi |
cache_generate.active_support
Key | Value |
---|---|
:key |
Key used in the store |
:store |
Name of the store class |
cache_fetch_hit.active_support
Key | Value |
---|---|
:key |
Key used in the store |
:store |
Name of the store class |
cache_write.active_support
Key | Value |
---|---|
:key |
Key used in the store |
:store |
Name of the store class |
cache_write_multi.active_support
Key | Value |
---|---|
:key |
Keys and values written to the store |
:store |
Name of the store class |
cache_increment.active_support
Key | Value |
---|---|
:key |
Key used in the store |
:store |
Name of the store class |
:amount |
Increment amount |
cache_decrement.active_support
Key | Value |
---|---|
:key |
Key used in the store |
:store |
Name of the store class |
:amount |
Decrement amount |
cache_delete.active_support
Key | Value |
---|---|
:key |
Key used in the store |
:store |
Name of the store class |
cache_delete_multi.active_support
Key | Value |
---|---|
:key |
Keys used in the store |
:store |
Name of the store class |
cache_delete_matched.active_support
Key | Value |
---|---|
:key |
Key pattern used |
:store |
Name of the store class |
cache_cleanup.active_support
Key | Value |
---|---|
:store |
Name of the store class |
:size |
Number of entries in the cache before cleanup |
cache_prune.active_support
Key | Value |
---|---|
:store |
Name of the store class |
:key |
Target size (in bytes) for the cache |
:from |
Size (in bytes) of the cache before prune |
cache_exist?.active_support
Key | Value |
---|---|
:key |
Key used in the store |
:store |
Name of the store class |
Active Support: Messages
message_serializer_fallback.active_support
Key | Value |
---|---|
:serializer |
Primary (intended) serializer |
:fallback |
Fallback (actual) serializer |
:serialized |
Serialized string |
:deserialized |
Deserialized value |
Rails
deprecation.rails
Key | Value |
---|---|
:message |
The deprecation warning |
:callstack |
Where the deprecation came from |
:gem_name |
Name of the gem reporting the deprecation |
:deprecation_horizon |
Version where the deprecated behavior will be removed |
Railties
load_config_initializer.railties
Key | Value |
---|---|
:initializer |
Path of loaded initializer in config/initializers |
That is not to say they should be included -- some may be invalid and I can see wanting to keep this PR to a reasonable size. It's a good time to think about which ones we might want to add after this is merged.
74b39ec
to
c6389d2
Compare
Ok, I think this is looking good now. Some things I want to look into next are:
But I think all of these can be done in followup changes. |
c6389d2
to
e729f86
Compare
controller: payload[:controller], | ||
action: payload[:action], | ||
format:, | ||
params:, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If anyone is wondering, params for this event and others in this file are pre-filtered by Action Pack. They show up like this: :params=>{"authenticity_token"=>"[FILTERED]", "user"=>{"email"=>"[FILTERED]"}, "commit"=>"Create User"}
.
Motivation / Background
This Pull Request has been created to add structured events for framework notifications via the use of
ActiveSupport::StructuredEventSubscriber
subclasses. Events may be emitted with the#emit_event
or#emit_debug_event
methods like this, similar to a traditionalLogSubscriber
:Detail
This Pull Request adds structured event subscribers for each framework library that has log subscribers for notifications. The idea is to mirror structured events for each existing framework log subscriber.
Additional information
Eventually, we would like to unify notification subscribing and emit logs for these structured events (instead of maintaining separate log subscribers), and that will happen in a followup change. For now, this is focused on adding support for events and minimizing the set of changes as to not break anything.
Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]