FLog

クイックでシンプルなロギングソリューションを提供する高度なロギングフレームワーク。全ログをデータベースに保存し、zipファイルとしてエクスポート可能。本番アプリでのログ収集と分析に特化した機能を提供。

GoログGolangシンプルログ高速ログフォーマットログ

ライブラリ

Flog

概要

Flogは「Go言語向けのシンプルで高速なログライブラリ」として、複数の実装が存在する多様なログソリューションです。Facebook版はglogから派生したライブラリ向け最適化ログライブラリ、Coder版は人間が読みやすいフォーマット重視のCLIアプリ向け軽量ログ、jellynian版はシンプルさとパフォーマンスを追求した高速ログと、それぞれ異なる特徴と用途を持ちます。

詳細

Flogライブラリ群は Go 言語のログライブラリエコシステムにおいて、異なるニーズに対応するように発展してきました。Facebook(現 Meta)の flog はライブラリ内部での使用を意識して glog を改善したバージョンで、環境変数設定対応、flag.Parse()不要の即座利用、DEBUGとCRITICALレベル追加などの機能を提供します。Coder 版は CLI アプリケーションでの使用を想定し、人間が読みやすい美しいフォーマットとミニマルなデザインを重視しています。jellynian 版はシンプルさと高速性を特徴とし、基本的なログ機能を軽量に実装しています。

主な特徴

  • 多様な実装: Facebook、Coder、jellynian版の3つの主要実装
  • 柔軟な設定: 環境変数や構造体での設定可能
  • 即座利用: flag.Parse()不要で即座にログ出力開始
  • 拡張ログレベル: DEBUGとCRITICALレベルの追加サポート
  • フォーマット最適化: CLIや人間可読性を重視した出力
  • 高パフォーマンス: 軽量で高速なログ処理

メリット・デメリット

メリット

  • Goネイティブな実装で高パフォーマンスと組み込み容易性
  • 複数の実装からプロジェクトニーズに最適な選択が可能
  • Facebook版はライブラリ内組み込みでの利用に最適化
  • Coder版は CLI アプリでの人間可読性と美しい出力を実現
  • jellynian版はシンプルさと高速性で基本的なニーズを満たす
  • 環境変数設定や即座利用可能な柔軟性

デメリット

  • 同名ライブラリが複数存在し選択時の混乱と調査コスト
  • 各実装でAPIや機能が異なり移植性や互換性の問題
  • コミュニティサポートが分散し統一的な情報収集の困難
  • 複雑なログ管理機能(ローテーション、集約等)の不足
  • 本格的なエンタープライズログ管理では機能不足の可能性
  • ドキュメントや例が各実装で異なり学習コストが増大

参考ページ

書き方の例

基本セットアップ

// Facebook Flog (facebookincubator/flog)
package main

import (
    "github.com/facebookincubator/flog"
)

func main() {
    // 設定なしで即座使用可能
    flog.Info("Application started")
    
    // 環境変数でログレベル設定
    // export FLOG_LEVEL=DEBUG
    flog.Debug("Debug information")
    flog.Critical("Critical system error")
}

// Coder Flog (coder/flog)
package main

import (
    "go.coder.com/flog"
)

func main() {
    // 人間可読性を重視したフォーマット
    flog.Info("Application started successfully")
    flog.Warn("Memory usage is high")
    flog.Error("Failed to connect to database")
}

// jellynian Flog (jellynian/flog)
package main

import (
    "github.com/jellynian/flog"
)

func main() {
    // シンプルで高速なログ出力
    flog.Println("Simple log message")
    flog.Printf("User %s logged in", "john")
}

基本的なログ出力

// Facebook Flog - ライブラリ向け最適化
package main

import (
    "context"
    "github.com/facebookincubator/flog"
)

