---
title: "Logging"
description: "Configure logging for debugging, monitoring, and troubleshooting your Bifrost integration."
icon: "file-lines"
---

Bifrost provides a flexible logging system with configurable log levels and output formats. You can use the built-in default logger or implement your own custom logger.

## Using the Default Logger

Bifrost includes a `DefaultLogger` that writes to stdout/stderr with timestamps. Create one with your desired log level:

```go
import (
    "github.com/maximhq/bifrost"
    "github.com/maximhq/bifrost/core/schemas"
)

func main() {
    // Create logger with desired level
    logger := bifrost.NewDefaultLogger(schemas.LogLevelInfo)

    // Initialize Bifrost with the logger
    client, err := bifrost.Init(schemas.BifrostConfig{
        Account: &MyAccount{},
        Logger:  logger,
    })
    if err != nil {
        panic(err)
    }
}
```

## Log Levels

Bifrost supports four log levels, from most to least verbose:

| Level | Constant | Description |
|-------|----------|-------------|
| Debug | `schemas.LogLevelDebug` | Detailed debugging information for development |
| Info | `schemas.LogLevelInfo` | General operational messages |
| Warn | `schemas.LogLevelWarn` | Potentially harmful situations |
| Error | `schemas.LogLevelError` | Serious problems requiring attention |

```go
// Debug level - most verbose, includes all messages
logger := bifrost.NewDefaultLogger(schemas.LogLevelDebug)

// Info level - general operational messages
logger := bifrost.NewDefaultLogger(schemas.LogLevelInfo)

// Warn level - only warnings and errors
logger := bifrost.NewDefaultLogger(schemas.LogLevelWarn)

// Error level - only errors (least verbose)
logger := bifrost.NewDefaultLogger(schemas.LogLevelError)
```

You can change the log level at runtime:

```go
logger.SetLevel(schemas.LogLevelDebug)
```

## Output Formats

The default logger supports two output formats:

### JSON Output (Default)

Structured JSON logs, ideal for log aggregation systems:

```go
logger := bifrost.NewDefaultLogger(schemas.LogLevelInfo)
logger.SetOutputType(schemas.LoggerOutputTypeJSON)
```

Output example:
```json
{"level":"info","time":"2024-01-15T10:30:00Z","message":"Request completed"}
```

### Pretty Output

Human-readable colored output, ideal for development:

```go
logger := bifrost.NewDefaultLogger(schemas.LogLevelInfo)
logger.SetOutputType(schemas.LoggerOutputTypePretty)
```

Output example:
```
10:30:00 INF Request completed
```

## Custom Logger Implementation

Implement the `Logger` interface to integrate with your existing logging infrastructure:

```go
type Logger interface {
    Debug(msg string, args ...any)
    Info(msg string, args ...any)
    Warn(msg string, args ...any)
    Error(msg string, args ...any)
    Fatal(msg string, args ...any)
    SetLevel(level schemas.LogLevel)
    SetOutputType(outputType schemas.LoggerOutputType)
}
```

### Example: Zap Logger Integration

```go
import (
    "go.uber.org/zap"
    "github.com/maximhq/bifrost/core/schemas"
)

type ZapLogger struct {
    logger *zap.SugaredLogger
    level  zap.AtomicLevel
}

func NewZapLogger() *ZapLogger {
    level := zap.NewAtomicLevelAt(zap.InfoLevel)
    config := zap.NewProductionConfig()
    config.Level = level
    logger, _ := config.Build()
    return &ZapLogger{
        logger: logger.Sugar(),
        level:  level,
    }
}

func (l *ZapLogger) Debug(msg string, args ...any) {
    l.logger.Debugf(msg, args...)
}

func (l *ZapLogger) Info(msg string, args ...any) {
    l.logger.Infof(msg, args...)
}

func (l *ZapLogger) Warn(msg string, args ...any) {
    l.logger.Warnf(msg, args...)
}

func (l *ZapLogger) Error(msg string, args ...any) {
    l.logger.Errorf(msg, args...)
}

func (l *ZapLogger) Fatal(msg string, args ...any) {
    l.logger.Fatalf(msg, args...)
}

func (l *ZapLogger) SetLevel(level schemas.LogLevel) {
    switch level {
    case schemas.LogLevelDebug:
        l.level.SetLevel(zap.DebugLevel)
    case schemas.LogLevelInfo:
        l.level.SetLevel(zap.InfoLevel)
    case schemas.LogLevelWarn:
        l.level.SetLevel(zap.WarnLevel)
    case schemas.LogLevelError:
        l.level.SetLevel(zap.ErrorLevel)
    }
}

func (l *ZapLogger) SetOutputType(outputType schemas.LoggerOutputType) {
    // Zap handles output format via encoder configuration
}
```

