Skip to main content

Overview

Pion WebRTC provides a customizable logging interface that allows you to integrate with your existing logging infrastructure. You can control log levels, format output, and direct logs to different destinations for each WebRTC subsystem (ICE, DTLS, SCTP, etc.).
Custom logging is particularly useful for debugging connection issues, monitoring production systems, or integrating with centralized logging platforms.

Logging Interface

Pion uses the logging.LeveledLogger interface from github.com/pion/logging:
logger-interface.go
type LeveledLogger interface {
    Trace(msg string)
    Tracef(format string, args ...interface{})
    Debug(msg string)
    Debugf(format string, args ...interface{})
    Info(msg string)
    Infof(format string, args ...interface{})
    Warn(msg string)
    Warnf(format string, args ...interface{})
    Error(msg string)
    Errorf(format string, args ...interface{})
}

LoggerFactory Interface

The LoggerFactory creates loggers for different subsystems:
factory-interface.go
type LoggerFactory interface {
    NewLogger(subsystem string) LeveledLogger
}
Subsystems include: ice, dtls, sctp, srtp, rtcp, and more.

Basic Custom Logger

Here’s a complete example of implementing a custom logger:
custom-logger.go
package main

import (
    "fmt"
    "github.com/pion/logging"
    "github.com/pion/webrtc/v4"
)

// customLogger implements logging.LeveledLogger
type customLogger struct{}

// Trace logs are typically very verbose
func (c customLogger) Trace(msg string) {}
func (c customLogger) Tracef(format string, args ...any) {}

// Debug logs for development and troubleshooting
func (c customLogger) Debug(msg string) {
    fmt.Printf("[DEBUG] %s\n", msg)
}

func (c customLogger) Debugf(format string, args ...any) {
    c.Debug(fmt.Sprintf(format, args...))
}

// Info logs for general information
func (c customLogger) Info(msg string) {
    fmt.Printf("[INFO] %s\n", msg)
}

func (c customLogger) Infof(format string, args ...any) {
    c.Info(fmt.Sprintf(format, args...))
}

// Warn logs for warnings
func (c customLogger) Warn(msg string) {
    fmt.Printf("[WARN] %s\n", msg)
}

func (c customLogger) Warnf(format string, args ...any) {
    c.Warn(fmt.Sprintf(format, args...))
}

// Error logs for errors
func (c customLogger) Error(msg string) {
    fmt.Printf("[ERROR] %s\n", msg)
}

func (c customLogger) Errorf(format string, args ...any) {
    c.Error(fmt.Sprintf(format, args...))
}

// customLoggerFactory implements logging.LoggerFactory
type customLoggerFactory struct{}

func (c customLoggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
    fmt.Printf("Creating logger for subsystem: %s\n", subsystem)
    return customLogger{}
}

func main() {
    // Create SettingEngine with custom logger
    s := webrtc.SettingEngine{
        LoggerFactory: customLoggerFactory{},
    }
    
    api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
    
    peerConnection, err := api.NewPeerConnection(webrtc.Configuration{})
    if err != nil {
        panic(err)
    }
    defer peerConnection.Close()
}
From examples/custom-logger/main.go:20-64.

Per-Subsystem Logging

You can create different loggers for each subsystem:
per-subsystem.go
import (
    "fmt"
    "log"
    "os"
    "github.com/pion/logging"
)

type subsystemLogger struct {
    subsystem string
    logger    *log.Logger
}

func (s *subsystemLogger) Trace(msg string) {}
func (s *subsystemLogger) Tracef(format string, args ...any) {}

func (s *subsystemLogger) Debug(msg string) {
    s.logger.Printf("[%s] DEBUG: %s", s.subsystem, msg)
}

func (s *subsystemLogger) Debugf(format string, args ...any) {
    s.Debug(fmt.Sprintf(format, args...))
}

func (s *subsystemLogger) Info(msg string) {
    s.logger.Printf("[%s] INFO: %s", s.subsystem, msg)
}

func (s *subsystemLogger) Infof(format string, args ...any) {
    s.Info(fmt.Sprintf(format, args...))
}

func (s *subsystemLogger) Warn(msg string) {
    s.logger.Printf("[%s] WARN: %s", s.subsystem, msg)
}

func (s *subsystemLogger) Warnf(format string, args ...any) {
    s.Warn(fmt.Sprintf(format, args...))
}

func (s *subsystemLogger) Error(msg string) {
    s.logger.Printf("[%s] ERROR: %s", s.subsystem, msg)
}

func (s *subsystemLogger) Errorf(format string, args ...any) {
    s.Error(fmt.Sprintf(format, args...))
}

type subsystemLoggerFactory struct{}

func (s subsystemLoggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
    return &subsystemLogger{
        subsystem: subsystem,
        logger:    log.New(os.Stdout, "", log.LstdFlags),
    }
}

