Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15148] ファイルの概要

このコミットは、Go言語の標準ライブラリ log/syslog パッケージにおける重要な改善を扱っています。log/syslog パッケージは、UNIXドメインソケット、UDP、またはTCP接続を使用して、システムログサービス(syslogデーモン)にメッセージを送信するためのシンプルなインターフェースを提供します。このパッケージは、Goアプリケーションがシステムレベルのログを生成し、集中管理されたログシステムに送信するために利用されます。

コミット

このコミットは、log/syslog パッケージにおいて、syslogへの書き込みが失敗した場合に一度だけ自動的に再試行するメカニズムを導入し、接続の遅延確立(deferred connections)を実装します。これにより、ネットワークの一時的な問題やsyslogデーモンの再起動などによって接続が切断された場合でも、ログメッセージが失われる可能性を低減し、より堅牢なログ送信を実現します。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/33995fe59eddb3f84e537a8d44e39ef93bc764e8

元コミット内容

commit 33995fe59eddb3f84e537a8d44e39ef93bc764e8
Author: Jeff R. Allen <jra@nella.org>
Date:   Tue Feb 5 09:54:01 2013 -0800

    log/syslog: retry once if write fails
    
    Implements deferred connections + single-attempt automatic
    retry. Based on CL 5078042 from kuroneko.
    
    Fixes #2264.
    
    R=mikioh.mikioh, rsc, bradfitz
    CC=golang-dev
    https://golang.org/cl/6782140

変更の背景

この変更の背景には、Goの log/syslog パッケージが抱えていた、syslogデーモンへの接続が切断された際にログメッセージが失われるという問題がありました。具体的には、Go issue #2264 で報告されたように、syslogデーモンが再起動したり、ネットワーク接続が一時的に不安定になったりすると、log/syslogWriter インスタンスが保持している接続が無効になり、それ以降のログ書き込みがすべて失敗してしまうという挙動がありました。

従来の log/syslog 実装では、Dial 関数が呼び出された時点で一度だけ接続を確立し、その接続を再利用していました。接続が切断された場合、自動的に再接続するメカニズムがなかったため、アプリケーションはエラーを検知して Dial を再呼び出しするか、新しい Writer インスタンスを作成する必要がありました。これは、ログ送信の堅牢性を損ない、開発者に追加の複雑なエラーハンドリングを強いるものでした。

このコミットは、この問題を解決するために、書き込み失敗時に一度だけ自動的に再接続を試み、ログメッセージの再送信を行うことで、より信頼性の高いログ送信を実現することを目的としています。また、「deferred connections(遅延接続)」という概念が導入され、Dial 時点では接続を確立せず、最初の書き込み時に初めて接続を試みることで、リソースの効率的な利用と、syslogデーモンがまだ利用可能でない場合の起動時のエラー回避にも貢献しています。

前提知識の解説

Syslog

Syslogは、システムメッセージやイベントログを収集、保存、管理するための標準的なプロトコルです。UNIX系OSで広く利用されており、アプリケーションやシステムデーモンが生成するログメッセージを一元的に管理するために使われます。ログメッセージは、ファシリティ(メッセージの発生源、例: カーネル、ユーザーレベル、メールシステムなど)とセベリティ(メッセージの重要度、例: 緊急、警告、情報など)という2つの属性を持ちます。

UNIXドメインソケット、UDP、TCP

Syslogメッセージの送信には、主に以下の3つの通信プロトコルが使用されます。

  • UNIXドメインソケット (UNIX domain sockets): 同じホスト上のプロセス間通信に特化したソケットです。ファイルシステム上のパス(例: /dev/log, /var/run/syslog)を介して通信します。ネットワークオーバーヘッドがなく、高速で信頼性が高いのが特徴です。
  • UDP (User Datagram Protocol): コネクションレス型のプロトコルで、メッセージをパケットとして送信します。高速ですが、パケットの到達保証や順序保証はありません。ログの量が非常に多く、一部のメッセージが失われても問題ない場合に利用されることがあります。
  • TCP (Transmission Control Protocol): コネクション指向型のプロトコルで、信頼性の高いデータ転送を提供します。メッセージの到達と順序が保証されますが、UDPよりもオーバーヘッドが大きくなります。

net.Dialnet.Conn

Go言語の net パッケージは、ネットワークI/Oのプリミティブを提供します。

  • net.Dial(network, address string): 指定されたネットワーク(例: "tcp", "udp", "unix")とアドレスに接続を確立します。成功すると net.Conn インターフェースを実装するオブジェクトを返します。
  • net.Conn インターフェース: ネットワーク接続の一般的なインターフェースを定義します。ReadWriteClose などのメソッドを持ち、TCP接続、UDP接続、UNIXドメインソケット接続など、様々な種類のネットワーク接続を抽象化します。

sync.Mutex

sync.Mutex は、Go言語の sync パッケージで提供される相互排他ロック(ミューテックス)です。複数のGoroutineが共有リソースに同時にアクセスするのを防ぎ、データ競合(data race)を回避するために使用されます。Lock() メソッドでロックを取得し、Unlock() メソッドでロックを解放します。このコミットでは、Writer 構造体の conn フィールドへのアクセスを保護するために使用され、複数のGoroutineからのログ書き込みが安全に行われるようにします。

Goの log パッケージ

Goの標準ライブラリ log パッケージは、シンプルなロギング機能を提供します。log.Logger 型は、メッセージの出力先(io.Writer インターフェースを実装する任意のオブジェクト)、プレフィックス、およびフラグを設定できます。log/syslog パッケージは、log.Logger の出力先としてsyslogデーモンを利用できるようにするものです。

CL (Change List)

Goコミュニティでは、コード変更の提案を「Change List (CL)」と呼びます。これは、Gerritというコードレビューシステムで管理され、変更がコミットされる前にレビューと承認を受けます。コミットメッセージに記載されている https://golang.org/cl/6782140 は、このコミットに対応するGerritのCLへのリンクです。また、Based on CL 5078042 from kuroneko は、この変更が別のCL(おそらく初期の提案や関連する作業)に基づいていることを示しています。

技術的詳細

このコミットの主要な技術的変更点は、log/syslog.Writer の内部構造と、ログメッセージの書き込みロジックにあります。

  1. 遅延接続 (Deferred Connections):

    • 以前の Dial 関数は、呼び出された時点で net.Dial を実行し、接続を確立していました。
    • 変更後、Dial 関数は Writer 構造体を初期化しますが、実際のネットワーク接続(w.conn)は nil のままです。
    • 接続の確立は、最初のログメッセージが書き込まれる際、または明示的に connect() メソッドが呼び出される際に初めて行われます。これにより、syslogデーモンがまだ起動していない場合でも、アプリケーションの起動時にエラーが発生するのを防ぎます。
  2. 自動再試行メカニズム:

    • 新しい writeAndRetry メソッドが導入されました。このメソッドは、すべてのログレベル(Emerg, Alert, Crit, Err, Warning, Notice, Info, Debug, および Write)から呼び出されます。
    • writeAndRetry は、まず既存の接続(w.conn)を使用してログメッセージの書き込みを試みます。
    • 書き込みが失敗した場合(エラーが発生した場合)、w.connnil に設定し、connect() メソッドを呼び出して新しい接続の確立を試みます。
    • 新しい接続が正常に確立された場合、ログメッセージは再度送信されます。これにより、一時的なネットワークの問題やsyslogデーモンの再起動による接続切断から自動的に回復し、ログメッセージの損失を防ぎます。
    • この再試行は「単一試行」であり、一度の書き込み失敗に対して一度だけ再接続と再送信を試みます。連続して失敗する場合は、それ以上の再試行は行いません。
  3. sync.Mutex を用いた並行処理の安全性:

    • Writer 構造体に sync.Mutex 型の mu フィールドが追加されました。
    • connect() メソッドと writeAndRetry メソッドは、w.mu.Lock()w.mu.Unlock() を使用して、w.conn フィールドへのアクセスを保護します。これにより、複数のGoroutineが同時にログメッセージを書き込もうとしたり、接続を再確立しようとしたりする際に発生する可能性のあるデータ競合を防ぎ、スレッドセーフな操作を保証します。
  4. Writer 構造体の変更:

    • Writer 構造体から conn serverConn フィールドが削除され、代わりに network stringraddr string フィールドが追加されました。これにより、Writer は接続先のネットワークタイプとアドレスを記憶し、必要に応じて再接続できるようになります。
    • mu sync.Mutex フィールドが追加されました。
    • serverConn インターフェースと netConn 構造体は削除され、Writer 自身が net.Conn を直接保持し、接続管理のロジックを内部に持つようになりました。これにより、コードが簡素化され、Writer が接続のライフサイクルを完全に制御できるようになります。
  5. テストの強化:

    • syslog_test.go には、新しい再試行ロジックと並行処理の安全性を検証するためのテストケースが追加されました。
    • TestFlap テストは、syslogサーバーが一度停止して再起動した場合に、Writer が自動的に再接続してログメッセージを送信できることを確認します。
    • TestConcurrentWrite は、複数のGoroutineが同時にログを書き込む際の並行処理の安全性をテストします。
    • TestConcurrentReconnect は、サーバーがクラッシュする状況で複数のGoroutineが同時にログを書き込み、再接続が正しく行われるかを検証します。

