Recommended Reading
Understand “Stack Overflow” and “Heap Overflow” in C Language, the most straightforward explanation on the internet
GitHub stars 88.9K, 9 amazing open-source projects!
Latest C Language interview questions summary PDF detailed version
Do you understand the original code, inverse code, and complement code?
From essence to implementation, let’s talk about what C and C++ standard libraries are?
C Language Chinese Community Source Code git repository: https://gitee.com/cyyzwsq/C-Coding.git
Main Content
Hello everyone, I am Chong Ge. Today we will implement a practical and classic project—a cross-platform C language logging library.
Design Details
1. Log Levels
Defines 5 standard log levels: DEBUG, INFO, WARNING, ERROR, and FATAL, allowing users to filter logs as needed.
2. Thread Safety
- Uses mutex locks to protect shared resources (log queue)
- Uses condition variables to implement an efficient producer-consumer model
- Cross-platform thread ID retrieval
3. Asynchronous Writing
- The main thread returns immediately after placing log messages into the queue
- A dedicated worker thread is responsible for retrieving messages from the queue and writing them to a file
- Reduces the impact of I/O operations on the main program’s performance
4. Cross-Platform Support
- Uses conditional compilation to handle differences between Windows and POSIX systems
- Unified API interface, hiding platform implementation details
5. Flexible Configuration
- Supports console and file output
- Configurable log levels, file paths, and buffer sizes
- Optional synchronous/asynchronous modes
6. Ease of Use
- Provides macros to simplify log calls
- Automatically includes file name and line number information
- Formats timestamps and thread IDs
This logging library provides the basic functionality required for production environments and can be further extended as needed, such as adding log rotation, network output, and more refined configuration options.
Source Code
Complete implementation of a cross-platform C language logging library, supporting hierarchical logging, thread safety, and asynchronous writing features.