func processUser(ctx context.Context, userID string) {
    // コンテキスト対応ログ
    flog.InfoContext(ctx, "Processing user", "user_id", userID)
    
    // ログレベル別出力
    flog.Debug("Detailed debug information")
    flog.Info("General information")
    flog.Warning("Warning message")
    flog.Error("Error occurred")
    flog.Critical("Critical system failure")
    
    // 構造化ログ
    flog.Info("User operation",
        "user_id", userID,
        "action", "login",
        "timestamp", time.Now(),
        "ip", "192.168.1.100",
    )
}

// 設定のカスタマイズ
func configureLogging() {
    config := flog.Config{
        Level:  flog.LevelInfo,
        Format: flog.FormatJSON, // JSON形式で出力
    }
    
    flog.Configure(config)
}

func main() {
    configureLogging()
    
    ctx := context.Background()
    processUser(ctx, "user-123")
}

高度な設定(フォーマット、フィルタリング)

// Coder Flog - CLIアプリ向け美しいフォーマット
package main

import (
    "fmt"
    "os"
    "time"
    
    "go.coder.com/flog"
)

// カスタムロガーの作成
func setupCustomLogger() *flog.Logger {
    return flog.New(
        flog.OutputHuman(), // 人間可読性を重視した出力
        flog.LevelFilter(flog.LevelInfo), // INFO以上のみ表示
        flog.TimeFormat(time.RFC3339), // 時刻フォーマット設定
    )
}

// コンテキスト付きロガー
func createContextLogger(component string) *flog.Logger {
    return flog.New(
        flog.OutputHuman(),
        flog.LevelFilter(flog.LevelDebug),
    ).With(
        flog.F("component", component),
        flog.F("version", "1.0.0"),
    )
}

func main() {
    // カスタムロガーの使用
    logger := setupCustomLogger()
    
    logger.Info("Starting application",
        flog.F("pid", os.Getpid()),
        flog.F("env", os.Getenv("ENV")),
    )
    
    // コンポーネント別ロガー
    dbLogger := createContextLogger("database")
    apiLogger := createContextLogger("api")
    
    dbLogger.Info("Database connection established",
        flog.F("host", "localhost"),
        flog.F("port", 5432),
    )
    
    apiLogger.Warn("API rate limit approaching",
        flog.F("current", 950),
        flog.F("limit", 1000),
    )
    
    // エラーログにスタックトレースを含める
    if err := processData(); err != nil {
        logger.Error("Failed to process data",
            flog.F("error", err),
            flog.F("stack", fmt.Sprintf("%+v", err)),
        )
    }
}

func processData() error {
    return fmt.Errorf("sample error for demonstration")
}

エラーハンドリング

// jellynian Flog - シンプル高速ログ
package main

import (
    "errors"
    "fmt"
    "time"
    
    "github.com/jellynian/flog"
)

// エラーハンドリング付きログ関数
func logWithErrorHandling(level, message string, args ...interface{}) {
    defer func() {
        if r := recover(); r != nil {
            // パニック時のフォールバック
            fmt.Printf("[PANIC] Logging failed: %v\n", r)
            fmt.Printf("[FALLBACK] %s: %s\n", level, fmt.Sprintf(message, args...))
        }
    }()
    
    switch level {
    case "INFO":
        flog.Printf("[INFO] "+message, args...)
    case "WARN":
        flog.Printf("[WARN] "+message, args...)
    case "ERROR":
        flog.Printf("[ERROR] "+message, args...)
    default:
        flog.Printf("[LOG] "+message, args...)
    }
}

// リトライ機能付きログ
func logWithRetry(message string, maxRetries int) error {
    var lastErr error
    
    for i := 0; i <= maxRetries; i++ {
        func() {
            defer func() {
                if r := recover(); r != nil {
                    lastErr = fmt.Errorf("log panic: %v", r)
                }
            }()
            
            flog.Println(message)
            lastErr = nil // 成功時はエラーをクリア
        }()
        
        if lastErr == nil {
            return nil // 成功
        }
        
        if i < maxRetries {
            time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
        }
    }
    
    return fmt.Errorf("failed to log after %d retries: %w", maxRetries, lastErr)
}

// バッチログ処理
type LogEntry struct {
    Level     string
    Message   string
    Timestamp time.Time
    Fields    map[string]interface{}
}