これらの変更により、log/syslog パッケージは、より堅牢で信頼性の高いログ送信機能を提供するようになりました。

コアとなるコードの変更箇所

src/pkg/log/syslog/syslog.go

  • Writer 構造体の変更:

    type Writer struct {
    	priority Priority
    	tag      string
    	hostname string
    	network  string // 追加
    	raddr    string // 追加
    	mu   sync.Mutex // 追加
    	conn net.Conn   // 変更: serverConn から net.Conn に
    }
    

    serverConn インターフェースと netConn 構造体が削除され、Writer が直接 net.Conn を保持し、接続情報 (network, raddr) とミューテックス (mu) を持つようになりました。

  • Dial 関数の変更:

    func Dial(network, raddr string, priority Priority, tag string) (*Writer, error) {
    	// ... (引数チェック、hostname, tagの取得)
    	w := &Writer{
    		priority: priority,
    		tag:      tag,
    		hostname: hostname,
    		network:  network,
    		raddr:    raddr,
    	}
    	w.mu.Lock()
    	defer w.mu.Unlock()
    	err := w.connect() // 最初の接続試行
    	if err != nil {
    		return nil, err
    	}
    	return w, err
    }
    

    DialWriter を初期化し、connect() を呼び出して最初の接続を試みます。

  • connect() メソッドの追加:

    func (w *Writer) connect() (err error) {
    	if w.conn != nil {
    		w.conn.Close() // 既存接続があれば閉じる
    		w.conn = nil
    	}
    	// ... (networkとraddrに基づいてnet.Dialを呼び出し、w.connを設定)
    	return
    }
    

    このメソッドは、Writer の内部で接続を確立または再確立するために使用されます。w.mu で保護されます。

  • Write およびログレベルごとのメソッドの変更: すべてのログ書き込みメソッド(Write, Emerg, Alert, Crit, Err, Warning, Notice, Info, Debug)が、writeString の代わりに writeAndRetry を呼び出すように変更されました。

  • writeAndRetry() メソッドの追加:

    func (w *Writer) writeAndRetry(p Priority, s string) (int, error) {
    	pr := (w.priority & facilityMask) | (p & severityMask)
    
    	w.mu.Lock()
    	defer w.mu.Unlock()
    
    	if w.conn != nil {
    		if n, err := w.write(pr, s); err == nil {
    			return n, err // 成功したら即座に返す
    		}
    	}
    	if err := w.connect(); err != nil { // 失敗したら再接続を試みる
    		return 0, err
    	}
    	return w.write(pr, s) // 再接続後、再度書き込みを試みる
    }
    

    このメソッドが、再試行ロジックとミューテックスによる保護の中心です。

  • write() メソッドの変更:

    func (w *Writer) write(p Priority, msg string) (int, error) {
    	// ... (syslogフォーマットの文字列を生成し、w.connに書き込む)
    	fmt.Fprintf(w.conn, "<%d>%s %s %s[%d]: %s%s",
    		p, timestamp, w.hostname,
    		w.tag, os.Getpid(), msg, nl)
    	return len(msg), nil
    }
    

    netConn のメソッドだった writeStringWriter のメソッド write に変更され、w.conn を直接使用するように簡素化されました。

  • Close() メソッドの変更:

    func (w *Writer) Close() error {
    	w.mu.Lock()
    	defer w.mu.Unlock()
    
    	if w.conn != nil {
    		err := w.conn.Close()
    		w.conn = nil
    		return err
    	}
    	return nil
    }
    

    Close メソッドもミューテックスで保護され、接続を安全に閉じ、w.connnil に設定します。

src/pkg/log/syslog/syslog_test.go

  • テストサーバーの改善: runPktSyslogrunStreamSyslog が追加され、UDP/UNIXグラムソケットとTCP/UNIXストリームソケットの両方に対応するテストサーバーがより柔軟に設定できるようになりました。 startServer 関数が、ネットワークタイプとアドレスに基づいて適切なサーバーを起動するように変更されました。

  • 新しいテストケースの追加:

    • TestWithSimulated: 様々なトランスポート(unix, unixgram, udp, tcp)でsyslogの書き込みが正しく機能するかを検証します。
    • TestFlap: syslogサーバーが一時的に利用できなくなった場合に、Writer が自動的に再接続してログを送信できるかをテストします。
    • TestConcurrentWrite: 複数のGoroutineが同時にログを書き込む際の並行処理の安全性を検証します。
    • TestConcurrentReconnect: サーバーがクラッシュする状況で、複数のGoroutineが同時にログを書き込み、再接続が正しく行われるかをテストします。

src/pkg/log/syslog/syslog_unix.go

  • unixSyslog() の変更:
    func unixSyslog() (conn net.Conn, err error) {
    	// ...
    	for _, network := range logTypes {
    		for _, path := range logPaths {
    			conn, err := net.Dial(network, path)
    			if err != nil {
    				continue
    			} else {
    				return conn, nil // netConn{conn} から conn に変更
    			}
    		}
    	}
    	// ...
    }
    
    unixSyslog 関数が serverConn インターフェースではなく、直接 net.Conn を返すように変更されました。これにより、syslog.go から serverConn インターフェースが削除されたことと整合性が取れます。

コアとなるコードの解説

このコミットの核心は、log/syslog.Writer がログメッセージの送信時に接続の健全性を自律的に管理するようになった点です。

  1. Writer 構造体の自己完結性: 以前は WriterserverConn インターフェースを介して実際の接続操作を委譲していましたが、変更後は Writer 自身が net.Conn を直接持ち、接続に必要な情報 (network, raddr) を保持するようになりました。これにより、Writer は接続の確立、使用、再確立、クローズといったライフサイクル全体を自己完結的に管理できるようになり、コードの凝集度が高まりました。

  2. Dial の「遅延接続」と初期接続の堅牢性: Dial 関数は、Writer インスタンスを作成する際に、すぐにネットワーク接続を確立しようとします。もしこの初期接続が失敗した場合、Dial はエラーを返します。これは、アプリケーションがログを送信する準備ができていないことを早期に通知する役割を果たします。しかし、writeAndRetry メソッドの存在により、この初期接続が失敗しても、その後の最初の書き込み時に再接続が試みられるため、一時的な問題であれば回復可能です。

  3. writeAndRetry による自動回復: writeAndRetry メソッドは、ログ送信の信頼性を劇的に向上させます。

    • 初回書き込み: w.connnil の場合(Dial で接続が確立されなかったか、以前の書き込みで接続が切断された場合)、まず connect() を呼び出して接続を確立しようとします。
    • 既存接続での書き込み: w.conn が存在する場合、まずその接続を使って書き込みを試みます。
    • エラー時の再試行: 書き込みが失敗した場合、これは接続が切断されたか、無効になったことを示唆します。writeAndRetryw.connnil にリセットし、再度 connect() を呼び出して新しい接続を確立します。そして、その新しい接続でログメッセージを再送信します。この「一度だけ再試行」というポリシーは、無限ループを防ぎつつ、一般的な一時的なネットワーク障害やsyslogデーモンの再起動に対応するバランスの取れたアプローチです。
  4. sync.Mutex による並行処理の安全性: Writer インスタンスは、複数のGoroutineから同時にアクセスされる可能性があります(例: 複数のGoroutineがそれぞれログメッセージを生成し、同じ Writer インスタンスを使ってsyslogに送信する場合)。w.mu ミューテックスは、w.conn フィールドへのアクセス(読み書き、Closeconnect)を排他的に保護します。これにより、接続の確立や再確立中に別のGoroutineが古い無効な接続を使おうとしたり、同時に複数のGoroutineが接続を確立しようとして競合状態に陥ったりするのを防ぎます。これは、Goの並行処理モデルにおいて、共有リソースを安全に扱うための標準的なプラクティスです。

これらの変更により、Goの log/syslog パッケージは、より堅牢で、障害回復力があり、並行処理に安全なログ送信クライアントとして機能するようになりました。

関連リンク

