Biblioteca de Log Configurable para Aplicaciones en C

Esta biblioteca en C ofrece un sistema de registro de logs con múltiples características para aplicaciones. Las funcionalidades principales incluyen gestión de niveles de log, configuración flexible meidante archivo JSON y rotación automática de archivos.

Características Implementadas

  • Soporte para múltiples niveles de registro: ERROR, WARNING, SYSTEM, INFO y DEBUG.
  • Configuración de la ruta de almacenamiento de los archivos de log.
  • Personalización del nombre base de los arcihvos de log.
  • Control del número máximo de archivos de log conservados.
  • Definición del tamaño máximo por archivo de log en megabytes.
  • Opción para habilitar o deshabilitar la escritura en archivo.
  • Configuración por módulos diferentes, permitiendo compartir un mismo archivo de configuración.

Archivo de Cabecera: log_utils.h


#ifndef LOG_UTILS_H
#define LOG_UTILS_H

#define MAX_LOG_MSG_LEN 10240

// Macros para capturar información de depuración
#define LOG_DEBUG __FILE__, __LINE__, 4
#define LOG_INFO __FILE__, __LINE__, 3
#define LOG_SYS __FILE__, __LINE__, 2
#define LOG_WARN __FILE__, __LINE__, 1
#define LOG_ERR __FILE__, __LINE__, 0
#define LOG_RAW __FILE__, __LINE__, -1

// Inicialización del sistema de logging
int log_init(const char *cfg_path, const char *module);

// Función principal de registro
void log_print(const char *src_file, int src_line, int lvl, const char *fmt, ...);

#endif

Implementación Principal: log_utils.c


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include "cJSON.h"
#include "log_utils.h"

typedef struct {
    char dir_path[100];
    char file_prefix[100];
    int max_log_files;
    int max_file_size_mb;
    int log_threshold;
    int file_output;
} LogSettings;

static LogSettings settings;
static int initialized = 0;
static char cfg_path_global[256];

static const char *default_cfg = "{"
    "\"default\": {"
        "\"dir_path\":\"./\","
        "\"file_prefix\":\"app\","
        "\"max_log_files\":1,"
        "\"max_file_size_mb\":1,"
        "\"log_threshold\":4,"
        "\"file_output\":1"
    "}"
"}";

static char *load_text_file(const char *path) {
    FILE *fp = fopen(path, "rb");
    if (!fp) {
        perror("Error al abrir archivo de configuración");
        return NULL;
    }
    
    fseek(fp, 0, SEEK_END);
    long size = ftell(fp);
    rewind(fp);
    
    char *buffer = (char *)malloc(size + 1);
    if (!buffer) {
        fclose(fp);
        return NULL;
    }
    
    fread(buffer, 1, size, fp);
    buffer[size] = '\0';
    fclose(fp);
    return buffer;
}

int log_init(const char *cfg_path, const char *module) {
    memset(&settings, 0, sizeof(LogSettings));
    initialized = 1;
    strncpy(cfg_path_global, cfg_path, sizeof(cfg_path_global) - 1);
    
    // Establecer valores predeterminados
    strcpy(settings.dir_path, "./");
    strcpy(settings.file_prefix, "app");
    settings.max_log_files = 1;
    settings.max_file_size_mb = 1;
    settings.log_threshold = 4;
    settings.file_output = 1;
    
    const char *target_module = module ? module : "default";
    
    // Verificar existencia del archivo de configuración
    if (access(cfg_path, F_OK) != 0) {
        FILE *file = fopen(cfg_path, "w");
        if (file) {
            fputs(default_cfg, file);
            fclose(file);
        }
    }
    
    char *content = load_text_file(cfg_path);
    if (!content) return -1;
    
    cJSON *json = cJSON_Parse(content);
    free(content);
    if (!json) {
        fprintf(stderr, "Error en formato JSON\n");
        return -1;
    }
    
    cJSON *module_node = cJSON_GetObjectItem(json, target_module);
    if (!module_node) module_node = cJSON_GetObjectItem(json, "default");
    
    cJSON *field;
    field = cJSON_GetObjectItem(module_node, "dir_path");
    if (field) strncpy(settings.dir_path, field->valuestring, sizeof(settings.dir_path) - 1);
    
    field = cJSON_GetObjectItem(module_node, "file_prefix");
    if (field) strncpy(settings.file_prefix, field->valuestring, sizeof(settings.file_prefix) - 1);
    
    field = cJSON_GetObjectItem(module_node, "max_log_files");
    if (field) settings.max_log_files = field->valueint;
    
    field = cJSON_GetObjectItem(module_node, "max_file_size_mb");
    if (field) settings.max_file_size_mb = field->valueint;
    
    field = cJSON_GetObjectItem(module_node, "log_threshold");
    if (field) settings.log_threshold = field->valueint;
    
    field = cJSON_GetObjectItem(module_node, "file_output");
    if (field) settings.file_output = field->valueint;
    
    cJSON_Delete(json);
    return 0;
}