Logging to File

Direct logs to a file:
file-logger.go
import (
    "fmt"
    "log"
    "os"
    "github.com/pion/logging"
    "github.com/pion/webrtc/v4"
)

type fileLogger struct {
    logger *log.Logger
}

func (f *fileLogger) Trace(msg string) {}
func (f *fileLogger) Tracef(format string, args ...any) {}

func (f *fileLogger) Debug(msg string) {
    f.logger.Println("DEBUG:", msg)
}

func (f *fileLogger) Debugf(format string, args ...any) {
    f.Debug(fmt.Sprintf(format, args...))
}

func (f *fileLogger) Info(msg string) {
    f.logger.Println("INFO:", msg)
}

func (f *fileLogger) Infof(format string, args ...any) {
    f.Info(fmt.Sprintf(format, args...))
}

func (f *fileLogger) Warn(msg string) {
    f.logger.Println("WARN:", msg)
}

func (f *fileLogger) Warnf(format string, args ...any) {
    f.Warn(fmt.Sprintf(format, args...))
}

func (f *fileLogger) Error(msg string) {
    f.logger.Println("ERROR:", msg)
}

func (f *fileLogger) Errorf(format string, args ...any) {
    f.Error(fmt.Sprintf(format, args...))
}

type fileLoggerFactory struct {
    file *os.File
}

func (f *fileLoggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
    return &fileLogger{
        logger: log.New(f.file, fmt.Sprintf("[%s] ", subsystem), log.LstdFlags),
    }
}

func main() {
    // Open log file
    logFile, err := os.OpenFile("webrtc.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        panic(err)
    }
    defer logFile.Close()
    
    // Create SettingEngine with file logger
    s := webrtc.SettingEngine{
        LoggerFactory: &fileLoggerFactory{file: logFile},
    }
    
    api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
    peerConnection, _ := api.NewPeerConnection(webrtc.Configuration{})
    defer peerConnection.Close()
}

Structured Logging (JSON)

Integrate with structured logging libraries:
json-logger.go
import (
    "encoding/json"
    "fmt"
    "os"
    "time"
    "github.com/pion/logging"
)

type jsonLogger struct {
    subsystem string
}

func (j *jsonLogger) log(level, msg string) {
    entry := map[string]interface{}{
        "timestamp": time.Now().Format(time.RFC3339),
        "level":     level,
        "subsystem": j.subsystem,
        "message":   msg,
    }
    
    data, _ := json.Marshal(entry)
    fmt.Fprintln(os.Stdout, string(data))
}

func (j *jsonLogger) Trace(msg string) {}
func (j *jsonLogger) Tracef(format string, args ...any) {}

func (j *jsonLogger) Debug(msg string) {
    j.log("DEBUG", msg)
}

func (j *jsonLogger) Debugf(format string, args ...any) {
    j.Debug(fmt.Sprintf(format, args...))
}

func (j *jsonLogger) Info(msg string) {
    j.log("INFO", msg)
}

func (j *jsonLogger) Infof(format string, args ...any) {
    j.Info(fmt.Sprintf(format, args...))
}

func (j *jsonLogger) Warn(msg string) {
    j.log("WARN", msg)
}

func (j *jsonLogger) Warnf(format string, args ...any) {
    j.Warn(fmt.Sprintf(format, args...))
}

func (j *jsonLogger) Error(msg string) {
    j.log("ERROR", msg)
}

func (j *jsonLogger) Errorf(format string, args ...any) {
    j.Error(fmt.Sprintf(format, args...))
}

type jsonLoggerFactory struct{}

func (j jsonLoggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
    return &jsonLogger{subsystem: subsystem}
}

Using Pion’s Default Logger

Pion provides a default logger implementation:
default-logger.go
import (
    "github.com/pion/logging"
    "github.com/pion/webrtc/v4"
)

// Create default logger factory
loggerFactory := logging.NewDefaultLoggerFactory()

// Set log level
loggerFactory.DefaultLogLevel = logging.LogLevelDebug

// Use with SettingEngine
s := webrtc.SettingEngine{
    LoggerFactory: loggerFactory,
}

api := webrtc.NewAPI(webrtc.WithSettingEngine(s))

Log Levels

Pion supports these log levels:
1

Trace

Most verbose - detailed packet-level information
2

Debug

Debugging information - state changes, decisions
3

Info

General information - connection established, etc.
4

Warn

Warnings - recoverable issues
5

Error

Errors - failures and exceptions
Trace logs can be extremely verbose and impact performance. Only enable in development.

Common Subsystems

These are the subsystems you’ll see in logs:
ice
string
ICE agent - candidate gathering, connectivity checks
dtls
string
DTLS handshake and encryption
sctp
string
SCTP for data channels
srtp
string
SRTP encryption/decryption
rtcp
string
RTCP packet processing
pc
string
PeerConnection lifecycle

