Skip to content

refactor: log emitter#11316

Open
loks0n wants to merge 4 commits into1.8.xfrom
refactor-log-emitter
Open

refactor: log emitter#11316
loks0n wants to merge 4 commits into1.8.xfrom
refactor-log-emitter

Conversation

@loks0n
Copy link
Member

@loks0n loks0n commented Feb 13, 2026

Introduce a Bus with class-based event routing and Listener base class to decouple execution completion signals from their side effects. Controllers/workers now dispatch ExecutionCompleted events, and the PersistExecutionLog listener handles queue persistence.

What does this PR do?

(Provide a description of what this PR does and why it's needed.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Screenshots may also be helpful.)

Related PRs and Issues

  • (Related PR or issue)

Checklist

  • Have you read the Contributing Guidelines on issues?
  • If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the previous direct Execution queue usage with a new Bus event system. Adds Bus, Event, Listener base classes, ExecutionCompleted event, and PersistExecution listener. Introduces a bus resource/service and app/listeners.php to load listeners. Updates controllers, router handlers, worker and function worker code to accept Bus and dispatch ExecutionCompleted (including execution and project payloads). Removes queueForExecutions resource registrations and trigger calls.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (28 files):

⚔️ .env (content)
⚔️ .github/workflows/benchmark.yml (content)
⚔️ .github/workflows/cleanup-cache.yml (content)
⚔️ .github/workflows/codeql-analysis.yml (content)
⚔️ .github/workflows/linter.yml (content)
⚔️ .github/workflows/nightly.yml (content)
⚔️ .github/workflows/pr-scan.yml (content)
⚔️ .github/workflows/publish.yml (content)
⚔️ .github/workflows/release.yml (content)
⚔️ .github/workflows/sdk-preview.yml (content)
⚔️ .github/workflows/static-analysis.yml (content)
⚔️ .github/workflows/tests.yml (content)
⚔️ app/cli.php (content)
⚔️ app/controllers/general.php (content)
⚔️ app/http.php (content)
⚔️ app/init/registers.php (content)
⚔️ app/init/resources.php (content)
⚔️ app/worker.php (content)
⚔️ composer.json (content)
⚔️ composer.lock (content)
⚔️ docker-compose.yml (content)
⚔️ src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php (content)
⚔️ src/Appwrite/Platform/Workers/Functions.php (content)
⚔️ tests/e2e/Client.php (content)
⚔️ tests/e2e/Services/Databases/Legacy/DatabasesBase.php (content)
⚔️ tests/e2e/Services/Databases/TablesDB/DatabasesBase.php (content)
⚔️ tests/e2e/Services/Proxy/ProxyCustomServerTest.php (content)
⚔️ tests/e2e/Services/Sites/SitesCustomServerTest.php (content)

These conflicts must be resolved before merging into 1.8.x.
Resolve conflicts locally and push changes to this branch.
Title check ❓ Inconclusive The title 'refactor: log emitter' is vague and does not clearly convey the main architectural change—introducing a Bus-based event system to decouple execution signals from their side effects. Consider a more descriptive title like 'refactor: decouple execution logging with event bus' or 'refactor: introduce Bus for execution completion events' to better reflect the core change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description adequately explains the main objective: introducing a Bus with class-based event routing and a Listener base class to decouple execution completion signals, with controllers/workers dispatching events and PersistExecution handling persistence.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-log-emitter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Feb 13, 2026

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
libecpg 18.1-r0 CVE-2026-2004 HIGH
libecpg 18.1-r0 CVE-2026-2005 HIGH
libecpg 18.1-r0 CVE-2026-2006 HIGH
libecpg 18.1-r0 CVE-2026-2007 HIGH
libecpg-dev 18.1-r0 CVE-2026-2004 HIGH
libecpg-dev 18.1-r0 CVE-2026-2005 HIGH
libecpg-dev 18.1-r0 CVE-2026-2006 HIGH
libecpg-dev 18.1-r0 CVE-2026-2007 HIGH
libpq 18.1-r0 CVE-2026-2004 HIGH
libpq 18.1-r0 CVE-2026-2005 HIGH
libpq 18.1-r0 CVE-2026-2006 HIGH
libpq 18.1-r0 CVE-2026-2007 HIGH
libpq-dev 18.1-r0 CVE-2026-2004 HIGH
libpq-dev 18.1-r0 CVE-2026-2005 HIGH
libpq-dev 18.1-r0 CVE-2026-2006 HIGH
libpq-dev 18.1-r0 CVE-2026-2007 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2004 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2005 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2006 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2007 HIGH

Source Code Scan Results

🎉 No vulnerabilities found!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@app/controllers/general.php`:
- Around line 706-712: The call to $bus->dispatch(new ExecutionCompleted(...))
in the finally block can throw (e.g. PersistExecutionLog::handle) and mask an
earlier exception; wrap the dispatch call in its own try-catch that catches
Throwable, logs the dispatch error (using the controller's logger or error_log)
and does not rethrow so the original exception from the catch block is
preserved; specifically, around $bus->dispatch(...) for the ($type ===
'function' || $type === 'site') case, add try { $bus->dispatch(new
ExecutionCompleted(...)); } catch (\Throwable $e) { /* log error about
ExecutionCompleted dispatch failure including $e->getMessage()/trace */ }.

In `@src/Appwrite/Bus/Event.php`:
- Line 5: The interface declaration `interface Event {}` violates PSR-12 brace
placement; change it so the opening brace is on its own line for the `Event`
interface (i.e., declare `interface Event` then put `{` on the next line) to
satisfy the linter and Pint's `class_definition`/`braces_position` rule.

In `@src/Appwrite/Bus/ExecutionCompleted.php`:
- Around line 7-10: The constructor in class ExecutionCompleted uses inline
empty braces which triggers the PSR-12 braces_position lint; update the
__construct declaration in class ExecutionCompleted so the opening brace is on
the next line and the closing brace is on its own line (i.e., place the method
body braces on separate lines even if empty) to satisfy Pint/PSR-12.

In `@src/Appwrite/Bus/Listener.php`:
- Line 9: The Listener::$callback property is untyped and can be null while
getCallback() declares a non-nullable callable return; add a runtime guard and a
stronger type: declare the property as protected $callback (typed as ?callable
if your PHP version allows) and update getCallback() in class Listener to check
if $this->callback is callable and throw a clear LogicException (or
InvalidArgumentException) if not, so callers like Bus::dispatch never receive
null; alternatively, add a Listener constructor or initialize method that
validates/sets the callback (e.g., require a callable in Listener::__construct
or call setCallback) and ensure subclasses (like PersistExecutionLog) call it.
🧹 Nitpick comments (3)
src/Appwrite/Bus/Bus.php (1)

24-30: Consider isolating listener failures to prevent one listener from blocking others.

Currently, if a listener throws an exception, dispatch() stops iterating and the exception propagates to the caller. As more listeners are added to the bus, a failure in one listener would prevent subsequent listeners from executing. Wrapping each listener invocation in a try-catch would make the bus more resilient.

Proposed approach
     public function dispatch(Event $event): void
     {
         foreach ($this->listeners[$event::class] ?? [] as $listener) {
-            $deps = array_map($this->resolver, $listener->getInjections());
-            ($listener->getCallback())($event, ...$deps);
+            try {
+                $deps = array_map($this->resolver, $listener->getInjections());
+                ($listener->getCallback())($event, ...$deps);
+            } catch (\Throwable $e) {
+                // Log and continue so other listeners are not affected
+                \Utopia\Console::error('[Bus] Listener ' . $listener::getName() . ' failed: ' . $e->getMessage());
+            }
         }
     }
src/Appwrite/Bus/Listener.php (1)

18-22: desc() is set but never accessible — is this intentional?

There is no public getDesc() method, so $desc is write-only. If it's meant for introspection (e.g., listing registered listeners), consider adding a public accessor. If it's purely decorative/internal documentation, this is fine — just flagging for awareness.

src/Appwrite/Platform/Workers/Functions.php (1)

254-276: PHPDoc for fail() is out of sync with the actual signature.

The @param block (lines 255–263) doesn't list Document $project or Bus $bus, both of which are in the method signature. Similarly, execute()'s docblock (lines 317–337) is missing Webhook $queueForWebhooks, Bus $bus, and array $platform.

Suggested additions for fail()
  /**
   * `@param` string $message
+  * `@param` Document $project
+  * `@param` Bus $bus
   * `@param` Document $function

{
protected ?string $desc = null;
protected array $injections = [];
protected $callback;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check the actual Listener.php file to see the property and method signatures
cat -n src/Appwrite/Bus/Listener.php | head -50

Repository: appwrite/appwrite

Length of output: 1251


🏁 Script executed:

# Also search for all classes that extend Listener
rg 'extends Listener' --type=php

Repository: appwrite/appwrite

Length of output: 154


🏁 Script executed:

# Check the Bus::dispatch method to see how it uses the callback
rg -A 5 'dispatch' src/Appwrite/Bus/Bus.php | head -30

Repository: appwrite/appwrite

Length of output: 330


🏁 Script executed:

# Check the PersistExecutionLog subclass to see if it properly sets the callback
cat -n src/Appwrite/Bus/Listeners/PersistExecutionLog.php

Repository: appwrite/appwrite

Length of output: 1301


🏁 Script executed:

# Also verify the Bus class structure to confirm dispatch behavior
cat -n src/Appwrite/Bus/Bus.php | head -50

Repository: appwrite/appwrite

Length of output: 1010


🏁 Script executed:

# Check if there are any other Listener subclasses besides PersistExecutionLog
rg 'extends Listener' --type=php

Repository: appwrite/appwrite

Length of output: 154


🏁 Script executed:

# Also check for any instantiations of Listener subclasses to confirm usage patterns
rg 'new (Persist|.*Listener)' --type=php | head -20

Repository: appwrite/appwrite

Length of output: 13422


$callback is untyped and uninitialized — getCallback() declares callable as the return type but could return null.

The property has no type hint and no default value. If a Listener subclass forgets to call $this->callback(...) during initialization, getCallback() will return null, violating the declared return type. When Bus::dispatch tries to invoke it (($listener->getCallback())($event, ...)), this will throw a TypeError.

Currently, PersistExecutionLog (the only subclass) properly initializes the callback in its constructor, preventing this issue. However, the design is fragile and should be hardened for future subclasses.

Consider either:

  1. Adding a type hint with a runtime guard in getCallback(), or
  2. Validating in a constructor or setup method that $callback is set before the listener can be used.
Example: Guard in getCallback()
+    protected ?callable $callback = null;
+
     public function getCallback(): callable
     {
+        if ($this->callback === null) {
+            throw new \LogicException(static::class . ' must set a callback via $this->callback()');
+        }
         return $this->callback;
     }
🤖 Prompt for AI Agents
In `@src/Appwrite/Bus/Listener.php` at line 9, The Listener::$callback property is
untyped and can be null while getCallback() declares a non-nullable callable
return; add a runtime guard and a stronger type: declare the property as
protected $callback (typed as ?callable if your PHP version allows) and update
getCallback() in class Listener to check if $this->callback is callable and
throw a clear LogicException (or InvalidArgumentException) if not, so callers
like Bus::dispatch never receive null; alternatively, add a Listener constructor
or initialize method that validates/sets the callback (e.g., require a callable
in Listener::__construct or call setCallback) and ensure subclasses (like
PersistExecutionLog) call it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Appwrite/Bus/Bus.php`:
- Around line 25-31: The dispatch method in Bus::dispatch silently uses a null
resolver and will pass raw injection names when setResolver() hasn't been
called, and an exception in one listener aborts remaining listeners; fix by
adding an explicit guard at the start of dispatch that throws a clear
RuntimeException if $this->resolver is null (or unset) so callers must call
setResolver() first, and wrap the per-listener invocation (the
($listener->getCallback())($event, ...$deps) call) in a try/catch to log or
rethrow per policy so one listener's exception doesn't silently stop others (or
alternatively add a docblock above dispatch stating fail-fast behavior if you
opt to keep current behavior); reference Bus::dispatch, setResolver,
$this->resolver, $listener->getInjections(), and $listener->getCallback().

@github-actions
Copy link

github-actions bot commented Feb 13, 2026

✨ Benchmark results

  • Requests per second: 1,886
  • Requests with 200 status code: 339,523
  • P99 latency: 0.083252109

⚡ Benchmark Comparison

Metric This PR Latest version
RPS 1,886 1,171
200 339,523 210,757
P99 0.083252109 0.177260821

@loks0n loks0n force-pushed the refactor-log-emitter branch 2 times, most recently from c82f174 to 623d5ea Compare February 13, 2026 10:59
@loks0n loks0n requested a review from Copilot February 13, 2026 11:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors execution completion handling by introducing an in-process event Bus with class-based listeners, moving execution log persistence and usage-metric recording into dedicated listeners triggered by an ExecutionCompleted event.

Changes:

  • Added Utopia\Bus\Bus, Event, and Listener abstractions plus an Appwrite\Bus\ExecutionCompleted event.
  • Updated HTTP controller and Functions worker to dispatch ExecutionCompleted instead of directly triggering execution/usage queues.
  • Wired Bus + listeners into HTTP/worker/CLI resource containers and enabled PHPStan scanning for the new namespaces.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/Utopia/Bus/Listener.php Introduces a base Listener API for event handling (injections + callback).
src/Utopia/Bus/Event.php Adds a marker interface for bus events.
src/Utopia/Bus/Bus.php Implements event subscription and asynchronous dispatch with dependency resolution.
src/Appwrite/Platform/Workers/Functions.php Dispatches ExecutionCompleted events instead of directly triggering execution log/usage queues.
src/Appwrite/Bus/Listeners/Usage.php New listener to record execution usage metrics on completion.
src/Appwrite/Bus/Listeners/Log.php New listener to persist execution logs via the existing queue.
src/Appwrite/Bus/ExecutionCompleted.php Defines the completion event payload (execution, project, spec).
phpstan.neon Adds new Bus directories to PHPStan analysis paths.
composer.json Adds PSR-4 autoload mapping for Utopia\Bus\.
app/worker.php Registers a bus resource and sets resolver using Server resources.
app/listeners.php Declares listeners to be subscribed to the bus.
app/init/resources.php Removes queueForExecutions HTTP resource wiring (replaced by bus dispatch).
app/init/registers.php Creates and registers the bus and subscribes listeners.
app/http.php Registers bus resource and sets resolver using Http resources.
app/controllers/general.php Dispatches ExecutionCompleted and removes inline execution usage metric additions/disable logic.
app/cli.php Registers bus resource and sets resolver using CLI resources.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@loks0n loks0n force-pushed the refactor-log-emitter branch 9 times, most recently from e616afc to af5820d Compare February 17, 2026 14:10
Introduce a generic event bus (Utopia\Bus) with typed events, listener
base class, Span instrumentation, and coroutine dispatch. Replace all
direct queueForExecutions and inline execution usage calls with
ExecutionCompleted event and dedicated listeners (Log, Usage).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@loks0n loks0n force-pushed the refactor-log-emitter branch from fa22ab5 to 577a2bc Compare February 23, 2026 20:01
loks0n and others added 3 commits February 23, 2026 20:01
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… optional deployment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants