대규모 C 응용 분야에서 로깅은 디버깅, 모니터링 및 유지 시스템 건강을위한 중요한 도구입니다. 기본 문 또는 라이브러리는 과 같은 작은 프로젝트에 대한 suffice가 될 수 있지만 성능, 유연성, 확장성이 비 협상적 요구가 될 때 종종 부족합니다. 응용 프로그램의 작업 부하에 맞게 사용자 정의 로깅 시스템을 구축하면 로그 레벨, 출력 대상, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전, 안전

왜 사용자 정의 로깅 시스템을 구축

또는 POSIX ] API와 같은 표준 로깅 메커니즘은 종종 큰 응용 프로그램에 의해 요구되는 과립 및 성능이 부족합니다. 사용자 정의 로거는 다음과 같습니다.

  • 최적 성능: 로그 레벨이 억제될 때 불필요한 할당 및 포맷을 피합니다.
  • Flexible formatting: 일관된 타임스탬프, 스레드 식별자 및 모듈 접두사는 로그 읽기 및 패싱을 개선합니다.
  • Granular control: Recompiling 없이 실행시 특정 로그 레벨을 활성화/disable.
  • 다중 대상: 콘솔, 파일, 네트워크 소켓, 또는 외부 모니터링 시스템에 쓰기.
  • 안전: 멀티 스레드 응용 분야에서 interleaved 출력을 방지하기 위해 mutexes 또는 atomic 작업을 사용합니다.

핵심 설계 고려

모든 코드를 작성하기 전에 로그인 시스템의 핵심 구성 요소를 정의합니다. 이 결정은 API 디자인에서 실행 성능에 이르기까지 모든 것을 형성합니다.

로그 레벨

애플리케이션의 필요에 대한지도를 정의합니다. 일반적인 레벨은 ], , , , ], ]를 포함합니다. ]를 사용하여 안전성을 확보하십시오. 각 레벨은 최소 임계값이 있어야 합니다. 임계값 아래에 메시지는 생산에서 오버 헤드를 줄이기 위해 무시됩니다.

여행 일정

로그가 작성된 것을 고려하십시오. 전형적인 목적지는 다음과 같습니다.

  • Console – 개발 및 빠른 디버깅을 위한.
  • File – 디스크 공간을 관리하기 위해 회전 및 아카이브와 함께.
  • Syslog - 유닉스 시스템에 중앙으로 로깅을 위한.
  • Network] - 원격 집계를 위한 UDP 또는 TCP 소켓 (예: Graylog, ELK stack).

잘 설계 된 로그거는 당신이 실행 시간에 목적지를 추가하거나 제거 할 수있는 싱크 요약을 사용합니다.

서식 및 협약

로그 형식에 대한 결정은 일관성을 유지하기 위해 일찍. 전형적인 형식에는 타임스탬프, 로그 레벨, 소스 파일 / 라인, 모듈 이름 및 메시지가 포함되어 있습니다. JSON과 같은 구조 된 형식은 자동화 된 패싱을 단순화하고 오버 헤드를 추가합니다. 성능에 대해서는 고정 ‐ 필드 디코너 (예 : 파이프 또는 탭)을 사용하여 동적 메모리 할당을 핫 경로에 방지합니다.

실 안전

다중화 응용 프로그램에서 동시 쓰기는 가시 광선 출력을 생산할 수 있습니다. ] (POSIX 시스템에서) 또는 실제 I / O 작동의 중요한 섹션 (Windows에서)을 사용하십시오. 높은 처리량을 위해, 고정 된 주기적으로 인 lock-free queue 또는 읽힌 로깅 버퍼를 고려하십시오.

Basic Logger 구현

필수를 다루는 간단한 구현으로 시작하십시오: 로그 레벨, 타임스탬프 포맷, 변수 정렬 메시지 구축. 다음 코드는 기초를 제공합니다.

Log Level Enum 및 헬퍼 기능

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <pthread.h>

typedef enum {
 LOG_LEVEL_TRACE,
 LOG_LEVEL_DEBUG,
 LOG_LEVEL_INFO,
 LOG_LEVEL_WARN,
 LOG_LEVEL_ERROR,
 LOG_LEVEL_FATAL
} LogLevel;

static const char* level_strings[] = {
 "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
};

static LogLevel g_min_level = LOG_LEVEL_INFO;
static pthread_mutex_t g_log_mutex = PTHREAD_MUTEX_INITIALIZER;

