# 第14章 レシーバー

岸が見えなくなることを恐れない勇気が無ければ、地平線の向こうまで泳ぎ着くことはできない。

—WILLIAM FAULKNER

## レシーバーとは何か？

レシーバーとは、リモートアペンダーからロギングイベントを受信し、ローカルポリシーに基づいてログに記録するlogbackのコンポーネントです。ソケットベースのアペンダーとレシーバーを組み合わせることで、分散アプリケーションのロギングイベントをネットワーク越しに配信する高度なトポロジーを実現することができます。

レシーバーは`ch.qos.logback.classic.net.ReceiverBase`クラスを継承しています。おかげでLifeCycleに参加できるし、レシーバー自体がContextAwareになります。

歴史的な経緯により、logback によるネットワーク越しのロギングイベントの配信は`SocketAppender`と`SimpleSocketServer`によって実現されてきました。アペンダーはクライアントとして動作します。サーバーアプリケーションへのネットワーク接続を確立し、ロギングイベントを配信するのです。レシーバーコンポーネントと対応するアペンダーを使うことで、より優れた柔軟性を実現することができます。

レシーバーコンポーネントは他のlogbackのコンポーネントと同様にlogback.xmlで設定します。つまり、Joranの全ての機能を利用することができるのです。さらに、複数のレシーバーコンポーネントを設定するだけで、あらゆるアプリケーションのリモートアペンダーからロギングイベントを受け付けることができます。

アペンダーとレシーバー間のネットワーク接続の確立はどちら側からでも始めることができます。レシーバーはサーバーとして振る舞うことができます。つまり、クライアントたるリモートアペンダーからの接続を待ち受けることができます。レシーバーはクライアントとして振る舞うこともできます。サーバーとして振る舞うリモートアペンダーに対してネットワーク接続を確立することができるのです。アペンダーとレシーバーのそれぞれの役割とは無関係に、ロギングイベントは常にアペンダーからレシーバーに送信されます。

レシーバーからアペンダーに接続できることは、特定の状況においてとても有用です。

- セキュリティ上の観点から、中央ロギングサーバーはファイアーウォールの後ろ側に配置されることが多いです。つまり、外部からの接続が許されないのです。レシーバーコンポーネントがクライアントとして振る舞うことで、中央ロギングサーバー（ファイアーウォールの内側）から対象のアプリケーション（ファイアーウォールの外側）に接続することができます。
- IDEのプラグインなどの開発者ツールに最適です。また、実行中のアプリケーションが生成するロギングイベントのストリームへのアクセスを、企業統制の仕組みによって管理するのにも適しています。伝統的に、logbackでこのような状況に対応する（たとえばlogback Beagleなど）には、宛先のアプリケーション（たとえばIDEで実行される開発者ツール）がサーバーの役割を担う必要がありました。管理が大変になることは明らかです。特に、開発者のワークステーションで実行しているツールならなおさらです。モバイルPCが一般的になってきているのも悩みの種になります。今では、それらのツールはクライアントとしてのレシーバーコンポーネントを実装すればよくなりました。手元でロギングイベントをフィルタしたり、警告を上げたり、内容を確認するには、リモートアペンダーに接続すればよいのです。

logbackの設定には、サーバーあるいはクライアントのいずれかの役割を担うレシーバーコンポーネントを混在させることができます。数少ない制限として、サーバーとして振る舞うレシーバーコンポーネントはそれぞれ固有のポート番号で接続を待ち受けなければならないということと、クライアントとして振る舞うレシーバーコンポーネントはそれぞれただ1つのリモートアペンダーにしか接続してはならないということがあります。

## サーバーとして振る舞うレシーバー

サーバーとして振る舞うレシーバーはリモートアペンダーからの接続を待ち受けます。これはスタンドアローンの`SimpleSocketServer`アプリケーションと同じ機能です。ただし、レシーバーコンポーネントなら、logback.xmlに設定するだけで、logback-classicモジュールを使っているあらゆる{\1}アプリケーションからロギングイベントを受信することができます。

logbackの配布物には二つのレシーバーコンポーネントが含まれています。`ServerSocketReceiver`とそのSSL対応の`SSLServerSocketReceiver`です。どちらも`SocketAppender`（あるいは`SSLSocketAppender`）からの接続を待ち受けるように設計されています。

`ServerSocketReceiver`の設定可能なプロパティは次のとおりです。

プロパティ名 | 型 | 説明
--- | --- | ---
address | `String` | レシーバーが待ち受けるローカルネットワークインターフェイスのネットワークアドレス。このプロパティが指定されない場合、レシーバーは全てのネットワークインターフェイスで待ち受けるようになります。
port | `int` | レシーバーが待ち受けるTCPのポート番号。このプロパティが指定されない場合、デフォルト値が使用されます。
ssl | `SSLConfiguration` | `SSLServerSocketReceiver`でのみサポートされているプロパティです。このプロパティにはSSLを使用するで説明されているSSLの設定を指定します。

### ServerSocketReceiverの使い方

次の設定は、最小限のアペンダーとロガーの設定とともに`ServerSocketReceiver`を使用するものです。リモートアペンダーから受信したロギングイベントは、ルートロガーに渡されて、ローカルのコンソールアペンダーに配信されます。

例：基本的なServerSocketReceiverの設定（logback-examples/src/main/java/chapters/receivers/socket/receiver1.xml）

Groovyとして表示

    <configuration debug="true">

      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
      </appender>

      <root level="DEBUG">
        <appender-ref ref="CONSOLE" />
      </root>

      <receiver class="ch.qos.logback.classic.net.server.ServerSocketReceiver">
        <port>${port}</port>
      </receiver>

    </configuration>

レシーバーコンポーネントのclass属性に指定した値によって、レシーバーのサブタイプを指定します。ここでは`ServerSocketReceiver`を指定しています。

例として、`SimpleSocketServer`と非常によく似た機能を持ったサーバーアプリケーションを用意しました。logbackの設定ファイルへのパスをコマンドライン引数から受け取って、指定された設定ファイルを読み込みます。この例のアプリケーションは大した仕事をするわけではありませんが、`ServerSocketReceiver` （あるいは`SSLServerSocketReceiver`）はどんなアプリケーションからでも利用できることを覚えておきましょう。

コマンドをプロンプトでlogback-examplesディレクトリに移動して、次のコマンドを実行しましょう。

java -Dport=6000 chapters.receivers.socket.ReceiverExample \ src/main/java/chapters/receivers/socket/receiver1.xml

クライアントアプリケーションで`SocketAppender`を設定して、実行中のレシーバーに接続することができます。サンプルのクライアントアプリケーションは、上記のレシーバーに接続する設定ファイルを読み込むだけのものです。クライアントアプリケーションはユーザー入力を待ちます。ユーザー入力はそのままレシーバーに中継されます。クライアントアプリケーションは次のように実行します。

java -Dhost=localhost -Dport=6000 \ chapters.receivers.socket.AppenderExample \ src/main/java/chapters/receivers/socket/appender1.xml

### SSLServerSocketReceiverの使い方

次の設定は、前の設定とは違ってSSLに対応したレシーバーを使うものです。

例：基本的なSSLServerSocketReceiverの設定（logback-examples/src/main/java/chapters/receivers/socket/receiver2.xml）

Groovyとして表示

    <configuration debug="true">

      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
      </appender>

      <root level="DEBUG">
        <appender-ref ref="CONSOLE" />
      </root>

      <receiver class="ch.qos.logback.classic.net.server.SSLServerSocketReceiver">
        <port>${port}</port>
        <ssl>
          <keyStore>
            <location>${keystore}</location>
            <password>${password}</password>
          </keyStore>
        </ssl>
      </receiver>

    </configuration>

前の`ServerSocketReceiver`を使った設定とこちらの設定で根本的に違うのは、レシーバーのclass属性に`SSLServerSocketReceiver`が指定されていることと、sslプロパティがネストされていることです。sslプロパティにはレシーバーの秘密鍵と証明書が配置されたキーストアの場所とパスワードを、変数で指定しています。sslプロパティの設定内容について詳しくはSSLを使用するを参照してください。

