Skip to content

Comments

feat: enhance logging error handling with Push exception#46

Open
eldadfux wants to merge 2 commits intomainfrom
fix-better-error-handling
Open

feat: enhance logging error handling with Push exception#46
eldadfux wants to merge 2 commits intomainfrom
fix-better-error-handling

Conversation

@eldadfux
Copy link
Member

@eldadfux eldadfux commented Feb 22, 2026

  • Updated composer.json to require utopia-php/console.
  • Modified Logger class to catch Push exceptions during log pushes and log detailed error messages.
  • Updated Adapter implementations (AppSignal, LogOwl, Raygun, Sentry) to throw Push exceptions on failure instead of returning error codes.

Summary by CodeRabbit

  • New Features

    • Exception-based error handling for logging adapters improves error propagation and diagnostics
    • Enhanced error reporting displays detailed information when log delivery operations fail
  • Bug Fixes

    • Better error visibility across logging service adapters for failed push operations
  • Chores

    • Added runtime and development dependencies for enhanced tooling
    • Reformatted configuration files for consistency

- Updated `composer.json` to require `utopia-php/console`.
- Modified `Logger` class to catch `Push` exceptions during log pushes and log detailed error messages.
- Updated `Adapter` implementations (AppSignal, LogOwl, Raygun, Sentry) to throw `Push` exceptions on failure instead of returning error codes.
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

Walkthrough

The pull request introduces exception-based error handling to the logger adapter system. A new Push exception class is added to represent adapter failures. Four adapter implementations (AppSignal, LogOwl, Raygun, Sentry) are refactored to throw Push exceptions on errors instead of returning status codes. The base Adapter class docblock is updated to document the new exception. The Logger class wraps adapter calls in a try-catch block to capture Push exceptions, logs detailed information to Console, and returns the exception's status code. Dependencies are updated in composer.json with utopia-php/console and development tools (psalm, pint), and the file is reformatted to multi-line arrays.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: enhance logging error handling with Push exception' accurately describes the main change: introducing a new Push exception class and refactoring error handling across adapters to throw exceptions instead of returning error codes.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-better-error-handling

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.

Copy link

@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

🧹 Nitpick comments (5)
src/Logger/Adapter/LogOwl.php (1)

168-174: Same error-message issues as Sentry.php$curlError is always 0 in the HTTP error branch, and $response is false (→ empty string) in the curl error branch. Apply the same fix described in Sentry.php.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Logger/Adapter/LogOwl.php` around lines 168 - 174, The error messages in
LogOwl.php use $curlError and $response incorrectly (they can be 0/false)
causing misleading logs; update the push error handling in the same place that
throws Push to capture and use the actual curl errno and curl_error() values and
ensure $response is stringified (e.g., convert false to an explanatory string or
cast to string) so the thrown messages include the real curl errno, curl error
string, HTTP status and the response body; apply this change in the same
conditional branches that currently reference $curlError and $response so the
thrown Push exceptions contain accurate diagnostic details.
composer.json (1)

48-48: vimeo/psalm is pinned to a very old version and is unused in scripts.

"vimeo/psalm": "4.0.1" dates to 2020 (current Psalm is 5.x). More importantly, the scripts.check entry invokes phpstan, not psalm, so this package is never actually called in the CI pipeline. Either update the version and wire it into a script, or remove it to avoid the vestigial dependency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composer.json` at line 48, composer.json currently declares "vimeo/psalm":
"4.0.1" but that old pin is unused by the scripts; either update the dependency
to a current Psalm v5 constraint (e.g. "^5.0") and add a scripts entry that runs
Psalm (e.g. add or modify "scripts": { "check:psalm": "vendor/bin/psalm" } or
call it from the existing "scripts.check"), or remove "vimeo/psalm" entirely
from composer.json and keep CI using phpstan only; adjust the "scripts.check"
entry accordingly so the declared dependency matches what CI actually runs.
src/Logger/Adapter/Sentry.php (1)

178-184: Error message clarity: misleading $curlError in HTTP branch and empty $response in curl branch.

Two issues with the exception messages:

  1. Line 183 — HTTP error branch: At this point $curlError is always CURLE_OK (0) because the preceding guard already filtered out non-zero curl errors. Interpolating {$curlError} produces messages like "status code 429: 0 (body)" where the 0 is confusing.
  2. Line 179 — curl error branch: curl_exec returns false on failure, so {$response} interpolates to an empty string, making the message read "...curl error (6): ". Use curl_strerror($curlError) for a human-readable description (it takes only the code, no handle required).

The same pattern appears identically in AppSignal.php, LogOwl.php, and Raygun.php.

