見出し画像

[2024年05月]C言語でログ出力プログラム

C言語のログ出力ライブラリは、いろいろあるけれど…
なぜか、いつも自作ログ出力を使ってしまう。
ログファイルに出力できて、ファイルはローテートできて、ファイル上限サイズができて(or 日付単位に出力できたり)
ログレベルが出力できて…
ミリセカンドまで出力時間が出力できて…
可変文字列が複数埋め込みできて…
ぐらいで良いかなぁと思ってしまい、いつも自作ログ出力を使ってしまう。

こんな風に…

/**
 * @file           log.c
 * @brief          ログ処理
 * @author         Me
 * @date           20xx.xx.xx
 * @note           -
 * @attention      -
 * @par            History
 *                 1.0.0 初版
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <pthread.h>
#include <stdarg.h>

#include "util.h"
#include "log.h"

/// ログファイル KBサイズ
#define	LOG_FILE_SIZE_KB			1024

/// ログファイル名(ベース名)
#define	LOG_FILE_NAME	"fc_app_"

/// 日時文字列フォーマット YYYY-MM-DD HH:MM:SS.999999
#define	DATETIME_FORMAT_01			"%04d-%02d-%02d %02d:%02d:%02d.%06d"

/// ログファイルNo 0:ファイル未確定 1以上:ファイル名確定中
int			gLogCurNo;

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

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

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

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

	ret = pthread_mutex_lock(&thMutexLog);

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

	// ファイルサイズチェック
	renewFile = 0;
	if(fileSize >= tIniValLog.iLogFileSizeMax * LOG_FILE_SIZE_KB){
		renewFile = 1;
		gLogCurNo++;
		if(gLogCurNo > tIniValLog.iLogFileNumMax){
			gLogCurNo = 1;
		}
	}

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

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

		// ログレベル出力
		switch(logLevel){
		case LOG_I:
			fprintf(fp, "I ");
			break;
		case LOG_W:
			fprintf(fp, "W ");
			break;
		case LOG_E:
			fprintf(fp, "E ");
			break;
		}

		// 現在日時出力
		fprintf(fp, "%s ", dateTime);
		// ファイル名、行番号出力、関数名
		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(&thMutexLog);

	return;
}

以下に有料記事ですが、ログメッセージの定義や出力例を含めたプログラムファイルおよびメイクファイルを掲載しています。

ここから先は

28,938字

¥ 200

この記事が気に入ったらチップで応援してみませんか?