Filtering by Subsystem

Only log specific subsystems:
filter-subsystem.go
type filteredLoggerFactory struct {
    allowedSubsystems map[string]bool
}

func (f *filteredLoggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
    if f.allowedSubsystems[subsystem] {
        return customLogger{subsystem: subsystem}
    }
    // Return a no-op logger for filtered subsystems
    return logging.NewDefaultLoggerFactory().NewLogger("noop")
}

// Only log ICE and DTLS
factory := &filteredLoggerFactory{
    allowedSubsystems: map[string]bool{
        "ice":  true,
        "dtls": true,
    },
}

Logrus Integration

logrus-integration.go
import (
    "github.com/pion/logging"
    "github.com/sirupsen/logrus"
)

type logrusLogger struct {
    entry *logrus.Entry
}

func (l *logrusLogger) Trace(msg string) {
    l.entry.Trace(msg)
}

func (l *logrusLogger) Tracef(format string, args ...any) {
    l.entry.Tracef(format, args...)
}

func (l *logrusLogger) Debug(msg string) {
    l.entry.Debug(msg)
}

func (l *logrusLogger) Debugf(format string, args ...any) {
    l.entry.Debugf(format, args...)
}

func (l *logrusLogger) Info(msg string) {
    l.entry.Info(msg)
}

func (l *logrusLogger) Infof(format string, args ...any) {
    l.entry.Infof(format, args...)
}

func (l *logrusLogger) Warn(msg string) {
    l.entry.Warn(msg)
}

func (l *logrusLogger) Warnf(format string, args ...any) {
    l.entry.Warnf(format, args...)
}

func (l *logrusLogger) Error(msg string) {
    l.entry.Error(msg)
}

func (l *logrusLogger) Errorf(format string, args ...any) {
    l.entry.Errorf(format, args...)
}

type logrusLoggerFactory struct {
    logger *logrus.Logger
}

func (l *logrusLoggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
    return &logrusLogger{
        entry: l.logger.WithField("subsystem", subsystem),
    }
}

// Usage
logrusInstance := logrus.New()
logrusInstance.SetLevel(logrus.DebugLevel)
logrusInstance.SetFormatter(&logrus.JSONFormatter{})

factory := &logrusLoggerFactory{logger: logrusInstance}

Zap Integration

zap-integration.go
import (
    "github.com/pion/logging"
    "go.uber.org/zap"
)

type zapLogger struct {
    logger *zap.SugaredLogger
}

func (z *zapLogger) Trace(msg string) {
    z.logger.Debug(msg) // Zap doesn't have Trace
}

func (z *zapLogger) Tracef(format string, args ...any) {
    z.logger.Debugf(format, args...)
}

func (z *zapLogger) Debug(msg string) {
    z.logger.Debug(msg)
}

func (z *zapLogger) Debugf(format string, args ...any) {
    z.logger.Debugf(format, args...)
}

func (z *zapLogger) Info(msg string) {
    z.logger.Info(msg)
}

func (z *zapLogger) Infof(format string, args ...any) {
    z.logger.Infof(format, args...)
}

func (z *zapLogger) Warn(msg string) {
    z.logger.Warn(msg)
}

func (z *zapLogger) Warnf(format string, args ...any) {
    z.logger.Warnf(format, args...)
}

func (z *zapLogger) Error(msg string) {
    z.logger.Error(msg)
}

func (z *zapLogger) Errorf(format string, args ...any) {
    z.logger.Errorf(format, args...)
}

type zapLoggerFactory struct {
    logger *zap.Logger
}

func (z *zapLoggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
    return &zapLogger{
        logger: z.logger.Sugar().Named(subsystem),
    }
}

// Usage
zapInstance, _ := zap.NewProduction()
defer zapInstance.Sync()

factory := &zapLoggerFactory{logger: zapInstance}

Complete Example

See the complete working example at examples/custom-logger/main.go.This example demonstrates:
  • Implementing LeveledLogger interface
  • Creating a LoggerFactory
  • Per-subsystem logger creation
  • Integration with SettingEngine
  • Full PeerConnection setup with custom logging

Debugging Tips

1

Enable Debug Logs for ICE

Most connection issues are ICE-related. Enable debug logging for the ice subsystem.
2

Log DTLS Handshake

Certificate and DTLS issues appear in dtls subsystem logs.
3

Monitor SCTP for Data Channels

Data channel issues show up in sctp logs.
4

Avoid Trace in Production

Trace logs can significantly impact performance. Use Debug or Info in production.

SettingEngine

Configure advanced WebRTC behavior

ICE Configuration

Debug ICE connectivity

Build docs developers (and LLMs) love