Skip to content

Commit 5ab0a6e

Browse files
committed
feat: Opt-in logging of deprecations
1 parent ee3e329 commit 5ab0a6e

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed

app/Config/Exceptions.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Config;
44

55
use CodeIgniter\Config\BaseConfig;
6+
use Psr\Log\LogLevel;
67

78
/**
89
* Setup how the exception handler works.
@@ -49,4 +50,28 @@ class Exceptions extends BaseConfig
4950
* ex. ['server', 'setup/password', 'secret_token']
5051
*/
5152
public array $sensitiveDataInTrace = [];
53+
54+
/**
55+
* --------------------------------------------------------------------------
56+
* LOG DEPRECATIONS INSTEAD OF THROWING?
57+
* --------------------------------------------------------------------------
58+
* By default, CodeIgniter converts deprecations into exceptions. Also,
59+
* starting in PHP 8.1 will cause a lot of deprecated usage warnings.
60+
* Use this option to temporarily cease the warnings and instead log those.
61+
* This option also works for user deprecations.
62+
*/
63+
public bool $logDeprecationsOnly = false;
64+
65+
/**
66+
* --------------------------------------------------------------------------
67+
* LOG LEVEL THRESHOLD FOR DEPRECATIONS
68+
* --------------------------------------------------------------------------
69+
* If `$logDeprecationsOnly` is set to `true`, this sets the log level
70+
* to which the deprecation will be logged. This should be one of the log
71+
* levels recognized by PSR-3.
72+
*
73+
* The related `Config\Logger::$threshold` should be adjusted, if needed,
74+
* to capture logging the deprecations.
75+
*/
76+
public string $deprecationLogLevel = LogLevel::WARNING;
5277
}

system/Debug/Exceptions.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Config\Exceptions as ExceptionsConfig;
2323
use Config\Paths;
2424
use ErrorException;
25+
use Psr\Log\LogLevel;
2526
use Throwable;
2627

2728
/**
@@ -82,6 +83,10 @@ public function __construct(ExceptionsConfig $config, $request, ResponseInterfac
8283
if (! isset($this->config->sensitiveDataInTrace)) {
8384
$this->config->sensitiveDataInTrace = [];
8485
}
86+
if (! isset($this->config->logDeprecationsOnly, $this->config->deprecationLogLevel)) {
87+
$this->config->logDeprecationsOnly = false;
88+
$this->config->deprecationLogLevel = LogLevel::WARNING;
89+
}
8590
}
8691

8792
/**
@@ -155,6 +160,10 @@ public function exceptionHandler(Throwable $exception)
155160
*/
156161
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
157162
{
163+
if ($this->isDeprecationError($severity) && $this->config->logDeprecationsOnly) {
164+
return $this->handleDeprecationError($message, $file, $line);
165+
}
166+
158167
if (! (error_reporting() & $severity)) {
159168
return;
160169
}
@@ -328,6 +337,32 @@ protected function determineCodes(Throwable $exception): array
328337
return [$statusCode, $exitStatus];
329338
}
330339

340+
private function isDeprecationError(int $error): bool
341+
{
342+
$deprecations = E_DEPRECATED | E_USER_DEPRECATED;
343+
344+
return ($error & $deprecations) !== 0;
345+
}
346+
347+
private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
348+
{
349+
// Remove the trace of the error handler.
350+
$trace = array_slice(debug_backtrace(), 2);
351+
352+
log_message(
353+
$this->config->deprecationLogLevel,
354+
"[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
355+
[
356+
'message' => $message,
357+
'errFile' => clean_path($file ?? ''),
358+
'errLine' => $line ?? 0,
359+
'trace' => self::renderBacktrace($trace),
360+
]
361+
);
362+
363+
return true;
364+
}
365+
331366
// --------------------------------------------------------------------
332367
// Display Methods
333368
// --------------------------------------------------------------------

tests/system/Debug/ExceptionsTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use CodeIgniter\Test\ReflectionHelper;
2020
use Config\Exceptions as ExceptionsConfig;
2121
use Config\Services;
22+
use ErrorException;
2223
use RuntimeException;
2324

2425
/**
@@ -32,9 +33,55 @@ final class ExceptionsTest extends CIUnitTestCase
3233

3334
protected function setUp(): void
3435
{
36+
parent::setUp();
37+
3538
$this->exception = new Exceptions(new ExceptionsConfig(), Services::request(), Services::response());
3639
}
3740

41+
/**
42+
* @requires PHP >= 8.1
43+
*/
44+
public function testDeprecationsDoNotThrow(): void
45+
{
46+
$config = new ExceptionsConfig();
47+
48+
$config->logDeprecationsOnly = true;
49+
$config->deprecationLogLevel = 'error';
50+
51+
$this->exception = new Exceptions($config, Services::request(), Services::response());
52+
$this->exception->initialize();
53+
54+
// this is only needed for IDEs not to complain that strlen does not accept explicit null
55+
$maybeNull = PHP_VERSION_ID >= 80100 ? null : 'random string';
56+
57+
try {
58+
strlen($maybeNull);
59+
$this->assertLogContains('error', '[DEPRECATED] strlen(): ');
60+
} catch (ErrorException $e) {
61+
$this->fail('The catch block should not be reached.');
62+
} finally {
63+
restore_error_handler();
64+
restore_exception_handler();
65+
}
66+
}
67+
68+
public function testSuppressedDeprecationsDoNotThrow(): void
69+
{
70+
$config = new ExceptionsConfig();
71+
72+
$config->logDeprecationsOnly = true;
73+
$config->deprecationLogLevel = 'error';
74+
75+
$this->exception = new Exceptions($config, Services::request(), Services::response());
76+
$this->exception->initialize();
77+
78+
@trigger_error('Hello! I am a deprecation!', E_USER_DEPRECATED);
79+
$this->assertLogContains('error', '[DEPRECATED] Hello! I am a deprecation!', false);
80+
81+
restore_error_handler();
82+
restore_exception_handler();
83+
}
84+
3885
public function testDetermineViews(): void
3986
{
4087
$determineView = $this->getPrivateMethodInvoker($this->exception, 'determineView');

0 commit comments

Comments
 (0)