Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: safely interpolate logger context values#10384

Merged
michalsn merged 2 commits into
codeigniter4:developfrom
michalsn:fix/logger-interpolate
Jul 8, 2026
Merged

fix: safely interpolate logger context values#10384
michalsn merged 2 commits into
codeigniter4:developfrom
michalsn:fix/logger-interpolate

Conversation

@michalsn

@michalsn michalsn commented Jul 7, 2026

Copy link
Copy Markdown
Member

Description
This PR fixes logger message interpolation so arbitrary PSR-3 context values do not raise PHP warnings or errors when used as placeholders.

Context values are now converted to safe strings before being passed to strtr(). Arrays and Entities are rendered with print_r() to match the logger's existing output style, Stringable values such as Time continue to use their string representation, and non-stringable objects/resources fall back to safe descriptive placeholders.

Reported in: https://forum.codeigniter.com/showthread.php?tid=94242

Ref: https://www.php-fig.org/psr/psr-3/

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication)
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

@michalsn michalsn added the bug Verified issues on the current code behavior or pull requests that will fix them label Jul 7, 2026
Comment thread system/Log/Logger.php Outdated
Co-authored-by: John Paul E Balandan <[email protected]>
@michalsn michalsn requested a review from paulbalandan July 7, 2026 14:32
@hiSandog

hiSandog commented Jul 8, 2026

Copy link
Copy Markdown

The safe interpolation behavior should be tested with the full PSR-3 variety: arrays, scalar values, Stringable objects, plain objects, resources, and null. That matters because the logger should never emit PHP warnings while handling context, but it also should not change the existing readable output style for arrays/entities.

@michalsn

michalsn commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

I believe we already covered a fair number of cases, and the previous behavior when it comes to the strings has not changed.

@gr8man

gr8man commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Hi!

While working on this PR to fix logger interpolation issues, I noticed there is another pre-existing bug in the environment variable placeholders interpolation. When a log message contains multiple {env:...} tags, only the first one is replaced.

This is because the current code uses preg_match() instead of preg_match_all():

        if (str_contains($message, 'env:')) {
            preg_match('/env:[^}]+/', $message, $matches);

            foreach ($matches as $str) {
                $key                 = str_replace('env:', '', $str);
                $replace["{{$str}}"] = $_ENV[$key] ?? 'n/a';
            }
        }

We can fix this easily in Logger::interpolate() by switching to preg_match_all() and iterating over $matches[0]:

        if (str_contains($message, 'env:')) {
            preg_match_all('/env:[^}]+/', $message, $matches);

            foreach ($matches[0] as $str) {
                $key                 = str_replace('env:', '', $str);
                $replace["{{$str}}"] = $_ENV[$key] ?? 'n/a';
            }
        }

Here is a test case verifying this behavior:

    public function testLogInterpolatesMultipleEnvironmentVars(): void
    {
        $config = new LoggerConfig();
        $logger = new Logger($config);

        Time::setTestNow('2023-11-25 12:00:00');

        $_ENV['foo'] = 'bar';
        $_ENV['baz'] = 'qux';

        $expected = 'DEBUG - ' . Time::now()->format('Y-m-d') . ' --> Test message bar and qux';

        $logger->log('debug', 'Test message {env:foo} and {env:baz}');

        $logs = TestHandler::getLogs();

        $this->assertCount(1, $logs);
        $this->assertSame($expected, $logs[0]);
    }

Would you like to include this fix in this PR, or should I open a separate PR/issue for it?

@gr8man

gr8man commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

The following test cases fail under the current implementation of PR
You can see how these cases (and others) are resolved in my PR #10387.

Test 1: Object Data Loss (testLogObjectContextLosesProperties)

Failure Reason: Generic objects without __toString() (e.g. stdClass DTOs) are replaced with [object stdClass], losing all of their properties.

Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'DEBUG - 2023-11-25 --> User: stdClass Object
-(
-    [name] => John
-    [role] => admin
-)
-'
+'DEBUG - 2023-11-25 --> User: [object stdClass]'
    public function testLogObjectContextLosesProperties(): void
    {
        $config = new LoggerConfig();
        $logger = new Logger($config);

        Time::setTestNow('2023-11-25 12:00:00');

        $user = new stdClass();
        $user->name = 'John';
        $user->role = 'admin';


        $expected = 'DEBUG - ' . Time::now()->format('Y-m-d') . ' --> User: ' . print_r($user, true);
        
        $logger->log('debug', 'User: {user}', ['user' => $user]);

        $logs = TestHandler::getLogs();

        $this->assertCount(1, $logs);
        $this->assertSame($expected, $logs[0]);
    }

Test 2: Boolean false Logged as Empty String (testLogBooleanFalseIsLoggedAsEmptyString)

Failure Reason: Boolean false values are cast directly to (string) false (which yields ""), making the value invisible in the log output.

Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'DEBUG - 2023-11-25 --> Active: false'
+'DEBUG - 2023-11-25 --> Active: '
    public function testLogBooleanFalseIsLoggedAsEmptyString(): void
    {
        $config = new LoggerConfig();
        $logger = new Logger($config);

        Time::setTestNow('2023-11-25 12:00:00');


        $expected = 'DEBUG - ' . Time::now()->format('Y-m-d') . ' --> Active: false';
        
        $logger->log('debug', 'Active: {status}', ['status' => false]);

        $logs = TestHandler::getLogs();

        $this->assertCount(1, $logs);
        $this->assertSame($expected, $logs[0]);
    }

@michalsn

michalsn commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

@gr8man

  1. This is intentional, I don't see why we should change that. Generic objects may contain sensitive/private data or expensive state.
  2. This is a change of behavior, and in this PR we're fixing a bug.

The multiple {env:...} placeholders issue looks valid, but it is a separate pre-existing interpolation bug. I think it should be handled in a separate PR with its own changelog entry, so this PR stays scoped to safe context value interpolation.

@michalsn michalsn merged commit 4a7d144 into codeigniter4:develop Jul 8, 2026
57 checks passed
@michalsn

michalsn commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

Thank you everyone for the reviews!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Verified issues on the current code behavior or pull requests that will fix them

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants