Splunk: カスタムコマンド入門(3/4) - 様々な機能を追加してみる1

###### 実施環境

CentOS 8-Stream

Splunk Free 8.2.2

Python 3.7.10

splunk-sdk-python 1.7.3

### 0. 概要

前回まで、カスタムコマンドには最低限の機能しか搭載していませんでした。
今回は最初に作成した以下のプログラムに対して、入出力に関する機能を追加していってみます。

```python:test.py
#!/usr/bin/env python

import sys

sys.path.insert(0, "/opt/splunk/etc/apps/search/lib")
from splunklib.searchcommands import \
    dispatch, GeneratingCommand, Configuration

@Configuration()
class test_class(GeneratingCommand):
    def generate(self):
        return [{"test_field": "Hello World !"}]

dispatch(test_class)
```

### 1. 出力の分割

上記のプログラムでは「 Hello World ! 」は1回しか出力していませんでしたが、これを3回にしてみます。

```python:test.py
#!/usr/bin/env python

import sys

sys.path.insert(0, "/opt/splunk/etc/apps/search/lib")
from splunklib.searchcommands import \
    dispatch, GeneratingCommand, Configuration

@Configuration()
class test_class(GeneratingCommand):
    def generate(self):
        test_result = []
        for i in range(3):
            test_result.append({"test_field": "Hello World !"})
        return test_result

dispatch(test_class)
```

上記の通り結果のリストに辞書形式のイベントを追加していけば実現はできます。
ただ、上の書き方だと最後まで処理が終わってからでないと結果が返されません。
性能的には順次結果を返してほしいところです。

では、順次結果を返すにはどうすればよいか。
答えは、「 return 」の代わりに「 yield 」を使用する、です。

```python:test.py
#!/usr/bin/env python

import sys

sys.path.insert(0, "/opt/splunk/etc/apps/search/lib")
from splunklib.searchcommands import \
    dispatch, GeneratingCommand, Configuration

@Configuration()
class test_class(GeneratingCommand):
    def generate(self):
        for i in range(3):
            yield {"test_field": "Hello World !"}
        return

dispatch(test_class)
```

「 yield 」の場合は辞書形式のイベントをリスト化せず直接返すことに注意してください。
「 return 」は上記で残していますが、「 yield 」を使用したメソッド内では単にメソッドを終了する意味しかないので消しても問題ありません。

実際に SPL で使用してみました。

```splunk:splunk
| TestCommand
| table test_field
```

![WS000040.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/c6bf3595-09d5-2517-e1d5-b172afc0352c.jpeg "出力分割")

なお、注意点が1つ。
 yield で返す辞書のキーは、全て同じでなくてはなりません。
キーが異なる辞書を同じコマンド内で返すと、上手くイベントを返せません。

```python:test.py
#!/usr/bin/env python

import sys

sys.path.insert(0, "/opt/splunk/etc/apps/search/lib")
from splunklib.searchcommands import \
    dispatch, GeneratingCommand, Configuration

@Configuration()
class test_class(GeneratingCommand):
    def generate(self):
        for i in range(3):
            yield {"test_field1": "Hello World 1 !"}
            yield {"test_field2": "Hello World 2 !"}
        return

dispatch(test_class)
```

```splunk:splunk
| TestCommand
| table test_field1, test_field2
```

![WS000053.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/047b4ca5-8f0f-2ee0-22d4-c0bb85ece5f7.jpeg "1field")

どうしても異なるキーを使いたい場合は、以下のように使用しないキーを「 None 」で埋めることで対応できます。

```python:test.py
#!/usr/bin/env python

import sys

sys.path.insert(0, "/opt/splunk/etc/apps/search/lib")
from splunklib.searchcommands import \
    dispatch, GeneratingCommand, Configuration

@Configuration()
class test_class(GeneratingCommand):
    def generate(self):
        for i in range(3):
            yield {"test_field1": "Hello World 1 !", \
                   "test_field2": None}
            yield {"test_field1": None, \
                   "test_field2": "Hello World 2 !"}
        return

dispatch(test_class)
```

```splunk:splunk
| TestCommand
| table test_field1, test_field2
```

![WS000054.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/fb240e28-f60d-0e51-5890-229fc64e2de5.jpeg "2field")

ちなみに、「 generate 」以外のメソッドからイベントを返したい場合は、以下のように「 yield from 」で「 yield 」を「 generate 」まで通せば実現できます。

