/*
 *	syslog.c
 *
 *	システムログの制御
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2016 Naoyuki Sawa
 *
 *	* Mon Jan 18 21:37:57 JST 2016 Naoyuki Sawa
 *	- 1st リリース。
 *	* Thu Mar 03 21:33:11 JST 2016 Naoyuki Sawa
 *	- プログラム起動⇒setlogmask()⇒syslog()の順で呼び出すと、暗黙のopenlog()が実行されず、stSysLogが初期化されないバグが有りました。
 *	  stSysLog.identがNULLのままでsyslog()の処理が行われ、syslog()内のstrconcat()でstSysLog.ident(=NULL)が終端と見なされそれ以降の要素が無いログが生成されてしまっていました。
 *	  今回、プログラム起動⇒setlogmask()⇒syslog()の順で呼び出した場合も、暗黙のopenlog()が実行されるように修正しました。
 */
#include "clip.h"
/*****************************************************************************
 *	
 *****************************************************************************/
typedef struct _ST_SysLog {
	int			mask;						//ログの優先度マスク。0ならばまだopenlog()が呼ばれていない事を示す。
	FILE*			fp;						//ログ記録用ファイル。最初のメッセージを記録する時に開く。
	const char*		ident;						//各メッセージの前に付与する文字列。						※未対応
	int			option;						//openlog()の動作とその後のsyslog()の呼び出しを制御するフラグ。			※一部未対応
	int			facility;					//メッセージに記録するプログラムのタイプ。					※未対応
} ST_SysLog;
static ST_SysLog stSysLog;
/*****************************************************************************
 *	
 *****************************************************************************/
