feat: verbose logging, file persistence, auto websocket log channel
- WebSocketHandler: log connection rejections, unknown app keys, message drops - Logger: persist all output to file via Laravel Log facade - ServiceProvider: auto-register 'websocket' daily log channel
This commit is contained in:
parent
781e329601
commit
859fcb6f89
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace BlaxSoftware\LaravelWebSockets\Server\Loggers;
|
namespace BlaxSoftware\LaravelWebSockets\Server\Loggers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
|
@ -15,14 +16,14 @@ class Logger
|
||||||
protected $consoleOutput;
|
protected $consoleOutput;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wether the logger is enabled.
|
* Whether the logger is enabled.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
protected $enabled = false;
|
protected $enabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wether the verbose mode is on.
|
* Whether the verbose mode is on.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
|
|
@ -116,10 +117,38 @@ class Logger
|
||||||
$this->line($message, 'error');
|
$this->line($message, 'error');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a message to the console and persist it to the websocket log file.
|
||||||
|
*/
|
||||||
protected function line(string $message, string $style)
|
protected function line(string $message, string $style)
|
||||||
{
|
{
|
||||||
|
// Console output (existing behavior)
|
||||||
$this->consoleOutput->writeln(
|
$this->consoleOutput->writeln(
|
||||||
$style ? "<{$style}>{$message}</{$style}>" : $message
|
$style ? "<{$style}>{$message}</{$style}>" : $message
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Also persist to log file so errors are visible outside the console
|
||||||
|
$this->fileLog($style, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a message to the websocket log channel.
|
||||||
|
* Uses the 'websocket' channel if available, falls back to the default.
|
||||||
|
*/
|
||||||
|
protected function fileLog(string $level, string $message): void
|
||||||
|
{
|
||||||
|
// Map console styles to log levels
|
||||||
|
$logLevel = match ($level) {
|
||||||
|
'error' => 'error',
|
||||||
|
'warning' => 'warning',
|
||||||
|
default => 'info',
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
$channel = config('logging.channels.websocket') ? 'websocket' : null;
|
||||||
|
Log::channel($channel)->log($logLevel, '[WebSocket] '.$message);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// Logging must never crash the WS server
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use BlaxSoftware\LaravelWebSockets\Events\NewConnection;
|
||||||
use BlaxSoftware\LaravelWebSockets\Helpers;
|
use BlaxSoftware\LaravelWebSockets\Helpers;
|
||||||
use BlaxSoftware\LaravelWebSockets\Server\Exceptions\WebSocketException;
|
use BlaxSoftware\LaravelWebSockets\Server\Exceptions\WebSocketException;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
use Ratchet\RFC6455\Messaging\MessageInterface;
|
use Ratchet\RFC6455\Messaging\MessageInterface;
|
||||||
|
|
@ -45,6 +46,7 @@ class WebSocketHandler implements MessageComponentInterface
|
||||||
public function onOpen(ConnectionInterface $connection)
|
public function onOpen(ConnectionInterface $connection)
|
||||||
{
|
{
|
||||||
if (! $this->connectionCanBeMade($connection)) {
|
if (! $this->connectionCanBeMade($connection)) {
|
||||||
|
$this->wsLog('warning', 'Connection rejected: server not accepting new connections');
|
||||||
return $connection->close();
|
return $connection->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,6 +66,8 @@ class WebSocketHandler implements MessageComponentInterface
|
||||||
|
|
||||||
$this->channelManager->connectionPonged($connection);
|
$this->channelManager->connectionPonged($connection);
|
||||||
|
|
||||||
|
$this->wsLog('info', "[{$connection->app->id}][{$connection->socketId}] Connection established (key: {$connection->app->key})");
|
||||||
|
|
||||||
NewConnection::dispatch($connection->app->id, $connection->socketId);
|
NewConnection::dispatch($connection->app->id, $connection->socketId);
|
||||||
}
|
}
|
||||||
} catch (WebSocketException $exception) {
|
} catch (WebSocketException $exception) {
|
||||||
|
|
@ -84,6 +88,7 @@ class WebSocketHandler implements MessageComponentInterface
|
||||||
public function onMessage(ConnectionInterface $connection, MessageInterface $message)
|
public function onMessage(ConnectionInterface $connection, MessageInterface $message)
|
||||||
{
|
{
|
||||||
if (! isset($connection->app)) {
|
if (! isset($connection->app)) {
|
||||||
|
$this->wsLog('warning', 'Message dropped: connection has no app (likely failed auth). Payload: '.Str::limit($message->getPayload(), 200));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,6 +175,10 @@ class WebSocketHandler implements MessageComponentInterface
|
||||||
$exception->getPayload()
|
$exception->getPayload()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$appId = $connection->app->id ?? 'unknown';
|
||||||
|
$socketId = $connection->socketId ?? 'unknown';
|
||||||
|
$this->wsLog('error', "[{$appId}][{$socketId}] {$exception->getMessage()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -201,6 +210,7 @@ class WebSocketHandler implements MessageComponentInterface
|
||||||
App::findByKey($appKey)
|
App::findByKey($appKey)
|
||||||
->then(function ($app) use ($appKey, $connection, $deferred) {
|
->then(function ($app) use ($appKey, $connection, $deferred) {
|
||||||
if (! $app) {
|
if (! $app) {
|
||||||
|
$this->wsLog('error', "Unknown app key: '{$appKey}'. Check that PUSHER_APP_KEY in .env matches the key used by the frontend. Configured apps: ".implode(', ', array_map(fn ($a) => $a['key'] ?? 'null', config('websockets.apps', []))));
|
||||||
$deferred->reject(new Exceptions\UnknownAppKey($appKey));
|
$deferred->reject(new Exceptions\UnknownAppKey($appKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,4 +316,18 @@ class WebSocketHandler implements MessageComponentInterface
|
||||||
{
|
{
|
||||||
return str_ends_with($event, '.' . $action) || str_ends_with($event, ':' . $action);
|
return str_ends_with($event, '.' . $action) || str_ends_with($event, ':' . $action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a WebSocket server message.
|
||||||
|
* Uses the 'websocket' channel if configured, falls back to the default channel.
|
||||||
|
*/
|
||||||
|
protected function wsLog(string $level, string $message): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$channel = config('logging.channels.websocket') ? 'websocket' : config('logging.default');
|
||||||
|
Log::channel($channel)->log($level, '[WebSocket] '.$message);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// Logging must never break the server
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ class WebSocketsServiceProvider extends ServiceProvider
|
||||||
__DIR__ . '/Websocket' => app_path('Websocket')
|
__DIR__ . '/Websocket' => app_path('Websocket')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->registerWebsocketLogChannel();
|
||||||
$this->registerDefaultWebsocketChannels();
|
$this->registerDefaultWebsocketChannels();
|
||||||
$this->registerEventLoop();
|
$this->registerEventLoop();
|
||||||
$this->registerWebSocketHandler();
|
$this->registerWebSocketHandler();
|
||||||
|
|
@ -85,6 +86,19 @@ class WebSocketsServiceProvider extends ServiceProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function registerWebsocketLogChannel()
|
||||||
|
{
|
||||||
|
// Register a dedicated 'websocket' log channel if the app hasn't defined one
|
||||||
|
if (! config('logging.channels.websocket')) {
|
||||||
|
config(['logging.channels.websocket' => [
|
||||||
|
'driver' => 'daily',
|
||||||
|
'path' => storage_path('logs/websocket.log'),
|
||||||
|
'level' => 'debug',
|
||||||
|
'days' => 7,
|
||||||
|
]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected function registerWebSocketHandler()
|
protected function registerWebSocketHandler()
|
||||||
{
|
{
|
||||||
$this->app->singleton('websockets.handler', function () {
|
$this->app->singleton('websockets.handler', function () {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue