Splunkでカスタムサーチコマンドを作る(Streaming Command)


Splunkで文字列を逆順にする。の最後あたりに

頻繁に使うなら、外部pythonスクリプトを用意した方がいいかもしれません。

と書いたこともあり、作ってみました。 本当大変だった

@uneyamauneko @msi さんの記事には本当助けられました。

コピペ元はcountmatches.py@splunk-sdk-python

準備

pip install splunk-sdk でいけるとInstall the Splunk Enterprise SDK for Pythonには書いてある。

でも自分の環境

環境変数等
% uname -v 
Darwin Kernel Version 19.6.0: Tue Nov 10 00:10:30 PST 2020
% which python
/opt/anaconda3/bin/python
% python --version
Python 3.7.6
% $SPLUNK_HOME/bin/splunk --version
Splunk 8.1.1 (build 08187535c166)

pip install splunk-sdkすると

pip結果
 % pip install splunk-sdk
Requirement already satisfied: splunk-sdk in /opt/anaconda3/lib/python3.7/site-packages (1.6.14)

と入っているよと。確かに brew install splunk-sdkした記憶あり。

ここでの通りcommands.conf$SPLUNK_HOME/etc/<<MYAPPS>>/default/に設置
なお https://docs.splunk.com/Documentation/Splunk/8.1.1/Admin/Commandsconf
の記載は間違っています。通報ずみ。

commands.conf
[rev]
chunked = true
filename = rev.py

重要事項

/Applications/Splunk/lib/python3.7/site-packagesにlinkを作成する。

ln -s /Applications/Splunk/lib/python3.7/site-packages $SPLUNK_HOME/lib/python3.7/site-packages/splunklib 
結果
% pwd
/Applications/Splunk/lib/python3.7/site-packages
% ls -la
lrwxr-xr-x    1 XXXXX  wheel     52 12 31 10:53 splunklib -> /opt/anaconda3/lib/python3.7/site-packages/splunklib

理由

  • 何度やっても import splunklibがうまくいかない
  • /usr/bin/env splunk で起動したpythonだとimport splunklibはうまく行っている。
  • $SPLUNK_HOME/bin/splunk cmd pythonpythonだとimport splunklibは失敗する。
  • 仕方がないので、シンボリックリンクを作成したらうまくいくようになった。

Code

rev.py
#!/usr/bin/env python

import sys
from splunklib.searchcommands import dispatch, StreamingCommand, Configuration, Option, validators

@Configuration()
class reverseCommand(StreamingCommand):
    """ Reverse the string.

    ##Syntax

    .. code-block::
        rev output=<field> <field>

    ##Description
    Outputs the string of the input field in reverse order.

    ##Example

    .. code-block::
        index=_internal clientip=* | head 1 | rev output=r_clientip clientip

    """

    output = Option(
        doc='''
        **Syntax:** **output=***<fieldname>*
        **Description:** Name of the field that will hold the revesed text''',
        require=True, validate=validators.Fieldname())

    def stream(self, records):
        self.logger.debug('revCommand: %s', self)  # logs command line
        for record in records:
            for fieldname in self.fieldnames:
                pass
            record[self.output]=record[fieldname][-1::-1]
            yield record

dispatch(reverseCommand, sys.argv, sys.stdin, sys.stdout, __name__)

使い方

| rev output=<output field> <field>

結果

rev.spl
index=_internal clientip=* | head 1
| rev clientip output=clientip_s

解説という名の苦労話

  • ほとんど元ネタのコピペ
  • シバンは効果がない(みたい)。

最初にcommands.confrev.pyを所定の場所(defaultbin)に格納してSplunkを再起動した。
その時はこんなファイル。

rev.py
#!/usr/bin/env python

import sys
from splunklib.searchcommands import dispatch, StreamingCommand, Configuration, Option, validators

@Configuration()
class reverseCommand(StreamingCommand):
    """ Reverse the string.
    """
    def stream(self, records):
        self.logger.debug('revCommand: %s', self)  # logs command line
        for record in records:
            yield record

dispatch(reverseCommand, sys.argv, sys.stdin, sys.stdout, __name__)

何もしないコマンド。とりあえず、これで動作を確認した。
いったんこれで |rev clientipがエラーなく動作することを確認できた。

このあと、色々と変更しては、実行して結果をみて、修正しては実行を繰り返した。

このコマンドの主な処理は下記の通り。

main
    def stream(self, records):
        self.logger.debug('revCommand: %s', self)  # logs command line
        for record in records:
            for fieldname in self.fieldnames:
                pass
            record[self.output]=record[fieldname][-1::-1]
            yield record

returnで戻り値を返さないでyieldで返しているのがStreamingCommandなのかな?

いろいろ試していると、コマンドの引数であるフィールド名はfor fieldname in self.fieldnames:で作ったfieldnameじゃないと取れないみたい。 
unhashable type: 'list'のエラーが出る。

リストを結合してみたら、無くても大丈夫だった。
外してみた。
    def stream(self, records):
        self.logger.debug('revCommand: %s', self)  # logs command line
        for record in records:
            record[self.output]=record[''.join(self.fieldnames)][-1::-1]
            yield record

参考はこちら StreamingCommand.fieldnames

単純に上書きするだけなら
record[fieldname]=record[fieldname][-1::-1]でいいけど使い勝手が微妙だなとおもって、オプションを設定して上記に変更

まとめ

初めて作ったこともあって、とても大変でした。
importができないでかなりの時間が・・・

ジョブの調査をみてみるとちょっと時間がかかっているような気がします。
取り敢えずは、単純にリターンするだけのpythonだとこれをテンプレートに作れると思います。