🛠️

C言語のログ(logging:ロギング)処理(6)

9 min read

ファイルサイズ定量で世代管理できるログ出力処理をまとめる

(4) と (5) を組み合わせて

https://zenn.dev/open_sesame/articles/323c47c43ef0e1
https://zenn.dev/open_sesame/articles/f03cab7fe97f43

一応、出力できるプログラムはできあがり。
ただ、時間を取得するのに localtime を使用しているのだが、ManPageにスレッドセーフではないとのこと。スレッドセーフを考慮するとlocaltime_r を使用すべきとある。
ということは、おそらくダメポで...

ManPageをみると

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/ctime.3.html

属性

インターフェース 属性
localtime_r() Thread safety MT-Safe env locale
localtime() Thread safety MT-Unsafe race:tmbuf env locale

注意
localtime()は、静的データへのポインターを返すので、スレッドセーフではない。
この関数のスレッドセーフ版である localtime_r() は SUSv2 で規定されている。
…のくだり

本来、localtime_r を使用していたのだが、今回のプログラムを作成するときに参考にしたサイトをコピペしたときに localtime を使ってしまったようで...

ManPageの黄色いページは大切だなと改めて思う。

追記:
localtime を localtime_r に修正しました。

プログラミング

// TinyLogger.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "logger.h"

int main(void) {

	int cnt = 0;
	char buff[16];

	// ログファイルのデフォルトパス
	strcpy(gIniValLog.logFilePathName, LOG_FILE_DEF_PATH);
	// ログファイルのデフォルト最大サイズ
	gIniValLog.logFileSizeMax = LOG_FILE_DEF_SIZE_MAX;
	// ログファイルのデフォルト最大世代数
	gIniValLog.logFileNumMax = LOG_FILE_DEF_NUM_MAX;

	for(cnt = 0; cnt < 6000; cnt++){
		memset(buff, 0x0, sizeof(buff));
		sprintf(buff, "%d:%s", cnt, "main");
		LogTrace(M000001, buff);
		LogDebug(M000001, buff);
		LogInfo(M000001, buff);
		LogWarn(M000001, buff);
		LogError(M000001, buff);
		LogFatal(M000001, buff);
	}

	return EXIT_SUCCESS;
}
// logger.h
#ifndef LOGGER_H_
#define LOGGER_H_

#define	LOG_FILE_SIZE_MAX_COL_MAX	7			// ログファイルサイズカラム数
#define	LOG_FILE_SIZE_MAX_MIN		1			// ログファイルサイズの最大サイズKB(1)
#define	LOG_FILE_SIZE_MAX_MAX		2097151		// ログファイルサイズの最大サイズKB個数(2097151)7FFFFC00

#define	LOG_FILE_NUM_MAX_COL_MAX	2			// カラム数
#define	LOG_FILE_NUM_MAX_MIN		1			// ログファイル世代数の最小値
#define	LOG_FILE_NUM_MAX_MAX		999			// ログファイル世代数の最大値

#define	LOG_FILE_DEF_PATH			"."		// ログファイルのデフォルトパス
#define	LOG_FILE_DEF_SIZE_MAX		1024		// ログファイルのデフォルト最大サイズ
#define	LOG_FILE_DEF_NUM_MAX		3			// ログファイルのデフォルト最大世代数

#define	LOG_LEVEL_TRACE		(0)		// トレース
#define	LOG_LEVEL_DEBUG		(20)	// デバッグ
#define	LOG_LEVEL_INFO		(40)	// インフォメーション
#define	LOG_LEVEL_WARN		(60)	// ワーニング
#define	LOG_LEVEL_ERROR		(80)	// エラー
#define	LOG_LEVEL_FATAL		(100)	// フェータル

#define	LogTrace(...)	putLog(LOG_LEVEL_TRACE, __FILE__, __LINE__, __func__, __VA_ARGS__)
#define	LogDebug(...)	putLog(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__)
#define	LogInfo(...)	putLog(LOG_LEVEL_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__)
#define	LogWarn(...)	putLog(LOG_LEVEL_WARN, __FILE__, __LINE__, __func__, __VA_ARGS__)
#define	LogError(...)	putLog(LOG_LEVEL_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__)
#define	LogFatal(...)	putLog(LOG_LEVEL_FATAL, __FILE__, __LINE__, __func__, __VA_ARGS__)