いくつか引数を追加して次のようにサーバーアプリケーションを実行します。

java -Dport=6001 \ -Dkeystore=file:src/main/java/chapters/appenders/socket/ssl/keystore.jks \ -Dpassword=changeit \ chapters.receivers.socket.ReceiverExample \ src/main/java/chapters/receivers/socket/receiver2.xml

コマンドラインで指定したプロパティkeystoreには、キーストアのfileから始まるURLを指定します。SSLを使用するでも説明していますが、クラスパス上のリソースを指定することもできます。

このレシーバーには、クライアントアプリケーションから`SSLSocketAppender`で接続することができます。SSL対応のアペンダーを使う設定ファイルを、前の例で使用したクライアントアプリケーションから使うことができます。次のように実行します。

java -Dhost=localhost -Dport=6001 \ -Dtruststore=file:src/main/java/chapters/appenders/socket/ssl/truststore.jks \ -Dpassword=changeit \ chapters.receivers.socket.AppenderExample \ src/main/java/chapters/receivers/socket/appender2.xml

この例では自己署名したX.509証明書を使用していますが、これを使っていいのはテストや実験的な用途だけです。本番環境では、SSL対応のアペンダーのために適切なX.509証明書を使うようにしてください 。詳細はSSLを使用するを参照してください。

## クライアントとして振る舞うレシーバー

クライアントとして振る舞うように設定したレシーバーは、リモートアペンダーに接続します。リモートアペンダーは`ServerSocketAppender`などのサーバ型でなければなりません。

logackの配布物にはクライアントとして振る舞うレシーバーコンポーネントが2つ含まれています。`SocketReceiver`とそのSSL対応版の`SSLSocketReceiver`です。どちらのレシーバーもリモートアペンダー（`ServerSocketAppender`あるいは`SSLServerSocketAppender`）に対して接続を確立するようになっています。

`SocketReceiver`の派生クラスで設定可能なプロパティは次のとおりです。

プロパティ名 | 型 | 説明
--- | --- | ---
remoteHost | `String` | リモートアペンダーのホスト名またはIPアドレス。
port | `int` | リモートアペンダーの待ち受けポート番号。
reconnectionDelay | `int` | 接続異常が発生した後で、再接続をする前に待機する時間を表す正の整数値。単位はミリ秒。デフォルト値は30000（30秒）です。
ssl | `SSLConfiguration` | `SSLSocketReceiver`でのみ設定可能なプロパティ。SSLを使用するで説明しているとおり、SSLの設定を指定します。

### SocketReceiverの使い方

`SocketReceiver`の設定は`ServerSocketReceiver`の設定と非常によく似ています。これらの差は、サーバーとクライアントという真逆の役割に起因するものです。`SocketReceiver`はクライアントで、リモートアペンダはサーバーとして動作します。

例：基本的なSocketReceiverの設定（logback-examples/src/main/java/chapters/receivers/socket/receiver3.xml）

Groovyとして表示

    <configuration debug="true">

      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          <pattern>%date %-5level [%thread] %logger - %message%n</pattern>
        </encoder>
      </appender>

      <root level="DEBUG">
        <appender-ref ref="CONSOLE" />
      </root>

      <receiver class="ch.qos.logback.classic.net.SocketReceiver">
        <remoteHost>${host}</remoteHost>
        <port>${port}</port>
        <reconnectionDelay>10000</reconnectionDelay>
      </receiver>

    </configuration>

この設定だと、logbackはhost変数で指定されたホストの、port変数で指定されたポート番号で実行中の`ServerSocketAppender`へ接続します。リモートアペンダーから受け取ったロギングイベントは、手元でコンソールアペンダーに渡されます。

コマンドプロンプトでlogback-examplesディレクトリに移動して、次のコマンドを実行してみましょう。

サンプルアプリケーションは上記の設定ファイルを読み込んだあと、リモートアペンダーからのロギングイベントを待ち受けます。リモートアペンダーが落ちているときは、定期的に接続を拒否されたメッセージが出力されます。再接続が成功するか、アプリケーションが停止するまで、レシーバーは定期的にリモートアペンダーへの再接続を繰り返します。設定例にあるとおり、再接続の間隔はreconnectionDelayプロパティで指定することができます。

