トリガーで改行文字を扱う方法

こんにちは。

文字列型のアイテムがあり、一回で複数行のデータ(つまり\nを含む)を取得しています。「このアイテム内のどこかにxxxを含み、その後でyyyを含む」場合に障害としたいのですが、トリガーで改行文字を使う方法が分かりません。
なんとかトリガーで改行文字をうまく扱う方法は無いものでしょうか?
Zabbix 2.0.6 です。

例:データは以下の内容とします。
abcdexxxfgh
ijkyyylmn

{hostX:itemA.regexp("xxx.*yyy")}=1
正規表現の「.」は改行を含まないのでこれではだめです(これは想定通り)。

{hostX:itemA.regexp("xxx(.|\n)*yyy")}=1
普通に考えるとこれでいけそうな気はするのですが、だめでした。

{hostX:itemA.regexp("\n")}=1
複数行なので必ず\nを含んでいるはずなのですが、これも引っかからないようです。
{hostX:itemA.str("\n")}=1
strでもだめでした。また、"\\n"のように\を二つにしてもだめでした。

管理→一般設定→正規表現で「xxx(.|\n)*yyy、結果が真」という条件式を一つだけ設定して設定画面の「テスト」で試す(テスト文字列は上の文字列をコピペ)ときちんと「真」になりますが、これをxxxyyyという名前にして
{hostX:itemA.regexp(@xxxyyy)}=1
とすると、やっぱりできませんでした。

{hostX:itemA.regexp("xxx")}=1&{hostX:itemA.regexp("yyy")}=1
{hostX:itemA.str("xxx")}=1&{hostX:itemA.str("yyy")}=1
これだとちゃんと引っかかってくれるようです(xxxとyyyの順番は無視することになりますが)。
※&が半角だとうまく表示できないので全角にしています。

よろしくお願いします。

コメント表示オプション

お好みのコメント表示方法を選び「設定の保存」をクリックすると変更が反映されます。
ユーザー KAZ の写真

heyaさん

■これは駄目でしょうか?
{hostX:itemA.regexp(".*xxx.*\n.*yyy.*")}=1

駄目な場合、Zabbixエージェントをdebugレベルでログ取得できますでしょうか?

ユーザー heya の写真

KAZさん、ご回答ありがとうございます。

> {hostX:itemA.regexp(".*xxx.*\n.*yyy.*")}=1
試してみましたが、だめでした。
zabbix_senderで手動で試した(ホスト=localhost、アイテム=error-log)のでエージェントログはありません。
shell> zabbix_sender -z localhost -p 10051 -s localhost -k error-log -o "abc xxx def
ghi yyy jkl"

debug=4でのサーバーログの、関係ありそうな場所です。
4260:20130827:095931.573 Trapper got [{
"request":"sender data",
"data":[
{
"host":"localhost",
"key":"error-log",
"value":"abc xxx def\nghi yyy jkl"}]}] len 127
(略)
4275:20130827:095931.744 In evaluate_function() function:'localhost:error-log.regexp(".*xxx.*\n.*yyy.*")'
4275:20130827:095931.744 In evaluate_STR()
4275:20130827:095931.744 In get_function_parameter_str() parameters:'".*xxx.*\n.*yyy.*"' Nparam:1
4275:20130827:095931.744 In substitute_simple_macros() data:'.*xxx.*\n.*yyy.*'
4275:20130827:095931.744 get_function_parameter_str() value:'.*xxx.*\n.*yyy.*'
4275:20130827:095931.744 End of get_function_parameter_str():SUCCEED
4275:20130827:095931.745 In get_function_parameter_uint() parameters:'".*xxx.*\n.*yyy.*"' Nparam:2
4275:20130827:095931.745 End of get_function_parameter_uint():FAIL
4275:20130827:095931.745 End of evaluate_STR():SUCCEED
4275:20130827:095931.745 End of evaluate_function():SUCCEED value:'0'