메시지 형식

핵심 로깅 기능 사용 ) 메시지를 안전하게 포맷합니다. 정적 버퍼는 핫 경로에 할당을 피하지만, 매우 긴 메시지에 대한 버퍼 오버 플로우의 마음을 염려합니다.

void log_message(LogLevel level, const char* file, int line, const char* func, const char* format, ...) {
 if (level < g_min_level) return;

 time_t now = time(NULL);
 struct tm* tm_info = localtime(&now);
 char time_buf[20];
 strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);

 pthread_mutex_lock(&g_log_mutex);

 // Write header
 fprintf(stdout, "[%s] [%s] [%s:%d %s] ", time_buf, level_strings[level], file, line, func);

 va_list args;
 va_start(args, format);
 vfprintf(stdout, format, args);
 va_end(args);

 fprintf(stdout, "\n");
 fflush(stdout); // Ensure immediate output for debugging

 pthread_mutex_unlock(&g_log_mutex);
}

편의를 위해, 소스 파일과 라인을 자동으로 캡처 매크로를 정의:

#define LOG_TRACE(...) log_message(LOG_LEVEL_TRACE, __FILE__, __LINE__, __func__, __VA_ARGS__)
#define LOG_DEBUG(...) log_message(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__)
// ... similar for INFO, WARN, ERROR, FATAL

사용법 예:

int main() {
 LOG_INFO("Application started on port %d", 8080);
 LOG_WARN("Disk usage exceeding %.1f%%", 85.3);
 LOG_ERROR("Failed to open file: %s", "config.dat");
 return 0;
}

파일 출력 및 회전 추가

콘솔 출력은 개발 중 유용하지만 생산 시스템에는 지속 가능한 로그가 필요합니다. 로그 파일의 회전 세트에 쓰기 파일 싱크를 추가하십시오.

파일 Logging

파일 포인터를 가진 로거를 확장하십시오. 경로가 하드 ‐code하거나 구성할 수 있습니다. 동일한 점은 파일 쓰기를 보호합니다.

static FILE* g_log_file = NULL;

int log_init_file(const char* path) {
 g_log_file = fopen(path, "a");
 return (g_log_file != NULL) ? 0 : -1;
}

void log_message_file(LogLevel level, const char* file, int line, const char* func, const char* format, ...) {
 // Same timestamp and header logic, but write to g_log_file
 // ...
}

회전 전략을 로그

모든 디스크 공간에 대한 로그 파일을 방지하려면 파일 크기, 날짜, 또는 둘 다에 따라 회전을 구현하십시오. 간단한 전략 :

  • 각 쓰기 후 ]을 검사하여 현재 파일 크기를 추적합니다. (또는 주기적으로).
  • 파일이 임계값을 초과할 때 (예를 들어, 100 MB), (]] → ], 그런 다음 오래된 파일을 압축) 신선한 를 다시 열 수 있습니다.
  • 최대의 회전 파일 유지; 가장 오래된 삭제.

예제 스켈레톤 :

void log_rotate_if_needed() {
 if (g_log_file && ftell(g_log_file) > MAX_LOG_SIZE) {
 fclose(g_log_file);
 // Rename and compress logic
 g_log_file = fopen(LOG_PATH, "a");
 }
}

성능에 대한 비동기 로깅

직접 I/O 뜨거운 경로에 스레드와 등급 성능을 차단할 수 있습니다. 비동기 로깅 디스크 또는 네트워크에서 포맷 메시지 프로듀서 ‐consumer 큐를 사용하여 작성합니다.

Producer-Consumer 패턴

바인딩된 링 버퍼 또는 잠금 ‐ 무료 큐 (예를 들어, ] 또는 간단한 링크 목록으로 mutex를 저장합니다) 로그 메시지를 저장합니다. 전용 스레드가 큐를 플러시합니다.

이점:

  • 신청 실은 I/O를 위해 결코 기다리지 않습니다.
  • 삭제 된 메시지는 우아하게 처리 될 수 있습니다 (예 : 카운터 증가).
  • Batching 쓰기는 시스템 호출 오버 헤드를 감소시킵니다.

Sketch 소개

#define QUEUE_SIZE 8192
static char g_log_queue[QUEUE_SIZE][MAX_LOG_LINE];
static volatile int g_queue_head = 0, g_queue_tail = 0;
static pthread_mutex_t g_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_queue_notify = PTHREAD_COND_INITIALIZER;