✏️ Proposed fix
-        if ($curlError !== CURLE_OK || $httpCode === 0) {
-            throw new Push("Sentry push failed with curl error ({$curlError}): {$response}", 500);
-        }
-
-        if ($httpCode >= 400) {
-            throw new Push("Sentry push failed with status code {$httpCode}: {$curlError} ({$response})", $httpCode);
-        }
+        if ($curlError !== CURLE_OK || $httpCode === 0) {
+            throw new Push("Sentry push failed with curl error ({$curlError}): ".\curl_strerror($curlError), 500);
+        }
+
+        if ($httpCode >= 400) {
+            throw new Push("Sentry push failed with status code {$httpCode}: {$response}", $httpCode);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Logger/Adapter/Sentry.php` around lines 178 - 184, Exception messages mix
up curl error code and HTTP response and can be empty; update the throw texts in
Sentry.php's send/push logic (the branch using $curlError, $httpCode, $response
that currently throws Push) to: for curl failures (when $curlError !== CURLE_OK
|| $httpCode === 0) include curl_strerror($curlError) as the human-readable
error and the raw $response only if non-empty; for HTTP errors (when $httpCode
>= 400) remove $curlError from the message and include the HTTP status code plus
the response body (or a trimmed indicator if empty) so the message reads e.g.
"Sentry push failed with status code {code}: {response}". Apply the same change
to the identical throw sites in AppSignal.php, LogOwl.php, and Raygun.php, and
keep the thrown exception type Push and original status code values.
src/Logger/Adapter/AppSignal.php (1)

151-157: Same error-message issues as Sentry.php$curlError is always 0 in the HTTP error branch, and $response is false (→ empty string) in the curl error branch. Apply the same fix described in Sentry.php.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Logger/Adapter/AppSignal.php` around lines 151 - 157, The error messages
use the wrong variables: $curlError (numeric) is meaningless in the HTTP-error
branch and $response may be false in the curl-error branch; update AppSignal's
push/error handling to mirror the Sentry.php fix by building a proper curl error
string and a safe response string before throwing Push. Concretely, in the
AppSignal sending code (the block that currently checks $curlError and $httpCode
and throws new Push(...)), compute a human-readable curl error (e.g. from
curl_error or a curl_strerror helper) and coerce $response to a string only when
truthy, then throw Push with the curl-error message when curl failed (include
the curl error string and any response if present) and throw Push with the HTTP
status and the safe response when $httpCode >= 400 (do not rely on the numeric
$curlError there).
src/Logger/Adapter/Raygun.php (1)

136-142: Same error-message issues as Sentry.php$curlError is always 0 in the HTTP error branch, and $response is false (→ empty string) in the curl error branch. Apply the same fix described in Sentry.php.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Logger/Adapter/Raygun.php` around lines 136 - 142, The Raygun adapter
builds error messages using stale/empty variables; update the error handling in
Raygun.php (around the Push throws) to read the real curl status and text from
the curl handle instead of relying on $curlError/$response as-is: call
curl_errno($ch) and curl_error($ch) to get the numeric and textual curl error
and include those in the curl-failure branch, and for the HTTP-status branch
include the actual $response body (cast or json-encoded if needed) and the http
code; keep throwing the same Push exceptions but substitute $curlError/$response
with the freshly retrieved curl_errno/curl_error and a safe stringified
$response so messages correctly reflect the underlying error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Logger/Logger.php`:
- Around line 74-82: The json_encode results for $tags and $extra can be false,
causing strlen/substr to fail; update the Logger logic around
getTags()/getExtra() so you capture the json_encode return into a variable (e.g.
$tagsJson and $extraJson), check is_string($tagsJson)/is_string($extraJson) (or
coerce with (string) only after handling false) before concatenating or calling
strlen/substr, and default to an empty string or a clear fallback when
json_encode returns false so Console::error always receives a string and PHPStan
type errors are resolved.

---

Nitpick comments:
In `@composer.json`:
- Line 48: composer.json currently declares "vimeo/psalm": "4.0.1" but that old
pin is unused by the scripts; either update the dependency to a current Psalm v5
constraint (e.g. "^5.0") and add a scripts entry that runs Psalm (e.g. add or
modify "scripts": { "check:psalm": "vendor/bin/psalm" } or call it from the
existing "scripts.check"), or remove "vimeo/psalm" entirely from composer.json
and keep CI using phpstan only; adjust the "scripts.check" entry accordingly so
the declared dependency matches what CI actually runs.

In `@src/Logger/Adapter/AppSignal.php`:
- Around line 151-157: The error messages use the wrong variables: $curlError
(numeric) is meaningless in the HTTP-error branch and $response may be false in
the curl-error branch; update AppSignal's push/error handling to mirror the
Sentry.php fix by building a proper curl error string and a safe response string
before throwing Push. Concretely, in the AppSignal sending code (the block that
currently checks $curlError and $httpCode and throws new Push(...)), compute a
human-readable curl error (e.g. from curl_error or a curl_strerror helper) and
coerce $response to a string only when truthy, then throw Push with the
curl-error message when curl failed (include the curl error string and any
response if present) and throw Push with the HTTP status and the safe response
when $httpCode >= 400 (do not rely on the numeric $curlError there).

In `@src/Logger/Adapter/LogOwl.php`:
- Around line 168-174: The error messages in LogOwl.php use $curlError and
$response incorrectly (they can be 0/false) causing misleading logs; update the
push error handling in the same place that throws Push to capture and use the
actual curl errno and curl_error() values and ensure $response is stringified
(e.g., convert false to an explanatory string or cast to string) so the thrown
messages include the real curl errno, curl error string, HTTP status and the
response body; apply this change in the same conditional branches that currently
reference $curlError and $response so the thrown Push exceptions contain
accurate diagnostic details.

In `@src/Logger/Adapter/Raygun.php`:
- Around line 136-142: The Raygun adapter builds error messages using
stale/empty variables; update the error handling in Raygun.php (around the Push
throws) to read the real curl status and text from the curl handle instead of
relying on $curlError/$response as-is: call curl_errno($ch) and curl_error($ch)
to get the numeric and textual curl error and include those in the curl-failure
branch, and for the HTTP-status branch include the actual $response body (cast
or json-encoded if needed) and the http code; keep throwing the same Push
exceptions but substitute $curlError/$response with the freshly retrieved
curl_errno/curl_error and a safe stringified $response so messages correctly
reflect the underlying error.

In `@src/Logger/Adapter/Sentry.php`:
- Around line 178-184: Exception messages mix up curl error code and HTTP
response and can be empty; update the throw texts in Sentry.php's send/push
logic (the branch using $curlError, $httpCode, $response that currently throws
Push) to: for curl failures (when $curlError !== CURLE_OK || $httpCode === 0)
include curl_strerror($curlError) as the human-readable error and the raw
$response only if non-empty; for HTTP errors (when $httpCode >= 400) remove
$curlError from the message and include the HTTP status code plus the response
body (or a trimmed indicator if empty) so the message reads e.g. "Sentry push
failed with status code {code}: {response}". Apply the same change to the
identical throw sites in AppSignal.php, LogOwl.php, and Raygun.php, and keep the
thrown exception type Push and original status code values.

Comment on lines +74 to +82
$tags = $log->getTags();
if ($tags !== []) {
Console::error('tags: '.\json_encode($tags));
}
$extra = $log->getExtra();
if ($extra !== []) {
$extraJson = \json_encode($extra);
Console::error('extra: '.(\strlen($extraJson) > 2000 ? \substr($extraJson, 0, 2000).'...' : $extraJson));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Pipeline failure: json_encode() returns string|false but strlen/substr require string.

PHPStan reports 2 errors on line 81 (strlen and substr both reject string|false), blocking the pipeline. json_encode($extra) returns false when encoding fails; passing that value directly to strlen/substr violates their type contract.

Line 76 has the same root cause — \json_encode($tags) concatenated directly without a false guard — but PHPStan's concatenation analysis let it pass at the current level. Both should be fixed together.

🐛 Proposed fix
-                $tags = $log->getTags();
-                if ($tags !== []) {
-                    Console::error('tags: '.\json_encode($tags));
-                }
-                $extra = $log->getExtra();
-                if ($extra !== []) {
-                    $extraJson = \json_encode($extra);
-                    Console::error('extra: '.(\strlen($extraJson) > 2000 ? \substr($extraJson, 0, 2000).'...' : $extraJson));
-                }
+                $tags = $log->getTags();
+                if ($tags !== []) {
+                    Console::error('tags: '.(\json_encode($tags) ?: '[]'));
+                }
+                $extra = $log->getExtra();
+                if ($extra !== []) {
+                    $extraJson = \json_encode($extra) ?: '{}';
+                    Console::error('extra: '.(\strlen($extraJson) > 2000 ? \substr($extraJson, 0, 2000).'...' : $extraJson));
+                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$tags = $log->getTags();
if ($tags !== []) {
Console::error('tags: '.\json_encode($tags));
}
$extra = $log->getExtra();
if ($extra !== []) {
$extraJson = \json_encode($extra);
Console::error('extra: '.(\strlen($extraJson) > 2000 ? \substr($extraJson, 0, 2000).'...' : $extraJson));
}
$tags = $log->getTags();
if ($tags !== []) {
Console::error('tags: '.(\json_encode($tags) ?: '[]'));
}
$extra = $log->getExtra();
if ($extra !== []) {
$extraJson = \json_encode($extra) ?: '{}';
Console::error('extra: '.(\strlen($extraJson) > 2000 ? \substr($extraJson, 0, 2000).'...' : $extraJson));
}
🧰 Tools
🪛 GitHub Actions: CodeQL

[error] 81-81: PHPStan: Found 2 errors. Parameter #1 $string of function strlen expects string, string|false given; Parameter #1 $string of function substr expects string, string|false given.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Logger/Logger.php` around lines 74 - 82, The json_encode results for
$tags and $extra can be false, causing strlen/substr to fail; update the Logger
logic around getTags()/getExtra() so you capture the json_encode return into a
variable (e.g. $tagsJson and $extraJson), check
is_string($tagsJson)/is_string($extraJson) (or coerce with (string) only after
handling false) before concatenating or calling strlen/substr, and default to an
empty string or a clear fallback when json_encode returns false so
Console::error always receives a string and PHPStan type errors are resolved.

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.

1 participant