この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
User Agentを解析する公式プラグインがリリースされていました。
https://www.elastic.co/guide/en/elasticsearch/plugins/master/ingest-user-agent.html
はじめに
こんにちは、藤本です。
先日エントリしたElasticsearchのIngest Nodeを試してみたの中でProcessorのPluginがあることをご紹介しました。今回はUser Agentヘッダの文字列を解析するIngest Pluginを書いてみたので簡単に書き方をご紹介します。
概要
Elasticsearchの5系からIngest Nodeが追加され、Elasticsearch側で入力データを加工/変換できるようになります。それにより、データ送信元でデータ加工/変換を行っている環境では生のデータを送信するだけでよくなり、データ加工/変換用のリソースを抑えることができ、本来のアプリケーションだけにリソースを割り当てることができます。またデータ加工/変換用に別途サーバーを用意していた環境ではそのサーバーが必要なくなる可能性があります。
ただ先日のエントリで現在は20(Plugin含む)のProcessorが実装されていることをご紹介しましたが、Logstash/Fluentdをご利用の方は物足りなさを感じるでしょう。そこで今回はIngest Pluginのソースコードを読んでみると比較的簡単に実装できそうだったので実装方法をご紹介します。
Ingest Nodeの説明やIngest Pluginの書き方に関して、Elastic社のスライドで上がっていましたのでご参照ください。
今回作成するPlugin
Ingest PluginにIPアドレスからローケーション情報を解析するGeoIP Processorがあります。今回はhttpdのアクセスログ解析でGeoipとともによく利用される?であろうUser Agentを解析するIngest Pluginを作成します。
ソースコードはGithubにアップしました。
環境
- 開発環境
- OS : OS X Yosemite 10.10.5
- Java : 1.8.0_72
- Elasticsearch : 5.0.0-alpha4?(2016/06/28時点のMasterブランチ)
- Gradle : 2.13
事前準備
ElasticsearchのソースコードをGithubからクローンしておきます。
ディレクトリ構成
クローンしたelasticsearchのpluginsディレクトリ配下に配置します。
elasticsearch
├ setting.gradle // (1)
:
└ plugins
└ ingest-useragent // (以下、新規)
├ build.gradle // (2)
├ licenses // (3)
│ ├ woothee-java-1.4.0.jar.sha1
│ ├ woothee-java-LICENSE.txt
│ └ woothee-java-NOTICE.txt
└ src
└ main
└ java
└ org
└ elasticsearch
└ ingest
└ useragent
├ IngestUserAgentPlugin.java // (4)
└ UserAgentProcessor.java // (5)
Gradle設定
Gradleを利用しない場合は、本章はスキップすることも可能です。Elasticsearch全体がGradleを利用していて、Plugin開発用の実装も用意されているのでGradleの利用を推奨します。
(1) setting.gradle
ElasticsearchのRootディレクトリにあるsetting.gradleに作成するPluginの名前を追加します。これにより、GradleのElasticsearch Plugin用のタスクを利用したり、GradleによるElasticsearch起動時にPluginを読み込むことが可能になります。
rootProject.name = 'elasticsearch'
List projects = [
:
'plugins:ingest-useragent', // (a)
:
]
:
(a) Plugin追加
今回作成するPluginを追加します。plugins
ディレクトリに作成するPluginのディレクトリ名に合わせてください。
(2) build.gradle
Plugin用のbuild.gradleを作成します。
esplugin { // (a)
description 'Ingest processor that uses analyzed User Agent'
classname 'org.elasticsearch.ingest.useragent.IngestUserAgentPlugin'
}
dependencies { // (b)
compile('is.tagomor.woothee:woothee-java:1.4.0')
}
thirdPartyAudit.excludes = [
'org.ho.yaml.Yaml', // (c)
]
(a) Plugin用のGradle追加
PluginはElasticsearch起動時にPluginディレクトリ下にあるplugin-descriptor.properties
という定義ファイルを介して読み込まれます。esplugin
を定義しておくと、Gradleのタスクによってplugin-descriptor.properties
を自動生成します。
classname
にPluginクラスを継承したクラスのパスを指定します。
(b) 依存ライブラリ
利用するライブラリを指定します。今回はUserAgentの解析にwootheeを利用します。
(c) 除外クラス
Pluginに利用しないクラスを除外クラスとして指定します。
Plugin初期化クラス
build.gradle
のesplugin
で指定したクラスを作成します。Elasticsearch起動時に読み込まれてElasticsearchのプラグインとして取り込まれます。
Pluginクラスを継承し、onModuleメソッドを実装します。
public class IngestUserAgentPlugin extends Plugin {
public void onModule(NodeModule nodeModule) throws IOException {
nodeModule.registerProcessor(UserAgentProcessor.TYPE,
(registry) -> new UserAgentProcessor.Factory()); // (a)
}
}
(a) Processor登録
実装するIngest PluginをElasticsearchで利用可能なProcessorとして登録します。
Processorクラス
処理を実装するクラスになります。
public final class UserAgentProcessor extends AbstractProcessor {
public static final String TYPE = "useragent";
private final String field;
private final String targetField;
UserAgentProcessor(String tag, String field, String targetField) {
super(tag);
this.field = field;
this.targetField = targetField;
}
public void execute(IngestDocument ingestDocument) { // (c)
String userAgent = ingestDocument.getFieldValue(field, String.class); // (d)
Map<String, String> u = Classifier.parse(userAgent); // (e)
ingestDocument.setFieldValue(this.targetField, u); // (f)
}
@Override
public String getType() {
return TYPE;
}
public static final class Factory extends AbstractProcessorFactory<UserAgentProcessor> { // (a)
public Factory() {}
public UserAgentProcessor doCreate(String processorTag, Map<String, Object> config) throws Exception {
String userAgentField = readStringProperty(TYPE, processorTag, config, "field"); // (b)
String targetField = readStringProperty(TYPE, processorTag, config, "targetField", "useragent"); // (b)
return new UserAgentProcessor(processorTag, userAgentField, targetField);
}
}
}
(a) インスタンス生成クラス
Factoryクラスを実装します。FactoryクラスではdoCreate
メソッドを実装します。
(b) オプション設定
Processorにて指定可能なフィールドを定義します。今回はUserAgentの文字列が入力されるfield
、解析したUserAgentのフィールドとなるtargetField
を定義します。デフォルト値の設定も可能です。
(c) 処理
execute
メソッドに処理を記述します。
(d) User Agent取得
ドキュメントからfield
で指定したフィールドの値を取得します。
(e) User Agent解析
wootheeライブラリを使用して、User Agentの文字列から、端末・OS名・OSバージョン・ベンダ・ブラウザ名・ブラウザバージョンを解析します。
(f) ドキュメントへのフィールド追加
ドキュメントに対して解析したUser Agentを追加します。Map型で追加するとElasticsearchではObject Typeのように扱われます。
動作確認
Simulate APIを利用して、作成したIngest Pluginが正常に動作するか確認してみましょう。
GradleでElasticsearchを起動します。(今回テストは未実装なため、除外します)
# cd $ELASTICSEARCH_ROOT/plugins/ingest-useragent
# gradle run -x test -x integTest
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy UP-TO-DATE
:buildSrc:writeVersionProperties UP-TO-DATE
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes UP-TO-DATE
:
:plugins:ingest-useragent:compileJava
:plugins:ingest-useragent:processResources UP-TO-DATE
:plugins:ingest-useragent:classes
:plugins:ingest-useragent:jar
:plugins:ingest-useragent:copyPluginPropertiesTemplate
:plugins:ingest-useragent:pluginProperties UP-TO-DATE
:plugins:ingest-useragent:bundlePlugin
:plugins:ingest-useragent:run#prepareCluster.cleanShared
:distribution:integ-test-zip:buildZip UP-TO-DATE
:plugins:ingest-useragent:run#clean
:plugins:ingest-useragent:run#checkPrevious SKIPPED
:plugins:ingest-useragent:run#stopPrevious SKIPPED
:plugins:ingest-useragent:run#extract
:plugins:ingest-useragent:run#configure
:plugins:ingest-useragent:run#copyPlugins
:plugins:ingest-useragent:run#installIngestUseragentPlugin
:plugins:ingest-useragent:run#start
[elasticsearch] [2016-07-15 16:41:27,376][INFO ][node ] [Mother Night] version[5.0.0-alpha4-SNAPSHOT], pid[73439], build[18d45b2/2016-06-29T06:54:05.832Z], OS[Mac OS X/10.10.5/x86_64], JVM[Oracle Corporation/Java HotSpot(TM) 64-Bit Server VM/1.8.0_72/25.72-b15]
[elasticsearch] [2016-07-15 16:41:27,376][INFO ][node ] [Mother Night] initializing ...
[elasticsearch] [2016-07-15 16:41:27,507][INFO ][plugins ] [Mother Night] modules [], plugins [ingest-useragent]
:
[elasticsearch] [2016-07-15 16:41:32,536][INFO ][node ] [Mother Night] started
Simulate APIでuseragent
Processorを利用して、ドキュメントを変換します。
### Simulate API
# curl -XPOST "http://localhost:9200/_ingest/pipeline/_simulate?pretty" -d'
{
"pipeline" : {
"processors" : [
{
"useragent" : {
"field" : "message"
}
}
]
},
"docs" : [
{
"_source" : {
"message" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
}
}
]
}'
### Output
{
"docs" : [
{
"doc" : {
"_id" : "_id",
"_index" : "_index",
"_type" : "_type",
"_source" : {
"useragent" : {
"name" : "Chrome",
"category" : "pc",
"os" : "Mac OSX",
"version" : "32.0.1700.77",
"vendor" : "Google",
"os_version" : "10.9.1"
},
"message" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
},
"_ingest" : {
"timestamp" : "2016-07-15T07:39:32.943+0000"
}
}
}
]
}
message
フィールドのUser Agent文字列を元に、
useragent
フィールドにname
、category
、os
、version
、os_version
というフィールド名で各種値が追加されました。
Elasticsearchへのデプロイ
次にLinuxサーバ上で動作するElasticsearchにデプロイしてみましょう。
Gradleのビルドによりデプロイ用のzipファイルを生成します。
# cd $ELASTICSEARCH_ROOT/plugins/ingest-useragent
# gradle build -x test -x integTest
:
BUILD SUCCESSFUL
Total time: 32.725 secs
zipファイルはbuild/distributions
配下に生成されています。
Linuxサーバに生成されたzipファイルを転送します。
# scp build/distributions/ingest-useragent-5.0.0-alpha4-SNAPSHOT.zip username@hostname:/tmp/
Linuxサーバにログインし、転送したzipファイルをPluginとしてインストールします。
(Linux)# /usr/share/elasticsearch/bin/elasticsearch-plugin install file:///tmp/ingest-useragent-5.0.0-alpha4-SNAPSHOT.zip
-> Downloading file:///tmp/ingest-useragent-5.0.0-alpha4-SNAPSHOT.zip
-> Installed ingest-useragent
Pluginsディレクトリ配下にコンポーネントが配置されます。
(Linux)# ls -l /usr/share/elasticsearch/plugins/ingest-useragent/
total 64
-rw-r--r--. 1 root root 8164 Jul 15 08:02 ingest-useragent-5.0.0-alpha4-SNAPSHOT.jar
-rw-r--r--. 1 root root 1318 Jul 15 08:02 plugin-descriptor.properties
-rw-r--r--. 1 root root 49900 Jul 15 08:02 woothee-java-1.4.0.jar
実装したPluginのjarファイル、依存ライブラリ、Pluginの定義ファイルとなるplugin-descriptor.properties
が展開されています。
Elasticsearchを再起動し、Pluginを読み込ませます。
(Linux)# systemctl restart elasticsearch
(Linux)# curl -XPOST "http://localhost:9200/_ingest/pipeline/_simulate?pretty" -d'
{
"pipeline" : {
"processors" : [
{
"useragent" : {
"field" : "message"
}
}
]
},
"docs" : [
{
"_source" : {
"message" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
}
}
]
}'
### Output
{
"docs" : [
{
"doc" : {
"_index" : "_index",
"_type" : "_type",
"_id" : "_id",
"_source" : {
"useragent" : {
"name" : "Chrome",
"category" : "pc",
"os" : "Mac OSX",
"version" : "32.0.1700.77",
"vendor" : "Google",
"os_version" : "10.9.1"
},
"message" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
},
"_ingest" : {
"timestamp" : "2016-07-15T08:03:30.042+0000"
}
}
}
]
}
各種情報が展開されました。
まとめ
いかがでしたでしょうか?
Javaをあまり書き慣れていない私でも簡単に実装することができました。LogstashやFluentdにないデータ加工処理も独自実装で追加できそうです。