void push_log(const char* logline) {
 pthread_mutex_lock(&g_queue_mutex);
 // Copy and advance head (ring buffer)
 strncpy(g_log_queue[g_queue_head], logline, MAX_LOG_LINE);
 g_queue_head = (g_queue_head + 1) % QUEUE_SIZE;
 pthread_cond_signal(&g_queue_notify);
 pthread_mutex_unlock(&g_queue_mutex);
}

void* log_flush_thread(void* arg) {
 while (1) {
 pthread_mutex_lock(&g_queue_mutex);
 while (g_queue_head == g_queue_tail)
 pthread_cond_wait(&g_queue_notify, &g_queue_mutex);
 // Dequeue and write to file/console
 char buffer[MAX_LOG_LINE];
 strncpy(buffer, g_log_queue[g_queue_tail], MAX_LOG_LINE);
 g_queue_tail = (g_queue_tail + 1) % QUEUE_SIZE;
 pthread_mutex_unlock(&g_queue_mutex);
 // Perform I/O (protected by the same file mutex)
 fputs(buffer, g_log_file);
 }
 return NULL;
}

고급 기능

기본 시스템은 가동되면 더 큰 응용 프로그램에 이러한 향상을 고려합니다.

구조화 (JSON)

Structured logs는 자동화된 분석이 가능합니다. ]cJSON과 같은 경량 JSON 라이브러리를 사용하여 객체를 구축합니다. 예 형식:

{"timestamp":"2025-03-20T10:15:30Z","level":"ERROR","module":"auth","message":"Login failed","user":"john"}

동서이지만, 이 형식은 Elasticsearch 및 Splunk와 같은 도구와 완벽하게 통합됩니다.

Syslog 또는 Network를 통해 원격 로깅

분산 시스템의 경우 중앙 수집가에 대한 로그를 전달합니다. POSIX ] 함수 (]man page)는 똑똑하지만 제한적입니다. 또는, 원시 소켓을 사용하여 Graylog 또는 Logstash 엔드 포인트로 UDP 데이터그램을 보내십시오.

구성 파일

재 컴파일 없이 tweak 로깅 매개 변수를 허용: 로그 레벨, 파일 경로, 회전 크기, 출력 대상. 간단한 INI ‐ 스타일 파일 또는 사용 환경 변수를 파. 구성은 신호를 통해 다시로드 될 수 있습니다 (예: ).

모범 사례 및 Pitfalls

로그인의 값을 언로그인을 피하십시오.

성능 오버헤드

로깅은 병목이되지 않아야합니다. 항상 인수를 포맷하기 전에 로그 레벨을 확인합니다. 레벨을 신속하게 평가하는 매크로를 사용하십시오. 중요한 경로에서 ]를 피하십시오. 대신 스택 버퍼를 사용하십시오.

보안 Concerns

비밀번호, 신용카드 번호, 개인 정보 등 민감한 정보를 절대로 기록하지 마십시오. 사용자 입력을 Sanitize하고 생산 빌드에서 적격 필드를 고려하십시오. 또한, 무단 액세스에서 로그 파일을 보호합니다.

일관성 Across 단위

회사 전체 로깅 컨벤션을 설치하십시오. 독특한 모듈 접두사 (예 : ], ) 및 표준 타임 탬프 형식 (UTC는 분산 시스템에 선호됩니다). 문서는 로그 형식을 사용하여 작업 팀이 신뢰할 수 있습니다.

관련 기사

C의 사용자 정의 로깅 시스템은 대규모 애플리케이션에 필요한 유연성과 성능을 제공합니다. 로그 레벨, 싱크, 실 안전 및 포맷을 포함하는 견고한 디자인으로 시작하면 비동기 출력, 구조 로그 및 원격 전달과 같은 고급 기능을 추가 할 수 있습니다. 이 문서의 예는 기초로 제공되며 특정 환경에 항상 오버 헤드를 측정합니다. 주의깊은 구현으로 로깅은 성능 배수보다 강력한 자산이됩니다. 자세한 내용을 보려면 [LT] [LT] [F] [F] [F]] [F] [F]] [F] [F]] [F] [F]] [F]] [F] [F]] [F]] [F]]] [F]] [F]] [F]] [F] [F]]] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F] [F]]]] [F]]]]] [F] [F]]