参考にした情報源リンク

このコミットは、Go言語の標準ライブラリ log/syslog パッケージにおける重要な改善を扱っています。log/syslog パッケージは、UNIXドメインソケット、UDP、またはTCP接続を使用して、システムログサービス(syslogデーモン)にメッセージを送信するためのシンプルなインターフェースを提供します。このパッケージは、Goアプリケーションがシステムレベルのログを生成し、集中管理されたログシステムに送信するために利用されます。

コミット

このコミットは、log/syslog パッケージにおいて、syslogへの書き込みが失敗した場合に一度だけ自動的に再試行するメカニズムを導入し、接続の遅延確立(deferred connections)を実装します。これにより、ネットワークの一時的な問題やsyslogデーモンの再起動などによって接続が切断された場合でも、ログメッセージが失われる可能性を低減し、より堅牢なログ送信を実現します。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/33995fe59eddb3f84e537a8d44e39ef93bc764e8

元コミット内容

commit 33995fe59eddb3f84e537a8d44e39ef93bc764e8
Author: Jeff R. Allen <jra@nella.org>
Date:   Tue Feb 5 09:54:01 2013 -0800

    log/syslog: retry once if write fails
    
    Implements deferred connections + single-attempt automatic
    retry. Based on CL 5078042 from kuroneko.
    
    Fixes #2264.
    
    R=mikioh.mikioh, rsc, bradfitz
    CC=golang-dev
    https://golang.org/cl/6782140

変更の背景

この変更の背景には、Goの log/syslog パッケージが抱えていた、syslogデーモンへの接続が切断された際にログメッセージが失われるという問題がありました。具体的には、Go issue #2264 で報告されたように、syslogデーモンが再起動したり、ネットワーク接続が一時的に不安定になったりすると、log/syslogWriter インスタンスが保持している接続が無効になり、それ以降のログ書き込みがすべて失敗してしまうという挙動がありました。

従来の log/syslog 実装では、Dial 関数が呼び出された時点で一度だけ接続を確立し、その接続を再利用していました。接続が切断された場合、自動的に再接続するメカニズムがなかったため、アプリケーションはエラーを検知して Dial を再呼び出しするか、新しい Writer インスタンスを作成する必要がありました。これは、ログ送信の堅牢性を損ない、開発者に追加の複雑なエラーハンドリングを強いるものでした。

このコミットは、この問題を解決するために、書き込み失敗時に一度だけ自動的に再接続を試み、ログメッセージの再送信を行うことで、より信頼性の高いログ送信を実現することを目的としています。また、「deferred connections(遅延接続)」という概念が導入され、Dial 時点では接続を確立せず、最初の書き込み時に初めて接続を試みることで、リソースの効率的な利用と、syslogデーモンがまだ利用可能でない場合の起動時のエラー回避にも貢献しています。

前提知識の解説

Syslog

Syslogは、システムメッセージやイベントログを収集、保存、管理するための標準的なプロトコルです。UNIX系OSで広く利用されており、アプリケーションやシステムデーモンが生成するログメッセージを一元的に管理するために使われます。ログメッセージは、ファシリティ(メッセージの発生源、例: カーネル、ユーザーレベル、メールシステムなど)とセベリティ(メッセージの重要度、例: 緊急、警告、情報など)という2つの属性を持ちます。

UNIXドメインソケット、UDP、TCP

Syslogメッセージの送信には、主に以下の3つの通信プロトコルが使用されます。

  • UNIXドメインソケット (UNIX domain sockets): 同じホスト上のプロセス間通信に特化したソケットです。ファイルシステム上のパス(例: /dev/log, /var/run/syslog)を介して通信します。ネットワークオーバーヘッドがなく、高速で信頼性が高いのが特徴です。
  • UDP (User Datagram Protocol): コネクションレス型のプロトコルで, メッセージをパケットとして送信します。高速ですが、パケットの到達保証や順序保証はありません。ログの量が非常に多く、一部のメッセージが失われても問題ない場合に利用されることがあります。
  • TCP (Transmission Control Protocol): コネクション指向型のプロトコルで、信頼性の高いデータ転送を提供します。メッセージの到達と順序が保証されますが、UDPよりもオーバーヘッドが大きくなります。

net.Dialnet.Conn