#define	M000001		"A system call error occurred: FUNC=[%s]"
#define	M000002		"A system call error occurred: FUNC=[%s] ERRNO=[%d]"
#define	M000003		"A system call error occurred: FUNC=[%s] ERRNO=[%d] FILE=[%s]"

#define LOG_TYPE_APL 0

typedef struct _INI_VALUE_LOG{
	char	logFilePathName[1024];		// ログファイルのパス名(フルパス)
	int		logFileSizeMax;				// ログファイルの最大サイズ(KB)
	int		logFileNumMax;				// ログファイルの最大世代数(1-99)
}INI_VALUE_LOG;

extern INI_VALUE_LOG	gIniValLog;

void putLog(int, char *, int, const char *, char *, ...);

#endif /* LOGGER_H_ */
// logger.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <pthread.h>
#include <stdarg.h>
#include <sys/time.h>
#include <fcntl.h>

#include "logger.h"

#define	LOG_FILE_SIZE_KB	1024
// ログファイル名
#define	LOG_FILE_NAME		"application"
// 日時文字列フォーマット YYYY-MM-DD HH:MM:SS.ssssss
#define	LOG_DATETIME_FORMAT	"%04d-%02d-%02d %02d:%02d:%02d.%06d"

// INI定義値(LOG)
INI_VALUE_LOG	gIniValLog;

// ログファイル排他用ミューテックス
pthread_mutex_t mutexLog = PTHREAD_MUTEX_INITIALIZER;

/**
 * 現在日時文字列取得処理\n
 * マシンの現在日時文字列を取得する(ローケル)
 * @param [in] format 日付フォーマット
 * @param [in] dateTimeLen 現在日時文字列の格納領域サイズ
 * @param [out] dateTime 現在日時文字列(日付フォーマットの形式)を設定する 正常終了でも設定できていない場合があるので呼び出し元で設定文字列長をチェックする事
 * @return 0:正常終了 -1:異常終了
 */
int getDateTime(char *format, int dateTimeLen, char *dateTime)
{
	int	ret = 0;

	struct timeval tiemvalData;
	//struct tm *timeData;
	struct tm timeData;

	ret = gettimeofday(&tiemvalData, NULL);
	if(ret == -1){
		return(-1);
	}

	//timeData = localtime(&tiemvalData.tv_sec);
	//if(timeData == NULL){
	//	return(-1);
	//}

	//sprintf(dateTime, format,
	//		timeData->tm_year + 1900,
	//		timeData->tm_mon + 1,
	//		timeData->tm_mday,
	//		timeData->tm_hour,
	//		timeData->tm_min,
	//		timeData->tm_sec,
	//		tiemvalData.tv_usec);

	if(NULL == localtime_r(&tiemvalData.tv_sec, &timeData)){
		return(-1);
	}

	sprintf(dateTime, format,
			timeData.tm_year + 1900,
			timeData.tm_mon + 1,
			timeData.tm_mday,
			timeData.tm_hour,
			timeData.tm_min,
			timeData.tm_sec,
			tiemvalData.tv_usec);

	return(ret);
}

void changeGen()
{
	int		ret;				// 戻り値
	struct stat	stStat;			// ファイル情報の格納領域
	char	fileName[2048];		// ログファイル名
	char	fileNameNew[2048];	// ログファイル名(リネーム後)

	int cnt = 0;

	memset(fileName, 0x0, sizeof(fileName));
	memset(fileNameNew, 0x0, sizeof(fileNameNew));
	for(cnt = (gIniValLog.logFileNumMax - 1); cnt >= 0; cnt--){
		if(cnt == 0){
			sprintf(fileName, "%s/%s.log", gIniValLog.logFilePathName, LOG_FILE_NAME);
		}
		else{
			sprintf(fileName, "%s/%s.log.%d", gIniValLog.logFilePathName, LOG_FILE_NAME, cnt);
		}
		sprintf(fileNameNew, "%s/%s.log.%d", gIniValLog.logFilePathName, LOG_FILE_NAME, cnt + 1);
		ret = stat(fileName, &stStat);
		if(ret != 0){
			// ファイルが存在しない場合、処理なし
		}
		else{
			// ファイルが存在する場合
			ret = rename(fileName, fileNameNew);
			if(ret == -1){
				// エラーの場合の処理をする
				printf("%s\n", "rename error");
			}
		}
	}
}

