Skip to main content
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:
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:
LevelConstantDescription
Debugschemas.LogLevelDebugDetailed debugging information for development
Infoschemas.LogLevelInfoGeneral operational messages
Warnschemas.LogLevelWarnPotentially harmful situations
Errorschemas.LogLevelErrorSerious problems requiring attention
// 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:
logger.SetLevel(schemas.LogLevelDebug)

Output Formats

The default logger supports two output formats:

JSON Output (Default)

Structured JSON logs, ideal for log aggregation systems:
logger := bifrost.NewDefaultLogger(schemas.LogLevelInfo)
logger.SetOutputType(schemas.LoggerOutputTypeJSON)
Output example:
{"level":"info","time":"2024-01-15T10:30:00Z","message":"Request completed"}

Pretty Output

Human-readable colored output, ideal for development:
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:
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

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

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:
client, err := bifrost.Init(schemas.BifrostConfig{
    Account: &MyAccount{},
    Logger:  NewZapLogger(),  // or NewLogrusLogger()
})

Disabling Logging

To disable logging, implement a no-op logger:
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

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