Add option to emit metrics and logs when players connect to the router (#391)

This commit is contained in:
Samuel McBroom
2025-04-22 05:37:25 -07:00
committed by GitHub
parent cc590524c4
commit d21ccb5b9f
6 changed files with 59 additions and 3 deletions
+2
View File
@@ -78,6 +78,8 @@ Routes Minecraft client connections to backend servers based upon the requested
Indicates if the webhook will only be called if a user is connecting rather than just server list/ping (env WEBHOOK_REQUIRE_USER)
-webhook-url string
If set, a POST request that contains connection status notifications will be sent to this HTTP address (env WEBHOOK_URL)
-record-logins
Log and generate metrics on player logins. Metrics only supported with influxdb or prometheus backend
```
## Docker Multi-Architecture Image
+2 -1
View File
@@ -54,6 +54,7 @@ type Config struct {
UseProxyProtocol bool `default:"false" usage:"Send PROXY protocol to backend servers"`
ReceiveProxyProtocol bool `default:"false" usage:"Receive PROXY protocol from backend servers, by default trusts every proxy header that it receives, combine with -trusted-proxies to specify a list of trusted proxies"`
TrustedProxies []string `usage:"Comma delimited list of CIDR notation IP blocks to trust when receiving PROXY protocol"`
RecordLogins bool `default:"false" usage:"Log and generate metrics on player logins. Metrics only supported with influxdb or prometheus backend"`
MetricsBackendConfig MetricsBackendConfig
RoutesConfig string `usage:"Name or full path to routes config file"`
NgrokToken string `usage:"If set, an ngrok tunnel will be established. It is HIGHLY recommended to pass as an environment variable."`
@@ -142,7 +143,7 @@ func main() {
trustedIpNets = append(trustedIpNets, ipNet)
}
connector := server.NewConnector(metricsBuilder.BuildConnectorMetrics(), config.UseProxyProtocol, config.ReceiveProxyProtocol, trustedIpNets)
connector := server.NewConnector(metricsBuilder.BuildConnectorMetrics(), config.UseProxyProtocol, config.ReceiveProxyProtocol, trustedIpNets, config.RecordLogins)
clientFilter, err := server.NewClientFilter(config.ClientsToAllow, config.ClientsToDeny)
if err != nil {
+16
View File
@@ -65,6 +65,8 @@ func (b expvarMetricsBuilder) BuildConnectorMetrics() *server.ConnectorMetrics {
ConnectionsFrontend: c,
ConnectionsBackend: c,
ActiveConnections: expvarMetrics.NewGauge("active_connections"),
ServerActivePlayer: expvarMetrics.NewGauge("server_active_player"),
ServerLogins: expvarMetrics.NewCounter("server_logins"),
}
}
@@ -83,6 +85,8 @@ func (b discardMetricsBuilder) BuildConnectorMetrics() *server.ConnectorMetrics
ConnectionsFrontend: discardMetrics.NewCounter(),
ConnectionsBackend: discardMetrics.NewCounter(),
ActiveConnections: discardMetrics.NewGauge(),
ServerActivePlayer: discardMetrics.NewGauge(),
ServerLogins: discardMetrics.NewCounter(),
}
}
@@ -132,6 +136,8 @@ func (b *influxMetricsBuilder) BuildConnectorMetrics() *server.ConnectorMetrics
ConnectionsFrontend: c.With("side", "frontend"),
ConnectionsBackend: c.With("side", "backend"),
ActiveConnections: metrics.NewGauge("mc_router_connections_active"),
ServerActivePlayer: metrics.NewGauge("mc_router_server_player_active"),
ServerLogins: metrics.NewCounter("mc_router_server_logins"),
}
}
@@ -178,5 +184,15 @@ func (b prometheusMetricsBuilder) BuildConnectorMetrics() *server.ConnectorMetri
Name: "active_connections",
Help: "The number of active connections",
}, nil)),
ServerActivePlayer: prometheusMetrics.NewGauge(promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "mc_router",
Name: "server_active_player",
Help: "Player is active on server",
}, []string{"player_name", "player_uuid", "server_address"})),
ServerLogins: prometheusMetrics.NewCounter(promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: "mc_router",
Name: "server_logins",
Help: "The total number of player logins",
}, []string{"player_name", "player_uuid", "server_address"})),
}
}
+2 -1
View File
@@ -45,7 +45,8 @@ func ReadPacket(reader *bufio.Reader, addr net.Addr, state State) (*Packet, erro
return nil, err
}
packet := &Packet{Length: frame.Length}
// Packet length is frame length (bytes for packetID and data) plus bytes used to store the frame length data
packet := &Packet{Length: frame.Length + PacketLengthFieldBytes}
remainder := bytes.NewBuffer(frame.Payload)
+4
View File
@@ -80,3 +80,7 @@ type LegacyServerListPing struct {
type ByteReader interface {
ReadByte() (byte, error)
}
const (
PacketLengthFieldBytes = 1
)
+33 -1
View File
@@ -34,6 +34,8 @@ type ConnectorMetrics struct {
ConnectionsFrontend metrics.Counter
ConnectionsBackend metrics.Counter
ActiveConnections metrics.Gauge
ServerActivePlayer metrics.Gauge
ServerLogins metrics.Counter
}
type ClientInfo struct {
@@ -57,13 +59,14 @@ type PlayerInfo struct {
Uuid uuid.UUID `json:"uuid"`
}
func NewConnector(metrics *ConnectorMetrics, sendProxyProto bool, receiveProxyProto bool, trustedProxyNets []*net.IPNet) *Connector {
func NewConnector(metrics *ConnectorMetrics, sendProxyProto bool, receiveProxyProto bool, trustedProxyNets []*net.IPNet, recordLogins bool) *Connector {
return &Connector{
metrics: metrics,
sendProxyProto: sendProxyProto,
connectionsCond: sync.NewCond(&sync.Mutex{}),
receiveProxyProto: receiveProxyProto,
trustedProxyNets: trustedProxyNets,
recordLogins: recordLogins,
}
}
@@ -72,6 +75,7 @@ type Connector struct {
metrics *ConnectorMetrics
sendProxyProto bool
receiveProxyProto bool
recordLogins bool
trustedProxyNets []*net.IPNet
activeConnections int32
@@ -383,9 +387,37 @@ func (c *Connector) findAndConnectBackend(ctx context.Context, frontendConn net.
c.metrics.ActiveConnections.Set(float64(
atomic.AddInt32(&c.activeConnections, 1)))
if c.recordLogins && userInfo != nil {
logrus.
WithField("client", clientAddr).
WithField("playerName", userInfo.Name).
WithField("playerUUID", userInfo.Uuid).
WithField("serverAddress", serverAddress).
Info("Player attempted to login to server")
c.metrics.ServerActivePlayer.
With("player_name", userInfo.Name).
With("player_uuid", userInfo.Uuid.String()).
With("server_address", serverAddress).
Set(1)
c.metrics.ServerLogins.
With("player_name", userInfo.Name).
With("player_uuid", userInfo.Uuid.String()).
With("server_address", serverAddress).
Add(1)
}
defer func() {
c.metrics.ActiveConnections.Set(float64(
atomic.AddInt32(&c.activeConnections, -1)))
if c.recordLogins && userInfo != nil {
c.metrics.ServerActivePlayer.
With("player_name", userInfo.Name).
With("player_uuid", userInfo.Uuid.String()).
With("server_address", serverAddress).
Set(0)
}
c.connectionsCond.Signal()
}()