Stackdriver

intra-mart AccelPlatformのリクエストログをStackdriverに出力してみた。

![SnapCrab_NoName_2017-7-29_1-37-28_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/131196/d0c8ca79-2c80-e82e-3be5-4ee96ccce69f.png)
![SnapCrab_NoName_2017-7-29_2-49-7_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/131196/c07a6006-0172-9085-b9b7-1039c597dea3.png)

さらに、StackdriverからPub/Sub, BigQuery, Cloud Storageにエクスポートしてみた。
シンクを作成すればいいだけなので簡単。
BigQuery, Cloud Storageへは1時間間隔で自動エクスポート、Pub/Subへは即時配信される模様。

![SnapCrab_NoName_2017-7-29_18-35-23_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/131196/3f5e4962-6b6f-403a-9dec-9ffa3b2a8971.png)
![SnapCrab_NoName_2017-7-29_18-36-27_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/131196/a090b47e-40f5-2237-db2c-26ec778e2c75.png)
![SnapCrab_NoName_2017-7-29_18-40-50_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/131196/034b3e0b-20d7-5f82-ecb4-b35393351e52.png)

Pub/Subに流したログは[Google_pubsub input plugin | Logstash](https://www.elastic.co/guide/en/logstash/current/plugins-inputs-google_pubsub.html)とかで受け取れるので他にも色々融通が利きそう。

以下、やったこと。

##### 1. [Google Cloud Console](https://console.cloud.google.com/) からプロジェクトを作成
既存のプロジェクトがあれば、新規に作成する必要は特になし。
##### 2. JSON形式でサービスアカウントキーを作成し、保存
##### 3. 必要なjarファイルを取得
###### 3.1 以下の依存に対して mvn dependency:copy-dependencies などして取得可能
```xml:pom.xml
<dependency>
  <groupId>com.google.cloud</groupId>
  <artifactId>google-cloud-logging-logback</artifactId>
  <version>0.21.0-alpha</version>
</dependency>
```
##### 4. 解決されるjarはこんな感じ
* com.google.auth:google-auth-library-oauth2-http:jar:0.7.0
* org.threeten:threetenbp:jar:1.3.3
* io.netty:netty-handler:jar:4.1.11.Final
* com.google.instrumentation:instrumentation-api:jar:0.4.2
* org.codehaus.mojo:animal-sniffer-annotations:jar:1.14
* com.google.auth:google-auth-library-credentials:jar:0.7.0
* com.google.auto.value:auto-value:jar:1.2
* io.netty:netty-codec:jar:4.1.11.Final
* com.google.http-client:google-http-client-jackson2:jar:1.19.0
* com.fasterxml.jackson.core:jackson-core:jar:2.1.3
* com.google.cloud:google-cloud-core:jar:1.2.3
* io.netty:netty-tcnative-boringssl-static:jar:2.0.3.Final
* joda-time:joda-time:jar:2.9.2
* io.grpc:grpc-protobuf:jar:1.4.0
* com.google.http-client:google-http-client:jar:1.19.0
* ch.qos.logback:logback-classic:jar:1.2.3
* io.netty:netty-common:jar:4.1.11.Final
* io.grpc:grpc-stub:jar:1.4.0
* commons-logging:commons-logging:jar:1.1.1
* org.json:json:jar:20160810
* com.google.protobuf:protobuf-java-util:jar:3.3.0
* io.netty:netty-codec-socks:jar:4.1.11.Final
* io.grpc:grpc-netty:jar:1.4.0
* com.google.protobuf:protobuf-java:jar:3.3.0
* com.google.cloud:google-cloud-logging-logback:jar:0.20.3-alpha
* io.netty:netty-handler-proxy:jar:4.1.11.Final
* org.apache.httpcomponents:httpclient:jar:4.0.1
* io.netty:netty-resolver:jar:4.1.11.Final
* com.google.cloud:google-cloud-logging:jar:1.2.3
* com.google.code.findbugs:jsr305:jar:1.3.9
* commons-codec:commons-codec:jar:1.3
* com.google.j2objc:j2objc-annotations:jar:1.1
* com.google.cloud:google-cloud-core-grpc:jar:1.2.3
* ch.qos.logback:logback-core:jar:1.2.3
* com.google.api.grpc:proto-google-cloud-logging-v2:jar:0.1.13
* io.netty:netty-transport:jar:4.1.11.Final
* com.google.errorprone:error_prone_annotations:jar:2.0.18
* com.google.api:gax:jar:1.4.2
* com.google.api.grpc:proto-google-iam-v1:jar:0.1.13
* com.google.guava:guava:jar:22.0
* com.google.code.gson:gson:jar:2.7
* io.grpc:grpc-auth:jar:1.4.0
* io.netty:netty-codec-http:jar:4.1.11.Final
* io.netty:netty-buffer:jar:4.1.11.Final
* org.slf4j:slf4j-api:jar:1.7.25
* com.google.api.grpc:proto-google-common-protos:jar:0.1.13
* com.google.api:gax-grpc:jar:0.21.2
* io.grpc:grpc-protobuf-lite:jar:1.4.0
* org.apache.httpcomponents:httpcore:jar:4.0.1
* io.netty:netty-codec-http2:jar:4.1.11.Final
* io.grpc:grpc-core:jar:1.4.0
* io.grpc:grpc-context:jar:1.4.0
* com.google.api:api-common:jar:1.1.0

##### 5. いくつかのjarを除去
いくつかはAccelPlatformがもつjarと競合していた。
jarjarで機械的にリパッケージしてしまうのが一番楽で早いと思われるが、恐らくその必要はない。

* commons-codec-1.3.jar
* commons-logging-1.1.1.jar
* gson-2.7.jar
* httpclient-4.0.1.jar
* httpcore-4.0.1.jar
* jackson-core-2.1.3.jar
* joda-time-2.9.2.jar
* logback-classic-1.2.3.jar
* logback-core-1.2.3.jar

これらはAccelPlatformがもつjarで代用できるため、除去する。

* guava-22.0.jar

AccelPlatformがもつjarのバージョンが古いせいで、このバージョンを使う必要がある。不安ならリパッケージだが、guavaは互換性に関しては優秀なので、そのままguava-22.0を使っても問題無いと判断した。

* slf4j-api-1.7.25.jar

AccelPlatformがもつlog4j-over-slf4jで代用可能なためこれも除去でいいだろう。
必要なjarだが、デプロイする度に消えるのは嫌なのでresin/webapp-jarsの中に放り込んで置く。

##### 6. GOOGLE_APPLICATION_CREDENTIALS

サービスアカウントキーのjsonファイルへのパスを、GOOGLE_APPLICATION_CREDENTIALS環境変数に設定しておく。
システムの環境変数に設定してもいいし、resinを起動するプロセスの親（コンソールやコマンドプロンプト）から
```
set GOOGLE_APPLICATION_CREDENTIALS=/hoge/foo/bar/service_account_key.json
```
や
```
export GOOGLE_APPLICATION_CREDENTIALS=/hoge/foo/bar/service_account_key.json
```
としておけばいいだろう。

##### 7. logbackの設定ファイル

```xml:resin/webapps/imart/WEB-INF/conf/log/im_logger_request_stackdriver.xml
<included>

	<appender name="CLOUD" class="com.google.cloud.logging.logback.LoggingAppender">
		<log>request.log</log>
		<enhancer>hoge.MDCEnhancer</enhancer>
		<flushLevel>INFO</flushLevel>
	</appender>

	<logger name="REQUEST_LOG" additivity="false">
		<level value="info" />
		<appender-ref ref="CLOUD" />
	</logger>

</included>
```

こんな感じで設定ファイルを放り込んでおく。

##### 8. MDCEnhancer

LoggingAppenderではencoderが使えないためMDCのエンハンサーを実装しておく。

```java:MDCEnhancer.java
package hoge;

import java.util.Map;

import org.slf4j.MDC;

import com.google.cloud.logging.LogEntry.Builder;
import com.google.cloud.logging.LoggingEnhancer;
import com.google.cloud.logging.Payload.StringPayload;

import jp.co.intra_mart.common.aid.jdk.java.lang.StringUtil;

public class MDCEnhancer implements LoggingEnhancer {
    @Override
    public void enhanceLogEntry(final Builder builder) {
        final Map<String, String> map = MDC.getCopyOfContextMap();

        if (map != null) {
            builder.setLabels(map);

            final String userCd = map.get("user.cd");
            final String method = map.get("request.method");
            final String url = map.get("request.url");

            if (!StringUtil.isBlank(userCd) && !StringUtil.isBlank(method) && !StringUtil.isBlank(url)) {
                builder.setPayload(StringPayload.of(userCd + ' ' + method + ' ' + url));
            }
        }
    }
}

```
こいつもjarにコンパイル後resin/webapp-jarsの中に放り込んで置く。

とりあえずこんな感じ。
BigQueryが使えるのとGoogle Cloud Pub/Subで連携も容易になるのは大きいかな。