java -Dhost=localhost -Dport=6000 \ chapters.receivers.socket.ReceiverExample \ src/main/java/chapters/receivers/socket/receiver3.xml

この例のレシーバーは、前のアプリケーションのアペンダーにそのまま接続できます。アペンダー用のサンプルアプリケーションは、`ServerSocketAppender`を使った設定ファイルを読み込んでから、ユーザー入力を待ちます。ユーザー入力はアペンダーに接続してきたレシーバーに配信されます。アペンダー用のサンプルアプリケーションを実行してみましょう。

java -Dport=6000 \ chapters.receivers.socket.AppenderExample \ src/main/java/chapters/receivers/socket/appender3.xml

レシーバーが接続する前に入力したメッセージは破棄されます。わかりやすいですね。

### SocketSSLReceiverの使い方

`SSLSocketReceiver`の設定は`SocketReceiver`の設定とほとんど変わりません。根本的に違うのは、レシーバーのclass属性の指定と、sslプロパティがネストされていることです。基本的な設定を見てみましょう。

例：基本的なSSLSocketReceiverの設定（logback-examples/src/main/java/chapters/receivers/socket/receiver4.xml）

Groovyとして表示

    <configuration debug="true">

      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          <pattern>%date %-5level [%thread] %logger - %message%n</pattern>
        </encoder>
      </appender>

      <root level="DEBUG">
        <appender-ref ref="CONSOLE" />
      </root>

      <receiver class="ch.qos.logback.classic.net.SSLSocketReceiver">
        <remoteHost>${host}</remoteHost>
        <port>${port}</port>
        <reconnectionDelay>10000</reconnectionDelay>
        <ssl>
          <trustStore>
            <location>${truststore}</location>
            <password>${password}</password>
          </trustStore>
        </ssl>
      </receiver>

    </configuration>

class属性に`SSLSocketReceiver`を指定していること、そして、リモートアペンダーが信頼できるかどうかを検証するために使用するトラストストアの場所とパスワードを指定しているところが重要なところです。sslプロパティの設定内容について詳しくはSSLを使用するを参照してください。

この設定をつかってレシーバーのサンプルアプリケーションを実行しましょう。

java -Dhost=localhost -Dport=6001 \ -Dtruststore=file:src/main/java/chapters/appenders/socket/ssl/truststore.jks \ -Dpassword=changeit \ chapters.receivers.socket.ReceiverExample \ src/main/java/chapters/receivers/socket/receiver4.xml

アプリケーションが開始すると、レシーバーは設定ファイルで指定されたリモートアペンダーに接続しようとします。まだアペンダーが実行されていないときは、定期的に"接続を拒否されました"というメッセージがログに出力されｒます。レシーバーはreconnectionDelayプロパティで指定した時間間隔で、アペンダーに接続できるまで、再接続を繰り返します。

レシーバーが接続するアペンダーのためのサンプルアプリケーションを実行しましょう。アプリケーションが開始すると、アプリケーションはユーザー入力を待ち受けます。`SSLServerSocketAppender`はレシーバーからの接続を待ち受けつつ、接続されているレシーバーにユーザー入力をメッセージとして発生したロギングイベントを配信します。アペンダーのサンプルアプリケーションを実行しましょう。

java -Dport=6001 \ -Dkeystore=file:src/main/java/chapters/appenders/socket/ssl/keystore.jks \ -Dpassword=changeit \ chapters.receivers.socket.AppenderExample \ src/main/java/chapters/receivers/socket/appender4.xml

レシーバーが接続していない状態で何か入力しても、そのメッセージは単純に破棄されるだけです。

繰り返しになりますが、この例では自己署名したX.509証明書を使用していますが、これはあくまでもテストだからです。本番環境においては、SSL対応のlogbackコンポーネントが自身の身元を証明するため、適切なX.509証明書を取得しなければなりません。詳しくはSSLを使用するを参照してください。