How to Implement a Custom Logger in Telego
To implement a custom logger in Telego, define a type that satisfies the Logger interface with Debugf and Errorf methods, then inject it using the WithLogger option when constructing your bot.
Telego is a fast, lightweight Telegram Bot API library for Go. While the framework ships with a default colored console logger, production workloads often require structured JSON output, integration with existing logging infrastructure, or custom security policies such as token redaction. The library exposes a minimal interface in main/logger.go that allows you to replace the internal logging pipeline without modifying source code.
The Telego Logger Interface
The logging contract is defined in main/logger.go as a simple interface requiring only two methods:
type Logger interface {
Debugf(format string, args ...any)
Errorf(format string, args ...any)
}
The default implementation (the unexported logger struct) provides colored output, timestamps, and automatic redaction of the bot token to prevent accidental credential leakage. However, because the Bot struct stores the logger in a field (bot.log), you can swap the implementation during initialization or even at runtime.
Implementing a Custom Logger in Telego
Basic Standard Output Logger
If you need plaintext output without ANSI colors or overhead, implement the interface with a simple mutex-protected writer:
type simpleLogger struct {
mu sync.Mutex
}
func (l *simpleLogger) Debugf(format string, args ...any) {
l.mu.Lock()
defer l.mu.Unlock()
fmt.Printf("[DEBUG] "+format+"\n", args...)
}
func (l *simpleLogger) Errorf(format string, args ...any) {
l.mu.Lock()
defer l.mu.Unlock()
fmt.Printf("[ERROR] "+format+"\n", args...)
}
// Usage
bot, err := telego.NewBot("YOUR_TOKEN",
telego.WithLogger(&simpleLogger{}))
Structured Logging with Zap
For production observability, integrate with Uber’s Zap library to emit JSON logs:
import "go.uber.org/zap"
type zapLogger struct {
sugar *zap.SugaredLogger
}
func (z *zapLogger) Debugf(format string, args ...any) {
z.sugar.Debugf(format, args...)
}
func (z *zapLogger) Errorf(format string, args ...any) {
z.sugar.Errorf(format, args...)
}
// Initialise once
zapCore, _ := zap.NewProduction()
defer zapCore.Sync()
sugar := zapCore.Sugar()
bot, err := telego.NewBot("YOUR_TOKEN",
telego.WithLogger(&zapLogger{sugar: sugar}))
Preserving Token Redaction
If you want to keep the security feature that strips the bot token from logs while using your own output format, reuse the library’s replacer logic:
type myLogger struct {
out io.Writer
debug bool
printErr bool
replacer *strings.Replacer
mu sync.Mutex
}
func (l *myLogger) Debugf(format string, args ...any) {
if !l.debug {
return
}
l.mu.Lock()
defer l.mu.Unlock()
msg := fmt.Sprintf(format+"\n", args...)
if l.replacer != nil {
msg = l.replacer.Replace(msg)
}
l.out.Write([]byte("[DEBUG] " + msg))
}
func (l *myLogger) Errorf(format string, args ...any) {
if !l.printErr {
return
}
l.mu.Lock()
defer l.mu.Unlock()
msg := fmt.Sprintf(format+"\n", args...)
if l.replacer != nil {
msg = l.replacer.Replace(msg)
}
l.out.Write([]byte("[ERROR] " + msg))
}
// Usage with token redaction
bot, _ := telego.NewBot("YOUR_TOKEN",
telego.WithLogger(&myLogger{
out: os.Stdout,
debug: true,
printErr: true,
replacer: telego.DefaultLoggerTokenReplacement, // or strings.NewReplacer("YOUR_TOKEN", "[REDACTED]")
}))
Runtime Logger Replacement
Because the logger is stored as a mutable field on the Bot struct, you can swap implementations dynamically. Start with a discard logger and switch to a structured logger later:
bot, _ := telego.NewBot("TOKEN", telego.WithDiscardLogger())
// Later, when configuration is loaded...
zapLog, _ := zap.NewProduction()
bot.SetLogger(&zapLogger{sugar: zapLog.Sugar()}) // If exposed, or use internal assignment
Note: Direct field assignment (bot.log = ...) requires access to the log field, which is unexported. Expose a setter method or initialize the bot with the final logger if cross-package runtime switching is required.
Configuration Options
The WithLogger option defined in main/bot_options.go is the primary mechanism for dependency injection. The library also provides convenience presets:
WithDefaultLogger()– Uses the built-in colored logger writing toos.Stderr.WithExtendedDefaultLogger(debug, printErrors bool)– Default logger with configurable verbosity.WithDiscardLogger()– A no-op logger that silently drops all log entries.
All custom implementations should be concurrency-safe because the bot invokes logging methods from multiple goroutines during concurrent API calls and webhook processing.
Summary
- Telego defines a minimal
Loggerinterface inmain/logger.gorequiring onlyDebugfandErrorfmethods. - Inject your implementation using
telego.WithLoggerwhen callingtelego.NewBot. - The default logger provides token redaction and colored output; custom loggers can replicate these features or implement structured JSON logging with libraries like Zap.
- Ensure thread safety with mutexes or atomic operations, as the bot logs concurrently from multiple goroutines.
Frequently Asked Questions
What methods must I implement for a custom logger in Telego?
You must implement the Logger interface defined in main/logger.go, which consists of two methods: Debugf(format string, args ...any) and Errorf(format string, args ...any). Both accept a format string and variadic arguments, matching the standard fmt.Printf signature.
How do I prevent the bot token from appearing in logs?
The default logger automatically redacts the token using a strings.Replacer. To maintain this security in your custom implementation, create a replacer that substitutes your token with a placeholder like [REDACTED], or use telego.DefaultLoggerTokenReplacement if available. Apply this replacer inside your Debugf and Errorf methods before writing to the output destination.
Can I use Zap or Logrus with Telego?
Yes. Because Telego only requires two simple methods, you can create a thin adapter struct that wraps *zap.SugaredLogger or *logrus.Logger. Implement Debugf and Errorf by delegating to the underlying library’s equivalent methods (e.g., zap.SugaredLogger.Debugf). Then pass your adapter to telego.WithLogger during bot initialization.
Is the Telego logger safe for concurrent use?
Yes, but the responsibility lies with your implementation. The Bot type invokes logging methods from multiple goroutines during concurrent API requests and webhook handling. If your custom logger maintains internal state (such as an io.Writer or counter), you must protect it with a sync.Mutex or use atomic operations and thread-safe writers to prevent race conditions.
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 →