# How to Implement a Custom Logger in Telego

> Learn to implement a custom logger in Telego by defining a Logger interface type and injecting it with the WithLogger option. Enhance your bot's logging capabilities easily.

- Repository: [Artem Yadelskyi/telego](https://github.com/mymmrac/telego)
- Tags: how-to-guide
- Published: 2026-03-07

---

**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`](https://github.com/mymmrac/telego/blob/main/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`](https://github.com/mymmrac/telego/blob/main/main/logger.go) as a simple interface requiring only two methods:

```go
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:

```go
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:

```go
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:

```go
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:

```go
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`](https://github.com/mymmrac/telego/blob/main/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 to `os.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 `Logger` interface in [`main/logger.go`](https://github.com/mymmrac/telego/blob/main/main/logger.go) requiring only `Debugf` and `Errorf` methods.
- Inject your implementation using `telego.WithLogger` when calling `telego.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`](https://github.com/mymmrac/telego/blob/main/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.