/**
 * ログ出力処理\n
 * ログをファイルに出力します\n
 * 定義ファイルで出力先ディレクトリ、ログファイルサイズ、ログ世代の定義が可能
 * @param [in] logLevel         ログレベル
 * @param [in] programFileName プログラムファイル名
 * @param [in] programLineNo   プログラムファイル行番号
 * @param [in] programFuncName プログラム関数名
 * @param [in] format          可変文字列
 */
void putLog(int logLevel, char *programFileName, int programLineNo, const char *programFuncName, char *format, ...)
{
	int		ret;				// 戻り値
	char	dateTime[32];		// 現在日時
	struct stat	stStat;			// ファイル情報の格納領域
	char	fileName[2048];		// ログファイル名
	FILE	*fp;				// ファイルディスクプリタ
	off_t	fileSize;			// ファイルサイズ
	va_list	vaList;				// 可変文字列
	int		renewFile;			// ファイル作成し直し要否


	// 現在日時文字列取得(YYYY-MM-DD HH:MM:SS.ssssss)
	memset(dateTime, 0x0, sizeof(dateTime));
	ret = getDateTime(LOG_DATETIME_FORMAT, sizeof(dateTime), dateTime);
	if(ret != 0){
		// エラーの場合ログ出力なし
		return;
	}

	ret = pthread_mutex_lock(&mutexLog);

	// ファイル名を取得
	memset(fileName, 0x0, sizeof(fileName));
	sprintf(fileName, "%s/%s.log", gIniValLog.logFilePathName, LOG_FILE_NAME);
	ret = stat(fileName, &stStat);
	if(ret != 0){
		// エラー
		// ファイルが存在しない場合、ファイルサイズに0を設定
		fileSize = 0;
	}
	else{
		// ファイルサイズ
		fileSize = stStat.st_size;
	}

	// ファイルサイズチェック
	renewFile = 0;
	if(fileSize >= gIniValLog.logFileSizeMax * LOG_FILE_SIZE_KB){
		renewFile = 1;
		changeGen();
	}

	// ログファイル名取得(フルパス)
	fp = NULL;
	if(renewFile == 0){
		// 既存ファイルに追加書き込み(ファイルがなければ新規作成)
		fp = fopen(fileName, "a");
	}
	else{
		// ファイルを新規作成し直す
		fp = fopen(fileName, "w");
	}

	if(fp == NULL){
		// ファイルオープンエラー
		// ファイルパス/ファイル名が不正の場合
		// パーミッションエラーの場合
		// ログ出力しない
		;
	}
	else{
		va_start(vaList, format);

		// 現在日時出力
		fprintf(fp, "%s ", dateTime);

		// ログレベル出力
		switch(logLevel){
		case LOG_LEVEL_TRACE:
			fprintf(fp, "TRACE ");
			break;
		case LOG_LEVEL_DEBUG:
			fprintf(fp, "DEBUG ");
			break;
		case LOG_LEVEL_INFO:
			fprintf(fp, "INFO  ");
			break;
		case LOG_LEVEL_WARN:
			fprintf(fp, "WARN  ");
			break;
		case LOG_LEVEL_ERROR:
			fprintf(fp, "ERROR ");
			break;
		case LOG_LEVEL_FATAL:
			fprintf(fp, "FATAL ");
			break;
		}

		// ファイル名、行番号出力、関数名
		fprintf(fp, "%s(%04d):%-20s ", programFileName, programLineNo, programFuncName);

		// 可変文字列出力
		vfprintf(fp, format, vaList);

		// 改行
		fprintf(fp, "\n");

		va_end(vaList);

		// ファイルフラッシュ
		ret = fflush(fp);
		if(ret == -1){
			// ファイルフラッシュエラー
			;
		}

		// ファイルクローズ
		ret = fclose(fp);
		if(ret == -1){
			// ファイルクローズエラー
			;
		}
	}

	ret = pthread_mutex_unlock(&mutexLog);

	return;
}

Discussion

ログインするとコメントできます