Go言語の net パッケージは、ネットワークI/Oのプリミティブを提供します。

  • net.Dial(network, address string): 指定されたネットワーク(例: "tcp", "udp", "unix")とアドレスに接続を確立します。成功すると net.Conn インターフェースを実装するオブジェクトを返します。
  • net.Conn インターフェース: ネットワーク接続の一般的なインターフェースを定義します。ReadWriteClose などのメソッドを持ち、TCP接続、UDP接続、UNIXドメインソケット接続など、様々な種類のネットワーク接続を抽象化します。

sync.Mutex

sync.Mutex は、Go言語の sync パッケージで提供される相互排他ロック(ミューテックス)です。複数のGoroutineが共有リソースに同時にアクセスするのを防ぎ、データ競合(data race)を回避するために使用されます。Lock() メソッドでロックを取得し、Unlock() メソッドでロックを解放します。このコミットでは、Writer 構造体の conn フィールドへのアクセスを保護するために使用され、複数のGoroutineからのログ書き込みが安全に行われるようにします。

Goの log パッケージ

Goの標準ライブラリ log パッケージは、シンプルなロギング機能を提供します。log.Logger 型は、メッセージの出力先(io.Writer インターフェースを実装する任意のオブジェクト)、プレフィックス、およびフラグを設定できます。log/syslog パッケージは、log.Logger の出力先としてsyslogデーモンを利用できるようにするものです。

CL (Change List)

Goコミュニティでは、コード変更の提案を「Change List (CL)」と呼びます。これは、Gerritというコードレビューシステムで管理され、変更がコミットされる前にレビューと承認を受けます。コミットメッセージに記載されている https://golang.org/cl/6782140 は、このコミットに対応するGerritのCLへのリンクです。また、Based on CL 5078042 from kuroneko は、この変更が別のCL(おそらく初期の提案や関連する作業)に基づいていることを示しています。

技術的詳細

このコミットの主要な技術的変更点は、log/syslog.Writer の内部構造と、ログメッセージの書き込みロジックにあります。

  1. 遅延接続 (Deferred Connections):

    • 以前の Dial 関数は、呼び出された時点で net.Dial を実行し、接続を確立していました。
    • 変更後、Dial 関数は Writer 構造体を初期化しますが、実際のネットワーク接続(w.conn)は nil のままです。
    • 接続の確立は、最初のログメッセージが書き込まれる際、または明示的に connect() メソッドが呼び出される際に初めて行われます。これにより、syslogデーモンがまだ起動していない場合でも、アプリケーションの起動時にエラーが発生するのを防ぎます。
  2. 自動再試行メカニズム:

    • 新しい writeAndRetry メソッドが導入されました。このメソッドは、すべてのログレベル(Emerg, Alert, Crit, Err, Warning, Notice, Info, Debug, および Write)から呼び出されます。
    • writeAndRetry は、まず既存の接続(w.conn)を使用してログメッセージの書き込みを試みます。
    • 書き込みが失敗した場合(エラーが発生した場合)、w.connnil に設定し、connect() メソッドを呼び出して新しい接続の確立を試みます。
    • 新しい接続が正常に確立された場合、ログメッセージは再度送信されます。これにより、一時的なネットワークの問題やsyslogデーモンの再起動による接続切断から自動的に回復し、ログメッセージの損失を防ぎます。
    • この再試行は「単一試行」であり、一度の書き込み失敗に対して一度だけ再接続と再送信を試みます。連続して失敗する場合は、それ以上の再試行は行いません。
  3. sync.Mutex を用いた並行処理の安全性:

    • Writer 構造体に sync.Mutex 型の mu フィールドが追加されました。
    • connect() メソッドと writeAndRetry メソッドは、w.mu.Lock()w.mu.Unlock() を使用して、w.conn フィールドへのアクセスを保護します。これにより、複数のGoroutineが同時にログメッセージを書き込もうとしたり、接続を再確立しようとしたりする際に発生する可能性のあるデータ競合を防ぎ、スレッドセーフな操作を保証します。
  4. Writer 構造体の変更:

    • Writer 構造体から conn serverConn フィールドが削除され、代わりに network stringraddr string フィールドが追加されました。これにより、Writer は接続先のネットワークタイプとアドレスを記憶し、必要に応じて再接続できるようになります。
    • mu sync.Mutex フィールドが追加されました。
    • serverConn インターフェースと netConn 構造体は削除され、Writer 自身が net.Conn を直接保持し、接続管理のロジックを内部に持つようになりました。これにより、コードが簡素化され、Writer が接続のライフサイクルを完全に制御できるようになります。
  5. テストの強化:

    • syslog_test.go には、新しい再試行ロジックと並行処理の安全性を検証するためのテストケースが追加されました。
    • TestFlap テストは、syslogサーバーが一度停止して再起動した場合に、Writer が自動的に再接続してログメッセージを送信できることを確認します。
    • TestConcurrentWrite は、複数のGoroutineが同時にログを書き込む際の並行処理の安全性をテストします。
    • TestConcurrentReconnect は、サーバーがクラッシュする状況で複数のGoroutineが同時にログを書き込み、再接続が正しく行われるかを検証します。

