/** * @file cf_log.c * @author myusgun */ #if defined(_WIN32) || defined(_WIN64) # define _USE_32BIT_TIME_T # if (_MSC_VER >= 1400) # define localtime _localtime32 # endif #endif #include "cf_log.h" #include "cf_file.h" #include "cf_thread.h" #include "cf_local.h" #include "cf_error.h" #include #include #include #include #include #if defined(_WIN32) || defined(_WIN64) # include #else # include #endif #define ASSERT_CTX(__ctx) \ if (__ctx == NULL) \ return CF_ERROR_LOG_INVALID_CTX #define ASSERT_INIT() \ if (gLogArray.ctxPool == NULL || \ gLogArray.ctxSize <= 0 ) \ return CF_ERROR_LOG_NOT_INITIALIZE #define ASSERT_MAPID(__mapid) \ if (gLogArray.ctxSize <= __mapid) \ return CF_ERROR_LOG_INVALID_MAPID #define ASSERT_MAPPED_CTX(__mapid) \ if (gLogArray.ctxPool[__mapid] != NULL) \ return CF_ERROR_LOG_ALREADY_MAPPED_ID #define ASSERT_NOT_MAPPED_CTX(__mapid) \ if (gLogArray.ctxPool[__mapid] == NULL) \ return CF_ERROR_LOG_NOT_MAPPED_ID #define LOCK_LOG_CTX(__ctx) CF_Mutex_Lock (&__ctx->mutex) #define UNLOCK_LOG_CTX(__ctx) CF_Mutex_Unlock (&__ctx->mutex) #define LOG_BUFFER_DEFAULT_SIZE 128 * 1024 #define LOG_DATETIME_LENGTH sizeof ("0000-00-00 00:00:00.000") - 1 /** * 로그 컨텍스트 * * @remark change from public to private */ typedef void * CF_Log_Ctx; typedef struct __cf_util_datetime__ { int year; int month; int day; int week; /* SUN:0 ~ SAT:6 */ int hour; int min; int sec; int usec; } S_CF_LOG_DATETIME, CF_LOG_DATETIME; typedef struct __cf_log_ctx__ { char path[NAME_LENGTH + 1]; int fd; char * buffer; size_t size; /* entire size of buffer */ size_t length; /* data length in current */ CF_Mutex mutex; } S_CF_LOG_CTX, CF_LOG_CTX; typedef struct __cf_log_array__ { CF_Log_Ctx * ctxPool; int ctxSize; int level; } S_CF_LOG_ARRAY, CF_LOG_ARRAY; static CF_LOG_ARRAY gLogArray; #if defined(_WIN32) || defined(_WIN64) /* {{{ */ struct timezone { int tz_minuteswest; /* minutes W of Greenwich */ int tz_dsttime; /* type of dst correction */ }; int gettimeofday (struct timeval *tv, struct timezone *tz) { FILETIME ft; unsigned __int64 buf =0; //static int tzflag = 0; if (NULL != tv) { GetSystemTimeAsFileTime (&ft); buf |= ft.dwHighDateTime; buf <<= 32; buf |= ft.dwLowDateTime; if (buf) { buf /= 10; buf -= ((369 * 365 + 89) * (unsigned __int64) 86400) * 1000000; } tv->tv_sec = (long)(buf / 1000000UL); tv->tv_usec = (long)(buf % 1000000UL); } /* if (NULL != tz) { if (!tzflag) { _tzset(); tzflag++; } // Adjust for the timezone west of Greenwich tz->tz_minuteswest = _timezone / 60; tz->tz_dsttime = _daylight; } */ return 0; } /* }}} */ #endif static int CF_Log_Local_GetTime (CF_LOG_DATETIME * dt) { struct timeval timeVal; struct tm * timeSt; gettimeofday (&timeVal, NULL); timeSt = localtime ((const time_t *)&timeVal.tv_sec); dt->year = timeSt->tm_year + 1900; dt->month = timeSt->tm_mon + 1; dt->day = timeSt->tm_mday; dt->week = timeSt->tm_wday; dt->hour = timeSt->tm_hour; dt->min = timeSt->tm_min; dt->sec = timeSt->tm_sec; dt->usec = (int) timeVal.tv_usec; return CF_OK; } static int CF_Log_Local_GetTimeString (char * buffer) { CF_LOG_DATETIME dt; CF_Log_Local_GetTime (&dt); snprintf (buffer, LOG_DATETIME_LENGTH, "%02d-%02d-%02d %02d:%02d:%02d.%03d", dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec, dt.usec); return CF_OK; } static int CF_Log_Local_Flush (CF_LOG_CTX * ctx) { if (CF_File_Write (ctx->fd, ctx->buffer, ctx->length) < 0) return CF_ERROR_LOG_FLUSH; ctx->length = 0; return CF_OK; } /** * 로그 데이터 처리 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param ctx 로그 컨텍스트 * @param buffer 로그 데이터 * @param demandSize 로그 데이터 길이 * * @author vfire */ /* static */int CF_Log_Local_Push (CF_LOG_CTX * ctx, const char * buffer, const size_t demandSize) { int result = CF_OK; if (ctx->size > 0) /* 버퍼단위 버퍼링.... */ { size_t writeSize; size_t remainSize; remainSize = demandSize; while (remainSize) { writeSize = (ctx->size - ctx->length) < remainSize ? (ctx->size - ctx->length) : remainSize; memcpy (ctx->buffer + ctx->length, buffer + demandSize - remainSize, writeSize); ctx->length += writeSize; if (ctx->length == ctx->size) CF_Log_Local_Flush (ctx); remainSize -= writeSize; } } else /* flush되어야 함. */ { ctx->buffer = (char *)buffer; ctx->length = demandSize; result = CF_Log_Local_Flush (ctx); } return result; } /** * 로그 컨텍스트에 멀티쓰레드 모드 설정 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param ctx 로그 컨텍스트 * * @see CF_Log_UnsetMultiThread */ static int CF_Log_SetMultiThread (CF_Log_Ctx ctx) { CF_LOG_CTX * context = (CF_LOG_CTX *) ctx; ASSERT_CTX (ctx); if (CF_Mutex_Create (&context->mutex) < 0) return CF_ERROR_LOG_SET_MULTITHREAD; return CF_OK; } /** * 로그 컨텍스트에 멀티쓰레드 모드 설정 해제 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param ctx 로그 컨텍스트 * * @see CF_Log_SetMultiThread */ static int CF_Log_UnsetMultiThread (CF_Log_Ctx ctx) { CF_LOG_CTX * context = (CF_LOG_CTX *) ctx; ASSERT_CTX (ctx); if (CF_Mutex_Destory (&context->mutex) < 0) return CF_ERROR_LOG_UNSET_MULTITHREAD; return CF_OK; } /** * 로그 컨텍스트에 따라 로그 쓰기 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param ctx 로그 컨텍스트 * @param prefix 로그의 프리픽스 문자열 * @param fmt 포맷 스트링 * @param ... 가변 인자 */ static int CF_Log_WriteCtx (CF_Log_Ctx ctx, const char * prefix, const char * fmt, // ...) va_list valist) { CF_LOG_CTX * context = (CF_LOG_CTX *) ctx; // va_list valist; char buffer[16 * 1024] = {0x00,}; char datetime[LOG_DATETIME_LENGTH + 1] = {0x00,}; ASSERT_CTX (ctx); LOCK_LOG_CTX (context); // va_start (valist, fmt); CF_Log_Local_GetTimeString (datetime); snprintf (buffer, sizeof (buffer) - 1, "[%s][%s] ", datetime, prefix); vsprintf (buffer + strlen (buffer), fmt, valist); CF_Log_Local_Push (context, buffer, strlen (buffer)); // va_end (valist); UNLOCK_LOG_CTX (context); return CF_OK; } /** * 로그 버퍼의 데이터를 즉시 로그 파일에 쓰기 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param ctx 로그 컨텍스트 */ static int CF_Log_FlushCtx (CF_Log_Ctx ctx) { CF_LOG_CTX * context = (CF_LOG_CTX *) ctx; ASSERT_CTX (ctx); LOCK_LOG_CTX (context); CF_Log_Local_Flush (context); UNLOCK_LOG_CTX (context); return CF_OK; } /** * 로그 컨텍스트 해제 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param ctx 로그 컨텍스트 */ static int CF_Log_DestroyCtx (CF_Log_Ctx ctx) { CF_LOG_CTX * context = (CF_LOG_CTX *) ctx; ASSERT_CTX (ctx); if (context->size > 0) { CF_Log_FlushCtx (ctx); free (context->buffer); context->buffer = NULL; context->size = 0; } CF_File_Close (context->fd); CF_Mutex_Destory (&context->mutex); context->mutex = NULL; return CF_OK; } /** * 로그 컨텍스트 생성 * * @return 성공 시, 로그 컨텍스트; 실패 시, NULL * * @param path 로그 파일 경로 * @param memsize 로그 버퍼 크기 * @param ctx 로그 컨텍스트 받을 주소 * * @see CF_LOG_BUFFER_DEFAULT, CF_LOG_BUFFER_NO */ static int CF_Log_CreateCtx (const char * path, const int memsize, CF_Log_Ctx * ctx) { int result = 0; int fileFlag = CF_FILE_CREATE|CF_FILE_WRITE|CF_FILE_APPEND; int size = (memsize == CF_LOG_BUFFER_DEFAULT) ? LOG_BUFFER_DEFAULT_SIZE : memsize; CF_LOG_CTX * context = NULL; if (path == NULL) return CF_ERROR_LOG_INVALID_ARGS; TRY { context = (CF_LOG_CTX *) calloc (sizeof (CF_LOG_CTX), 1); if (context == NULL) { result = CF_ERROR_LOG_ALLOCATE_CTX; TRY_BREAK; } context->fd = CF_File_Open (path, fileFlag); if (context->fd < 0) { result = CF_ERROR_LOG_CREATE_FILE; TRY_BREAK; } snprintf (context->path, sizeof (context->path) -1, "%s", path); if (size > 0) { context->buffer = (char *) calloc ((size_t) size + 1, 1); if (context->buffer == NULL) { result = CF_ERROR_LOG_ALLOCATE_BUFFER; TRY_BREAK; } context->size = (size_t) size; } *ctx = (CF_Log_Ctx) context; } CATCH_IF (result < 0) { CF_Log_DestroyCtx ((CF_Log_Ctx) context); } return result; } /** * 로그 컨텍스트에 아이디 넘버 할당
* 로그 기록 시, 아이디 넘버를 사용하면 해당 로그로 기록할 수 있음 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param mapid 부여할 아이디 넘버 * @param ctx 로그 컨텍스트 * * @remark 반드시 먼저 초기화 해야하며, 초기화 시에 주어진 번호보다 작은 아이디 넘버를 사용 * * @see CF_LOG_OPEN, CF_Log_CreateCtx */ static int CF_Log_MapCtxID (const int mapid, const CF_Log_Ctx ctx) { ASSERT_INIT (); ASSERT_MAPID (mapid); ASSERT_MAPPED_CTX (mapid); gLogArray.ctxPool[mapid] = ctx; return CF_OK; } /** * 아이디 넘버에 해당하는 로그를 닫고 해당하는 컨텍스트를 해제 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param mapid 로그의 아이디 넘버 * * @remark 아이디 넘버에 해당하는 컨텍스트가 해제되므로 주의 * * @see CF_LOG_CLOSE, CF_Log_DestroyCtx */ static int CF_Log_UnmapCtxID (const int mapid) { ASSERT_INIT (); ASSERT_MAPID (mapid); ASSERT_NOT_MAPPED_CTX (mapid); CF_Log_DestroyCtx (gLogArray.ctxPool[mapid]); free (gLogArray.ctxPool[mapid]); gLogArray.ctxPool[mapid] = NULL; return CF_OK; } /** * 아이디 넘버에 해당하는 로그 컨텍스트를 얻기 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param mapid 로그의 아이디 넘버 * @param ctx 로그 컨텍스트 받을 주소 */ static int CF_Log_GetMappedCtx (const int mapid, CF_Log_Ctx * ctx) { ASSERT_INIT (); ASSERT_MAPID (mapid); ASSERT_NOT_MAPPED_CTX (mapid); *ctx = gLogArray.ctxPool[mapid]; return CF_OK; } /** * 로그를 사용하기 위해 초기화 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param logPool 아이디 넘버 최대 값 */ int CF_Log_Initialize (const int logPool) { memset (&gLogArray, 0x00, sizeof (CF_LOG_ARRAY)); if (logPool > 0) { gLogArray.ctxPool = (CF_Log_Ctx *) calloc ((size_t) logPool, sizeof (CF_Log_Ctx)); if (gLogArray.ctxPool == NULL) return CF_ERROR_LOG_INITIALIZE; gLogArray.ctxSize = logPool; gLogArray.level = CF_LOG_NO_LEVEL; } return CF_OK; } /** * 로그가 모두 사용된 후 자원 해제 * * @return CF_OK 반환 */ int CF_Log_Finalize (void) { int mapid = 0; for (mapid = 0 ; mapid < gLogArray.ctxSize ; mapid++) { CF_Log_UnmapCtxID (mapid); } if (gLogArray.ctxPool != NULL) free (gLogArray.ctxPool); memset (&gLogArray, 0x00, sizeof (CF_LOG_ARRAY)); return CF_OK; } /** * 로그 열기 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param mapid 로그의 아이디 넘버 * @param path 로그 파일 경로 * @param memsize 로그 버퍼 크기 * * @see CF_LOG_BUFFER_DEFAULT, CF_LOG_BUFFER_NO */ int CF_Log_Open (const int mapid, const char * path, const int memsize) { int result = 0; CF_Log_Ctx ctx = NULL; result = CF_Log_CreateCtx (path, memsize, &ctx); if (result < 0) return result; result = CF_Log_MapCtxID (mapid, ctx); if (result < 0) CF_Log_DestroyCtx (ctx); return result; } /** * 로그 닫기 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param mapid 로그의 아이디 넘버 */ int CF_Log_Close (const int mapid) { return CF_Log_UnmapCtxID (mapid); } /** * 로그 버퍼의 데이터를 즉시 로그 파일에 쓰기 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param mapid 로그의 아이디 넘버 */ int CF_Log_Flush (const int mapid) { int result = 0; CF_Log_Ctx ctx = NULL; result = CF_Log_GetMappedCtx (mapid, &ctx); if (result < 0) return result; result = CF_Log_FlushCtx (ctx); return result; } /** * 로그 컨텍스트에 멀티쓰레드 모드 설정 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param mapid 로그의 아이디 넘버 * @param flag 설정/해제 bool 플래그 * * @see CF_BOOL */ int CF_Log_SetMT (const int mapid, const CF_BOOL flag) { int result = 0; CF_Log_Ctx ctx = NULL; result = CF_Log_GetMappedCtx (mapid, &ctx); if (result < 0) return result; result = (flag) ? CF_Log_SetMultiThread (ctx) : CF_Log_UnsetMultiThread (ctx); return result; } /** * 현재 설정된 로그 레벨을 가져옴 * * @return 로그 레벨이 설정된 경우, 로그 풀 크기; 그 외에, 설정된 로그 레벨 */ static int CF_Log_GetLevel (void) { return (gLogArray.level == CF_LOG_NO_LEVEL) ? gLogArray.ctxSize : gLogArray.level; } /** * 로그 레벨을 설정 * 지정된 로그 레벨 이하의 아이디 넘버만 로그로 기록하도록 설정 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param level 설정할 로그 레벨 * * @see CF_LOG_NO_LEVEL */ int CF_Log_SetLevel (const int level) { gLogArray.level = level; return CF_OK; } /** * 로그 쓰기 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param mapid 로그의 아이디 넘버 * @param prefix 로그의 프리픽스 문자열 * @param fmt 포맷 스트링 * @param ... 가변 인자 */ int CF_Log_Write (const int mapid, const char * prefix, const char * fmt, ...) { int result = 0; CF_Log_Ctx ctx = NULL; va_list valist; result = CF_Log_GetLevel (); if (result < mapid) return CF_OK; result = CF_Log_GetMappedCtx (mapid, &ctx); if (result < 0) return result; va_start (valist, fmt); result = CF_Log_WriteCtx (ctx, prefix, fmt, valist); va_end (valist); return result; }