### Example: Logrus Integration

```go
import (
    "github.com/sirupsen/logrus"
    "github.com/maximhq/bifrost/core/schemas"
)

type LogrusLogger struct {
    logger *logrus.Logger
}

func NewLogrusLogger() *LogrusLogger {
    logger := logrus.New()
    logger.SetLevel(logrus.InfoLevel)
    return &LogrusLogger{logger: logger}
}

func (l *LogrusLogger) Debug(msg string, args ...any) {
    l.logger.Debugf(msg, args...)
}

func (l *LogrusLogger) Info(msg string, args ...any) {
    l.logger.Infof(msg, args...)
}

func (l *LogrusLogger) Warn(msg string, args ...any) {
    l.logger.Warnf(msg, args...)
}

func (l *LogrusLogger) Error(msg string, args ...any) {
    l.logger.Errorf(msg, args...)
}

func (l *LogrusLogger) Fatal(msg string, args ...any) {
    l.logger.Fatalf(msg, args...)
}

func (l *LogrusLogger) SetLevel(level schemas.LogLevel) {
    switch level {
    case schemas.LogLevelDebug:
        l.logger.SetLevel(logrus.DebugLevel)
    case schemas.LogLevelInfo:
        l.logger.SetLevel(logrus.InfoLevel)
    case schemas.LogLevelWarn:
        l.logger.SetLevel(logrus.WarnLevel)
    case schemas.LogLevelError:
        l.logger.SetLevel(logrus.ErrorLevel)
    }
}

func (l *LogrusLogger) SetOutputType(outputType schemas.LoggerOutputType) {
    switch outputType {
    case schemas.LoggerOutputTypeJSON:
        l.logger.SetFormatter(&logrus.JSONFormatter{})
    case schemas.LoggerOutputTypePretty:
        l.logger.SetFormatter(&logrus.TextFormatter{
            FullTimestamp: true,
        })
    }
}
```

## Using Your Custom Logger

Pass your custom logger to Bifrost during initialization:

```go
client, err := bifrost.Init(schemas.BifrostConfig{
    Account: &MyAccount{},
    Logger:  NewZapLogger(),  // or NewLogrusLogger()
})
```

## Disabling Logging

To disable logging, implement a no-op logger:

```go
type NoOpLogger struct{}

func (l *NoOpLogger) Debug(msg string, args ...any)                   {}
func (l *NoOpLogger) Info(msg string, args ...any)                    {}
func (l *NoOpLogger) Warn(msg string, args ...any)                    {}
func (l *NoOpLogger) Error(msg string, args ...any)                   {}
func (l *NoOpLogger) Fatal(msg string, args ...any)                   {}
func (l *NoOpLogger) SetLevel(level schemas.LogLevel)                 {}
func (l *NoOpLogger) SetOutputType(outputType schemas.LoggerOutputType) {}

// Use it
client, err := bifrost.Init(schemas.BifrostConfig{
    Account: &MyAccount{},
    Logger:  &NoOpLogger{},
})
```

## Best Practices

### Development vs Production

```go
func createLogger(env string) schemas.Logger {
    logger := bifrost.NewDefaultLogger(schemas.LogLevelInfo)

    if env == "development" {
        logger.SetLevel(schemas.LogLevelDebug)
        logger.SetOutputType(schemas.LoggerOutputTypePretty)
    } else {
        logger.SetLevel(schemas.LogLevelInfo)
        logger.SetOutputType(schemas.LoggerOutputTypeJSON)
    }

    return logger
}
```

### Log Level Guidelines

- **Debug**: Use during development to trace request flow, inspect payloads, and diagnose issues
- **Info**: Use for normal operational events like successful requests, provider switches
- **Warn**: Use for recoverable issues like retries, fallback activations, deprecated usage
- **Error**: Use for failures that need attention but don't crash the application

## Next Steps

- **[Context Keys](./context-keys)** - Pass metadata through requests
- **[Provider Configuration](./provider-configuration)** - Configure multiple providers
- **[Streaming Responses](./streaming)** - Real-time response handling
- **[Core Features](../../features/)** - Advanced Bifrost capabilities
