DbTarget logging fails silently when database connection is unavailable
0 reputation · 11 Nov 2022, 11:33 UTC
0 reputation · 11 Nov 2022, 11:33 UTC
Goal: guarantee that important log messages are recorded even when the database connection used by a yii\log\DbTarget becomes unavailable during a deployment.
Constraint: the DbTarget’s export() method wraps the DB insert in a try/catch block, logs the caught exception internally with Yii::error(), and does not provide a configurable fallback to another log target; if the error handler also fails or YII_DEBUG is false, the failure can go completely unnoticed, leaving no trace in the database or application output.
Uncertainty: Yii 2 does not expose an option to automatically redirect DbTarget output to an alternate route or to re‑throw the connection exception, so it is unclear how developers can reliably detect or mitigate this silent logging failure without custom extensions.
Questions: Is there a way to configure DbTarget to automatically fall back to another log target when the database connection fails? Can the DbTarget be configured to re‑throw the connection exception so that the application’s error handling can detect the failure?
33275 reputation · 11 Nov 2022, 13:59 UTC
No to both questions as asked: stock yii\log\DbTarget has no configuration option to automatically fall back to another target, and no option to re-throw the connection exception from export(). The try/catch in export() that reports the failure via Yii::error() is the entire built-in behavior. But you can get the practical effect of both features — one through normal configuration, one through a small subclass.
Yii's logger is fan-out: every message is dispatched to every enabled target. That means the standard mitigation is not "fallback" at all — it is parallelism. Configure a FileTarget alongside your DbTarget:
'log' => [
'targets' => [
[
'class' => 'yii\log\DbTarget',
'levels' => ['error', 'warning'],
],
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
Now important messages survive a DB outage with zero custom code, because they were never exclusively dependent on the database in the first place. Additionally, when DbTarget::export() catches the insert exception and calls Yii::error(), that error record is itself dispatched to all targets — so the FileTarget will normally capture evidence that the DB logging failed. Caveat: this records the failure event, not the original messages that were lost in that export batch.
If you need the original messages forwarded on failure, extend DbTarget and override export():
class FallbackDbTarget extends \yii\log\DbTarget
{
public $fallbackTarget; // e.g. a configured FileTarget instance
public function export()
{
try {
parent::export();
} catch (\Throwable $e) {
if ($this->fallbackTarget !== null) {
$this->fallbackTarget->collect($this->messages, true);
}
\Yii::error('DbTarget export failed: ' . $e->getMessage(), __METHOD__);
}
}
}
Note that parent::export() swallows the exception internally, so in practice you would reimplement the insert logic in your override rather than call the parent — check the exact export() source in your installed vendor/yiisoft/yii2/log/DbTarget.php, since internals vary across Yii 2 minor versions.
Re-throwing from export() is possible in a subclass but risky: export() runs during logger flush, which typically happens at request end or via flushInterval, not at the original log call site. A throw there can surface inside shutdown or error-handler execution, and a logging failure triggered during error handling can cause recursion or a white screen. If you re-throw, test that path explicitly. Also ensure your override never re-enters the failing target when logging the error, or you get an infinite loop.
Messages sit buffered in memory until flush. During a deployment with a long DB outage, buffered messages can be lost at process end regardless of target configuration. Two mitigations:
Yii::getLogger()->flushInterval (or the dispatcher's flush setting) so batches export more frequently.Yii::getLogger()->flush(true) explicitly at known-safe points in your deployment scripts.db component at an invalid DSN (or stop the DB), trigger an error-level log message, and confirm the failure record appears in the file log.One detail that changes the recommendation: if your "important" messages are audit-grade (must never be lost), fan-out to a file is still not a guarantee — consider a queue-backed target instead, since any in-process buffer is vulnerable at process termination.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.