最後、valueが0になっています。
ざっとソースも眺めてみましたが、よく分かりませんでした。

試しに簡単なcのコードを書いてみたんですが、こっちはちゃんとマッチしているようです。

#include <stdio.h>
#include <sys/types.h>
#include <regex.h>

static char *zbx_regexp(const char *string, const char *pattern, int *len, int flags) {
libs/zbxcommon/regexp.cからコピペ
}

int main(void) {
int len;
char *str = "abc xxx def\nghi yyy jkl";
char *c = zbx_regexp(str, ".*xxx.*\n.*yyy.*", &len, REG_EXTENDED | REG_NEWLINE);

if (c == NULL)
printf("not match\n");
else
printf("match\n%s\n", c);
}

↓結果
match
abc xxx def
ghi yyy jkl

zbx_regexp()に渡される値が何か加工されているんだろうか・・・?

#余談ですが、コードの貼り付け、なんとかならないものですかね。codeタグだとインデントとかが保存されないし空行も入れられないし、でもpreは使えないし。以前みたいに[code]が使えるといいんですが・・・。

ユーザー KAZ の写真
heyaさん
> {hostX:itemA.regexp(".*xxx.*\n.*yyy.*")}=1
試してみましたが、だめでした。
駄目でしたか…
debug=4でのサーバーログの、関係ありそうな場所です。

これからソース追ってみます。
#余談ですが、コードの貼り付け、なんとかならないものですかね。codeタグだとインデントとかが保存されないし空行も入れられないし、でもpreは使えないし。以前みたいに[code]が使えるといいんですが・・・。

そーなんですよね。
入力書式をFull HTMLで上手くいかないかと思っているのですが…
こんな感じで<pre>タグ使えるんですが…
  1444:20130827:113823.151 End of send_buffer():SUCCEED
  1444:20130827:113823.151 Sleeping for 1 second(s)
  1496:20130827:113823.258 In collect_perfstat()
