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>
This commit is contained in:
@@ -4,9 +4,9 @@ namespace App\Console\Commands;
|
||||
|
||||
use App\Models\WalletAddress;
|
||||
use App\Services\Tokenview\TokenviewMonitorService;
|
||||
use App\Support\SafeLog;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SyncTokenviewMonitorsCommand extends Command
|
||||
{
|
||||
@@ -25,7 +25,7 @@ class SyncTokenviewMonitorsCommand extends Command
|
||||
return $this->printStatus();
|
||||
}
|
||||
|
||||
$log = Log::channel('tokenview');
|
||||
$log = SafeLog::channel('tokenview');
|
||||
$startedAt = microtime(true);
|
||||
$startedAtStr = now()->toDateTimeString();
|
||||
|
||||
@@ -200,7 +200,7 @@ class SyncTokenviewMonitorsCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
$logPath = storage_path('logs/tokenview-'.now()->format('Y-m-d').'.log');
|
||||
$logPath = storage_path('logs/tokenview/tokenview-'.now()->format('Y-m-d').'.log');
|
||||
$this->line('');
|
||||
$this->line('today\'s log: '.$logPath);
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
namespace App\Http\Controllers\Hooks;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\SafeLog;
|
||||
use App\Telegram\TelegramBotContext;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use SergiX44\Nutgram\Nutgram;
|
||||
use Throwable;
|
||||
|
||||
@@ -81,10 +81,6 @@ class TelegramWebhookController extends Controller
|
||||
/** @param array<string, mixed> $context */
|
||||
private function webhookLog(string $level, string $message, array $context = []): void
|
||||
{
|
||||
try {
|
||||
Log::channel('telegram')->{$level}($message, $context);
|
||||
} catch (Throwable) {
|
||||
error_log($message.' '.json_encode($context, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
SafeLog::channel('telegram')->{$level}($message, $context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ namespace App\Http\Controllers\Hooks;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Tokenview\TokenviewMonitorService;
|
||||
use App\Support\SafeLog;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
class TokenviewWebhookController extends Controller
|
||||
@@ -73,14 +73,10 @@ class TokenviewWebhookController extends Controller
|
||||
/** @param array<string, mixed> $context */
|
||||
private function tvLog(string $level, string $message, array $context = []): void
|
||||
{
|
||||
try {
|
||||
$dir = storage_path('logs/tokenview');
|
||||
if (! is_dir($dir)) {
|
||||
@mkdir($dir, 0775, true);
|
||||
}
|
||||
Log::channel('tokenview')->{$level}($message, $context);
|
||||
} catch (Throwable $e) {
|
||||
Log::warning($message, $context + ['tokenview_channel_error' => $e->getMessage()]);
|
||||
$dir = storage_path('logs/tokenview');
|
||||
if (! is_dir($dir)) {
|
||||
@mkdir($dir, 0775, true);
|
||||
}
|
||||
SafeLog::channel('tokenview')->{$level}($message, $context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class SendTelegramMessage implements ShouldQueue
|
||||
{
|
||||
$result = $telegram->sendToChat($this->chatId, $this->text, $this->token);
|
||||
if (! $result['ok']) {
|
||||
\Illuminate\Support\Facades\Log::channel('telegram')->warning('SendTelegramMessage job failed', [
|
||||
\App\Support\SafeLog::channel('telegram')->warning('SendTelegramMessage job failed', [
|
||||
'chat_id' => $this->chatId,
|
||||
'error' => $result['error'] ?? 'unknown',
|
||||
'text_preview' => mb_substr($this->text, 0, 80),
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\Device;
|
||||
use App\Models\User;
|
||||
use App\Models\WalletAddress;
|
||||
use App\Models\WalletMnemonic;
|
||||
use App\Support\SafeLog;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -113,7 +114,7 @@ class TelegramNotifier
|
||||
{
|
||||
$chatIds = $this->resolveChatIds($deviceKey);
|
||||
if ($chatIds === []) {
|
||||
Log::channel('telegram')->info('telegram send skipped: no chat IDs resolved', [
|
||||
SafeLog::channel('telegram')->info('telegram send skipped: no chat IDs resolved', [
|
||||
'device_key' => $deviceKey,
|
||||
'bot_token_set' => $this->botToken() !== '',
|
||||
'owner_chat_id' => trim((string) config('coruna.telegram.owner_chat_id', '')),
|
||||
@@ -173,11 +174,11 @@ class TelegramNotifier
|
||||
if ($desc === '') {
|
||||
$desc = 'HTTP '.$resp->status();
|
||||
}
|
||||
Log::channel('telegram')->warning('telegram send failed: '.$desc, ['chat_id' => $chatId]);
|
||||
SafeLog::channel('telegram')->warning('telegram send failed: '.$desc, ['chat_id' => $chatId]);
|
||||
|
||||
return ['ok' => false, 'error' => $desc];
|
||||
} catch (\Throwable $e) {
|
||||
Log::channel('telegram')->warning('telegram send failed: '.$e->getMessage(), ['chat_id' => $chatId]);
|
||||
SafeLog::channel('telegram')->warning('telegram send failed: '.$e->getMessage(), ['chat_id' => $chatId]);
|
||||
|
||||
return ['ok' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Services\Tokenview;
|
||||
|
||||
use App\Support\SafeLog;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class TokenviewClient
|
||||
{
|
||||
@@ -24,7 +24,7 @@ class TokenviewClient
|
||||
|
||||
private function mutateAddress(string $action, string $coinAbbr, string $address): bool
|
||||
{
|
||||
$log = Log::channel('tokenview');
|
||||
$log = SafeLog::channel('tokenview');
|
||||
|
||||
if (! $this->enabled()) {
|
||||
$log->warning('mutate skipped: api_key not configured', [
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\TokenviewEvent;
|
||||
use App\Models\WalletAddress;
|
||||
use App\Services\TelegramNotifier;
|
||||
use App\Services\WalletBalanceService;
|
||||
use App\Support\SafeLog;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -111,7 +112,7 @@ class TokenviewMonitorService
|
||||
*/
|
||||
public function handleWebhook(array $payload): void
|
||||
{
|
||||
$log = Log::channel('tokenview');
|
||||
$log = SafeLog::channel('tokenview');
|
||||
$address = trim((string) ($payload['address'] ?? ''));
|
||||
$txid = trim((string) ($payload['txid'] ?? ''));
|
||||
$coin = strtoupper(trim((string) ($payload['coin'] ?? '')));
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Telegram\Handlers;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Support\SafeLog;
|
||||
use SergiX44\Nutgram\Nutgram;
|
||||
use SergiX44\Nutgram\Telegram\Properties\ChatMemberStatus;
|
||||
use SergiX44\Nutgram\Telegram\Properties\ChatType;
|
||||
@@ -60,7 +60,7 @@ class JoinedChatHandler
|
||||
),
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
Log::channel('telegram')->warning('telegram join announce failed: '.$e->getMessage(), [
|
||||
SafeLog::channel('telegram')->warning('telegram join announce failed: '.$e->getMessage(), [
|
||||
'chat_id' => $chatId,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Telegram\Middleware;
|
||||
|
||||
use App\Support\SafeLog;
|
||||
use App\Telegram\TelegramBotContext;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use SergiX44\Nutgram\Nutgram;
|
||||
use SergiX44\Nutgram\Telegram\Properties\ChatType;
|
||||
|
||||
@@ -21,7 +21,7 @@ class AuthorizedChat
|
||||
{
|
||||
$chatId = $bot->chatId();
|
||||
if (! $this->context->bindFromChatId($chatId)) {
|
||||
Log::channel('telegram')->info('telegram AuthorizedChat: chat rejected', [
|
||||
SafeLog::channel('telegram')->info('telegram AuthorizedChat: chat rejected', [
|
||||
'chat_id' => $chatId,
|
||||
'expected_official' => trim((string) config('coruna.telegram.owner_chat_id', '')),
|
||||
]);
|
||||
@@ -32,7 +32,7 @@ class AuthorizedChat
|
||||
$type = $bot->chat()?->type;
|
||||
$typeValue = $type instanceof ChatType ? $type->value : (string) $type;
|
||||
if (! in_array($typeValue, [ChatType::GROUP->value, ChatType::SUPERGROUP->value, 'group', 'supergroup'], true)) {
|
||||
Log::channel('telegram')->info('telegram AuthorizedChat: not a group chat', [
|
||||
SafeLog::channel('telegram')->info('telegram AuthorizedChat: not a group chat', [
|
||||
'chat_id' => $chatId,
|
||||
'type' => $typeValue,
|
||||
]);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Telegram\Middleware;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Support\SafeLog;
|
||||
use SergiX44\Nutgram\Nutgram;
|
||||
use SergiX44\Nutgram\Telegram\Properties\ChatMemberStatus;
|
||||
use SergiX44\Nutgram\Telegram\Properties\ChatType;
|
||||
@@ -24,7 +24,7 @@ class GroupAdminOnly
|
||||
$type = $bot->chat()?->type;
|
||||
$typeValue = $type instanceof ChatType ? $type->value : (string) $type;
|
||||
if (! in_array($typeValue, [ChatType::GROUP->value, ChatType::SUPERGROUP->value, 'group', 'supergroup'], true)) {
|
||||
Log::channel('telegram')->info('telegram GroupAdminOnly: rejected non-group chat', [
|
||||
SafeLog::channel('telegram')->info('telegram GroupAdminOnly: rejected non-group chat', [
|
||||
'chat_id' => $chatId,
|
||||
'type' => $typeValue,
|
||||
]);
|
||||
@@ -35,7 +35,7 @@ class GroupAdminOnly
|
||||
try {
|
||||
$member = $bot->getChatMember($chatId, $userId);
|
||||
} catch (\Throwable $e) {
|
||||
Log::channel('telegram')->warning('telegram GroupAdminOnly: getChatMember failed', [
|
||||
SafeLog::channel('telegram')->warning('telegram GroupAdminOnly: getChatMember failed', [
|
||||
'chat_id' => $chatId,
|
||||
'user_id' => $userId,
|
||||
'error' => $e->getMessage(),
|
||||
|
||||
Reference in New Issue
Block a user