func processBatchLogs(entries []LogEntry) {
    for _, entry := range entries {
        safeLog := func(e LogEntry) {
            defer func() {
                if r := recover(); r != nil {
                    fmt.Printf("[BATCH_LOG_ERROR] Failed to log entry: %v\n", r)
                }
            }()
            
            timestamp := entry.Timestamp.Format("2006-01-02 15:04:05")
            message := fmt.Sprintf("[%s] [%s] %s", timestamp, entry.Level, entry.Message)
            
            if len(entry.Fields) > 0 {
                message += fmt.Sprintf(" %+v", entry.Fields)
            }
            
            flog.Println(message)
        }
        
        safeLog(entry)
    }
}

func main() {
    // 安全なログ出力
    logWithErrorHandling("INFO", "Application started at %v", time.Now())
    logWithErrorHandling("WARN", "Memory usage: %d MB", 512)
    
    // リトライ付きログ
    if err := logWithRetry("Critical system message", 3); err != nil {
        fmt.Printf("Final log failure: %v\n", err)
    }
    
    // バッチログ処理
    entries := []LogEntry{
        {
            Level:     "INFO",
            Message:   "User login",
            Timestamp: time.Now(),
            Fields:    map[string]interface{}{"user_id": 123, "ip": "192.168.1.1"},
        },
        {
            Level:     "ERROR",
            Message:   "Payment failed",
            Timestamp: time.Now(),
            Fields:    map[string]interface{}{"order_id": "ORD-456", "amount": 9800},
        },
    }
    
    processBatchLogs(entries)
}

実用例(統合ログシステム)

// 複数Flog実装を統合したユニバーサルロガー
package main

import (
    "context"
    "fmt"
    "io"
    "os"
    "runtime"
    "sync"
    "time"
    
    // 各 Flog 実装をインポート
    facebooklog "github.com/facebookincubator/flog"
    coderlog "go.coder.com/flog"
    jellynianlog "github.com/jellynian/flog"
)

type LogLevel int

const (
    DEBUG LogLevel = iota
    INFO
    WARN
    ERROR
    CRITICAL
)

type LoggerType int

const (
    FacebookLogger LoggerType = iota
    CoderLogger
    JellynianLogger
)

// 統合ロガーインターフェース
type UniversalLogger struct {
    loggerType LoggerType
    level      LogLevel
    output     io.Writer
    mu         sync.RWMutex
    
    // 各ロガーのインスタンス
    facebookLogger *facebooklog.Logger
    coderLogger    *coderlog.Logger
}

func NewUniversalLogger(loggerType LoggerType, level LogLevel) *UniversalLogger {
    ul := &UniversalLogger{
        loggerType: loggerType,
        level:      level,
        output:     os.Stderr,
    }
    
    switch loggerType {
    case FacebookLogger:
        // Facebook Flog の設定
        config := facebooklog.Config{
            Level: facebooklog.Level(level),
            Format: facebooklog.FormatJSON,
        }
        facebooklog.Configure(config)
        
    case CoderLogger:
        // Coder Flog の設定
        ul.coderLogger = coderlog.New(
            coderlog.OutputHuman(),
            coderlog.LevelFilter(coderlog.Level(level)),
            coderlog.TimeFormat(time.RFC3339),
        )
    }
    
    return ul
}

