Refactored the dashboard logger

This commit is contained in:
Alex Renoki 2020-08-17 21:06:51 +03:00
parent 38b2e4d404
commit 5890659102
6 changed files with 155 additions and 164 deletions

View File

@ -10,9 +10,9 @@ class DashboardLogger
{ {
const LOG_CHANNEL_PREFIX = 'private-websockets-dashboard-'; 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'; const TYPE_VACATED = 'vacated';
@ -20,7 +20,7 @@ class DashboardLogger
const TYPE_SUBSCRIBED = 'subscribed'; const TYPE_SUBSCRIBED = 'subscribed';
const TYPE_CLIENT_MESSAGE = 'client-message'; const TYPE_WS_MESSAGE = 'ws-message';
const TYPE_API_MESSAGE = 'api-message'; const TYPE_API_MESSAGE = 'api-message';
@ -28,101 +28,36 @@ class DashboardLogger
const TYPE_REPLICATOR_UNSUBSCRIBED = 'replicator-unsubscribed'; const TYPE_REPLICATOR_UNSUBSCRIBED = 'replicator-unsubscribed';
public static function connection(ConnectionInterface $connection) const TYPE_REPLICATOR_JOINED_CHANNEL = 'replicator-joined';
{
/** @var \GuzzleHttp\Psr7\Request $request */
$request = $connection->httpRequest;
static::log($connection->app->id, static::TYPE_CONNECTION, [ const TYPE_REPLICATOR_LEFT_CHANNEL = 'replicator-left';
'details' => [
'origin' => "{$request->getUri()->getScheme()}://{$request->getUri()->getHost()}",
'socketId' => $connection->socketId,
],
]);
}
public static function occupied(ConnectionInterface $connection, string $channelName) const TYPE_REPLICATOR_MESSAGE_PUBLISHED = 'replicator-message-published';
{
static::log($connection->app->id, static::TYPE_OCCUPIED, [
'details' => [
'channel' => $channelName,
],
]);
}
public static function subscribed(ConnectionInterface $connection, string $channelName) const TYPE_REPLICATOR_MESSAGE_RECEIVED = 'replicator-message-received';
{
static::log($connection->app->id, static::TYPE_SUBSCRIBED, [
'details' => [
'socketId' => $connection->socketId,
'channel' => $channelName,
],
]);
}
public static function clientMessage(ConnectionInterface $connection, stdClass $payload) /**
{ * The list of all channels.
static::log($connection->app->id, static::TYPE_CLIENT_MESSAGE, [ *
'details' => [ * @var array
'socketId' => $connection->socketId, */
'channel' => $payload->channel, public static $channels = [
'event' => $payload->event, self::TYPE_DISCONNECTED,
'data' => $payload, 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) public static function log($appId, string $type, array $details = [])
{
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 = [])
{ {
$channelName = static::LOG_CHANNEL_PREFIX.$type; $channelName = static::LOG_CHANNEL_PREFIX.$type;
@ -134,7 +69,8 @@ class DashboardLogger
'data' => [ 'data' => [
'type' => $type, 'type' => $type,
'time' => strftime('%H:%M:%S'), 'time' => strftime('%H:%M:%S'),
] + $attributes, 'details' => $details,
],
]); ]);
} }
} }

View File

@ -21,12 +21,11 @@ class TriggerEventController extends Controller
'data' => $request->json()->get('data'), 'data' => $request->json()->get('data'),
], $request->json()->get('socket_id'), $request->appId); ], $request->json()->get('socket_id'), $request->appId);
DashboardLogger::apiMessage( DashboardLogger::log($request->appId, DashboardLogger::TYPE_API_MESSAGE, [
$request->appId, 'channel' => $channelName,
$channelName, 'event' => $request->json()->get('name'),
$request->json()->get('name'), 'payload' => $request->json()->get('data'),
$request->json()->get('data') ]);
);
StatisticsLogger::apiMessage($request->appId); StatisticsLogger::apiMessage($request->appId);
} }

View File

