diff --git a/.editorconfig b/.editorconfig index cd8eb86..32de2af 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,5 +11,8 @@ end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true +[*.blade.php] +indent_size = 2 + [*.md] trim_trailing_whitespace = false diff --git a/config/websockets.php b/config/websockets.php index a798251..8001a3b 100644 --- a/config/websockets.php +++ b/config/websockets.php @@ -209,9 +209,13 @@ return [ | store them into an array and then store them into the database | on each interval. | + | You can opt-in to avoid any statistics storage by setting the logger + | to the built-in NullLogger. + | */ - 'logger' => BeyondCode\LaravelWebSockets\Statistics\Logger\HttpStatisticsLogger::class, + 'logger' => \BeyondCode\LaravelWebSockets\Statistics\Logger\HttpStatisticsLogger::class, + // 'logger' => \BeyondCode\LaravelWebSockets\Statistics\Logger\NullStatisticsLogger::class, /* |-------------------------------------------------------------------------- diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index e4a761b..33a69b1 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -1,265 +1,423 @@ - - WebSockets Dashboard - - - - - + + + WebSockets Dashboard + + + + + + + + + + + - -
-
-
-
- - - - - - -
-
-
-
-
-

Realtime Statistics

-
-
-
-

Event Creator

-
-
-
- -
-
- -
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-

Events

- - - - - - - - - - - - - - - -
TypeDetailsTime
@{{ log.type }}
@{{ log.details }}
@{{ log.time }}
-
-
-
- diff --git a/src/Dashboard/DashboardLogger.php b/src/Dashboard/DashboardLogger.php index 2b00d3f..f5d0980 100644 --- a/src/Dashboard/DashboardLogger.php +++ b/src/Dashboard/DashboardLogger.php @@ -3,16 +3,14 @@ namespace BeyondCode\LaravelWebSockets\Dashboard; use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager; -use Ratchet\ConnectionInterface; -use stdClass; class DashboardLogger { const LOG_CHANNEL_PREFIX = 'private-websockets-dashboard-'; - const TYPE_DISCONNECTION = 'disconnection'; + const TYPE_DISCONNECTED = 'disconnected'; - const TYPE_CONNECTION = 'connection'; + const TYPE_CONNECTED = 'connected'; const TYPE_VACATED = 'vacated'; @@ -20,7 +18,7 @@ class DashboardLogger const TYPE_SUBSCRIBED = 'subscribed'; - const TYPE_CLIENT_MESSAGE = 'client-message'; + const TYPE_WS_MESSAGE = 'ws-message'; const TYPE_API_MESSAGE = 'api-message'; @@ -28,101 +26,36 @@ class DashboardLogger const TYPE_REPLICATOR_UNSUBSCRIBED = 'replicator-unsubscribed'; - public static function connection(ConnectionInterface $connection) - { - /** @var \GuzzleHttp\Psr7\Request $request */ - $request = $connection->httpRequest; + const TYPE_REPLICATOR_JOINED_CHANNEL = 'replicator-joined'; - static::log($connection->app->id, static::TYPE_CONNECTION, [ - 'details' => [ - 'origin' => "{$request->getUri()->getScheme()}://{$request->getUri()->getHost()}", - 'socketId' => $connection->socketId, - ], - ]); - } + const TYPE_REPLICATOR_LEFT_CHANNEL = 'replicator-left'; - public static function occupied(ConnectionInterface $connection, string $channelName) - { - static::log($connection->app->id, static::TYPE_OCCUPIED, [ - 'details' => [ - 'channel' => $channelName, - ], - ]); - } + const TYPE_REPLICATOR_MESSAGE_PUBLISHED = 'replicator-message-published'; - public static function subscribed(ConnectionInterface $connection, string $channelName) - { - static::log($connection->app->id, static::TYPE_SUBSCRIBED, [ - 'details' => [ - 'socketId' => $connection->socketId, - 'channel' => $channelName, - ], - ]); - } + const TYPE_REPLICATOR_MESSAGE_RECEIVED = 'replicator-message-received'; - public static function clientMessage(ConnectionInterface $connection, stdClass $payload) - { - static::log($connection->app->id, static::TYPE_CLIENT_MESSAGE, [ - 'details' => [ - 'socketId' => $connection->socketId, - 'channel' => $payload->channel, - 'event' => $payload->event, - 'data' => $payload, - ], - ]); - } + /** + * The list of all channels. + * + * @var array + */ + public static $channels = [ + self::TYPE_DISCONNECTED, + self::TYPE_CONNECTED, + self::TYPE_VACATED, + self::TYPE_OCCUPIED, + self::TYPE_SUBSCRIBED, + self::TYPE_WS_MESSAGE, + self::TYPE_API_MESSAGE, + self::TYPE_REPLICATOR_SUBSCRIBED, + self::TYPE_REPLICATOR_UNSUBSCRIBED, + self::TYPE_REPLICATOR_JOINED_CHANNEL, + self::TYPE_REPLICATOR_LEFT_CHANNEL, + self::TYPE_REPLICATOR_MESSAGE_PUBLISHED, + self::TYPE_REPLICATOR_MESSAGE_RECEIVED, + ]; - public static function disconnection(ConnectionInterface $connection) - { - static::log($connection->app->id, static::TYPE_DISCONNECTION, [ - 'details' => [ - 'socketId' => $connection->socketId, - ], - ]); - } - - public static function vacated(ConnectionInterface $connection, string $channelName) - { - static::log($connection->app->id, static::TYPE_VACATED, [ - 'details' => [ - 'socketId' => $connection->socketId, - 'channel' => $channelName, - ], - ]); - } - - public static function apiMessage($appId, string $channel, string $event, string $payload) - { - static::log($appId, static::TYPE_API_MESSAGE, [ - 'details' => [ - 'channel' => $connection, - 'event' => $event, - 'payload' => $payload, - ], - ]); - } - - public static function replicatorSubscribed(string $appId, string $channel, string $serverId) - { - static::log($appId, static::TYPE_REPLICATOR_SUBSCRIBED, [ - 'details' => [ - 'serverId' => $serverId, - 'channel' => $channel, - ], - ]); - } - - public static function replicatorUnsubscribed(string $appId, string $channel, string $serverId) - { - static::log($appId, static::TYPE_REPLICATOR_UNSUBSCRIBED, [ - 'details' => [ - 'serverId' => $serverId, - 'channel' => $channel, - ], - ]); - } - - public static function log($appId, string $type, array $attributes = []) + public static function log($appId, string $type, array $details = []) { $channelName = static::LOG_CHANNEL_PREFIX.$type; @@ -134,7 +67,8 @@ class DashboardLogger 'data' => [ 'type' => $type, 'time' => strftime('%H:%M:%S'), - ] + $attributes, + 'details' => $details, + ], ]); } } diff --git a/src/Dashboard/Http/Controllers/ShowDashboard.php b/src/Dashboard/Http/Controllers/ShowDashboard.php index 47088ef..7f22a45 100644 --- a/src/Dashboard/Http/Controllers/ShowDashboard.php +++ b/src/Dashboard/Http/Controllers/ShowDashboard.php @@ -3,6 +3,7 @@ namespace BeyondCode\LaravelWebSockets\Dashboard\Http\Controllers; use BeyondCode\LaravelWebSockets\Apps\AppManager; +use BeyondCode\LaravelWebSockets\Dashboard\DashboardLogger; use Illuminate\Http\Request; class ShowDashboard @@ -12,6 +13,8 @@ class ShowDashboard return view('websockets::dashboard', [ 'apps' => $apps->all(), 'port' => config('websockets.dashboard.port', 6001), + 'channels' => DashboardLogger::$channels, + 'logPrefix' => DashboardLogger::LOG_CHANNEL_PREFIX, ]); } } diff --git a/src/Facades/StatisticsLogger.php b/src/Facades/StatisticsLogger.php index 9aadfa7..5183342 100644 --- a/src/Facades/StatisticsLogger.php +++ b/src/Facades/StatisticsLogger.php @@ -6,7 +6,7 @@ use BeyondCode\LaravelWebSockets\Statistics\Logger\StatisticsLogger as Statistic use Illuminate\Support\Facades\Facade; /** - * @see \BeyondCode\LaravelWebSockets\Statistics\Logger\HttpStatisticsLogger + * @see \BeyondCode\LaravelWebSockets\Statistics\Logger\HttpStatisticsLogger * @mixin \BeyondCode\LaravelWebSockets\Statistics\Logger\HttpStatisticsLogger */ class StatisticsLogger extends Facade diff --git a/src/HttpApi/Controllers/TriggerEventController.php b/src/HttpApi/Controllers/TriggerEventController.php index bc921e4..819d417 100644 --- a/src/HttpApi/Controllers/TriggerEventController.php +++ b/src/HttpApi/Controllers/TriggerEventController.php @@ -21,12 +21,11 @@ class TriggerEventController extends Controller 'data' => $request->json()->get('data'), ], $request->json()->get('socket_id'), $request->appId); - DashboardLogger::apiMessage( - $request->appId, - $channelName, - $request->json()->get('name'), - $request->json()->get('data') - ); + DashboardLogger::log($request->appId, DashboardLogger::TYPE_API_MESSAGE, [ + 'channel' => $channelName, + 'event' => $request->json()->get('name'), + 'payload' => $request->json()->get('data'), + ]); StatisticsLogger::apiMessage($request->appId); } diff --git a/src/PubSub/Drivers/LocalClient.php b/src/PubSub/Drivers/LocalClient.php index 3e24c73..8209e83 100644 --- a/src/PubSub/Drivers/LocalClient.php +++ b/src/PubSub/Drivers/LocalClient.php @@ -78,7 +78,7 @@ class LocalClient implements ReplicationInterface */ public function joinChannel(string $appId, string $channel, string $socketId, string $data) { - $this->channelData["$appId:$channel"][$socketId] = $data; + $this->channelData["{$appId}:{$channel}"][$socketId] = $data; } /** @@ -92,10 +92,10 @@ class LocalClient implements ReplicationInterface */ public function leaveChannel(string $appId, string $channel, string $socketId) { - unset($this->channelData["$appId:$channel"][$socketId]); + unset($this->channelData["{$appId}:{$channel}"][$socketId]); - if (empty($this->channelData["$appId:$channel"])) { - unset($this->channelData["$appId:$channel"]); + if (empty($this->channelData["{$appId}:{$channel}"])) { + unset($this->channelData["{$appId}:{$channel}"]); } } @@ -108,7 +108,7 @@ class LocalClient implements ReplicationInterface */ public function channelMembers(string $appId, string $channel): PromiseInterface { - $members = $this->channelData["$appId:$channel"] ?? []; + $members = $this->channelData["{$appId}:{$channel}"] ?? []; $members = array_map(function ($user) { return json_decode($user); @@ -130,8 +130,8 @@ class LocalClient implements ReplicationInterface // Count the number of users per channel foreach ($channelNames as $channel) { - $results[$channel] = isset($this->channelData["$appId:$channel"]) - ? count($this->channelData["$appId:$channel"]) + $results[$channel] = isset($this->channelData["{$appId}:{$channel}"]) + ? count($this->channelData["{$appId}:{$channel}"]) : 0; } diff --git a/src/PubSub/Drivers/RedisClient.php b/src/PubSub/Drivers/RedisClient.php index ef48149..11a479e 100644 --- a/src/PubSub/Drivers/RedisClient.php +++ b/src/PubSub/Drivers/RedisClient.php @@ -12,7 +12,7 @@ use React\EventLoop\LoopInterface; use React\Promise\PromiseInterface; use stdClass; -class RedisClient implements ReplicationInterface +class RedisClient extends LocalClient { /** * The running loop. @@ -89,6 +89,175 @@ class RedisClient implements ReplicationInterface return $this; } + /** + * Publish a message to a channel on behalf of a websocket user. + * + * @param string $appId + * @param string $channel + * @param stdClass $payload + * @return bool + */ + public function publish(string $appId, string $channel, stdClass $payload): bool + { + $payload->appId = $appId; + $payload->serverId = $this->getServerId(); + + $payload = json_encode($payload); + + $this->publishClient->__call('publish', ["{$appId}:{$channel}", $payload]); + + DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_MESSAGE_PUBLISHED, [ + 'channel' => $channel, + 'serverId' => $this->getServerId(), + 'payload' => $payload, + 'pubsub' => "{$appId}:{$channel}", + ]); + + return true; + } + + /** + * Subscribe to a channel on behalf of websocket user. + * + * @param string $appId + * @param string $channel + * @return bool + */ + public function subscribe(string $appId, string $channel): bool + { + if (! isset($this->subscribedChannels["{$appId}:{$channel}"])) { + // We're not subscribed to the channel yet, subscribe and set the count to 1 + $this->subscribeClient->__call('subscribe', ["{$appId}:{$channel}"]); + $this->subscribedChannels["{$appId}:{$channel}"] = 1; + } else { + // Increment the subscribe count if we've already subscribed + $this->subscribedChannels["{$appId}:{$channel}"]++; + } + + DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_SUBSCRIBED, [ + 'channel' => $channel, + 'serverId' => $this->getServerId(), + 'pubsub' => "{$appId}:{$channel}", + ]); + + return true; + } + + /** + * Unsubscribe from a channel on behalf of a websocket user. + * + * @param string $appId + * @param string $channel + * @return bool + */ + public function unsubscribe(string $appId, string $channel): bool + { + if (! isset($this->subscribedChannels["{$appId}:{$channel}"])) { + return false; + } + + // Decrement the subscription count for this channel + $this->subscribedChannels["{$appId}:{$channel}"]--; + + // If we no longer have subscriptions to that channel, unsubscribe + if ($this->subscribedChannels["{$appId}:{$channel}"] < 1) { + $this->subscribeClient->__call('unsubscribe', ["{$appId}:{$channel}"]); + + unset($this->subscribedChannels["{$appId}:{$channel}"]); + } + + DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_UNSUBSCRIBED, [ + 'channel' => $channel, + 'serverId' => $this->getServerId(), + 'pubsub' => "{$appId}:{$channel}", + ]); + + return true; + } + + /** + * Add a member to a channel. To be called when they have + * subscribed to the channel. + * + * @param string $appId + * @param string $channel + * @param string $socketId + * @param string $data + * @return void + */ + public function joinChannel(string $appId, string $channel, string $socketId, string $data) + { + $this->publishClient->__call('hset', ["{$appId}:{$channel}", $socketId, $data]); + + DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_JOINED_CHANNEL, [ + 'channel' => $channel, + 'serverId' => $this->getServerId(), + 'socketId' => $socketId, + 'data' => $data, + 'pubsub' => "{$appId}:{$channel}", + ]); + } + + /** + * Remove a member from the channel. To be called when they have + * unsubscribed from the channel. + * + * @param string $appId + * @param string $channel + * @param string $socketId + * @return void + */ + public function leaveChannel(string $appId, string $channel, string $socketId) + { + $this->publishClient->__call('hdel', ["{$appId}:{$channel}", $socketId]); + + DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_LEFT_CHANNEL, [ + 'channel' => $channel, + 'serverId' => $this->getServerId(), + 'socketId' => $socketId, + 'pubsub' => "{$appId}:{$channel}", + ]); + } + + /** + * Retrieve the full information about the members in a presence channel. + * + * @param string $appId + * @param string $channel + * @return PromiseInterface + */ + public function channelMembers(string $appId, string $channel): PromiseInterface + { + return $this->publishClient->__call('hgetall', ["{$appId}:{$channel}"]) + ->then(function ($members) { + // The data is expected as objects, so we need to JSON decode + return array_map(function ($user) { + return json_decode($user); + }, $members); + }); + } + + /** + * Get the amount of users subscribed for each presence channel. + * + * @param string $appId + * @param array $channelNames + * @return PromiseInterface + */ + public function channelMemberCounts(string $appId, array $channelNames): PromiseInterface + { + $this->publishClient->__call('multi', []); + + foreach ($channelNames as $channel) { + $this->publishClient->__call('hlen', ["{$appId}:{$channel}"]); + } + + return $this->publishClient->__call('exec', []) + ->then(function ($data) use ($channelNames) { + return array_combine($channelNames, $data); + }); + } + /** * Handle a message received from Redis on a specific channel. * @@ -101,7 +270,7 @@ class RedisClient implements ReplicationInterface $payload = json_decode($payload); // Ignore messages sent by ourselves. - if (isset($payload->serverId) && $this->serverId === $payload->serverId) { + if (isset($payload->serverId) && $this->getServerId() === $payload->serverId) { return; } @@ -125,6 +294,7 @@ class RedisClient implements ReplicationInterface } $socket = $payload->socket ?? null; + $serverId = $payload->serverId ?? null; // Remove fields intended for internal use from the payload. unset($payload->socket); @@ -133,143 +303,15 @@ class RedisClient implements ReplicationInterface // Push the message out to connected websocket clients. $channel->broadcastToEveryoneExcept($payload, $socket, $appId, false); - } - /** - * Subscribe to a channel on behalf of websocket user. - * - * @param string $appId - * @param string $channel - * @return bool - */ - public function subscribe(string $appId, string $channel): bool - { - if (! isset($this->subscribedChannels["$appId:$channel"])) { - // We're not subscribed to the channel yet, subscribe and set the count to 1 - $this->subscribeClient->__call('subscribe', ["$appId:$channel"]); - $this->subscribedChannels["$appId:$channel"] = 1; - } else { - // Increment the subscribe count if we've already subscribed - $this->subscribedChannels["$appId:$channel"]++; - } - - DashboardLogger::replicatorSubscribed($appId, $channel, $this->serverId); - - return true; - } - - /** - * Unsubscribe from a channel on behalf of a websocket user. - * - * @param string $appId - * @param string $channel - * @return bool - */ - public function unsubscribe(string $appId, string $channel): bool - { - if (! isset($this->subscribedChannels["$appId:$channel"])) { - return false; - } - - // Decrement the subscription count for this channel - $this->subscribedChannels["$appId:$channel"]--; - - // If we no longer have subscriptions to that channel, unsubscribe - if ($this->subscribedChannels["$appId:$channel"] < 1) { - $this->subscribeClient->__call('unsubscribe', ["$appId:$channel"]); - - unset($this->subscribedChannels["$appId:$channel"]); - } - - DashboardLogger::replicatorUnsubscribed($appId, $channel, $this->serverId); - - return true; - } - - /** - * Publish a message to a channel on behalf of a websocket user. - * - * @param string $appId - * @param string $channel - * @param stdClass $payload - * @return bool - */ - public function publish(string $appId, string $channel, stdClass $payload): bool - { - $payload->appId = $appId; - $payload->serverId = $this->serverId; - - $this->publishClient->__call('publish', ["$appId:$channel", json_encode($payload)]); - - return true; - } - - /** - * Add a member to a channel. To be called when they have - * subscribed to the channel. - * - * @param string $appId - * @param string $channel - * @param string $socketId - * @param string $data - * @return void - */ - public function joinChannel(string $appId, string $channel, string $socketId, string $data) - { - $this->publishClient->__call('hset', ["$appId:$channel", $socketId, $data]); - } - - /** - * Remove a member from the channel. To be called when they have - * unsubscribed from the channel. - * - * @param string $appId - * @param string $channel - * @param string $socketId - * @return void - */ - public function leaveChannel(string $appId, string $channel, string $socketId) - { - $this->publishClient->__call('hdel', ["$appId:$channel", $socketId]); - } - - /** - * Retrieve the full information about the members in a presence channel. - * - * @param string $appId - * @param string $channel - * @return PromiseInterface - */ - public function channelMembers(string $appId, string $channel): PromiseInterface - { - return $this->publishClient->__call('hgetall', ["$appId:$channel"]) - ->then(function ($members) { - // The data is expected as objects, so we need to JSON decode - return array_map(function ($user) { - return json_decode($user); - }, $members); - }); - } - - /** - * Get the amount of users subscribed for each presence channel. - * - * @param string $appId - * @param array $channelNames - * @return PromiseInterface - */ - public function channelMemberCounts(string $appId, array $channelNames): PromiseInterface - { - $this->publishClient->__call('multi', []); - - foreach ($channelNames as $channel) { - $this->publishClient->__call('hlen', ["$appId:$channel"]); - } - - return $this->publishClient->__call('exec', []) - ->then(function ($data) use ($channelNames) { - return array_combine($channelNames, $data); - }); + DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_MESSAGE_RECEIVED, [ + 'channel' => $channel->getChannelName(), + 'redisChannel' => $redisChannel, + 'serverId' => $this->getServer(), + 'incomingServerId' => $serverId, + 'incomingSocketId' => $socket, + 'payload' => $payload, + ]); } /** diff --git a/src/Statistics/Logger/NullStatisticsLogger.php b/src/Statistics/Logger/NullStatisticsLogger.php new file mode 100644 index 0000000..885703e --- /dev/null +++ b/src/Statistics/Logger/NullStatisticsLogger.php @@ -0,0 +1,47 @@ +channelManager = $channelManager; + $this->browser = $browser; + } + + public function webSocketMessage(ConnectionInterface $connection) + { + // + } + + public function apiMessage($appId) + { + // + } + + public function connection(ConnectionInterface $connection) + { + // + } + + public function disconnection(ConnectionInterface $connection) + { + // + } + + public function save() + { + // + } +} diff --git a/src/WebSockets/Channels/Channel.php b/src/WebSockets/Channels/Channel.php index 8e301c1..cd7e473 100644 --- a/src/WebSockets/Channels/Channel.php +++ b/src/WebSockets/Channels/Channel.php @@ -82,7 +82,10 @@ class Channel $this->replicator->unsubscribe($connection->app->id, $this->channelName); if (! $this->hasConnections()) { - DashboardLogger::vacated($connection, $this->channelName); + DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_VACATED, [ + 'socketId' => $connection->socketId, + 'channel' => $this->channelName, + ]); } } @@ -93,10 +96,15 @@ class Channel $this->subscribedConnections[$connection->socketId] = $connection; if (! $hadConnectionsPreviously) { - DashboardLogger::occupied($connection, $this->channelName); + DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_OCCUPIED, [ + 'channel' => $this->channelName, + ]); } - DashboardLogger::subscribed($connection, $this->channelName); + DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_SUBSCRIBED, [ + 'socketId' => $connection->socketId, + 'channel' => $this->channelName, + ]); } public function broadcast($payload) diff --git a/src/WebSockets/Messages/PusherClientMessage.php b/src/WebSockets/Messages/PusherClientMessage.php index f7c4c45..1ef519c 100644 --- a/src/WebSockets/Messages/PusherClientMessage.php +++ b/src/WebSockets/Messages/PusherClientMessage.php @@ -38,7 +38,12 @@ class PusherClientMessage implements PusherMessage return; } - DashboardLogger::clientMessage($this->connection, $this->payload); + DashboardLogger::log($this->connection->app->id, DashboardLogger::TYPE_WS_MESSAGE, [ + 'socketId' => $this->connection->socketId, + 'channel' => $this->payload->channel, + 'event' => $this->payload->event, + 'data' => $this->payload, + ]); $channel = $this->channelManager->find($this->connection->app->id, $this->payload->channel); diff --git a/src/WebSockets/WebSocketHandler.php b/src/WebSockets/WebSocketHandler.php index d2fdb6c..7820960 100644 --- a/src/WebSockets/WebSocketHandler.php +++ b/src/WebSockets/WebSocketHandler.php @@ -48,7 +48,9 @@ class WebSocketHandler implements MessageComponentInterface { $this->channelManager->removeFromAllChannels($connection); - DashboardLogger::disconnection($connection); + DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_DISCONNECTED, [ + 'socketId' => $connection->socketId, + ]); StatisticsLogger::disconnection($connection); } @@ -106,7 +108,13 @@ class WebSocketHandler implements MessageComponentInterface ]), ])); - DashboardLogger::connection($connection); + /** @var \GuzzleHttp\Psr7\Request $request */ + $request = $connection->httpRequest; + + DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_CONNECTED, [ + 'origin' => "{$request->getUri()->getScheme()}://{$request->getUri()->getHost()}", + 'socketId' => $connection->socketId, + ]); StatisticsLogger::connection($connection);