これらの変更により、log/syslog パッケージは、より堅牢で信頼性の高いログ送信機能を提供するようになりました。

コアとなるコードの変更箇所

src/pkg/log/syslog/syslog.go

  • Writer 構造体の変更:

    type Writer struct {
    	priority Priority
    	tag      string
    	hostname string
    	network  string // 追加
    	raddr    string // 追加
    	mu   sync.Mutex // 追加
    	conn net.Conn   // 変更: serverConn から net.Conn に
    }
    

    serverConn インターフェースと netConn 構造体が削除され、Writer が直接 net.Conn を保持し、接続情報 (network, raddr) とミューテックス (mu) を持つようになりました。

  • Dial 関数の変更:

    func Dial(network, raddr string, priority Priority, tag string) (*Writer, error) {
    	// ... (引数チェック、hostname, tagの取得)
    	w := &Writer{
    		priority: priority,
    		tag:      tag,
    		hostname: hostname,
    		network:  network,
    		raddr:    raddr,
    	}
    	w.mu.Lock()
    	defer w.mu.Unlock()
    	err := w.connect() // 最初の接続試行
    	if err != nil {
    		return nil, err
    	}
    	return w, err
    }
    

    DialWriter を初期化し、connect() を呼び出して最初の接続を試みます。

  • connect() メソッドの追加:

    func (w *Writer) connect() (err error) {
    	if w.conn != nil {
    		w.conn.Close() // 既存接続があれば閉じる
    		w.conn = nil
    	}
    	// ... (networkとraddrに基づいてnet.Dialを呼び出し、w.connを設定)
    	return
    }
    

    このメソッドは、Writer の内部で接続を確立または再確立するために使用されます。w.mu で保護されます。

  • Write およびログレベルごとのメソッドの変更: すべてのログ書き込みメソッド(Write, Emerg, Alert, Crit, Err, Warning, Notice, Info, Debug)が、writeString の代わりに writeAndRetry を呼び出すように変更されました。

  • writeAndRetry() メソッドの追加:

    func (w *Writer) writeAndRetry(p Priority, s string) (int, error) {
    	pr := (w.priority & facilityMask) | (p & severityMask)
    
    	w.mu.Lock()
    	defer w.mu.Unlock()
    
    	if w.conn != nil {
    		if n, err := w.write(pr, s); err == nil {
    			return n, err // 成功したら即座に返す
    		}
    	}
    	if err := w.connect(); err != nil { // 失敗したら再接続を試みる
    		return 0, err
    	}
    	return w.write(pr, s) // 再接続後、再度書き込みを試みる
    }
    

    このメソッドが、再試行ロジックとミューテックスによる保護の中心です。

  • write() メソッドの変更:

    func (w *Writer) write(p Priority, msg string) (int, error) {
    	// ... (syslogフォーマットの文字列を生成し、w.connに書き込む)
    	fmt.Fprintf(w.conn, "<%d>%s %s %s[%d]: %s%s",
    		p, timestamp, w.hostname,
    		w.tag, os.Getpid(), msg, nl)
    	return len(msg), nil
    }
    

    netConn のメソッドだった writeStringWriter のメソッド write に変更され、w.conn を直接使用するように簡素化されました。

  • Close() メソッドの変更:

    func (w *Writer) Close() error {
    	w.mu.Lock()
    	defer w.mu.Unlock()
    
    	if w.conn != nil {
    		err := w.conn.Close()
    		w.conn = nil
    		return err
    	}
    	return nil
    }
    

    Close メソッドもミューテックスで保護され、接続を安全に閉じ、w.connnil に設定します。