//□「Linux Programmer's Manual - SYSLOG」(http://linuxjm.osdn.jp/html/LDP_man-pages/man3/openlog.3.html)より引用:
//│openlog()はログを出力しようとしているプログラムからログ記録用プログラム(systemlogger)への接続を開始する。
//│identで指定した文字列は各メッセージの前に付与される。
//│通常はidentにはプログラム名が設定される。
//│identがNULLの場合、プログラムがidentとして使用される(POSIX.1-2008ではidentがNULLの場合の動作は規定されていない)。
//│option引き数は、openlog()の動作とその後のsyslog()の呼び出しを制御するフラグを指定する。
//│facility引き数は、後でsyslog()を呼び出す際にfacilityが指定されなかった場合に使用されるデフォルト値を決定する。						※未対応
//│openlog()は必須ではなく、必要に応じてsyslog()から呼び出される。
//│syslog()が呼び出した場合、identのデフォルト値はNULLになる。
void openlog(const char* ident, int option, int facility) {
	closelog();
	if(!ident) { ident = program_invocation_short_name; }			//identがNULLの場合、プログラムがidentとして使用される(POSIX.1-2008ではidentがNULLの場合の動作は規定されていない)。
	stSysLog.ident		= ident;					//openlog()呼び出しのident引き数は、値がそのまま保持されていることを前提にしている。それゆえ、identで指定された文字列が変更されると、syslog()は変更された文字列の追加するだろうし、指定された文字列が存在しなくなった場合、結果は未定義である。
	stSysLog.option		= option;
	stSysLog.facility	= facility;
	stSysLog.mask		= -1;						//マスクの初期値は、すべての優先度に対してログ収集が有効になるような値である。
	if(stSysLog.option & LOG_NDELAY) {					//optionにLOG_NDELAYが指定されていたら、ログ記録用ファイルを即座に開く(通常は、最初のメッセージを記録する時に開く)。
		stSysLog.fp = fopen("syslog.out", "a");
	 //不要	if(!stSysLog.fp) { DIE(); }	⇒開けなかった場合の動作はLOG_CONSに依存する。
	}
}
/*---------------------------------------------------------------------------*/
//□「Linux Programmer's Manual - SYSLOG」(http://linuxjm.osdn.jp/html/LDP_man-pages/man3/openlog.3.html)より引用:
//│syslog()はログメッセージを出力し、syslogdがそのメッセージを記録する。
//│priority引き数はfacilityとlevelとの組合せで指定する。
//│残りのformat引き数はprintfと似たスタイルの書式とその書式に与える値である。
//│format中の2文字%mはその時点でのerrnoに関連するエラーメッセージ文字列(strerror)によって置き換えられる。							※未対応
//│必要な場合は末尾に改行が加えられる。
void syslog(int priority, const char* format, ...) {
	va_list ap;
	va_start(ap, format);
	vsyslog(priority, format, ap);
	va_end(ap);
}
/*---------------------------------------------------------------------------*/
//□「Linux Programmer's Manual - SYSLOG」(http://linuxjm.osdn.jp/html/LDP_man-pages/man3/openlog.3.html)より引用:
//│vsyslog()関数はsyslog()と同じ機能を持つが、可変引き数リストを指定することができる点が異なる。
//│指定された引き数は、stdarg可変引き数リストマクロを用いて取得される。
void vsyslog(int priority, const char* format, va_list ap) {
	if(!stSysLog.mask) { openlog(NULL, LOG_ODELAY, LOG_USER); }		//openlog()が明示的に呼ばれていなければ、暗黙に呼び出す。
	if(stSysLog.mask & LOG_MASK(priority)) {				//maskの各ビットは優先度に対応しており、対応するビットが1にセットされている優先度に対してログ収集(logging)が有効になる。
		//□「ログ管理〜syslogとその周辺〜【入門編】syslogについて」(http://www.infoscience.co.jp/technical/press/intro02.html)より引用:
		//│<PRI>の部分は主にログを振り分けるために用いられるので、ファイルに書き出されるときには通常は記述されません。
		//│FreeBSDのsyslogdでは、-vオプションを付けることで、このfacilityやpriorityの情報をログに書き出すことができます。
		//│[syslogd -v のとき]
		//│Nov 11 13:33:56 <16.7> kei test[13079]: test message
		//│[syslogd -v -v のとき]
		//│Nov 11 13:34:30 <local0.debug> kei test[13080]: test message
		//当実装では、facilityは書き出さないが、priorityは書き出す事にしました。
		static const char* const TBL_Priority[]={
			"emerg",	//0 LOG_EMERG
			"alert",	//1 LOG_ALERT
			"crit",		//2 LOG_CRIT
			"err",		//3 LOG_ERR
			"warning",	//4 LOG_WARNING
			"notice",	//5 LOG_NOTICE
			"info",		//6 LOG_INFO
			"debug",	//7 LOG_DEBUG
		};
		/*  000000000011111１  */
		/*  012345678901234５  */
		/* "Jan 18 21:37:57\0" */
		char buf[16], *s1, *s2;
		time_t t;
		struct tm* tm;
		time(&t);
		tm = localtime(&t);
		strftime(buf, sizeof buf, "%b %d %H:%M:%S", tm);
		s1 = strdup_vprintf(format, ap);
		s2 = strconcat(
			buf,							//タイムスタンプ
			" <",
			TBL_Priority[priority],					//優先度
			"> ",
			stSysLog.ident,						//各メッセージの前に付与する文字列
			": ",
			s1,							//メッセージ
			str_has_suffix(s1, "\n") ? NULL/*終端*/ : "\n",		//必要な場合は末尾に改行が加えられる。
			NULL/*終端(上で終端した場合はダミー)*/);
		if(!stSysLog.fp) {						//最初のメッセージを記録する時に、ログ記録用ファイルを開く。
			stSysLog.fp = fopen("syslog.out", "a");
		 //不要	if(!stSysLog.fp) { DIE(); }	⇒開けなかった場合の動作はLOG_CONSに依存する。
		}
		if(stSysLog.fp) {
			fputs(s2, stSysLog.fp);					//syslogdへメッセージを送る。(当実装ではログ記録用ファイルへ書き込む。)
#ifndef PIECE
			fflush(stSysLog.fp);					//ログ記録用ファイルはバッファリングしない。ただし、P/ECEではフラッシュメモリを傷めないように、ログ記録用ファイルもバッファリングする事にした。
#endif//PIECE
		} else {
			if(stSysLog.option & LOG_CONS) { fputs(s2, stdout); }	//syslogdへメッセージを送れない場合、optionにLOG_CONSが指定されていたら、コンソールに出力する。
		}
		if(stSysLog.option & LOG_PERROR) { fputs(s2, stderr); }		//optionにLOG_PERRORが指定されていたら、stderrにも出力する。(POSIX.1-2001とPOSIX.1-2008では定義されていない)
		free(s1);
		free(s2);
	}
}
/*---------------------------------------------------------------------------*/
//□「Linux Programmer's Manual - SYSLOG」(http://linuxjm.osdn.jp/html/LDP_man-pages/man3/openlog.3.html)より引用:
//│closelog()はシステムのログ記録用プログラム(システムロガーsyslogd)ヘの接続を終了する。
//│closelog()は必須ではない。
void closelog() {
	if(stSysLog.fp) { fclose(stSysLog.fp); }
	memset(&stSysLog, 0, sizeof stSysLog);
}
/*---------------------------------------------------------------------------*/
//□「Linux Programmer's Manual - SETLOGMASK」(http://linuxjm.osdn.jp/html/LDP_man-pages/man3/setlogmask.3.html)より引用:
//│プロセスにはログ優先度マスク(logprioritymask)が用意されていて、これを用いてsyslogが呼び出された場合にログ記録を行うかどうかを決定する。
//│他のすべての関数が呼び出された場合はログ記録が行われない。
//│maskの各ビットは優先度に対応しており、対応するビットが1にセットされている優先度に対してログ収集(logging)が有効になる。
//│マスクの初期値は、すべての優先度に対してログ収集が有効になるような値である。
//│setlogmask()関数は、呼び出し元のプロセスに対するログマスクを設定し、変更前のマスクを返す。
//│mask引き数が0ならば、現在のログマスクは変更されない。
int setlogmask(int mask) {
    //{{2016/03/03修正:プログラム起動⇒setlogmask()⇒syslog()の順で呼び出すと、暗黙のopenlog()が実行されず、stSysLogが初期化されないバグが有りました。stSysLog.identがNULLのままでsyslog()の処理が行われ、syslog()内のstrconcat()でstSysLog.ident(=NULL)が終端と見なされそれ以降の要素が無いログが生成されてしまっていました。今回、プログラム起動⇒setlogmask()⇒syslog()の順で呼び出した場合も、暗黙のopenlog()が実行されるように修正しました。
    //	int old_mask = stSysLog.mask;
    //↓2016/03/03修正:プログラム起動⇒setlogmask()⇒syslog()の順で呼び出すと、暗黙のopenlog()が実行されず、stSysLogが初期化されないバグが有りました。stSysLog.identがNULLのままでsyslog()の処理が行われ、syslog()内のstrconcat()でstSysLog.ident(=NULL)が終端と見なされそれ以降の要素が無いログが生成されてしまっていました。今回、プログラム起動⇒setlogmask()⇒syslog()の順で呼び出した場合も、暗黙のopenlog()が実行されるように修正しました。
	int old_mask;
	if(!stSysLog.mask) { openlog(NULL, LOG_ODELAY, LOG_USER); }		//openlog()が明示的に呼ばれていなければ、暗黙に呼び出す。
	old_mask = stSysLog.mask;
    //}}2016/03/03修正:プログラム起動⇒setlogmask()⇒syslog()の順で呼び出すと、暗黙のopenlog()が実行されず、stSysLogが初期化されないバグが有りました。stSysLog.identがNULLのままでsyslog()の処理が行われ、syslog()内のstrconcat()でstSysLog.ident(=NULL)が終端と見なされそれ以降の要素が無いログが生成されてしまっていました。今回、プログラム起動⇒setlogmask()⇒syslog()の順で呼び出した場合も、暗黙のopenlog()が実行されるように修正しました。
	if(mask) { stSysLog.mask = mask; }					//mask引き数が0ならば、現在のログマスクは変更されない。
	return old_mask;
}
