Complete Source Code | Cross-Platform Logging Library in C Language for Enterprise Development

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.

Complete Source Code | Cross-Platform Logging Library in C Language for Enterprise Development

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

Complete Source Code | Cross-Platform Logging Library in C Language for Enterprise Development–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

Leave a Comment