Splunk カスタムサーチコマンド (custom search command) 作成 ― はじめの一歩 ―

Splunk SDK を使用して、カスタムサーチコマンド (Custom Search Command) を作成します。

ドキュメントがあるといえばあるのですが、なかなかわかりずらいので、参照先と手順をまとめておきます。
はじめの一歩として、"hello, world"[^1] を出力するカスタムサーチコマンド `generatehello` を作成[^2]します。

[^1]: Kernighan & Ritchie への敬意をこめて、小文字で。
[^2]: run を用いてスクリプトを呼び出すのではなく、Splunk SDK のフレームワークを利用した、いわば Native なコマンドです。run コマンドを用いたスクリプトをカスタムサーチコマンドとして作成する方法は、「 [カスタムサーチコマンドの作り方（Splunk） - Qiita](https://qiita.com/myogada/items/c5a2985882c4b3f30e31)」が参考になると思います。

:point_up_tone3: 少し古いのですが、内容は今でも通用する Splunk 公式ブログ "[Building custom search commands in Python part I – A simple Generating command](https://www.splunk.com/en_us/blog/tips-and-tricks/building-custom-search-commands-in-python-part-i-a-simple-generating-command.html)"[^3] をベースとしています。ただし、本稿では、ボトムアップのアプローチをとります。


[^3]: 2014年4月のブログで、"Part I" となっていますが、"Part II" のブログは削除されたか、投稿されていないようです。


# ゴール

`|generatehello count=3` という SPL文で "hello, world" を、引数 count に指定した個数のイベント (この例では 3イベント) を出力します。

![Splunk カスタムサーチコマンド (custom search command) 作成 ― はじめの一歩 ― ゴール](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555032/7377e5f9-b087-a7df-909d-c42379b9cb74.png "Splunk カスタムサーチコマンド (custom search command) 作成 ― はじめの一歩 ― ゴール")

本稿では、ひとまず、引数を取らない、簡単なカスタムサーチコマンドを作成します。

# はじめる前に
開発の手引きは "[Develop apps | Documentation | Splunk Developer Program](https://dev.splunk.com/enterprise/docs/developapps)" ですが、これを読み進めるうえで、混乱することが多いので、用語を整理しておきます。

|用語|簡単な説明|
|:--|:--|
|**App(s)**|表示部分 (UI)、Add-on、カスタムサーチコマンドを含む、アプリケーション全体<br />単数では App だが、Apps (アップス) と複数形で呼ばれることが多い。<br />`$SPLUNKHOME/etc/apps/` 下にディレクトリ単位で配置される。 |
|**Add-on(s)**|表示部分 (UI) を伴わない、App の一部、または全体|
|**Custom Search Command**|SPL のコマンドとして呼び出される自前のプログラム。一般的に App の一部として構成する。|

なお、本稿では `SPLUNKHOME=/opt/splunk` として記述します[^5]。

[^5]: 余談ですが、いくつかのファイルで SPLANKHOME=/opt/splunk_home とハードコードされているようです。配布されているパッケージによるインストール先は /opt/splunk ですので、ちょっとしたところでつまずいたりすることがあります。 

# 開発環境 ＆ 実行環境

* **OS:** [Ubuntu 18.04](https://wiki.ubuntu.com/BionicBeaver/ReleaseNotes) (厳密には、Windows 10 Pro の Hyper-V - Ubuntu 18.04 - docker 内)
* **Python:** 開発・テスト環境: [3.6.9](https://docs.python.org/ja/3.6/)、Splunk Python: [3.7.4](https://docs.python.org/ja/3.7/)
* **Splunk:** [Version 8.0.2.1](https://docs.splunk.com/Documentation/Splunk/8.0.2/ReleaseNotes/MeetSplunk) (本稿執筆時点で最新、日本時間2020年3月12日リリース)  
  (開発時にはインストールされている必要はありません。実行環境として必要です。)
* **Splunk SDK** (Software Development Kit)**:** [Version 1.6.12](https://github.com/splunk/splunk-sdk-python/releases/tag/1.6.12)


# 準備

Splunk SDK をダウンロードして展開、開発用ディレクトリの設定を行います。

## Splunk SDK のダウンロード
参照: [Create custom search commands | Documentation | Splunk Developer Program](https://dev.splunk.com/enterprise/docs/developapps/customsearchcommands)
参照: [Downloads | Splunk Developer Program](https://dev.splunk.com/enterprise/downloads/)
参照: [GitHub - splunk/splunk-sdk-python: Splunk Software Development Kit for Python](https://github.com/splunk/splunk-sdk-python)
参照: [Releases · splunk/splunk-sdk-python · GitHub](https://github.com/splunk/splunk-sdk-python/releases)

pip でのインストール方法などもドキュメントには書かれています[^4]が、今回は Python 仮想環境も使わず、 pip インストールも行いません。.zip ファイルか .tar.gz ファイルをダウンロードして使用します。

[^4]: cd $SPLUNKHOME/etc/apps/_yourapp_/bin; pip install -t . splunk-sdk などの方法が書かれたものもありますが、公式ドキュメントに記載された正規の方法で、別ディレクトリでの開発とテスト、Splunk への導入を行います。

:point_up_tone3: "[Releases · splunk/splunk-sdk-python · GitHub](https://github.com/splunk/splunk-sdk-python/releases)" から、最新(現時点では 1.6.12) の .zip ファイルか .tar.gz ファイルをダウンロードして、展開します。

```shell_session:Splunk&nbsp;SDK&nbsp;のダウンロードと展開
$ wget https://github.com/splunk/splunk-sdk-python/archive/1.6.12.tar.gz
...
2020-03-17 01:50:38 (3.12 MB/s) - ‘1.6.12.tar.gz’ saved [3876452]

$ ls
1.6.12.tar.gz
$ tar xvfz 1.6.12.tar.gz
splunk-sdk-python-1.6.12/
splunk-sdk-python-1.6.12/.gitattributes
...
splunk-sdk-python-1.6.12/utils/cmdopts.py
$ 
```

(展開したディレクトリの中に setup.py がありますが、今回は使いません。)

## 開発作業用ディレクトリのセットアップ
1. SDK のテンプレートを作業用ディレクトリ `HelloWorld-work/` としてコピーしたのち、  
   `cp -rp splunk-sdk-python-1.6.12/examples/searchcommands_template HelloWorld-work`
2. `HelloWorld-work/lib/` ディレクトリを作成して  
   `mkdir HelloWorld-work/lib`
3. その下に `splunklib` ディレクトリをコピーします。  
   `cp -rp splunk-sdk-python-1.6.12/splunklib/ HelloWorld-work/lib/`

```shell_session:HelloWorld-work&nbsp;のセットアップ
$ cp -rp splunk-sdk-python-1.6.12/examples/searchcommands_template HelloWorld-work
$ mkdir HelloWorld-work/lib
$ cp -rp splunk-sdk-python-1.6.12/splunklib/ HelloWorld-work/lib/
$
```

これで、以下のようなディレクトリ構造ができあがりました。

```text:HelloWork-work&nbsp;ディレクトリのツリー構造
HelloWorld-work/
|-- bin
|   |-- filter.py  ................ Eventing commands (後述) のテンプレート。
|   |-- generate.py  .............. Generating commands (後述) のテンプレート。
|   |                               今回は、このファイルを使用します。
|   |-- report.py  ................ Reporting commands (後述) のテンプレート。
|   `-- stream.py  ................ Streaming commands (後述) のテンプレート。
|-- default  ...................... 提供者のデフォルト設定ファイルを格納するディレクトリ。
|   |-- app.conf  ................. Splunk 設定時に修正します。
|   |-- commands-scpv1.conf  ...... このファイルは削除します。
|   |-- commands-scpv2.conf  ...... Splunk 設定時にこのファイルを commands.conf に置き換えます。
|   |                               最終的には削除します。
|   |-- commands.conf  ............ Splunk 設定時に、
|   |                               このファイルを command-scpv2.conf で置き換えて修正します。
|   |-- data
|   |   `-- ui
|   |       `-- nav
|   |           `-- default.xml  .. [設定]-[ユーザインタフェース]-[ナビゲーションメニュー]-[default]
|   |                               にて表示される、App 表示構成です。
|   `-- logging.conf  ............. 今回、このファイルは削除します。
|-- lib  .......................... PYTHONPATH として参照するライブラリディレクトリ。
|   |                               この場所は決まっているわけではないが、公式ドキュメントで推奨しています。
|   |                               Python 仮想環境で splunklib に含まれないパッケージを追加した場合は、
|   |                               site-packages/ 下のライブラリを置く必要があります。
|   `-- splunklib  ................ SDK の splunklib と同じもの。import splunklib で呼び出します。
|       |-- __init__.py
:       :
:       :
|       `-- six.pyc
`-- metadata
    `-- default.meta  ............. 参照/書き込み権限が指定されています。

12 directories, 79 files
```

このディレクトリ構造は、$SPLUNKHOME/etc/apps/_yourapplication_/ ディレクトリの構造を模したものです。
不要なファイルを削除して、このまま $SPLUNKHOME/etc/apps/ へコピーすることで App として登録できます。

:point_up_tone3: 設定ファイルのディレクトリには `default/` と `local/` があり、`default/` ディレクトリは提供者がデフォルトの設定を記載し、使用者は `local/` ディレクトリで(なければディレクトリを作成して)変更分のみを設定するルールになっています。
今回は、開発者＝提供者ですので、`default/` ディレクトリの設定ファイルを直接修正します。


# 開発

カスタムサーチコマンドの作成を始めるにあたり、種類について説明しておきます。

## カスタムサーチコマンドの4つの種類
参照: [Create custom search commands | Documentation | Splunk Developer Program](https://dev.splunk.com/enterprise/docs/developapps/customsearchcommands)
参照: [Python classes | Documentation | Splunk Developer Program](https://dev.splunk.com/enterprise/docs/developapps/customsearchcommands/pythonclassescustom/)

:point_up_tone3: カスタムサーチコマンドには4つの種類があります。

<blockquote>
The core of the <strong>search commands</strong> module contains four classes:</p><ul class="Typography__UL-sc-7idj16-8 hkmgJE"><li class="Typography__LI-sc-7idj16-11 ELRbk"><strong><a class="Link__ExternalLink-g5zdrm-1 bgDeTL" href="https://docs.splunk.com/DocumentationStatic/PythonSDK/1.6.11/searchcommands.html#splunklib.searchcommands.EventingCommand">EventingCommand</a>:</strong> Defines a search command that applies a transformation to search results as they travel through the events pipeline. Examples of eventing commands include <strong>sort</strong>, <strong>dedup</strong>, and <strong>cluster</strong>.</li><li class="Typography__LI-sc-7idj16-11 ELRbk"><strong><a class="Link__ExternalLink-g5zdrm-1 bgDeTL" href="https://docs.splunk.com/DocumentationStatic/PythonSDK/1.6.11/searchcommands.html#splunklib.searchcommands.GeneratingCommand">GeneratingCommand</a>:</strong> Defines a search command that generates event records based on command arguments. Examples of generating commands include <strong>search</strong> (at the beginning of the pipeline), <strong>inputcsv</strong>, <strong>input lookup</strong>, and <strong>metadata</strong>.</li><li class="Typography__LI-sc-7idj16-11 ELRbk"><strong><a class="Link__ExternalLink-g5zdrm-1 bgDeTL" href="https://docs.splunk.com/DocumentationStatic/PythonSDK/1.6.11/searchcommands.html#splunklib.searchcommands.ReportingCommand">ReportingCommand</a>:</strong> Defines a search command that processes search results and generates a reporting data structure. Examples of reporting commands include <strong>stats</strong>, <strong>top</strong>, and <strong>timechart</strong>.</li><li class="Typography__LI-sc-7idj16-11 ELRbk"><strong><a class="Link__ExternalLink-g5zdrm-1 bgDeTL" href="https://docs.splunk.com/DocumentationStatic/PythonSDK/1.6.11/searchcommands.html#splunklib.searchcommands.StreamingCommand">StreamingCommand</a>:</strong> Defines a search command that applies a transformation to search results as they travel through the processing pipeline. Examples of streaming commands include <strong>search</strong>, <strong>eval</strong>, and <strong>where</strong>.</li>
</blockquote>

簡単に説明すると、以下のようになります。

|タイプ|簡単な説明|実行場所|コマンド例|
|:--|:--|:--|:--|
|Eventing commands|入力を受けて、加工して出力するコマンド|Search Head のみ|sort, dedup, cluster
|Generating commands|入力を**受けず**、出力を行うコマンド|Search Head のみ|(先頭に書かれた)search, inputcsv, input lookup, metadata
|Reporting commands|入力を受けて、加工してレポート形式にするコマンド|Search Head のみ|stats, top, timechart
|Streaming commands|分散環境で、加工して出力するコマンド|並列で Indexer でも可|search, eval, where

Eventing commands と Streaming commands の区別がつきにくいかもしれませんが、Eventing commands は Search Head でのみ実行可能ですが、Streaming commands は Indexer でも実行可能という違いがあります[^7]。(詳しくは、"[Types of commands](https://docs.splunk.com/Documentation/Splunk/latest/Search/Typesofcommands)" も参照してください)

[^7]: "Stateful Streaming commands" という、 Search Head でしか実行できないコマンドもあるとか……もう、何が何やら……

はじめの一歩としては、入力を行わない、**Generating commands** を作成していきます。

## Generating commands のテンプレート
参照: [Templates - Create custom search commands | Documentation | Splunk Developer Program](https://dev.splunk.com/enterprise/docs/developapps/customsearchcommands#Templates)

4つのタイプそれぞれにテンプレートが用意されています。Generating comannds のテンプレートを参照します。

[Generating commands テンプレート - generate.py](https://github.com/splunk/splunk-sdk-python/blob/master/examples/searchcommands_template/bin/generate.py)

Splunk SDK ディレクトリの `examples/searchcommands_template/bin/generate.py` にありますが、セットアップした作業ディレクトリでは、`bin/` の下にあります。

```python:generate.py
     1  #!/usr/bin/env python
     2
     3  import sys
     4  import os
     5
     6  sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
     7  from splunklib.searchcommands import \
     8      dispatch, GeneratingCommand, Configuration, Option, validators
     9
    10  @Configuration()
    11  class %(command.title())Command(GeneratingCommand):
    12      """ %(synopsis)
    13
    14      ##Syntax
    15
    16      %(syntax)
    17
    18      ##Description
    19
    20      %(description)
    21
    22      """
    23      def generate(self):
    24         # Put your event  code here
    25         pass
    26
    27  dispatch(%(command.title())Command, sys.argv, sys.stdin, sys.stdout, __name__)
```

* **6行目** Python ライブラリを読み込むパスを指定しています。  
  このディレクトリ構造では、`generate.py` があるディレクトリの親ディレクトリにある lib ディレクトリ (`..../HellWorld-work/bin/../lib` ＝ `..../HelloWorld-work/lib`) を sys.path に加えています。  
  この lib の下にある `splunklib` モジュールを呼び出します。  
  このことからわかることは、`lib/` を Splunk が自動的に PYTHONPATH に設定するわけではないということです。  
  自前のライブラリを追加する場合には、sys.path の設定が必要になりますが、このテンプレートを使用する場合には、lib ディレクトリに置けば読み込むことができます。
* **7行目** Splunk カスタムサーチコマンドに必要なモジュールを呼び出しています。
* **10行目** カスタムサーチコマンドのためのデコレータを記述しています。  
  (デコレータの説明は「[デコレータ(decorator) とは - Python の汎用デコレータ(decorator)フレームワークを作る - Qiita](https://qiita.com/msi/items/acfa737842416580deaf#%E3%83%87%E3%82%B3%E3%83%AC%E3%83%BC%E3%82%BFdecorator-%E3%81%A8%E3%81%AF)」をご参照ください)
* **11行目** Splunk から呼び出されるコマンドのクラスの定義部分です。`%(command.title())` の部分を適切な名前に変えます。  
  (27行目にも出てきますので、同じ文字列で置き換えます)  
  基底クラスとして GeneratingCommand クラスを指定しています。
* 12行目～22行目 pydoc 部分は、できれば、`%( )` の部分をそれぞれ適切なものに置き換えて記述する必要がありますが、今回は省略します。
* **23行目** コマンドの本体になります。`generate` という名前はそのまま使用する必要があります。  
  `generate()` 関数は、iterator である必要があります(後述)。
* **27行目** コマンドのディスパッチャーです。`%(command.title())` 部分は、11行目に出てきたものと同じ文字列で置き換えます。

## テンプレートの修正

テンプレート `generate.py` を `HelloWorld.py` にコピーして、コマンド名などを修正します。

11行目と27行目の `%(command.title())` を `HelloWorld`にします。
エディタでもいいですが、sed で修正すると楽かもしれません。

```shell_session:sed&nbsp;を使った修正
$ sed -e 's/%(command.title())/HelloWorld/' generate.py > HelloWorld.py
$ diff generate.py HelloWorld.py
11c11
< class %(command.title())Command(GeneratingCommand):
---
> class HelloWorldCommand(GeneratingCommand):
27c27
< dispatch(%(command.title())Command, sys.argv, sys.stdin, sys.stdout, __name__)
---
> dispatch(HelloWorldCommand, sys.argv, sys.stdin, sys.stdout, __name__)
$ 
```

## generate() の修正 ― イベントをひとつだけ返す

greeting というフィールドに "hello, world" をひとつだけ返すように修正します。

戻す型は辞書型です。このとき、Splunk は時刻をキーにすることが多いので、`_time` フィールドに時刻を設定します。

また、上にも書きましたが、generate() は iterate タイプである必要があります。
:point_up_tone3: これには二つの方法があります。

1. return で返す: 戻す型はリストである必要があります。return で返す場合には、すべてのデータがそろった時点で、それぞれの辞書型をリストにして、return する必要があります。
2. yield で返す: 単一の辞書型を都度 yeild で返していきます。

まずは、ひとつだけエントリを返しますので、return で試してみましょう。
時刻を追加するために 5行目に `import time()` を挿入し、24行目と25行目の部分を次のように修正します。

```python
     1  #!/usr/bin/env python
     2
     3  import sys
     4  import os
     5  import time      # _time を設定するために新たに import
     6
     7  sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
     8  from splunklib.searchcommands import \
     9      dispatch, GeneratingCommand, Configuration, Option, validators
    10
    11  @Configuration()
    12  class HelloWorldCommand(GeneratingCommand):
    13      """ %(synopsis)
    14
    15      ##Syntax
    16
    17      %(syntax)
    18
    19      ##Description
    20
    21      %(description)
    22
    23      """
    24      def generate(self):
    25          return [{"_time": time.time(), "greeting": "hello, world"}] # リストであることに注意
    26
    27
    28  dispatch(HelloWorldCommand, sys.argv, sys.stdin, sys.stdout, __name__)
```

25 行目は `_time` フィールドに時刻を入れ、`greeting` フィールドに "hello, world" を入れて、リストとして return しています。

## 動作確認

### 不要な設定ファイルを削除

動作確認は `python3 HelloWorld.py __EXCECUTE__ < /dev/null` で行えますが、今のまま実行するとエラーになります。

```shell_session:エラー発生
$ python3 --version
Python 3.6.9
$ python3 HelloWorld.py __EXECUTE__ < /dev/null
Traceback (most recent call last):
  File "HelloWorld.py", line 8, in <module>
    from splunklib.searchcommands import \
  File "../lib/splunklib/searchcommands/__init__.py", line 145, in <module>
    from .environment import *
  File "../lib/splunklib/searchcommands/environment.py", line 120, in <module>
    splunklib_logger, logging_configuration = configure_logging('splunklib')
  File "../lib/splunklib/searchcommands/environment.py", line 103, in configure_logging
    fileConfig(filename, {'SPLUNK_HOME': splunk_home})
  File "/usr/lib/python3.6/logging/config.py", line 84, in fileConfig
    handlers = _install_handlers(cp, formatters)
  File "/usr/lib/python3.6/logging/config.py", line 131, in _install_handlers
    hlist = cp["handlers"]["keys"]
  File "/usr/lib/python3.6/configparser.py", line 959, in __getitem__
    raise KeyError(key)
KeyError: 'handlers'
$
```

これは、.conf ファイルを読み込む際にエラーになっているものです。

`default/` ディレクトリ下のファイルを読みに行きますが、現在は何も設定していないので、テンプレート文字がそのまま残っています。

必要な設定はいずれ行うとして、本稿では、一旦不要なファイルを削除します。
今回の対象は logging.conf です。(トラブルシューティングのキーワードは、上のエラーの `in configure_logging` と `KeyError: 'handlers'` です。)

`default/` ディレクトリにある、logging.conf を削除します。(復活したければ、SDK のディレクトリから改めてコピーします。)

```shell_session:logging.conf&nbsp;の削除
$ rm ../default/logging.conf
$ ls ../default/
app.conf  commands-scpv1.conf  commands-scpv2.conf  commands.conf  data
$
```

### テスト実行

開発環境内でテストで実行します。

`python3 HelloWorld.py __EXECUTE__ < /dev/null`

というコマンド列で実行可能です。
:point_up_tone3: `__EXECUTE__` は `EXECUTE` の前後にアンダースコア (`_`) ふたつずつです。
Generating command であっても標準入力待ちになる[^6]ので、入力として /dev/null を指定しておきます。

[^6]: Splunk 上で実行した場合には、Splunk から meta 情報が入力され、Splunk ライブラリによって処理されます。

```shell_session:テスト実行
$ python3 --version
Python 3.6.9
$ python3 HelloWorld.py __EXECUTE__ < /dev/null

_time,__mv__time,greeting,__mv_greeting
1584528442.0660899,,"hello, world",
$
```

`__mv__time` と `__mv_greeting` という知らないフィールドが追加されていますが、これは Splunk ライブラリが自動的に付加するものです。ここでは、意図した `_time` と `greeting` が出力されていることを確認してください。

## Splunk へのインストール

Splunk へインストール(登録)して、実行してみましょう。

### 設定ファイルの修正

`default/` ディレクトリにある `app.conf` と `commands.conf` を修正します。

#### `app.conf` の修正
参照: [app.conf - Splunk Documentation](https://docs.splunk.com/Documentation/Splunk/8.0.2/Admin/Appconf)

テンプレートのままの `app.conf` は下のようになっています。

```ini:app.conf
     1  # Splunk app configuration file
     2
     3  [ui]
     4  label = %(app_label)        ＜＝ ここを App の名前に修正します。
     5  is_visible = 1
     6
     7  [launcher]
     8  description = %(app_description)
     9  author = %(app_author)
    10  version = %(app_version)
    11
    12  [package]
    13  id = %(app_id)
    14
    15  [install]
    16  is_configured = 0
```

カスタムサーチコマンド単体でもインストール可能ですが、システムのディレクトリに設定することになるので、新規の App として登録するために、`label` に名前を設定します。

ここでは、"Hello World" とします。他の部分も本来は修正が必要ですが、カスタムサーチコマンドの動作には影響しませんので、このままにします。

```diff
*** app.conf.orig       2020-03-18 20:00:07.303785404 +0900
--- app.conf    2020-03-18 20:00:18.943809800 +0900
***************
*** 1,7 ****
  # Splunk app configuration file

  [ui]
! label = %(app_label)
  is_visible = 1

  [launcher]
--- 1,7 ----
  # Splunk app configuration file

  [ui]
! label = Hello World
  is_visible = 1

  [launcher]
```

```ini:修正後の&nbsp;app.conf
# Splunk app configuration file

[ui]
label = Hello World
is_visible = 1

[launcher]
description = %(app_description)
author = %(app_author)
version = %(app_version)

[package]
id = %(app_id)

[install]
is_configured = 0
```

#### `commands.conf` の修正
参照: [commands.conf - Splunk Documentation](https://docs.splunk.com/Documentation/Splunk/8.0.2/Admin/Commandsconf)

カスタムサーチコマンドの設定を行うのが `commands.conf` です。

`default/` ディレクトリの下には `commands.conf`、 `commands-scpv1.conf`、`commands-scpv2.conf` という三つのファイルがあります。カスタムサーチコマンドが Splunk とやり取りをするプロトコルのバージョンには v1 と v2 があるため、二つのテンプレートが用意されています。テンプレートのままの `commands.conf` は v1 の内容になっています。

厳密にはどちらのプロトコルを使うのかを吟味する必要がありますが、今後は v2 になっていくと思いますので、`commands-scpv2.conf` を `commands.conf` にコピーして修正します。

```shell_session:commands-scpv2.conf&nbsp;のコピー
$ cp commands-scpv2.conf commands.conf
$ 
```

```ini:commands.conf&nbsp;(v2)&nbsp;テンプレート
     1  # [commands.conf]($SPLUNK_HOME/etc/system/README/commands.conf.spec)
     2  # Configuration for Search Commands Protocol version 2
     3
     4  [%(command.lower()]
     5  filename = %(command.lower()).py
     6  chunked = true
```

SPL から呼び出すコマンド名と `.py` を除いたファイル名が同じ場合には `filename` の設定は不要なのですが、今回は呼び出すコマンド名を `generatehello`、ファイル名が `HelloWorld.py` ですので、設定を行います。

また、冒頭で書いたように、Python3 を使いますので、この設定を行います。
:point_up_tone3: Python3 を使用する場合には、

`python.version = python3` 

を設定します。

```ini:書き換え後の&nbsp;commands.conf
     1  # [commands.conf]($SPLUNK_HOME/etc/system/README/commands.conf.spec)
     2  # Configuration for Search Commands Protocol version 2
     3
     4  [generatehello]
     5  filename = HelloWorld.py
     6  chunked = true
     7  python.version = python3
```

### Splunk apps ディレクトリへのコピー

`app.conf` に App 名を書きましたので、Splunk の他の Apps とディレクトリ名がぶつからなければ、インストールするディレクトリは何でも構いません。ここでは、`helloworld` としておきます。インストール(コピー)の前に、不要な設定ファイルは削除しておきます。

:point_up_tone3: インストール先のディレクトリは `$SPLUNKHOME/etc/apps/` 下です。`HelloWorld-work/` ディレクトリを丸ごとコピーします。この時、ファイルのオーナやパーミッションに注意してください。Splunk を root 以外で起動している場合には特に注意が必要です。

```shell_session
$ rm HelloWorld-work/default/commands-scpv[12].conf; sudo sh -c "cp -rp HelloWorld-work /opt/splunk/etc/apps/helloworld; chown -R splunk:splunk /opt/splunk/etc/apps/helloworld"
$ ls -ld /opt/splunk/etc/apps/helloworld/{*,}
drwxr-xr-x 6 splunk splunk 4096 Mar 18 07:52 /opt/splunk/etc/apps/helloworld/
drwxr-xr-x 2 splunk splunk 4096 Mar 18 09:33 /opt/splunk/etc/apps/helloworld/bin
drwxr-xr-x 3 splunk splunk 4096 Mar 18 11:43 /opt/splunk/etc/apps/helloworld/default
drwxr-xr-x 3 splunk splunk 4096 Mar 18 07:52 /opt/splunk/etc/apps/helloworld/lib
drwxr-xr-x 2 splunk splunk 4096 Feb 13 23:30 /opt/splunk/etc/apps/helloworld/metadata
$
```

### btool によるチェック
参照: [Use btool to troubleshoot configurations - Splunk Documentation](https://docs.splunk.com/Documentation/Splunk/8.0.2/Troubleshooting/Usebtooltotroubleshootconfigurations)

設定ファイルに間違いがないか、btool を使用してチェックします。

`$SPLUNKHOME/bin/splunk btool check`

問題が無ければ何も表示されませんが、何か間違いなどがあると、次のような表示が出ます。(`python.version` を誤って `ython.version` としてしまった例)

```shell_session:設定ファイルに問題がある場合の表示
$ sudo su splunk -c "/opt/splunk/bin/splunk btool check"
                Invalid key in stanza [generatehello] in /opt/splunk/etc/apps/helloworld/default/commands.conf, line 7: ython.version  (value:  python3).
$
```

`--debug` オプションを付けると、問題のない設定ファイルについても大量にメッセージがでます。 

### Splunk の Refresh または再起動

http(s)://_yourURL_/debug/refresh で設定が読み込まれますので、再起動は基本的に不要ですが、読み込まれないようであれば、Splunk を再起動してください。

# カスタムサーチコマンド実行

## SPL 投入

いよいよ、Splunk 上で実行します。App から `Hello World` を選択してサーチ窓にコマンドを入力します。

`|generatehello`

![コマンド実行 (1)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555032/0d3587cc-43a4-89b3-4c72-832058b72025.png "コマンド実行 (1)")

動きました。

1. App を選択
2. App が "Hello World" になっていることを確認
3. `| generatehello` を投入
4. イベントが一つ表示されていて、時刻は表示されているが「イベント」は空欄
5. 「関連するフィールド」に "greeting" が存在

greeting が「イベント」として表示されないのは、生成されたデータに `_raw` フィールドが含まれていないためです。
左側のメニューの `greeting` の部分をクリックすると、"hello, world" が出力されているのがわかります。

![SplunkHelloWorld-002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555032/39dad64d-b522-0e7a-d23a-7719304d482b.png)

明示的に出力したければ、 `table` などでフィールドを指定すると表示されます。

![SplunkHelloWorld-003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555032/58621b77-0bc7-fdf5-0008-12e1028ba374.png)


## トラブルシューティング

なんか変だなと思った場合には、ジョブのログを見ましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555032/85273c47-eae1-6a22-db61-82b43e01653f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555032/2c33e6cc-7ccd-e32e-e105-c46cc9183157.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555032/63262606-03b9-a360-89a1-f00dca7e442f.png)

上の例は、テストのために例外を発生させてみたものです。

---

今回はここまで。
はじめの一歩が踏み出せれば、次の一歩も踏み出しやすくなります。

# まとめ

* `| generatehello count=3` と入力してイベントが3つ表示されるカスタムサーチコマンド作成を目標として、今回は引数のないコマンドでイベントをひとつだけ表示するところまで作成しました。
* 今のままでは「イベント」には表示されず、明示的にフィールドを参照しなければ "hello, world" が表示されません。

次回は、コマンドをブラシアップして仕上げます。 ⇒ 続きの「[Splunk カスタムサーチコマンド (custom search command) 作成 その2 - Qiita](https://qiita.com/msi/items/ca5d3c553bd49d8665ac)」を書きました。

* Splunk SDK のプログラム例はこちら→ [splunk-sdk-python/generatehello.py at master · splunk/splunk-sdk-python · GitHub](https://github.com/splunk/splunk-sdk-python/blob/master/examples/searchcommands_app/package/bin/generatehello.py)


* 【2020/05/04 追記】GitHub でサンプルコードを公開しました → [Splunk-CustomSearchCommand-FirstStep at step1](https://github.com/s-yamano/Splunk-CustomSearchCommand-FirstStep/tree/step1)