void log_print(const char *src_file, int src_line, int lvl, const char *fmt, ...) {
    if (!initialized || lvl > settings.log_threshold) return;
    
    static const char *level_tags[] = {"ERR", "WRN", "SYS", "INF", "DBG"};
    const char *tag = (lvl >= 0 && lvl <= 4) ? level_tags[lvl] : "";
    
    char log_path[256];
    FILE *log_fp = NULL;
    
    if (settings.file_output) {
        snprintf(log_path, sizeof(log_path), "%s/%s.log", settings.dir_path, settings.file_prefix);
        
        // Crear directorio si no existe
        struct stat st;
        if (stat(settings.dir_path, &st) != 0) {
            mkdir(settings.dir_path, 0755);
        }
        
        // Verificar necesidad de rotación
        if (access(log_path, F_OK) == 0) {
            if (stat(log_path, &st) == 0) {
                if (st.st_size >= (off_t)settings.max_file_size_mb * 1024 * 1024) {
                    char old_path[256], new_path[256];
                    for (int i = settings.max_log_files; i > 0; i--) {
                        snprintf(old_path, sizeof(old_path), "%s/%s_%d.log", settings.dir_path, settings.file_prefix, i);
                        if (access(old_path, F_OK) == 0) {
                            if (i == settings.max_log_files) {
                                unlink(old_path);
                            } else {
                                snprintf(new_path, sizeof(new_path), "%s/%s_%d.log", settings.dir_path, settings.file_prefix, i + 1);
                                rename(old_path, new_path);
                            }
                        }
                    }
                    snprintf(old_path, sizeof(old_path), "%s/%s_1.log", settings.dir_path, settings.file_prefix);
                    rename(log_path, old_path);
                }
            }
        }
        
        log_fp = fopen(log_path, "a");
        if (!log_fp) {
            perror("No se pudo abrir archivo de log");
            return;
        }
    }
    
    char msg_buffer[MAX_LOG_MSG_LEN];
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(msg_buffer, sizeof(msg_buffer), fmt, ap);
    va_end(ap);
    
    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);
    
    if (lvl == -1) {
        printf("%s", msg_buffer);
        if (log_fp) fprintf(log_fp, "%s", msg_buffer);
    } else {
        printf("[%s] [%s] [%s:%d] %s\n", time_buf, tag, src_file, src_line, msg_buffer);
        if (log_fp) fprintf(log_fp, "[%s] [%s] [%s:%d] %s\n", time_buf, tag, src_file, src_line, msg_buffer);
    }
    
    if (log_fp) fclose(log_fp);
}

Makefile para Compilación


CC = gcc
CFLAGS = -Wall -O2 -fPIC
SHARED_FLAGS = -shared

OBJETIVO = liblog.so

OBJS = log_utils.o cJSON.o

all: $(OBJETIVO)

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

$(OBJETIVO): $(OBJS)
	$(CC) $(SHARED_FLAGS) -o $@ $^

clean:
	rm -f $(OBJS) $(OBJETIVO)

Archivo de Configuración de Ejemplo (log_config.json)


{
    "default": {
        "dir_path": "./logs",
        "file_prefix": "main",
        "max_log_files": 3,
        "max_file_size_mb": 5,
        "log_threshold": 4,
        "file_output": 1
    },
    "network": {
        "dir_path": "./net_logs",
        "file_prefix": "net",
        "max_log_files": 2,
        "max_file_size_mb": 10,
        "log_threshold": 3,
        "file_output": 1
    }
}

El análisis del archivo JSON se realiza meidante la biblioteca cJSON, cuyo código fuente está disponible en el repositorio oficial: cJSON en GitHub.

Etiquetas: C logging cJSON json software-development

Publicado el 6-17 18:06