func (ul *UniversalLogger) log(ctx context.Context, level LogLevel, message string, fields ...interface{}) {
    ul.mu.RLock()
    defer ul.mu.RUnlock()
    
    if level < ul.level {
        return
    }
    
    // 呼び出し元情報を取得
    _, file, line, _ := runtime.Caller(2)
    
    // 共通フィールドを追加
    commonFields := []interface{}{
        "timestamp", time.Now(),
        "file", file,
        "line", line,
        "goroutine", runtime.NumGoroutine(),
    }
    
    allFields := append(commonFields, fields...)
    
    switch ul.loggerType {
    case FacebookLogger:
        switch level {
        case DEBUG:
            facebooklog.DebugContext(ctx, message, allFields...)
        case INFO:
            facebooklog.InfoContext(ctx, message, allFields...)
        case WARN:
            facebooklog.WarningContext(ctx, message, allFields...)
        case ERROR:
            facebooklog.ErrorContext(ctx, message, allFields...)
        case CRITICAL:
            facebooklog.CriticalContext(ctx, message, allFields...)
        }
        
    case CoderLogger:
        logger := ul.coderLogger
        for i := 0; i < len(allFields); i += 2 {
            if i+1 < len(allFields) {
                logger = logger.With(coderlog.F(fmt.Sprint(allFields[i]), allFields[i+1]))
            }
        }
        
        switch level {
        case DEBUG:
            logger.Debug(message)
        case INFO:
            logger.Info(message)
        case WARN:
            logger.Warn(message)
        case ERROR:
            logger.Error(message)
        case CRITICAL:
            logger.Error("[CRITICAL] " + message)
        }
        
    case JellynianLogger:
        // jellynian flog はシンプルな Printf スタイル
        levelStr := map[LogLevel]string{
            DEBUG:    "DEBUG",
            INFO:     "INFO",
            WARN:     "WARN",
            ERROR:    "ERROR",
            CRITICAL: "CRITICAL",
        }
        
        timestamp := time.Now().Format("2006-01-02 15:04:05")
        logLine := fmt.Sprintf("[%s] [%s] %s", timestamp, levelStr[level], message)
        
        if len(fields) > 0 {
            logLine += fmt.Sprintf(" %+v", fields)
        }
        
        jellynianlog.Println(logLine)
    }
}

// コンビニエンスメソッド
func (ul *UniversalLogger) Debug(ctx context.Context, message string, fields ...interface{}) {
    ul.log(ctx, DEBUG, message, fields...)
}

func (ul *UniversalLogger) Info(ctx context.Context, message string, fields ...interface{}) {
    ul.log(ctx, INFO, message, fields...)
}

func (ul *UniversalLogger) Warn(ctx context.Context, message string, fields ...interface{}) {
    ul.log(ctx, WARN, message, fields...)
}

func (ul *UniversalLogger) Error(ctx context.Context, message string, fields ...interface{}) {
    ul.log(ctx, ERROR, message, fields...)
}

func (ul *UniversalLogger) Critical(ctx context.Context, message string, fields ...interface{}) {
    ul.log(ctx, CRITICAL, message, fields...)
}

// パフォーマンスモニタリング機能
func (ul *UniversalLogger) WithTiming(ctx context.Context, operation string, fn func() error) error {
    start := time.Now()
    
    ul.Debug(ctx, "Operation started", "operation", operation)
    
    err := fn()
    
    duration := time.Since(start)
    
    if err != nil {
        ul.Error(ctx, "Operation failed",
            "operation", operation,
            "duration", duration,
            "error", err,
        )
    } else {
        ul.Info(ctx, "Operation completed",
            "operation", operation,
            "duration", duration,
        )
    }
    
    return err
}

func main() {
    // 各ロガーのテスト
    ctx := context.Background()
    
    // Facebook Flog でテスト
    fbLogger := NewUniversalLogger(FacebookLogger, DEBUG)
    fbLogger.Info(ctx, "Testing Facebook Flog", "component", "main")
    
    // Coder Flog でテスト
    coderLogger := NewUniversalLogger(CoderLogger, INFO)
    coderLogger.Warn(ctx, "Testing Coder Flog", "component", "main")
    
    // jellynian Flog でテスト
    jellynianLogger := NewUniversalLogger(JellynianLogger, DEBUG)
    jellynianLogger.Error(ctx, "Testing jellynian Flog", "component", "main")
    
    // パフォーマンスモニタリングのテスト
    err := fbLogger.WithTiming(ctx, "database_query", func() error {
        time.Sleep(100 * time.Millisecond) // ダミー処理
        return nil
    })
    
    if err != nil {
        fbLogger.Critical(ctx, "Critical error in main", "error", err)
    }
}