Header File (logging.h)
#ifndef LOGGING_H
#define LOGGING_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#ifdef _WIN32
#include <windows.h>
#include <process.h>
#define THREAD_ID GetCurrentThreadId()
#else
#include <pthread.h>
#include <unistd.h>
#define THREAD_ID (unsigned long)pthread_self()
#endif
// Log level definitions
typedef enum {
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARNING,
LOG_LEVEL_ERROR,
LOG_LEVEL_FATAL
} log_level_t;
// Log configuration structure
typedef struct {
log_level_t level;
const char* file_path;
int use_console;
int async_mode;
size_t buffer_size;
} log_config_t;
// Initialize logging system
int log_init(const log_config_t* config);
// Shutdown logging system
void log_shutdown(void);
// Log writing function
void log_write(log_level_t level, const char* file, int line, const char* fmt, ...);
// Macros to simplify log calls
#define LOG_DEBUG(...) log_write(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...) log_write(LOG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_WARNING(...) log_write(LOG_LEVEL_WARNING, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_ERROR(...) log_write(LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_FATAL(...) log_write(LOG_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__)
#endif // LOGGING_H
Implementation File (logging.c)
#include "logging.h"
// Log message structure
typedef struct {
char* message;
log_level_t level;
time_t timestamp;
unsigned long thread_id;
const char* file;
int line;
} log_message_t;
// Log queue node
typedef struct log_node {
log_message_t* msg;
struct log_node* next;
} log_node_t;
// Log queue
typedef struct {
log_node_t* head;
log_node_t* tail;
size_t size;
} log_queue_t;
// Global variables
static log_config_t g_config;
static FILE* g_log_file = NULL;
static int g_initialized = 0;
// Thread synchronization
#ifdef _WIN32
static CRITICAL_SECTION g_queue_mutex;
static CONDITION_VARIABLE g_queue_cond;
static HANDLE g_worker_thread;
#else
static pthread_mutex_t g_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_queue_cond = PTHREAD_COND_INITIALIZER;
static pthread_t g_worker_thread;
#endif
// Asynchronous mode related
static log_queue_t g_log_queue;
static volatile int g_shutdown = 0;
// Queue operation functions
static void queue_init(log_queue_t* queue) {
queue->head = queue->tail = NULL;
queue->size = 0;
}
static void queue_push(log_queue_t* queue, log_message_t* msg) {
log_node_t* node = (log_node_t*)malloc(sizeof(log_node_t));
node->msg = msg;
node->next = NULL;
if (queue->tail) {
queue->tail->next = node;
queue->tail = node;
} else {
queue->head = queue->tail = node;
}
queue->size++;
}
static log_message_t* queue_pop(log_queue_t* queue) {
if (!queue->head) return NULL;
log_node_t* node = queue->head;
log_message_t* msg = node->msg;
queue->head = node->next;
if (!queue->head) queue->tail = NULL;
free(node);
queue->size--;
return msg;
}
static void queue_clear(log_queue_t* queue) {
log_node_t* current = queue->head;
while (current) {
log_node_t* next = current->next;
free(current->msg->message);
free(current->msg);
free(current);
current = next;
}
queue->head = queue->tail = NULL;
queue->size = 0;
}
// Get log level name
static const char* get_level_string(log_level_t level) {
switch (level) {
case LOG_LEVEL_DEBUG: return "DEBUG";
case LOG_LEVEL_INFO: return "INFO";
case LOG_LEVEL_WARNING: return "WARNING";
case LOG_LEVEL_ERROR: return "ERROR";
case LOG_LEVEL_FATAL: return "FATAL";
default: return "UNKNOWN";
}
}
// Format time
static void format_time(char* buffer, size_t size) {
time_t now = time(NULL);
struct tm* tm_info = localtime(&now);
strftime(buffer, size, "%Y-%m-%d %H:%M:%S", tm_info);
}
// Write log to file and console
static void write_log_direct(const log_message_t* msg) {
char time_buffer[20];
format_time(time_buffer, sizeof(time_buffer));
// Format log message
const char* level_str = get_level_string(msg->level);
const char* file_name = strrchr(msg->file, '/');
if (!file_name) file_name = strrchr(msg->file, '\');
file_name = file_name ? file_name + 1 : msg->file;
// Output to file
if (g_log_file) {
fprintf(g_log_file, "[%s] [%lu] [%s] [%s:%d] %s\n",
time_buffer, msg->thread_id, level_str,
file_name, msg->line, msg->message);
fflush(g_log_file);
}
// Output to console
if (g_config.use_console) {
fprintf(msg->level >= LOG_LEVEL_WARNING ? stderr : stdout,
"[%s] [%lu] [%s] [%s:%d] %s\n",
time_buffer, msg->thread_id, level_str,
file_name, msg->line, msg->message);
}
}
// Worker thread function (asynchronous mode)
#ifdef _WIN32
static unsigned __stdcall worker_thread(void* arg)
#else
static void* worker_thread(void* arg)
#endif
{
while (!g_shutdown || g_log_queue.size > 0) {
// Lock the queue
#ifdef _WIN32
EnterCriticalSection(&g_queue_mutex);
if (g_log_queue.size == 0 && !g_shutdown) {
SleepConditionVariableCS(&g_queue_cond, &g_queue_mutex, INFINITE);
}
#else
pthread_mutex_lock(&g_queue_mutex);
if (g_log_queue.size == 0 && !g_shutdown) {
pthread_cond_wait(&g_queue_cond, &g_queue_mutex);
}
#endif
// Process all messages in the queue
while (g_log_queue.size > 0) {
log_message_t* msg = queue_pop(&g_log_queue);
// Unlock so other threads can continue adding logs
#ifdef _WIN32
LeaveCriticalSection(&g_queue_mutex);
#else
pthread_mutex_unlock(&g_queue_mutex);
#endif
// Write log
if (msg) {
write_log_direct(msg);
free(msg->message);
free(msg);
}
// Relock to check the queue
#ifdef _WIN32
EnterCriticalSection(&g_queue_mutex);
#else
pthread_mutex_lock(&g_queue_mutex);
#endif
}
// Unlock the queue
#ifdef _WIN32
LeaveCriticalSection(&g_queue_mutex);
#else
pthread_mutex_unlock(&g_queue_mutex);
#endif
// Sleep briefly to reduce CPU usage
#ifdef _WIN32
Sleep(10);
#else
usleep(10000);
#endif
}
#ifdef _WIN32
return 0;
#else
return NULL;
#endif
}
// Initialize logging system
int log_init(const log_config_t* config) {
if (g_initialized) return 0;
// Copy configuration
memcpy(&g_config, config, sizeof(log_config_t));
// Open log file
if (g_config.file_path) {
g_log_file = fopen(g_config.file_path, "a");
if (!g_log_file) {
fprintf(stderr, "Failed to open log file: %s\n", g_config.file_path);
return -1;
}
}
// Initialize queue
queue_init(&g_log_queue);
// Initialize synchronization primitives
#ifdef _WIN32
InitializeCriticalSection(&g_queue_mutex);
InitializeConditionVariable(&g_queue_cond);
#endif
// Start worker thread (asynchronous mode)
if (g_config.async_mode) {
#ifdef _WIN32
g_worker_thread = (HANDLE)_beginthreadex(NULL, 0, worker_thread, NULL, 0, NULL);
if (!g_worker_thread) {
fprintf(stderr, "Failed to create worker thread\n");
return -1;
}
#else
if (pthread_create(&g_worker_thread, NULL, worker_thread, NULL) != 0) {
fprintf(stderr, "Failed to create worker thread\n");
return -1;
}
#endif
}
g_initialized = 1;
return 0;
}
// Shutdown logging system
void log_shutdown(void) {
if (!g_initialized) return;
g_shutdown = 1;
// Notify worker thread
#ifdef _WIN32
EnterCriticalSection(&g_queue_mutex);
WakeConditionVariable(&g_queue_cond);
LeaveCriticalSection(&g_queue_mutex);
// Wait for worker thread to finish
WaitForSingleObject(g_worker_thread, INFINITE);
CloseHandle(g_worker_thread);
DeleteCriticalSection(&g_queue_mutex);
#else
pthread_mutex_lock(&g_queue_mutex);
pthread_cond_signal(&g_queue_cond);
pthread_mutex_unlock(&g_queue_mutex);
// Wait for worker thread to finish
pthread_join(g_worker_thread, NULL);
pthread_mutex_destroy(&g_queue_mutex);
pthread_cond_destroy(&g_queue_cond);
#endif
// Clean up remaining messages in the queue
queue_clear(&g_log_queue);
// Close log file
if (g_log_file) {
fclose(g_log_file);
g_log_file = NULL;
}
g_initialized = 0;
}
// Log writing function
void log_write(log_level_t level, const char* file, int line, const char* fmt, ...) {
if (!g_initialized || level < g_config.level) return;
// Format message
char message[1024];
va_list args;
va_start(args, fmt);
vsnprintf(message, sizeof(message), fmt, args);
va_end(args);
// Create log message
log_message_t* msg = (log_message_t*)malloc(sizeof(log_message_t));
msg->level = level;
msg->timestamp = time(NULL);
msg->thread_id = THREAD_ID;
msg->file = file;
msg->line = line;
msg->message = strdup(message);
// Direct write in synchronous mode
if (!g_config.async_mode) {
write_log_direct(msg);
free(msg->message);
free(msg);
return;
}
// Asynchronous mode: add to queue
#ifdef _WIN32
EnterCriticalSection(&g_queue_mutex);
#else
pthread_mutex_lock(&g_queue_mutex);
#endif
queue_push(&g_log_queue, msg);
#ifdef _WIN32
WakeConditionVariable(&g_queue_cond);
LeaveCriticalSection(&g_queue_mutex);
#else
pthread_cond_signal(&g_queue_cond);
pthread_mutex_unlock(&g_queue_mutex);
#endif
}
Usage Example (example.c)
#include "logging.h"
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#define sleep(n) Sleep(n * 1000)
#else
#include <unistd.h>
#endif
// Simulate multi-threaded environment
#ifdef _WIN32
static unsigned __stdcall thread_func(void* arg)
#else
static void* thread_func(void* arg)
#endif
{
int thread_id = *(int*)arg;
for (int i = 0; i < 5; i++) {
LOG_INFO("Thread %d: message %d", thread_id, i);
sleep(1);
}
#ifdef _WIN32
return 0;
#else
return NULL;
#endif
}
int main() {
// Configure logging system
log_config_t config = {
.level = LOG_LEVEL_DEBUG,
.file_path = "app.log",
.use_console = 1,
.async_mode = 1,
.buffer_size = 1024
};
// Initialize logging system
if (log_init(&config) != 0) {
fprintf(stderr, "Failed to initialize logging system\n");
return 1;
}
// Log some messages
LOG_DEBUG("This is a debug message");
LOG_INFO("This is an info message");
LOG_WARNING("This is a warning message");
LOG_ERROR("This is an error message");
LOG_FATAL("This is a fatal message");
// Create multiple threads to test thread safety
#ifdef _WIN32
HANDLE threads[3];
#else
pthread_t threads[3];
#endif
int thread_ids[3] = {1, 2, 3};
for (int i = 0; i < 3; i++) {
#ifdef _WIN32
threads[i] = (HANDLE)_beginthreadex(NULL, 0, thread_func, &thread_ids[i], 0, NULL);
#else
pthread_create(&threads[i], NULL, thread_func, &thread_ids[i]);
#endif
}
// Wait for all threads to finish
for (int i = 0; i < 3; i++) {
#ifdef _WIN32
WaitForSingleObject(threads[i], INFINITE);
CloseHandle(threads[i]);
#else
pthread_join(threads[i], NULL);
#endif
}
// Shutdown logging system
log_shutdown();
printf("Logging completed. Check app.log for output.\n");
return 0;
}
Compilation Instructions
Linux/macOS Compilation:
gcc -o example example.c logging.c -lpthread -D_POSIX_C_SOURCE=199309L
Windows Compilation (using MinGW):
gcc -o example example.c logging.c -lwinpthread
–The End–If you have read this far, it means you like the articles from this public account. Feel free to pin (star) this public account C Language Chinese Community to receive notifications promptly~In this public account, reply: 1024 to receive a free C language learning gift package