src/pkg/log/syslog/syslog_test.go

  • テストサーバーの改善: runPktSyslogrunStreamSyslog が追加され、UDP/UNIXグラムソケットとTCP/UNIXストリームソケットの両方に対応するテストサーバーがより柔軟に設定できるようになりました。 startServer 関数が、ネットワークタイプとアドレスに基づいて適切なサーバーを起動するように変更されました。

  • 新しいテストケースの追加:

    • TestWithSimulated: 様々なトランスポート(unix, unixgram, udp, tcp)でsyslogの書き込みが正しく機能するかを検証します。
    • TestFlap: syslogサーバーが一時的に利用できなくなった場合に、Writer が自動的に再接続してログを送信できるかをテストします。
    • TestConcurrentWrite: 複数のGoroutineが同時にログを書き込む際の並行処理の安全性を検証します。
    • TestConcurrentReconnect: サーバーがクラッシュする状況で、複数のGoroutineが同時にログを書き込み、再接続が正しく行われるかをテストします。

src/pkg/log/syslog/syslog_unix.go

  • unixSyslog() の変更:
    func unixSyslog() (conn net.Conn, err error) {
    	// ...
    	for _, network := range logTypes {
    		for _, path := range logPaths {
    			conn, err := net.Dial(network, path)
    			if err != nil {
    				continue
    			} else {
    				return conn, nil // netConn{conn} から conn に変更
    			}
    		}
    	}
    	// ...
    }
    
    unixSyslog 関数が serverConn インターフェースではなく、直接 net.Conn を返すように変更されました。これにより、syslog.go から serverConn インターフェースが削除されたことと整合性が取れます。

コアとなるコードの解説

このコミットの核心は、log/syslog.Writer がログメッセージの送信時に接続の健全性を自律的に管理するようになった点です。

  1. Writer 構造体の自己完結性: 以前は WriterserverConn インターフェースを介して実際の接続操作を委譲していましたが、変更後は Writer が直接 net.Conn を持ち、接続に必要な情報 (network, raddr) を保持するようになりました。これにより、Writer は接続の確立、使用、再確立、クローズといったライフサイクル全体を自己完結的に管理できるようになり、コードの凝集度が高まりました。

  2. Dial の「遅延接続」と初期接続の堅牢性: Dial 関数は、Writer インスタンスを作成する際に、すぐにネットワーク接続を確立しようとします。もしこの初期接続が失敗した場合、Dial はエラーを返します。これは、アプリケーションがログを送信する準備ができていないことを早期に通知する役割を果たします。しかし、writeAndRetry メソッドの存在により、この初期接続が失敗しても、その後の最初の書き込み時に再接続が試みられるため、一時的な問題であれば回復可能です。

  3. writeAndRetry による自動回復: writeAndRetry メソッドは、ログ送信の信頼性を劇的に向上させます。

    • 初回書き込み: w.connnil の場合(Dial で接続が確立されなかったか、以前の書き込みで接続が切断された場合)、まず connect() を呼び出して接続を確立しようとします。
    • 既存接続での書き込み: w.conn が存在する場合、まずその接続を使って書き込みを試みます。
    • エラー時の再試行: 書き込みが失敗した場合、これは接続が切断されたか、無効になったことを示唆します。writeAndRetryw.connnil にリセットし、再度 connect() を呼び出して新しい接続を確立します。そして、その新しい接続でログメッセージを再送信します。この「一度だけ再試行」というポリシーは、無限ループを防ぎつつ、一般的な一時的なネットワーク障害やsyslogデーモンの再起動に対応するバランスの取れたアプローチです。
  4. sync.Mutex による並行処理の安全性: Writer インスタンスは、複数のGoroutineから同時にアクセスされる可能性があります(例: 複数のGoroutineがそれぞれログメッセージを生成し、同じ Writer インスタンスを使ってsyslogに送信する場合)。w.mu ミューテックスは、w.conn フィールドへのアクセス(読み書き、Closeconnect)を排他的に保護します。これにより、接続の確立や再確立中に別のGoroutineが古い無効な接続を使おうとしたり、同時に複数のGoroutineが接続を確立しようとして競合状態に陥ったりするのを防ぎます。これは、Goの並行処理モデルにおいて、共有リソースを安全に扱うための標準的なプラクティスです。

これらの変更により、Goの log/syslog パッケージは、より堅牢で、障害回復力があり、並行処理に安全なログ送信クライアントとして機能するようになりました。

関連リンク

参考にした情報源リンク