```python:test.py
#!/usr/bin/env python

import sys

sys.path.insert(0, "/opt/splunk/etc/apps/search/lib")
from splunklib.searchcommands import \
    dispatch, GeneratingCommand, Configuration

@Configuration()
class test_class(GeneratingCommand):
    def test_func1(self):
        yield from self.test_func2()

    def test_func2(self):
        yield {"test_field": "Hello World !"}

    def generate(self):
        for i in range(3):
            yield from self.test_func1()
        return

dispatch(test_class)
```

### 2. 引数の取得

引数を取得する場合は、「 Option 」を使用します。
ライブラリからの読み込み対象に「 Option 」を追加し、それをクラス変数に代入します。
引数名は特に指定がなければ代入先のクラス変数名と同じになります。

```python:test.py
#!/usr/bin/env python

import sys

sys.path.insert(0, "/opt/splunk/etc/apps/search/lib")
from splunklib.searchcommands import \
    dispatch, GeneratingCommand, Configuration, Option

@Configuration()
class test_class(GeneratingCommand):
    testarg = Option()

    def generate(self):
        for i in range(int(self.testarg)):
            yield {"test_field": "Hello World !"}
        return

dispatch(test_class)
```

実際に SPL で使用してみました。

```splunk:splunk
| TestCommand testarg=5
| table test_field
```

![WS000041.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/2a3826a4-367b-8b3e-53a7-60c5559772fc.jpeg "引数")

ちなみに、「 Option 」には引数も指定可能です。
主に使用する引数は以下のようなところでしょうか。

* name : 変数名と引数名を変える場合に使用
* require : 変数が必須な場合は True 、そうでない場合は False を指定
* default : 変数省略時のデフォルト値を指定
* validate : 変数のフォーマットを指定、使用する場合は「 validators 」の読み込みが必須

実際に使用すると以下のようになります。

```python:test.py
#!/usr/bin/env python

import sys

sys.path.insert(0, "/opt/splunk/etc/apps/search/lib")
from splunklib.searchcommands import \
    dispatch, GeneratingCommand, Configuration, Option, validators

@Configuration()
class test_class(GeneratingCommand):
    test_count = \
        Option(name="testarg", \
               require=False, default=1, \
               validate=validators.Integer())

    def generate(self):
        for i in range(int(self.test_count)):
            yield {"test_field": "Hello World !"}
        return

dispatch(test_class)
```

### 3. _time , _raw の追加

ここまでカスタムコマンドを使用する際には table コマンドを併用してきました。
では table コマンドを取り除くとどのような画面になるでしょうか。

```splunk:splunk
| TestCommand testarg=5
```

![WS000047.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/a59e28ce-8c32-1dd8-34bd-94385b9fb954.jpeg "no_raw")

このように、一見何も出力されていないように見えてしまいます。
これは、「イベント」画面で右下に表示されるのが「 _time 」「 _raw 」の2つの内部的なフィールドであり、上記のカスタムコマンドだとこれらが生成されないからです。
ちゃんと確認すれば、指定したフィールド自体はきちんと生成されていることがわかるはずです。

![WS000048.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/ad20a5e8-c8fb-9588-0847-c63fc37fa486.jpeg "フィールド")

「イベント」画面で表示したい場合は、「 _time 」「 _raw 」を明示的に指定すればよいです。
なお、「 _time 」は float 型の UNIX 時間で指定する必要があります。

```python:test.py
#!/usr/bin/env python

import sys, datetime

sys.path.insert(0, "/opt/splunk/etc/apps/search/lib")
from splunklib.searchcommands import \
    dispatch, GeneratingCommand, Configuration, Option

@Configuration()
class test_class(GeneratingCommand):
    testarg = Option()

    def generate(self):
        for i in range(int(self.testarg)):
            yield {"_time": \
                   datetime.datetime(2000,2,29,9,30,21).timestamp(), \
                   "_raw": "Hello World !"}
        return

dispatch(test_class)
```

実際に実行してみました。

```splunk:splunk
| TestCommand testarg=5
```

![WS000049.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/4b7e3ab3-0b07-a4e6-1f9b-c6e6d74e9c6c.jpeg "_raw")

「時間」と「イベント」がきちんと出力されていることがわかります。

今回は入出力について機能を追加してみました。
次回はそれ以外にも機能を追加してみます。

[戻る](https://qiita.com/frozencatpisces/items/14e4de24c306502efcc8)