Logrus
Goの古参ロギングフレームワークの一つ。豊富な機能セットとコミュニティサポートにより2025年でも関連性を維持。しかし、高性能アプリケーションでは実用的な選択肢ではなくなっているが、機能の豊富さを重視する場合には有効。
GitHub概要
スター25,384
ウォッチ313
フォーク2,272
作成日:2013年10月16日
言語:Go
ライセンス:MIT License
トピックス
gologginglogrus
スター履歴
データ取得日時: 2025/7/17 08:20
ライブラリ
Logrus
概要
LogrusはGo言語向けの構造化ロギングライブラリで、プラグ可能なアーキテクチャとシンプルなAPIを提供します。複数の出力フォーマット(JSON、テキスト)、構造化フィールド、ログレベル制御、フック機能による外部システム統合をサポートし、開発からプロダクション環境まで幅広く対応します。2025年現在、メンテナンスモードに移行していますが、豊富な機能と安定性により既存プロジェクトで継続的に活用されているGo生態系の定番ログソリューションです。
詳細
Logrus 2025年版は、Go言語における構造化ロギングの先駆けとして確立された地位を保持しています。WithFields()による構造化データの追加、JSONフォーマッターによるマシン可読出力、カスタムフック機能によるSyslog、Airbrake、Slack等の外部サービス統合を実現。複数のログレベル(Trace、Debug、Info、Warn、Error、Fatal、Panic)をサポートし、実行時の動的レベル変更が可能。テスト用フック、カラーフォーマッター、カスタムフォーマッター作成により開発体験を向上させ、大規模なGoアプリケーションでの信頼性実績を持ちます。
主な特徴
- 構造化ロギング: WithFields()による構造化データの効率的な管理
- プラグ可能なフォーマッター: JSON、テキスト、カスタムフォーマッター対応
- フック機能: 外部サービス統合用のプラグイン拡張機能
- 7段階ログレベル: Trace~Panicまでの詳細なレベル制御
- テスト支援: テスト用フックによるログ検証機能
- 豊富なエコシステム: Syslog、Airbrake、Slack等の多様なフック
メリット・デメリット
メリット
- Go言語での構造化ロギングの先駆けとして成熟した設計
- 豊富なフォーマッターとフック機能による高い拡張性
- シンプルで直感的なAPIによる学習コストの低さ
- JSON出力により監視・分析ツールとの高い親和性
- WithFields()によるコンテキスト固有ログの効率的な管理
- テスト用フック機能によるログ検証の容易さ
- 活発なコミュニティと豊富なドキュメント
デメリット
- 現在メンテナンスモードのため新機能追加が期待できない
- Zerolog、Zapと比較してパフォーマンスが劣る
- 新規プロジェクトには推奨されない状況
- 高負荷環境では性能ボトルネックになる可能性
- メモリアロケーションが比較的多い
- 一部のフック機能で外部依存関係が増加
参考ページ
書き方の例
インストールと基本セットアップ
# Goモジュールにlogrusを追加
go get github.com/sirupsen/logrus
# フック機能を使用する場合(オプション)
go get github.com/sirupsen/logrus/hooks/syslog
go get github.com/sirupsen/logrus/hooks/writer
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
// 基本的なログ出力
log.Info("アプリケーション開始")
log.Debug("デバッグ情報")
log.Warn("警告メッセージ")
log.Error("エラーが発生")
// 構造化ログ(推奨)
log.WithFields(log.Fields{
"user_id": 12345,
"action": "login",
"ip": "192.168.1.100",
}).Info("ユーザーログイン")
// オブジェクトとメッセージの組み合わせ
log.WithFields(log.Fields{
"event": "database_connection",
"host": "db-server-01",
"port": 5432,
}).Error("データベース接続失敗")
// コンテキストロガーの再利用
requestLogger := log.WithFields(log.Fields{
"request_id": "req-123456",
"user_ip": "10.0.1.50",
})
requestLogger.Info("リクエスト処理開始")
requestLogger.Warn("レスポンス時間が閾値を超過")
requestLogger.Info("リクエスト処理完了")
}
ログレベルとフォーマッター設定
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func init() {
// 環境に応じたフォーマッター設定
if os.Getenv("ENVIRONMENT") == "production" {
// プロダクション: JSON形式(機械可読)
log.SetFormatter(&log.JSONFormatter{
TimestampFormat: "2006-01-02T15:04:05.000Z07:00",
FieldMap: log.FieldMap{
log.FieldKeyTime: "timestamp",
log.FieldKeyLevel: "severity",
log.FieldKeyMsg: "message",
},
})
} else {
// 開発: テキスト形式(人間可読)
log.SetFormatter(&log.TextFormatter{
DisableColors: false,
FullTimestamp: true,
TimestampFormat: "2006-01-02 15:04:05",
})
}
// 出力先設定(デフォルトはstderr)
log.SetOutput(os.Stdout)
// ログレベル設定
if os.Getenv("DEBUG") == "true" {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.InfoLevel)
}
// 呼び出し元情報の有効化(デバッグ用、パフォーマンス影響あり)
log.SetReportCaller(true)
}
func main() {
// 全ログレベルの使用例
log.Trace("非常に詳細なデバッグ情報")
log.Debug("デバッグ情報")
log.Info("情報メッセージ")
log.Warn("警告メッセージ")
log.Error("エラーメッセージ")
// log.Fatal("致命的エラー(os.Exit(1)を呼び出し)")
// log.Panic("パニック(panic()を呼び出し)")
// 動的ログレベル変更
log.SetLevel(log.WarnLevel)
log.Info("この情報メッセージは出力されない")
log.Warn("この警告メッセージは出力される")
}
フック機能と外部システム統合
package main
import (
"io"
"log/syslog"
"os"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/syslog"
"github.com/sirupsen/logrus/hooks/writer"
)
func init() {
// 出力を無効化(フックで制御)
log.SetOutput(io.Discard)
// Writer Hook: レベル別出力先設定
log.AddHook(&writer.Hook{
Writer: os.Stderr,
LogLevels: []log.Level{
log.PanicLevel,
log.FatalLevel,
log.ErrorLevel,
log.WarnLevel,
},
})
log.AddHook(&writer.Hook{
Writer: os.Stdout,
LogLevels: []log.Level{
log.InfoLevel,
log.DebugLevel,
log.TraceLevel,
},
})
// Syslog Hook(ローカル)
hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "myapp")
if err == nil {
log.AddHook(hook)
}
// Syslog Hook(リモート)
remoteHook, err := lSyslog.NewSyslogHook("udp", "log-server:514", syslog.LOG_INFO, "myapp")
if err == nil {
log.AddHook(remoteHook)
}
}
// カスタムフック例:Slack通知
type SlackHook struct {
WebhookURL string
Username string
Channel string
}
func (hook *SlackHook) Levels() []log.Level {
return []log.Level{
log.ErrorLevel,
log.FatalLevel,
log.PanicLevel,
}
}
func (hook *SlackHook) Fire(entry *log.Entry) error {
// Slack通知の実装
message := fmt.Sprintf("🚨 *%s*: %s", entry.Level.String(), entry.Message)
// 構造化フィールドの追加
if len(entry.Data) > 0 {
fields := make([]string, 0, len(entry.Data))
for key, value := range entry.Data {
fields = append(fields, fmt.Sprintf("*%s*: %v", key, value))
}
message += "\n" + strings.Join(fields, "\n")
}
// Slack APIへのリクエスト送信(実装省略)
return hook.sendToSlack(message)
}
func (hook *SlackHook) sendToSlack(message string) error {
// Slack webhook APIの実装
// この部分では実際のHTTPリクエストを送信
return nil
}
func main() {
// カスタムフックの追加
slackHook := &SlackHook{
WebhookURL: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
Username: "logrus-bot",
Channel: "#alerts",
}
log.AddHook(slackHook)
// 通常ログ(標準出力)
log.Info("これは標準出力に表示される")
// エラーログ(標準エラー + Syslog + Slack)
log.WithFields(log.Fields{
"error_code": "DB_001",
"connection": "primary",
"retry_count": 3,
}).Error("データベース接続エラー")
}
高度な設定とカスタムフォーマッター
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"strings"
log "github.com/sirupsen/logrus"
)
// カスタムフォーマッター例
type CustomFormatter struct {
TimestampFormat string
LogFormat string
}
func (f *CustomFormatter) Format(entry *log.Entry) ([]byte, error) {
output := f.LogFormat
// タイムスタンプ
if f.TimestampFormat == "" {
f.TimestampFormat = "2006-01-02 15:04:05"
}
output = strings.Replace(output, "%time%", entry.Time.Format(f.TimestampFormat), 1)
// ログレベル
output = strings.Replace(output, "%lvl%", strings.ToUpper(entry.Level.String()), 1)
// メッセージ
output = strings.Replace(output, "%msg%", entry.Message, 1)
// フィールド
var fieldsStr string
if len(entry.Data) > 0 {
fields := make([]string, 0, len(entry.Data))
for key, value := range entry.Data {
fields = append(fields, fmt.Sprintf("%s=%v", key, value))
}
fieldsStr = "[" + strings.Join(fields, " ") + "]"
}
output = strings.Replace(output, "%fields%", fieldsStr, 1)
return []byte(output + "\n"), nil
}
// アプリケーション固有のロガー
type ApplicationLogger struct {
*log.Logger
serviceName string
version string
}
func NewApplicationLogger(serviceName, version string) *ApplicationLogger {
logger := log.New()
// 環境別設定
env := os.Getenv("ENVIRONMENT")
if env == "production" {
logger.SetLevel(log.InfoLevel)
logger.SetFormatter(&log.JSONFormatter{
TimestampFormat: "2006-01-02T15:04:05.000Z07:00",
})
} else if env == "development" {
logger.SetLevel(log.DebugLevel)
logger.SetFormatter(&CustomFormatter{
LogFormat: "%time% [%lvl%] %msg% %fields%",
})
} else {
logger.SetLevel(log.WarnLevel)
logger.SetFormatter(&log.TextFormatter{
DisableColors: true,
FullTimestamp: true,
})
}
return &ApplicationLogger{
Logger: logger,
serviceName: serviceName,
version: version,
}
}
func (al *ApplicationLogger) WithContext(fields map[string]interface{}) *log.Entry {
return al.WithFields(log.Fields{
"service": al.serviceName,
"version": al.version,
}).WithFields(log.Fields(fields))
}
func (al *ApplicationLogger) LogRequest(method, path string, statusCode int, duration float64) {
entry := al.WithContext(map[string]interface{}{
"component": "http",
"method": method,
"path": path,
"status": statusCode,
"duration": fmt.Sprintf("%.2fms", duration),
})
if statusCode >= 500 {
entry.Error("HTTPリクエストエラー")
} else if statusCode >= 400 {
entry.Warn("HTTPリクエスト警告")
} else {
entry.Info("HTTPリクエスト完了")
}
}
func (al *ApplicationLogger) LogDatabaseOperation(operation, table string, duration float64, err error) {
entry := al.WithContext(map[string]interface{}{
"component": "database",
"operation": operation,
"table": table,
"duration": fmt.Sprintf("%.2fms", duration),
})
if err != nil {
entry.WithError(err).Error("データベース操作エラー")
} else {
entry.Info("データベース操作完了")
}
}
func main() {
// アプリケーションロガーの初期化
appLogger := NewApplicationLogger("user-service", "v1.2.3")
// HTTPリクエストログ
appLogger.LogRequest("GET", "/api/users/123", 200, 45.67)
appLogger.LogRequest("POST", "/api/users", 400, 12.34)
appLogger.LogRequest("GET", "/api/users/999", 500, 1234.56)
// データベース操作ログ
appLogger.LogDatabaseOperation("SELECT", "users", 23.45, nil)
appLogger.LogDatabaseOperation("INSERT", "users", 67.89, fmt.Errorf("duplicate key"))
// 業務ロジックログ
userLogger := appLogger.WithContext(map[string]interface{}{
"component": "business",
"user_id": 12345,
})
userLogger.Info("ユーザー認証開始")
userLogger.Debug("認証トークン検証")
userLogger.Info("認証成功")
// エラーハンドリング例
if err := performCriticalOperation(); err != nil {
appLogger.WithContext(map[string]interface{}{
"component": "critical",
"operation": "payment_process",
}).WithError(err).Fatal("致命的なエラーが発生")
}
}
func performCriticalOperation() error {
// クリティカルな処理のシミュレーション
return nil
}
テスト支援とログ検証
package main
import (
"testing"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
)
// テスト対象の関数例
func ProcessUser(userID int, action string) error {
logger := log.WithFields(log.Fields{
"user_id": userID,
"action": action,
})
logger.Info("ユーザー処理開始")
if userID <= 0 {
logger.Error("無効なユーザーID")
return fmt.Errorf("invalid user ID: %d", userID)
}
if action == "delete" {
logger.Warn("ユーザー削除操作")
}
logger.Info("ユーザー処理完了")
return nil
}
func TestProcessUser(t *testing.T) {
// テスト用ロガーとフックの初期化
logger, hook := test.NewNullLogger()
log.SetOutput(logger.Out)
tests := []struct {
name string
userID int
action string
expectError bool
expectedLogs int
expectedLevels []log.Level
expectedMsgs []string
}{
{
name: "正常なユーザー処理",
userID: 123,
action: "update",
expectError: false,
expectedLogs: 2,
expectedLevels: []log.Level{log.InfoLevel, log.InfoLevel},
expectedMsgs: []string{"ユーザー処理開始", "ユーザー処理完了"},
},
{
name: "無効なユーザーID",
userID: -1,
action: "create",
expectError: true,
expectedLogs: 2,
expectedLevels: []log.Level{log.InfoLevel, log.ErrorLevel},
expectedMsgs: []string{"ユーザー処理開始", "無効なユーザーID"},
},
{
name: "削除操作の警告",
userID: 456,
action: "delete",
expectError: false,
expectedLogs: 3,
expectedLevels: []log.Level{log.InfoLevel, log.WarnLevel, log.InfoLevel},
expectedMsgs: []string{"ユーザー処理開始", "ユーザー削除操作", "ユーザー処理完了"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// フックのリセット
hook.Reset()
// テスト実行
err := ProcessUser(tt.userID, tt.action)
// エラー検証
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
// ログエントリ数の検証
assert.Equal(t, tt.expectedLogs, len(hook.Entries))
// 各ログエントリの検証
for i, entry := range hook.Entries {
if i < len(tt.expectedLevels) {
assert.Equal(t, tt.expectedLevels[i], entry.Level)
}
if i < len(tt.expectedMsgs) {
assert.Equal(t, tt.expectedMsgs[i], entry.Message)
}
// フィールドの検証
assert.Equal(t, tt.userID, entry.Data["user_id"])
assert.Equal(t, tt.action, entry.Data["action"])
}
})
}
}
// パフォーマンステスト例
func BenchmarkLogrus(b *testing.B) {
// ベンチマーク用設定
log.SetFormatter(&log.JSONFormatter{})
log.SetLevel(log.InfoLevel)
b.ResetTimer()
for i := 0; i < b.N; i++ {
log.WithFields(log.Fields{
"iteration": i,
"user_id": 12345,
"action": "benchmark",
}).Info("ベンチマークテスト")
}
}
func BenchmarkLogrusWithCaller(b *testing.B) {
// 呼び出し元情報有効化(パフォーマンス影響測定)
log.SetReportCaller(true)
log.SetFormatter(&log.JSONFormatter{})
log.SetLevel(log.InfoLevel)
b.ResetTimer()
for i := 0; i < b.N; i++ {
log.WithFields(log.Fields{
"iteration": i,
"user_id": 12345,
"action": "benchmark_caller",
}).Info("呼び出し元情報付きベンチマーク")
}
}