<br/>を入れないと改行してくれないんですよ…A(^^;




ユーザー KAZ の写真
heyaさん

4275:20130827:095931.745 In get_function_parameter_uint() parameters:'".*xxx.*\n.*yyy.*"' Nparam:2
4275:20130827:095931.745 End of get_function_parameter_uint():FAIL
get_function_parameter_uintがFAILしてますね。
問題の正規表現の場所です。
ここが問題っぽいですね…

もうちょっと追ってみます。

■追記 2013-08-27 16:51
~\zabbix-2.0.6\src\libs\zbxserver\evalfunc.cのget_function_parameter_uint関数
ログから見ると、ここで、goto clean;に流れてますね。
	if (0 != get_param(parameters, Nparam, parameter, FUNCTION_PARAMETER_LEN_MAX))
		goto clean;
■追記 2013-08-27 17:07
strタイプのトリガーなのでget_function_parameter_uint関数はFAILでOKでした。A(^^;













ユーザー KAZ の写真
heyaさん

↓ログにあるevaluate_STR関数から追い始めました。
4275:20130827:095931.744 In evaluate_STR()

\zabbix-2.0.6\src\libs\zbxserver\evalfunc.cのevaluate_STR関数

4275:20130827:095931.744 In get_function_parameter_str() parameters:'".*xxx.*\n.*yyy.*"' Nparam:1
4275:20130827:095931.744 In substitute_simple_macros() data:'.*xxx.*\n.*yyy.*'
4275:20130827:095931.744 get_function_parameter_str() value:'.*xxx.*\n.*yyy.*'
4275:20130827:095931.744 End of get_function_parameter_str():SUCCEED
4275:20130827:095931.745 In get_function_parameter_uint() parameters:'".*xxx.*\n.*yyy.*"' Nparam:2
4275:20130827:095931.745 End of get_function_parameter_uint():FAIL
上記より、以下の2つ目のif文はFAILとなり…
	if (FAIL == get_function_parameter_str(item->hostid, parameters, 1, &arg1))
		goto exit;

	if (FAIL == get_function_parameter_uint(item->hostid, parameters, 2, &arg2, &flag))
	{
		arg2 = 1;
		flag = ZBX_FLAG_VALUES;
	}
次のif分↓は正規表現の登録をしていないので飛ばして…
	if ((ZBX_FUNC_REGEXP == func || ZBX_FUNC_IREGEXP == func) && '@' == *arg1)
	{
		arg1_esc = DBdyn_escape_string(arg1 + 1);
		result = DBselect("select e.expression,e.expression_type,e.exp_delimiter,e.case_sensitive"
				" from regexps r,expressions e"
				" where r.regexpid=e.regexpid"
					" and r.name='%s'" DB_NODE,
				arg1_esc, DBnode_local("r.regexpid"));
		zbx_free(arg1_esc);

		while (NULL != (row = DBfetch(result)))
		{
			add_regexp_ex(®exps, ®exps_alloc, ®exps_num,
					arg1 + 1, row[0], atoi(row[1]), row[2][0], atoi(row[3]));
		}
		DBfree_result(result);
	}
次のif文は「arg2 = 1」「flag = ZBX_FLAG_VALUES」「item->lastvalue[0]!=NULL」なはずなので…
evaluate_STR_local関数が呼ばれます。
	if (ZBX_FLAG_VALUES == flag)
	{
		if (0 == arg2 || NULL == item->lastvalue[0])
		{
			zabbix_log(LOG_LEVEL_DEBUG, "result for STR is empty");
			goto clean;
		}

		if (SUCCEED == evaluate_STR_local(item, func, regexps, regexps_num, arg1, arg2, &found))
			goto skip_get_history;

		h_value = DBget_history(item->itemid, item->value_type, ZBX_DB_GET_HIST_VALUE,
				0, now, NULL, NULL, arg2);
	}
	else
		h_value = DBget_history(item->itemid, item->value_type, ZBX_DB_GET_HIST_VALUE,
				now - arg2, now, NULL, NULL, 0);
ここからログがしばらくでません。A(^^;
推測ですが、「goto skip_get_history;」が実行されてます。
で、「1 == found」じゃなく「zbx_strlcpy(value, "0", MAX_BUFFER_LEN);」が実行され…
skip_get_history:
	if (1 == found)
		zbx_strlcpy(value, "1", MAX_BUFFER_LEN);
	else
		zbx_strlcpy(value, "0", MAX_BUFFER_LEN);

	res = SUCCEED;
下記のログの様に「value:'0'」になるのかなと…
4275:20130827:095931.745 End of evaluate_STR():SUCCEED
4275:20130827:095931.745 End of evaluate_function():SUCCEED value:'0'



下記の関数に「zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);」とか
パラメータを出力するデバック文を追加して動かしてみれば追えそうですが…A(^^;
・evaluate_STR_local関数
・evaluate_STR_one関数
・regexp_match_ex

本日はちょっと厳しい状態です。A(^^
























ユーザー heya の写真

こんにちは。

KAZさん、ありがとうございます。
こちらでももう少しソースを追ってみます。

ユーザー heya の写真

こんにちは。

原因が分かりました。「\n」≠LFということです。
以下に経緯を書きますが、少し長いです。

■「\n」と改行
どうやら、トリガーで指定した条件式は、データベースに保存するときに改行が「\n」(「\」と「n」の二文字)になって保存してあったようです。
mysql> select * from functions where functionid=12980;
+------------+--------+-----------+----------+--------------------+
| functionid | itemid | triggerid | function | parameter |
+------------+--------+-----------+----------+--------------------+
| 12980 | 23332 | 13522 | regexp | ".*xxx.*\n.*yyy.*" |
+------------+--------+-----------+----------+--------------------+
これはトリガーの設定ページで条件式の欄に改行そのものではなく「\n」という文字を書いたからです。

一方のデータは、改行は改行のまま保存されていました。
mysql> select * from history_str where clock=1378090084;
+--------+------------+-------------------------+-----------+
| itemid | clock | value | ns |
+--------+------------+-------------------------+-----------+
| 23332 | 1378090084 | abc xxx def
ghi yyy jkl | 258548227 |
+--------+------------+-------------------------+-----------+
zabbix_senderで送信するときは、""で囲みながら普通に改行した→改行文字が使われた、ということでしょうね。

試しに、src/libs/zbxcommon/regexp.cの、zbx_regexp()の先頭部分に
zabbix_log(LOG_LEVEL_DEBUG, "zbx_regexp: string=%s, pattern=%s", string, pattern);
という行を追加してみると、ログに
26592:20130902:121531.425 zbx_regexp: string=abc xxx def
ghi yyy jkl, pattern=.*xxx.*\n.*yyy.*
と記録されました。ここでも改行と「\n」と食い違っています。

先日書いたテストコードにも同様に↓を追加して試してみました。
printf("string=%s, pattern=%s\n", string, pattern);

char *c = zbx_regexp(str, ".*xxx.*\n.*yyy.*", &len, REG_EXTENDED | REG_NEWLINE); の場合
 string=abc xxx def
 ghi yyy jkl, pattern=.*xxx.*
 .*yyy.*
 match
 abc xxx def
 ghi yyy jkl

char *c = zbx_regexp(str, ".*xxx.*\\n.*yyy.*", &len, REG_EXTENDED | REG_NEWLINE); の場合
 string=abc xxx def
 ghi yyy jkl, pattern=.*xxx.*\n.*yyy.*
 not match

zbx_regexp()は内部でregcomp()とregexec()を呼び出して正規表現部分の処理は全てこの関数に丸投げしています。で、reg~()は「\n」を改行とみなしてくれないということですかね。

というわけで、トリガーの条件式に、「\n」ではなく改行そのものを入れてみました。
・・・しかしこれも失敗しました。

■CRLFとLF
調べてみると、どうやらトリガーの条件式の方の改行はCRLFになっているようで(ZabbixのページにはWindowsのブラウザからアクセスしている)、zabbix_senderで送った方はLFのみ。この辺が原因のようです。
というわけで、今度はfrontends/triggers.phpを見てみました。138行目辺り、
$trigger = array(
'expression' => $_REQUEST['expression'],
'description' => $_REQUEST['description'],
'priority' => $_REQUEST['priority'],
'status' => $_REQUEST['status'],
'type' => $_REQUEST['type'],
'comments' => $_REQUEST['comments'],
'url' => $_REQUEST['url'],
'dependencies' => zbx_toObject(get_request('dependencies', array()), 'triggerid')
);
POSTで受け取った値をそのまま記憶しているようです。試しに$_REQUEST['expression']をダンプして表示させてみると、やっぱりCRLFになっていました。

そこで、$_REQUEST['expression']の部分をpreg_replace("/\r\n|\r/", "\n", $_REQUEST['expression'])と書き換えてみると、改行がLFだけになり、zabbix_senderで送ったときに障害として検知されるようになりました(マルチバイトのことも考えるとmb_ereg_replace()の方がいいかも)。

■さてどうしたものか
ただ、今の挙動はバグなのか仕様なのか?本来はどうあるべきなんでしょうね。個人的には(トリガーの条件式に限らず)パラメーターを読んだ時点で改行を置換してくれると親切な気がしますが・・・。
frontends/php/include/defines.inc.phpで
$_REQUEST = $_POST + $_GET;
とありますが、ここで置換?

ユーザー KAZ の写真

heyaさん

返信遅くなりました。

CRLFとLFの問題ですか…
難しいですね…

以前、私が作ったwebシステムではDB格納時は改行コードはLFとして置換していました。
Zabbixだと収集したデータとトリガー条件を比較する前に改行コードをLFに変換する処理入れれば良いかと思いますが…A(^^;