Files
hashbro 51336e346a Keep Tokenview and Telegram running when log files are not writable.
Daily logs created by root cron were blocking www from appending, which aborted webhook ingest and bot pushes. File channels now use 0664 plus exception-safe stacks, and writes go through SafeLog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 11:40:44 +08:00

94 lines
2.7 KiB
PHP

<?php
namespace App\Support;
use Illuminate\Support\Facades\Log;
use Throwable;
/**
* Logging that never throws. File permission errors on a dedicated channel
* (root-owned daily logs vs php-fpm www) must not abort webhooks or Telegram.
*/
final class SafeLog
{
public static function channel(string $name): SafeLogChannel
{
return new SafeLogChannel($name);
}
}
final class SafeLogChannel
{
public function __construct(private readonly string $name) {}
/** @param array<string, mixed> $context */
public function debug(string $message, array $context = []): void
{
$this->write('debug', $message, $context);
}
/** @param array<string, mixed> $context */
public function info(string $message, array $context = []): void
{
$this->write('info', $message, $context);
}
/** @param array<string, mixed> $context */
public function warning(string $message, array $context = []): void
{
$this->write('warning', $message, $context);
}
/** @param array<string, mixed> $context */
public function error(string $message, array $context = []): void
{
$this->write('error', $message, $context);
}
/** @param array<string, mixed> $context */
public function critical(string $message, array $context = []): void
{
$this->write('critical', $message, $context);
}
/** @param array<int, mixed> $arguments */
public function __call(string $level, array $arguments): void
{
$message = (string) ($arguments[0] ?? '');
$context = is_array($arguments[1] ?? null) ? $arguments[1] : [];
$this->write($level, $message, $context);
}
/** @param array<string, mixed> $context */
private function write(string $level, string $message, array $context): void
{
try {
Log::channel($this->name)->{$level}($message, $context);
} catch (Throwable $e) {
$this->fallback($level, $message, $context, $e);
}
}
/** @param array<string, mixed> $context */
private function fallback(string $level, string $message, array $context, Throwable $e): void
{
$context['safe_log_channel'] = $this->name;
$context['safe_log_error'] = $e->getMessage();
try {
$default = (string) config('logging.default', 'stack');
if ($default !== '' && $default !== $this->name) {
Log::channel($default)->warning($message, $context);
return;
}
} catch (Throwable) {
}
try {
error_log('['.$this->name.'] '.$level.': '.$message.' '.$e->getMessage());
} catch (Throwable) {
}
}
}