diff --git a/core/lib/Drupal/Core/Database/Database.php b/core/lib/Drupal/Core/Database/Database.php
index 9b5364942b..e3996da17c 100644
--- a/core/lib/Drupal/Core/Database/Database.php
+++ b/core/lib/Drupal/Core/Database/Database.php
@@ -4,6 +4,7 @@
 
 use Composer\Autoload\ClassLoader;
 use Drupal\Core\Extension\ExtensionDiscovery;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * Primary front-controller for the database system.
@@ -106,6 +107,9 @@ abstract class Database {
    *   The logging key to log.
    * @param string $key
    *   The database connection key for which we want to log.
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface|null $event_dispatcher
+   *   If an optional event dispatcher service is passed in, log events will be
+   *   dispatched for this logging key, rather than collected in a query log.
    *
    * @return \Drupal\Core\Database\Log
    *   The query log object. Note that the log object does support richer
@@ -114,7 +118,7 @@ abstract class Database {
    *
    * @see \Drupal\Core\Database\Log
    */
-  final public static function startLog($logging_key, $key = 'default') {
+  final public static function startLog($logging_key, $key = 'default', ?EventDispatcherInterface $event_dispatcher = NULL) {
     if (empty(self::$logs[$key])) {
       self::$logs[$key] = new Log($key);
 
@@ -127,7 +131,7 @@ final public static function startLog($logging_key, $key = 'default') {
       }
     }
 
-    self::$logs[$key]->start($logging_key);
+    self::$logs[$key]->start($logging_key, $event_dispatcher);
     return self::$logs[$key];
   }
 
diff --git a/core/lib/Drupal/Core/Database/Log.php b/core/lib/Drupal/Core/Database/Log.php
index b310d17284..78b1019c3e 100644
--- a/core/lib/Drupal/Core/Database/Log.php
+++ b/core/lib/Drupal/Core/Database/Log.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Core\Database;
 
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
 /**
  * Database query logger.
  *
@@ -12,6 +14,10 @@
  *
  * Every connection has one and only one logging object on it for all targets
  * and logging keys.
+ *
+ * By default, queries will be logged and can later be retrieved. However, if
+ * the event dispatcher service is passed in at log start, log events will
+ * instead be dipatched, using "database.log.{$logging_key}" as the event name.
  */
 class Log {
 
@@ -31,6 +37,13 @@ class Log {
    */
   protected $queryLog = [];
 
+  /**
+   * An array of event dispatcher services, or null, keyed by logging key.
+   *
+   * @var array
+   */
+  protected $eventDispatcher = [];
+
   /**
    * The connection key for which this object is logging.
    *
@@ -57,11 +70,15 @@ public function __construct($key = 'default') {
    *   The identification key for this log request. By specifying different
    *   logging keys we are able to start and stop multiple logging runs
    *   simultaneously without them colliding.
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface|null $event_dispatcher
+   *   If an optional event dispatcher service is passed in, log events will be
+   *   dispatched for this logging key, rather than collected in a query log.
    */
-  public function start($logging_key) {
+  public function start($logging_key, ?EventDispatcherInterface $event_dispatcher = NULL) {
     if (empty($this->queryLog[$logging_key])) {
       $this->clear($logging_key);
     }
+    $this->eventDispatcher[$logging_key] = $event_dispatcher;
   }
 
   /**
@@ -98,6 +115,7 @@ public function clear($logging_key) {
    */
   public function end($logging_key) {
     unset($this->queryLog[$logging_key]);
+    unset($this->eventDispatcher[$logging_key]);
   }
 
   /**
@@ -116,7 +134,7 @@ public function end($logging_key) {
    */
   public function log(StatementInterface $statement, $args, $time, float $start = NULL) {
     foreach (array_keys($this->queryLog) as $key) {
-      $this->queryLog[$key][] = [
+      $query = [
         'query' => $statement->getQueryString(),
         'args' => $args,
         'target' => $statement->getConnectionTarget(),
@@ -124,6 +142,13 @@ public function log(StatementInterface $statement, $args, $time, float $start =
         'time' => $time,
         'start' => $start,
       ];
+      if (isset($this->eventDispatcher[$key])) {
+        $event = new LogEvent($this->connectionKey, ...$query);
+        $this->eventDispatcher[$key]->dispatch($event, "database.log.$key");
+      }
+      else {
+        $this->queryLog[$key][] = $query;
+      }
     }
   }
 
diff --git a/core/lib/Drupal/Core/Database/LogEvent.php b/core/lib/Drupal/Core/Database/LogEvent.php
new file mode 100644
index 0000000000..631ec6e690
--- /dev/null
+++ b/core/lib/Drupal/Core/Database/LogEvent.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Drupal\Core\Database;
+
+use Drupal\Component\EventDispatcher\Event;
+
+/**
+ * Represents a database query log record as an event.
+ */
+class LogEvent extends Event {
+
+  /**
+   * Constructs a LogEvent object.
+   */
+  public function __construct(
+    protected string $key,
+    protected string $query,
+    protected ?array $args,
+    protected string $target,
+    protected ?array $caller,
+    protected float $time,
+    protected float $start,
+  ) {}
+
+  /**
+   * Returns the connection key.
+   */
+  public function getKey(): string {
+    return $this->key;
+  }
+
+  /**
+   * Returns the query string.
+   */
+  public function getQuery(): string {
+    return $this->query;
+  }
+
+  /**
+   * Returns the query arguments.
+   */
+  public function getArgs(): ?array {
+    return $this->args;
+  }
+
+  /**
+   * Returns the query target.
+   */
+  public function getTarget(): string {
+    return $this->target;
+  }
+
+  /**
+   * Returns the caller backtrace.
+   */
+  public function getCaller(): ?array {
+    return $this->caller;
+  }
+
+  /**
+   * Returns the query duration.
+   */
+  public function getTime(): float {
+    return $this->time;
+  }
+
+  /**
+   * Returns the query start time.
+   */
+  public function getStart(): float {
+    return $this->start;
+  }
+
+}