@ -12,7 +12,7 @@ use React\EventLoop\LoopInterface;
use React\Promise\PromiseInterface; use React\Promise\PromiseInterface;
use stdClass; use stdClass;
class RedisClient implements ReplicationInterface class RedisClient extends LocalClient
{ {
/** /**
* The running loop. * The running loop.
@ -90,49 +90,29 @@ class RedisClient implements ReplicationInterface
} }
/** /**
* Handle a message received from Redis on a specific channel. * Publish a message to a channel on behalf of a websocket user.
* *
* @param string $redisChannel * @param string $appId
* @param string $payload * @param string $channel
* @return void * @param stdClass $payload
* @return bool
*/ */
protected function onMessage(string $redisChannel, string $payload) public function publish(string $appId, string $channel, stdClass $payload): bool
{ {
$payload = json_decode($payload); $payload->appId = $appId;
$payload->serverId = $this->getServerId();
// Ignore messages sent by ourselves. $payload = json_encode($payload);
if (isset($payload->serverId) && $this->serverId === $payload->serverId) {
return;
}
// Pull out the app ID. See RedisPusherBroadcaster $this->publishClient->__call('publish', ["$appId:$channel", $payload]);
$appId = $payload->appId;
// We need to put the channel name in the payload. DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_MESSAGE_PUBLISHED, [
// We strip the app ID from the channel name, websocket clients 'channel' => $channel,
// expect the channel name to not include the app ID. 'serverId' => $this->getServerId(),
$payload->channel = Str::after($redisChannel, "{$appId}:"); 'payload' => $payload,
]);
$channelManager = app(ChannelManager::class); return true;
// Load the Channel instance to sync.
$channel = $channelManager->find($appId, $payload->channel);
// If no channel is found, none of our connections want to
// receive this message, so we ignore it.
if (! $channel) {
return;
}
$socket = $payload->socket ?? null;
// Remove fields intended for internal use from the payload.
unset($payload->socket);
unset($payload->serverId);
unset($payload->appId);
// Push the message out to connected websocket clients.
$channel->broadcastToEveryoneExcept($payload, $socket, $appId, false);
} }
/** /**
@ -153,7 +133,10 @@ class RedisClient implements ReplicationInterface
$this->subscribedChannels["$appId:$channel"]++; $this->subscribedChannels["$appId:$channel"]++;
} }
DashboardLogger::replicatorSubscribed($appId, $channel, $this->serverId); DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_SUBSCRIBED, [
'channel' => $channel,
'serverId' => $this->getServerId(),
]);
return true; return true;
} }
@ -181,25 +164,10 @@ class RedisClient implements ReplicationInterface
unset($this->subscribedChannels["$appId:$channel"]); unset($this->subscribedChannels["$appId:$channel"]);
} }
DashboardLogger::replicatorUnsubscribed($appId, $channel, $this->serverId); DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_UNSUBSCRIBED, [
'channel' => $channel,
return true; 'serverId' => $this->getServerId(),
} ]);
/**
* 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; return true;
} }
@ -217,6 +185,13 @@ class RedisClient implements ReplicationInterface
public function joinChannel(string $appId, string $channel, string $socketId, string $data) public function joinChannel(string $appId, string $channel, string $socketId, string $data)
{ {
$this->publishClient->__call('hset', ["$appId:$channel", $socketId, $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,
]);
} }
/** /**
@ -231,6 +206,12 @@ class RedisClient implements ReplicationInterface
public function leaveChannel(string $appId, string $channel, string $socketId) public function leaveChannel(string $appId, string $channel, string $socketId)
{ {
$this->publishClient->__call('hdel', ["$appId:$channel", $socketId]); $this->publishClient->__call('hdel', ["$appId:$channel", $socketId]);
DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_LEFT_CHANNEL, [
'channel' => $channel,
'serverId' => $this->getServerId(),
'socketId' => $socketId,
]);
} }
/** /**
@ -272,6 +253,62 @@ class RedisClient implements ReplicationInterface
}); });
} }
/**
* Handle a message received from Redis on a specific channel.
*
* @param string $redisChannel
* @param string $payload
* @return void
*/
protected function onMessage(string $redisChannel, string $payload)
{
$payload = json_decode($payload);
// Ignore messages sent by ourselves.
if (isset($payload->serverId) && $this->getServerId() === $payload->serverId) {
return;
}
// Pull out the app ID. See RedisPusherBroadcaster
$appId = $payload->appId;
// We need to put the channel name in the payload.
// We strip the app ID from the channel name, websocket clients
// expect the channel name to not include the app ID.
$payload->channel = Str::after($redisChannel, "{$appId}:");
$channelManager = app(ChannelManager::class);
// Load the Channel instance to sync.
$channel = $channelManager->find($appId, $payload->channel);
// If no channel is found, none of our connections want to
// receive this message, so we ignore it.
if (! $channel) {
return;
}
$socket = $payload->socket ?? null;
$serverId = $payload->serverId ?? null;
// Remove fields intended for internal use from the payload.
unset($payload->socket);
unset($payload->serverId);
unset($payload->appId);
// Push the message out to connected websocket clients.
$channel->broadcastToEveryoneExcept($payload, $socket, $appId, false);
DashboardLogger::log($appId, DashboardLogger::TYPE_REPLICATOR_MESSAGE_RECEIVED, [
'channel' => $channel->getChannelName(),
'redisChannel' => $redisChannel,
'serverId' => $this->getServer(),
'incomingServerId' => $serverId,
'incomingSocketId' => $socket,
'payload' => $payload,
]);
}
/** /**
* Build the Redis connection URL from Laravel database config. * Build the Redis connection URL from Laravel database config.
* *

View File

@ -82,7 +82,10 @@ class Channel
$this->replicator->unsubscribe($connection->app->id, $this->channelName); $this->replicator->unsubscribe($connection->app->id, $this->channelName);
if (! $this->hasConnections()) { 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; $this->subscribedConnections[$connection->socketId] = $connection;
if (! $hadConnectionsPreviously) { 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) public function broadcast($payload)

View File

@ -38,7 +38,12 @@ class PusherClientMessage implements PusherMessage
return; 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); $channel = $this->channelManager->find($this->connection->app->id, $this->payload->channel);

View File

@ -48,7 +48,9 @@ class WebSocketHandler implements MessageComponentInterface
{ {
$this->channelManager->removeFromAllChannels($connection); $this->channelManager->removeFromAllChannels($connection);
DashboardLogger::disconnection($connection); DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_DISCONNECTED, [
'socketId' => $connection->socketId,
]);
StatisticsLogger::disconnection($connection); StatisticsLogger::disconnection($connection);
} }
@ -106,9 +108,13 @@ class WebSocketHandler implements MessageComponentInterface
]), ]),
])); ]));
DashboardLogger::connection($connection); /** @var \GuzzleHttp\Psr7\Request $request */
$request = $connection->httpRequest;
StatisticsLogger::connection($connection); DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_CONNECTED, [
'origin' => "{$request->getUri()->getScheme()}://{$request->getUri()->getHost()}",
'socketId' => $connection->socketId,
]);
return $this; return $this;
} }