Hysteria Core Logging Options: EventLogger and TrafficLogger Explained
Hysteria core provides two independent logging hooks—EventLogger** for connection events and TrafficLogger for per-stream traffic statistics—that you implement via Go interfaces in core/server/config.go to monitor and control server activity.**
The apernet/hysteria repository offers a high-performance proxy core that supports custom instrumentation through optional logging interfaces. When integrating the Hysteria core library into your own server implementation, you can tap into two distinct logging options to capture authentication events, proxy requests, and real-time traffic metrics without affecting the data flow.
Core Logging Interfaces in Hysteria
Hysteria core defines two complementary interfaces in core/server/config.go that serve different observability needs. Both are optional; the server validates their presence with if config.EventLogger != nil and if config.TrafficLogger != nil checks before invoking any methods, as seen in core/server/server.go (lines 113-120 and 242-254).
EventLogger: Connection-Level Events
The EventLogger interface (lines 218-226 in core/server/config.go) records high-level connection lifecycle events and protocol-specific requests. It is invoked once per event and does not participate in data forwarding decisions. This interface captures:
- Client authentication and disconnection
- TCP proxy requests and their failures
- UDP session creation and errors
The server also contains a thin UDP event wrapper in core/server/server.go (lines 401-415) that forwards UDP-specific calls to your EventLogger implementation, mapping udpEventLoggerImpl.New to EventLogger.UDPRequest and Close to EventLogger.UDPError.
TrafficLogger: Per-Stream Statistics and Control
The TrafficLogger interface (lines 242-251 in core/server/config.go) tracks granular traffic data and can enforce disconnection policies. Unlike EventLogger, this interface can reject clients by returning false from its primary method. Key capabilities include:
- Byte counters for every read and write operation
- Online/offline state change notifications
- Stream lifecycle tracing for latency measurement
Interface Methods Reference
EventLogger Method Signatures
Implement these methods to capture connection events:
Connect(addr, id, tx): Called after successful client authentication. Receives the remote address, client identifier, and initial transmitted bytes.Disconnect(addr, id, err): Called when the connection terminates, either normally or with an error.TCPRequest(addr, id, reqAddr): Called when a client initiates a TCP proxy request to a target address.TCPError(addr, id, reqAddr, err): Called if TCP request handling fails.UDPRequest(addr, id, sessionID, reqAddr): Called when a new UDP session is created.UDPError(addr, id, sessionID, err): Called when a UDP session ends with an error.
TrafficLogger Method Signatures
Implement these methods to monitor and control traffic:
LogTraffic(id, tx, rx) bool: Called after each read or write on a stream or UDP packet. Thetxandrxparameters represent bytes sent to and received from the remote target. Returnfalseto force the server to disconnect the client immediately.LogOnlineState(id, online): Called when a client's presence state changes, useful for building real-time dashboards.TraceStream(stream, stats): Called at the start of a TCP stream to attach custom tracing instrumentation.UntraceStream(stream): Called when a stream ends to clean up tracing resources.
Implementation Requirements
Thread safety is mandatory. Both loggers are invoked from multiple goroutines handling concurrent connections, so your implementations must synchronize access to shared state using mutexes or other concurrency primitives.
Optional attachment. You may provide one, both, or neither logger. The core server skips logging calls when the respective interface field is nil.
Code Examples
Minimal EventLogger Implementation
package main
import (
"fmt"
"net"
"hysteria/core/server"
)
type simpleEventLogger struct{}
func (l *simpleEventLogger) Connect(addr net.Addr, id string, tx uint64) {
fmt.Printf("[CONNECT] %s (id=%s, tx=%d)\n", addr, id, tx)
}
func (l *simpleEventLogger) Disconnect(addr net.Addr, id string, err error) {
fmt.Printf("[DISCONNECT] %s (id=%s, err=%v)\n", addr, id, err)
}
func (l *simpleEventLogger) TCPRequest(addr net.Addr, id, reqAddr string) {
fmt.Printf("[TCP REQ] %s → %s (id=%s)\n", addr, reqAddr, id)
}
func (l *simpleEventLogger) TCPError(addr net.Addr, id, reqAddr string, err error) {
fmt.Printf("[TCP ERR] %s → %s (id=%s, err=%v)\n", addr, reqAddr, id, err)
}
func (l *simpleEventLogger) UDPRequest(addr net.Addr, id string, sessionID uint32, reqAddr string) {
fmt.Printf("[UDP REQ] %s (session=%d) → %s (id=%s)\n", addr, sessionID, reqAddr, id)
}
func (l *simpleEventLogger) UDPError(addr net.Addr, id string, sessionID uint32, err error) {
fmt.Printf("[UDP ERR] %s (session=%d) (id=%s, err=%v)\n", addr, sessionID, id, err)
}
Minimal TrafficLogger Implementation
import (
"fmt"
"hysteria/core/internal/protocol"
"hysteria/core/server"
)
type simpleTrafficLogger struct{}
func (l *simpleTrafficLogger) LogTraffic(id string, tx, rx uint64) bool {
fmt.Printf("[TRAFFIC] %s tx=%d rx=%d\n", id, tx, rx)
return true // Allow connection to continue
}
func (l *simpleTrafficLogger) LogOnlineState(id string, online bool) {
state := "offline"
if online {
state = "online"
}
fmt.Printf("[STATE] %s is %s\n", id, state)
}
func (l *simpleTrafficLogger) TraceStream(stream protocol.HyStream, stats *protocol.StreamStats) {
fmt.Printf("[STREAM START] ID=%d\n", stream.StreamID())
}
func (l *simpleTrafficLogger) UntraceStream(stream protocol.HyStream) {
fmt.Printf("[STREAM END] ID=%d\n", stream.StreamID())
}
Wiring Loggers into Server Configuration
cfg := &server.Config{
// ... other required fields: TLSConfig, QUICConfig, Conn, Authenticator
EventLogger: &simpleEventLogger{},
TrafficLogger: &simpleTrafficLogger{},
}
srv, err := server.New(cfg)
if err != nil {
// handle error
}
srv.Serve()
Enforcing Bandwidth Limits with LogTraffic
Use the return value of LogTraffic to implement hard limits:
const maxTx = 10 << 20 // 10 MiB per connection
func (l *simpleTrafficLogger) LogTraffic(id string, tx, rx uint64) bool {
if tx > maxTx {
fmt.Printf("[LIMIT] %s exceeded tx limit (%d > %d)\n", id, tx, maxTx)
return false // Disconnect the client
}
return true
}
Key Source Files and Architecture
Understanding the logging architecture requires familiarity with these specific files in the apernet/hysteria repository:
core/server/config.go– Contains theConfigstruct and theEventLoggerandTrafficLoggerinterface definitions.core/server/server.go– Implements the server logic that checks for logger presence and invokes methods (lines 113-120, 242-254). Also contains the UDP event logger wrapper (lines 401-415).core/server/copy.go– Houses thecopyTwoWayExhelper that forwards traffic statistics to theTrafficLogger.core/server/udp.go– Manages UDP sessions and utilizes theudpEventLoggerImplto map UDP events toEventLoggercalls.core/internal/integration_tests/trafficlogger_test.go– Contains unit tests demonstrating expectedTrafficLoggercall patterns and the disconnect behavior whenLogTrafficreturnsfalse.
Summary
- EventLogger captures high-level connection events (authentication, TCP/UDP requests, errors) without affecting traffic flow.
- TrafficLogger provides per-stream byte counters and online state tracking, with the ability to forcibly disconnect clients by returning
falsefromLogTraffic. - Both interfaces are defined in
core/server/config.goand are optional; the server checks fornilbefore invoking them. - Implementations must be thread-safe due to concurrent goroutine access.
- The UDP event wrapper in
core/server/server.gobridges UDP session management to theEventLoggerinterface.
Frequently Asked Questions
What is the difference between EventLogger and TrafficLogger in Hysteria?
EventLogger records discrete connection events like authentication and proxy requests, while TrafficLogger tracks continuous byte-level statistics and can control whether a connection remains active. EventLogger is purely observational, whereas TrafficLogger can deny service by returning false from LogTraffic.
How do I make my Hysteria logger thread-safe?
Since Hysteria invokes logger methods from multiple concurrent goroutines, protect shared state with a sync.Mutex or use lock-free patterns like atomic counters for statistics. Each method call happens independently, so your implementation must handle concurrent access to any internal maps or counters.
Can I reject client connections using the TrafficLogger?
Yes. Implement LogTraffic(id, tx, rx) bool to return false when a client exceeds bandwidth quotas or violates policy. The server checks this return value in core/server/copy.go and will immediately terminate the client connection when it receives false.
Where are the logging interfaces defined in the Hysteria source code?
The EventLogger interface is defined at lines 218-226 and TrafficLogger at lines 242-251 in core/server/config.go. The server implementation that invokes these interfaces is located in core/server/server.go, with specific invocation logic at lines 113-120 for setup and lines 242-254 for traffic logging.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →