Skip to content

Conversation

gmcgibbon
Copy link
Member

@gmcgibbon gmcgibbon commented Sep 17, 2025

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 traditional LogSubscriber:

class MyStructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber
  def notification(event)
    emit_event("my.notification", data: event.payload)
  end
end

MyStructuredEventSubscriber.attach_to :namespace

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:

  • This Pull Request is related to one change. Unrelated changes should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

end

def rails_root # :doc:
@root ||= "#{Rails.try(:root)}/"
Copy link
Member Author

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.

@gmcgibbon
Copy link
Member Author

Looks like there's some CI failures I need to look at in Action View and the base class tests. Will do that ASAP.

@gmcgibbon gmcgibbon force-pushed the ac-structured-event-subscribers branch 11 times, most recently from 3ad8ba5 to f8b663a Compare September 20, 2025 04:43
exception_message: exception.message,
mail: event.payload[:mail],
)
elsif event.payload[:perform_deliveries]
Copy link
Member

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?

Copy link
Member Author

@gmcgibbon gmcgibbon Sep 20, 2025

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.

Copy link
Member

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! :)

Copy link
Member Author

@gmcgibbon gmcgibbon Sep 23, 2025

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!

@zzak
Copy link
Member

zzak commented Sep 20, 2025

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!

@gmcgibbon
Copy link
Member Author

gmcgibbon commented Sep 20, 2025

Are there any concern of overhead by essentially doubling all the notification events?

I tried doing this already by disabling method calls for debug mode events with debug_only, but I agree there could be more done. I can try to silence all event method calls if there are no subscribers. That should help reduce overhead in all scenarios. Thanks for the feedback!!

@gmcgibbon gmcgibbon force-pushed the ac-structured-event-subscribers branch 3 times, most recently from e629953 to 0a3763a Compare September 20, 2025 08:29
@gmcgibbon gmcgibbon force-pushed the ac-structured-event-subscribers branch from 0a3763a to f4153e7 Compare September 20, 2025 08:37
@gmcgibbon gmcgibbon force-pushed the ac-structured-event-subscribers branch from f4153e7 to 74b39ec Compare September 21, 2025 00:05
@zzak
Copy link
Member

zzak commented Sep 21, 2025

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 PR

Action Controller

start_processing.action_controller -> action_controller.request_started

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.

@gmcgibbon gmcgibbon force-pushed the ac-structured-event-subscribers branch from 74b39ec to c6389d2 Compare September 21, 2025 19:09
@gmcgibbon
Copy link
Member Author

gmcgibbon commented Sep 23, 2025

Ok, I think this is looking good now. Some things I want to look into next are:

  • Unification of subscribers (have log subscribers consume events, or similar).
  • Potentially remove the Structured prefix from the code. I'm not sure about this yet. We need a way to disambiguate between notification events and structured events, so I might not end up doing this.
  • Look at adding a test helper for event debug mode. We shouldn't need to reach for the reporter itself.
  • Documentation for events (see Structured event subscribers #55690 (comment) and give credit to @zzak if we use that text).

But I think all of these can be done in followup changes.

@gmcgibbon gmcgibbon force-pushed the ac-structured-event-subscribers branch from c6389d2 to e729f86 Compare September 23, 2025 16:51
controller: payload[:controller],
action: payload[:action],
format:,
params:,
Copy link
Member Author

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"}.

@gmcgibbon gmcgibbon merged commit a8a0d29 into rails:main Sep 23, 2025
2 of 3 checks passed
@gmcgibbon gmcgibbon deleted the ac-structured-event-subscribers branch September 23, 2025 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants