[インデックス 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/syslog
の Writer
インスタンスが保持している接続が無効になり、それ以降のログ書き込みがすべて失敗してしまうという挙動がありました。
従来の 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.Dial
と net.Conn
Go言語の net
パッケージは、ネットワークI/Oのプリミティブを提供します。
net.Dial(network, address string)
: 指定されたネットワーク(例: "tcp", "udp", "unix")とアドレスに接続を確立します。成功するとnet.Conn
インターフェースを実装するオブジェクトを返します。net.Conn
インターフェース: ネットワーク接続の一般的なインターフェースを定義します。Read
、Write
、Close
などのメソッドを持ち、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
の内部構造と、ログメッセージの書き込みロジックにあります。
-
遅延接続 (Deferred Connections):
- 以前の
Dial
関数は、呼び出された時点でnet.Dial
を実行し、接続を確立していました。 - 変更後、
Dial
関数はWriter
構造体を初期化しますが、実際のネットワーク接続(w.conn
)はnil
のままです。 - 接続の確立は、最初のログメッセージが書き込まれる際、または明示的に
connect()
メソッドが呼び出される際に初めて行われます。これにより、syslogデーモンがまだ起動していない場合でも、アプリケーションの起動時にエラーが発生するのを防ぎます。
- 以前の
-
自動再試行メカニズム:
- 新しい
writeAndRetry
メソッドが導入されました。このメソッドは、すべてのログレベル(Emerg
,Alert
,Crit
,Err
,Warning
,Notice
,Info
,Debug
, およびWrite
)から呼び出されます。 writeAndRetry
は、まず既存の接続(w.conn
)を使用してログメッセージの書き込みを試みます。- 書き込みが失敗した場合(エラーが発生した場合)、
w.conn
をnil
に設定し、connect()
メソッドを呼び出して新しい接続の確立を試みます。 - 新しい接続が正常に確立された場合、ログメッセージは再度送信されます。これにより、一時的なネットワークの問題やsyslogデーモンの再起動による接続切断から自動的に回復し、ログメッセージの損失を防ぎます。
- この再試行は「単一試行」であり、一度の書き込み失敗に対して一度だけ再接続と再送信を試みます。連続して失敗する場合は、それ以上の再試行は行いません。
- 新しい
-
sync.Mutex
を用いた並行処理の安全性:Writer
構造体にsync.Mutex
型のmu
フィールドが追加されました。connect()
メソッドとwriteAndRetry
メソッドは、w.mu.Lock()
とw.mu.Unlock()
を使用して、w.conn
フィールドへのアクセスを保護します。これにより、複数のGoroutineが同時にログメッセージを書き込もうとしたり、接続を再確立しようとしたりする際に発生する可能性のあるデータ競合を防ぎ、スレッドセーフな操作を保証します。
-
Writer
構造体の変更:Writer
構造体からconn serverConn
フィールドが削除され、代わりにnetwork string
とraddr string
フィールドが追加されました。これにより、Writer
は接続先のネットワークタイプとアドレスを記憶し、必要に応じて再接続できるようになります。mu sync.Mutex
フィールドが追加されました。serverConn
インターフェースとnetConn
構造体は削除され、Writer
自身がnet.Conn
を直接保持し、接続管理のロジックを内部に持つようになりました。これにより、コードが簡素化され、Writer
が接続のライフサイクルを完全に制御できるようになります。
-
テストの強化:
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 }
Dial
はWriter
を初期化し、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
のメソッドだったwriteString
がWriter
のメソッド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.conn
をnil
に設定します。
src/pkg/log/syslog/syslog_test.go
-
テストサーバーの改善:
runPktSyslog
とrunStreamSyslog
が追加され、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
がログメッセージの送信時に接続の健全性を自律的に管理するようになった点です。
-
Writer
構造体の自己完結性: 以前はWriter
がserverConn
インターフェースを介して実際の接続操作を委譲していましたが、変更後はWriter
自身がnet.Conn
を直接持ち、接続に必要な情報 (network
,raddr
) を保持するようになりました。これにより、Writer
は接続の確立、使用、再確立、クローズといったライフサイクル全体を自己完結的に管理できるようになり、コードの凝集度が高まりました。 -
Dial
の「遅延接続」と初期接続の堅牢性:Dial
関数は、Writer
インスタンスを作成する際に、すぐにネットワーク接続を確立しようとします。もしこの初期接続が失敗した場合、Dial
はエラーを返します。これは、アプリケーションがログを送信する準備ができていないことを早期に通知する役割を果たします。しかし、writeAndRetry
メソッドの存在により、この初期接続が失敗しても、その後の最初の書き込み時に再接続が試みられるため、一時的な問題であれば回復可能です。 -
writeAndRetry
による自動回復:writeAndRetry
メソッドは、ログ送信の信頼性を劇的に向上させます。- 初回書き込み:
w.conn
がnil
の場合(Dial
で接続が確立されなかったか、以前の書き込みで接続が切断された場合)、まずconnect()
を呼び出して接続を確立しようとします。 - 既存接続での書き込み:
w.conn
が存在する場合、まずその接続を使って書き込みを試みます。 - エラー時の再試行: 書き込みが失敗した場合、これは接続が切断されたか、無効になったことを示唆します。
writeAndRetry
はw.conn
をnil
にリセットし、再度connect()
を呼び出して新しい接続を確立します。そして、その新しい接続でログメッセージを再送信します。この「一度だけ再試行」というポリシーは、無限ループを防ぎつつ、一般的な一時的なネットワーク障害やsyslogデーモンの再起動に対応するバランスの取れたアプローチです。
- 初回書き込み:
-
sync.Mutex
による並行処理の安全性:Writer
インスタンスは、複数のGoroutineから同時にアクセスされる可能性があります(例: 複数のGoroutineがそれぞれログメッセージを生成し、同じWriter
インスタンスを使ってsyslogに送信する場合)。w.mu
ミューテックスは、w.conn
フィールドへのアクセス(読み書き、Close
、connect
)を排他的に保護します。これにより、接続の確立や再確立中に別のGoroutineが古い無効な接続を使おうとしたり、同時に複数のGoroutineが接続を確立しようとして競合状態に陥ったりするのを防ぎます。これは、Goの並行処理モデルにおいて、共有リソースを安全に扱うための標準的なプラクティスです。
これらの変更により、Goの log/syslog
パッケージは、より堅牢で、障害回復力があり、並行処理に安全なログ送信クライアントとして機能するようになりました。
関連リンク
- Go Issue #2264: https://github.com/golang/go/issues/2264 - このコミットが修正したバグの報告。
- Go Change List 6782140: https://golang.org/cl/6782140 - このコミットに対応するGerritの変更リスト。
- Go Change List 5078042: https://golang.org/cl/5078042 - コミットメッセージで参照されている、関連する先行の変更リスト。
参考にした情報源リンク
- Go
log/syslog
package documentation (GoDoc): https://pkg.go.dev/log/syslog - Go
net
package documentation (GoDoc): https://pkg.go.dev/net - Go
sync
package documentation (GoDoc): https://pkg.go.dev/sync - Syslog - Wikipedia: https://ja.wikipedia.org/wiki/Syslog
- UNIXドメインソケット - Wikipedia: https://ja.wikipedia.org/wiki/UNIX%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88
- TCP/IP - Wikipedia: https://ja.wikipedia.org/wiki/TCP/IP
- UDP - Wikipedia: https://ja.wikipedia.org/wiki/User_Datagram_Protocol
- Go言語における並行処理と同期 (Mutex): https://go.dev/blog/sync-mutex (Go公式ブログの関連トピック)
- Gerrit Code Review: https://www.gerritcodereview.com/# [インデックス 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/syslog
の Writer
インスタンスが保持している接続が無効になり、それ以降のログ書き込みがすべて失敗してしまうという挙動がありました。
従来の 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.Dial
と net.Conn
Go言語の net
パッケージは、ネットワークI/Oのプリミティブを提供します。
net.Dial(network, address string)
: 指定されたネットワーク(例: "tcp", "udp", "unix")とアドレスに接続を確立します。成功するとnet.Conn
インターフェースを実装するオブジェクトを返します。net.Conn
インターフェース: ネットワーク接続の一般的なインターフェースを定義します。Read
、Write
、Close
などのメソッドを持ち、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
の内部構造と、ログメッセージの書き込みロジックにあります。
-
遅延接続 (Deferred Connections):
- 以前の
Dial
関数は、呼び出された時点でnet.Dial
を実行し、接続を確立していました。 - 変更後、
Dial
関数はWriter
構造体を初期化しますが、実際のネットワーク接続(w.conn
)はnil
のままです。 - 接続の確立は、最初のログメッセージが書き込まれる際、または明示的に
connect()
メソッドが呼び出される際に初めて行われます。これにより、syslogデーモンがまだ起動していない場合でも、アプリケーションの起動時にエラーが発生するのを防ぎます。
- 以前の
-
自動再試行メカニズム:
- 新しい
writeAndRetry
メソッドが導入されました。このメソッドは、すべてのログレベル(Emerg
,Alert
,Crit
,Err
,Warning
,Notice
,Info
,Debug
, およびWrite
)から呼び出されます。 writeAndRetry
は、まず既存の接続(w.conn
)を使用してログメッセージの書き込みを試みます。- 書き込みが失敗した場合(エラーが発生した場合)、
w.conn
をnil
に設定し、connect()
メソッドを呼び出して新しい接続の確立を試みます。 - 新しい接続が正常に確立された場合、ログメッセージは再度送信されます。これにより、一時的なネットワークの問題やsyslogデーモンの再起動による接続切断から自動的に回復し、ログメッセージの損失を防ぎます。
- この再試行は「単一試行」であり、一度の書き込み失敗に対して一度だけ再接続と再送信を試みます。連続して失敗する場合は、それ以上の再試行は行いません。
- 新しい
-
sync.Mutex
を用いた並行処理の安全性:Writer
構造体にsync.Mutex
型のmu
フィールドが追加されました。connect()
メソッドとwriteAndRetry
メソッドは、w.mu.Lock()
とw.mu.Unlock()
を使用して、w.conn
フィールドへのアクセスを保護します。これにより、複数のGoroutineが同時にログメッセージを書き込もうとしたり、接続を再確立しようとしたりする際に発生する可能性のあるデータ競合を防ぎ、スレッドセーフな操作を保証します。
-
Writer
構造体の変更:Writer
構造体からconn serverConn
フィールドが削除され、代わりにnetwork string
とraddr string
フィールドが追加されました。これにより、Writer
は接続先のネットワークタイプとアドレスを記憶し、必要に応じて再接続できるようになります。mu sync.Mutex
フィールドが追加されました。serverConn
インターフェースとnetConn
構造体は削除され、Writer
自身がnet.Conn
を直接保持し、接続管理のロジックを内部に持つようになりました。これにより、コードが簡素化され、Writer
が接続のライフサイクルを完全に制御できるようになります。
-
テストの強化:
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 }
Dial
はWriter
を初期化し、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
のメソッドだったwriteString
がWriter
のメソッド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.conn
をnil
に設定します。
src/pkg/log/syslog/syslog_test.go
-
テストサーバーの改善:
runPktSyslog
とrunStreamSyslog
が追加され、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
がログメッセージの送信時に接続の健全性を自律的に管理するようになった点です。
-
Writer
構造体の自己完結性: 以前はWriter
がserverConn
インターフェースを介して実際の接続操作を委譲していましたが、変更後はWriter
が直接net.Conn
を持ち、接続に必要な情報 (network
,raddr
) を保持するようになりました。これにより、Writer
は接続の確立、使用、再確立、クローズといったライフサイクル全体を自己完結的に管理できるようになり、コードの凝集度が高まりました。 -
Dial
の「遅延接続」と初期接続の堅牢性:Dial
関数は、Writer
インスタンスを作成する際に、すぐにネットワーク接続を確立しようとします。もしこの初期接続が失敗した場合、Dial
はエラーを返します。これは、アプリケーションがログを送信する準備ができていないことを早期に通知する役割を果たします。しかし、writeAndRetry
メソッドの存在により、この初期接続が失敗しても、その後の最初の書き込み時に再接続が試みられるため、一時的な問題であれば回復可能です。 -
writeAndRetry
による自動回復:writeAndRetry
メソッドは、ログ送信の信頼性を劇的に向上させます。- 初回書き込み:
w.conn
がnil
の場合(Dial
で接続が確立されなかったか、以前の書き込みで接続が切断された場合)、まずconnect()
を呼び出して接続を確立しようとします。 - 既存接続での書き込み:
w.conn
が存在する場合、まずその接続を使って書き込みを試みます。 - エラー時の再試行: 書き込みが失敗した場合、これは接続が切断されたか、無効になったことを示唆します。
writeAndRetry
はw.conn
をnil
にリセットし、再度connect()
を呼び出して新しい接続を確立します。そして、その新しい接続でログメッセージを再送信します。この「一度だけ再試行」というポリシーは、無限ループを防ぎつつ、一般的な一時的なネットワーク障害やsyslogデーモンの再起動に対応するバランスの取れたアプローチです。
- 初回書き込み:
-
sync.Mutex
による並行処理の安全性:Writer
インスタンスは、複数のGoroutineから同時にアクセスされる可能性があります(例: 複数のGoroutineがそれぞれログメッセージを生成し、同じWriter
インスタンスを使ってsyslogに送信する場合)。w.mu
ミューテックスは、w.conn
フィールドへのアクセス(読み書き、Close
、connect
)を排他的に保護します。これにより、接続の確立や再確立中に別のGoroutineが古い無効な接続を使おうとしたり、同時に複数のGoroutineが接続を確立しようとして競合状態に陥ったりするのを防ぎます。これは、Goの並行処理モデルにおいて、共有リソースを安全に扱うための標準的なプラクティスです。
これらの変更により、Goの log/syslog
パッケージは、より堅牢で、障害回復力があり、並行処理に安全なログ送信クライアントとして機能するようになりました。
関連リンク
- Go Issue #2264: https://github.com/golang/go/issues/2264 - このコミットが修正したバグの報告。
- Go Change List 6782140: https://golang.org/cl/6782140 - このコミットに対応するGerritの変更リスト。
- Go Change List 5078042: https://golang.org/cl/5078042 - コミットメッセージで参照されている、関連する先行の変更リスト。
参考にした情報源リンク
- Go
log/syslog
package documentation (GoDoc): https://pkg.go.dev/log/syslog - Go
net
package documentation (GoDoc): https://pkg.go.dev/net - Go
sync
package documentation (GoDoc): https://pkg.go.dev/sync - Syslog - Wikipedia: https://ja.wikipedia.org/wiki/Syslog
- UNIXドメインソケット - Wikipedia: https://ja.wikipedia.org/wiki/UNIX%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88
- TCP/IP - Wikipedia: https://ja.wikipedia.org/wiki/TCP/IP
- UDP - Wikipedia: https://ja.wikipedia.org/wiki/User_Datagram_Protocol
- Go言語における並行処理と同期 (Mutex): https://go.dev/blog/sync-mutex (Go公式ブログの関連トピック)
- Gerrit Code Review: https://www.gerritcodereview.com/