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. The tx and rx parameters represent bytes sent to and received from the remote target. Return false to 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 the Config struct and the EventLogger and TrafficLogger interface 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 the copyTwoWayEx helper that forwards traffic statistics to the TrafficLogger.
  • core/server/udp.go – Manages UDP sessions and utilizes the udpEventLoggerImpl to map UDP events to EventLogger calls.
  • core/internal/integration_tests/trafficlogger_test.go – Contains unit tests demonstrating expected TrafficLogger call patterns and the disconnect behavior when LogTraffic returns false.

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 false from LogTraffic.
  • Both interfaces are defined in core/server/config.go and are optional; the server checks for nil before invoking them.
  • Implementations must be thread-safe due to concurrent goroutine access.
  • The UDP event wrapper in core/server/server.go bridges UDP session management to the EventLogger interface.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →