C语言高效日志记录:减少磁盘I/O的方法

news2024/11/29 8:43:09

        在软件开发过程中,日志记录是一项非常重要的功能。它可以帮助我们追踪程序的运行状态、调试错误以及分析性能问题。然而,频繁的日志写入操作可能会对磁盘I/O造成较大压力,影响程序的整体性能。本文将探讨如何在C语言中实现高效日志记录,并重点介绍减少磁盘I/O的方法。

日志记录的基本实现

首先,我们需要一个基本的日志记录函数。以下是一个简单的log_printf函数,它接受一个格式字符串和一系列参数,类似于printf

#include <stdio.h>
#include <stdarg.h>

void log_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

这个函数使用了stdarg.h头文件中的宏来处理可变数量的参数。

减少磁盘I/O的方法

以下是一些减少磁盘I/O的有效方法:

1. 缓冲和批处理

将日志消息先写入内存缓冲区,当缓冲区满了或者达到一定时间间隔后再一次性写入磁盘。

#define LOG_BUFFER_SIZE 1024
#define MAX_LOG_MESSAGES 100

typedef struct {
    char buffer[LOG_BUFFER_SIZE];
    int length;
} LogMessage;

typedef struct {
    LogMessage messages[MAX_LOG_MESSAGES];
    int count;
} LogBuffer;

LogBuffer logBuffer;

void log_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);

    if (logBuffer.count < MAX_LOG_MESSAGES) {
        LogMessage *msg = &logBuffer.messages[logBuffer.count++];
        vsnprintf(msg->buffer, LOG_BUFFER_SIZE, format, args);
        msg->length = strlen(msg->buffer);
        if (logBuffer.count == MAX_LOG_MESSAGES) {
            log_flush(); // Flush when buffer is full
        }
    }

    va_end(args);
}

void log_flush() {
    for (int i = 0; i < logBuffer.count; i++) {
        fwrite(logBuffer.messages[i].buffer, 1, logBuffer.messages[i].length, stdout);
    }
    logBuffer.count = 0; // Reset message count after flush
    fflush(stdout); // Ensure data is written to disk
}

2. 异步写入

使用单独的线程或进程来处理日志写入操作,这样主程序可以继续执行而不必等待磁盘I/O完成。

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

#define LOG_BUFFER_SIZE 1024
#define MAX_LOG_MESSAGES 100

typedef struct {
    char buffer[LOG_BUFFER_SIZE];
    int length;
} LogMessage;

typedef struct {
    LogMessage messages[MAX_LOG_MESSAGES];
    int count;
    pthread_mutex_t lock;
    pthread_cond_t cond;
    FILE *file;
    int finished;
} LogQueue;

LogQueue logQueue;

void log_init(const char *filename) {
    pthread_mutex_init(&logQueue.lock, NULL);
    pthread_cond_init(&logQueue.cond, NULL);
    logQueue.count = 0;
    logQueue.finished = 0;
    logQueue.file = fopen(filename, "a"); // Open file in append mode
    if (!logQueue.file) {
        perror("Error opening log file");
        exit(EXIT_FAILURE);
    }
}

void log_deinit() {
    logQueue.finished = 1;
    pthread_cond_broadcast(&logQueue.cond); // Wake up the logging thread
}

void log_flush() {
    pthread_mutex_lock(&logQueue.lock);
    for (int i = 0; i < logQueue.count; i++) {
        fwrite(logQueue.messages[i].buffer, 1, logQueue.messages[i].length, logQueue.file);
    }
    logQueue.count = 0; // Reset message count after flush
    pthread_mutex_unlock(&logQueue.lock);
    fflush(logQueue.file); // Ensure data is written to disk
}

void log_worker() {
    while (1) {
        pthread_mutex_lock(&logQueue.lock);
        while (logQueue.count == 0 && !logQueue.finished) {
            pthread_cond_wait(&logQueue.cond, &logQueue.lock);
        }
        if (logQueue.finished && logQueue.count == 0) {
            pthread_mutex_unlock(&logQueue.lock);
            break;
        }
        log_flush();
        pthread_mutex_unlock(&logQueue.lock);
    }
}

void log_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);

    pthread_mutex_lock(&logQueue.lock);
    if (logQueue.count < MAX_LOG_MESSAGES) {
        LogMessage *msg = &logQueue.messages[logQueue.count++];
        vsnprintf(msg->buffer, LOG_BUFFER_SIZE, format, args);
        msg->length = strlen(msg->buffer);
        if (logQueue.count == MAX_LOG_MESSAGES) {
            pthread_cond_signal(&logQueue.cond); // Signal the logging thread to flush
        }
    }
    pthread_mutex_unlock(&logQueue.lock);

    va_end(args);
}

int main() {
    log_init("log.txt");

    pthread_t worker_thread;
    pthread_create(&worker_thread, NULL, (void *)log_worker, NULL);

    // Use log_printf as before...

    log_deinit();
    pthread_join(worker_thread, NULL);
    log_flush(); // Final flush to ensure all messages are written

    fclose(logQueue.file); // Close the log file
    return 0;
}

        在这个示例中,我们定义了一个LogQueue结构体,它包含一个消息队列、一个互斥锁、一个条件变量、一个文件指针以及一个标志来指示日志线程何时停止。log_worker函数是日志线程的工作函数,它等待条件变量被触发,然后刷新队列中的所有消息到文件。

log_printf函数将日志消息添加到队列中,如果队列满了,它会通过条件变量通知日志线程进行刷新。log_deinit函数设置finished标志并通知日志线程结束。

main函数中,我们创建了一个日志线程,并在程序结束前等待它完成。这样,日志消息的写入操作就会异步进行,不会阻塞主线程。

3. 减少日志量

根据日志级别减少不必要的日志记录,比如在生产环境中关闭DEBUG级别的日志。

#define LOG_LEVEL INFO

enum {
    DEBUG,
    INFO,
    WARNING,
    ERROR
};

void log_printf(int level, const char *format, ...) {
    if (level >= LOG_LEVEL) {
        va_list args;
        va_start(args, format);
        vprintf(format, args);
        va_end(args);
    }
}

4. 日志轮转和归档

定期将日志文件轮转和归档是常见的日志管理实践,它可以避免单个日志文件过大,从而减少单个文件写入时的磁盘I/O。以下是一个简单的C语言示例,展示了如何实现日志文件的轮转和归档:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>

// 日志文件的最大大小
#define MAX_LOG_FILE_SIZE 1024 * 1024 * 10 // 10MB

// 日志文件名格式
#define LOG_FILE_FORMAT "log_%Y%m%d.txt"

// 用于存储日志文件的目录
#define LOG_DIR "logs/"

// 检查目录是否存在,如果不存在则创建
void check_log_dir() {
    struct stat sb;
    if (stat(LOG_DIR, &sb) != 0) {
        mkdir(LOG_DIR, 0755); // 创建目录,权限设置为0755
    }
}

// 创建新的日志文件
void create_new_log_file() {
    // 获取当前日期
    time_t now = time(NULL);
    struct tm *tm_info = localtime(&now);
    char buf[20];
    strftime(buf, sizeof(buf), LOG_FILE_FORMAT, tm_info);

    // 创建新日志文件的路径
    char log_path[100];
    snprintf(log_path, sizeof(log_path), "%s%s", LOG_DIR, buf);

    // 打开新日志文件
    FILE *new_log_file = fopen(log_path, "a");
    if (!new_log_file) {
        perror("Error opening new log file");
        exit(EXIT_FAILURE);
    }

    // 关闭旧日志文件
    fclose(log_file);
    log_file = new_log_file;
}

// 检查日志文件大小,如果超过最大限制则轮转日志文件
void check_log_file_size() {
    if (ftell(log_file) >= MAX_LOG_FILE_SIZE) {
        create_new_log_file();
    }
}

// 打印日志消息到文件
void log_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);

    check_log_file_size(); // 检查日志文件大小

    vfprintf(log_file, format, args);
    fflush(log_file); // 确保日志消息立即写入文件

    va_end(args);
}

int main() {
    check_log_dir(); // 检查日志目录是否存在

    // 打开或创建新的日志文件
    char log_path[100];
    snprintf(log_path, sizeof(log_path), "%s%s", LOG_DIR, LOG_FILE_FORMAT);
    log_file = fopen(log_path, "a");
    if (!log_file) {
        perror("Error opening log file");
        exit(EXIT_FAILURE);
    }

    // 使用log_printf进行日志记录
    log_printf("This is a log message.\n");
    log_printf("Another log message.\n");

    // 关闭日志文件
    fclose(log_file);

    return 0;
}

        在这个示例中,我们定义了MAX_LOG_FILE_SIZE来限制日志文件的大小,当日志文件达到这个大小时,程序会创建一个新的日志文件。check_log_dir函数用于检查日志目录是否存在,如果不存在则创建。create_new_log_file函数用于创建新的日志文件,并更新全局日志文件指针。check_log_file_size函数用于检查日志文件大小,如果超过限制则轮转日志文件。

log_printf函数使用vfprintf来打印日志消息,并确保在每次写入后立即刷新文件,以减少磁盘I

如果日志消息内容相同,可以考虑合并或重用,避免重复写入。

结论

通过上述方法,我们可以有效地减少日志记录过程中的磁盘I/O,从而提高程序的整体性能。在实际应用中,应根据具体情况选择最合适的优化策略。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2249636.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【仓颉学习02】编译后运行报错:“由于找不到libcanjie-runtime.dll,无法继续执行代码。重新安装程序可能会解决此问题。”

敲了段《仓颉语言实践》&#xff08;张磊著&#xff09;书上的代码&#xff0c;如下&#xff1a; package test //coding:utf-8struct Employee{var name:Stringprivate var realSalary:Float64public Employee(name:String,realSalary:Float64){this.namenamethis.realSalary…

AI智算-正式上架GPU资源监控概览 Grafana Dashboard

下载链接 https://grafana.com/grafana/dashboards/22424-ai-gpu-20241127/

LINUX2.4.x网络安全框架

在分析LINUX2.4.x网络安全的实现之前先简介一下它里面包括的几个重要概念&#xff1a;netfilter、iptables、match、target、nf_sockopt_ops、网络安全功能点的实现。详解会在后面的分析中讲到。 首先是netfilter&#xff0c;它定义了协议栈中的检查点和在检查点上引用的数据结…

【博主推荐】C# Winform 拼图小游戏源码详解(附源码)

文章目录 前言摘要1.设计来源拼图小游戏讲解1.1 拼图主界面设计1.2 一般难度拼图效果1.3 普通难度拼图效果1.4 困难难度拼图效果1.5 地域难度拼图效果1.6 内置五种拼图效果 2.效果和源码2.1 动态效果2.2 源代码 源码下载结束语 前言 在数字浪潮汹涌澎湃的时代&#xff0c;程序开…

pytorch错误: 找不到指定的模块 Error loading “torch_python.dll“ or one of its dependencies

省流&#xff1a;python(3.12.7) 和 pytorch(2.4.0)版本不配套 问题 起因是看到了这本书《Build a Large Language Model (From Scratch) 》&#xff0c;是2024年9月新出的&#xff0c; 作者 Sebastian Raschka&#xff0c;想要按照作者给出的步骤来手搓一个大语言模型&#…

渗透测试学习笔记(一)渗透测试方法论

一.渗透测试方法论 渗透测试方法论是制定实施信息安全审计方案时&#xff0c;需要遵循的规则&#xff0c;惯例和过程。人们在评估网络&#xff0c;应用&#xff0c;系统或者三者组合的安全状态时&#xff0c;不断摸索各种实务的理念和成熟的做法&#xff0c;并总结了一套理论-…

【论文阅读】如何高效阅读和理解学术论文

【论文阅读】如何高效阅读和理解学术论文 写在最前面一、为什么需要系统的阅读方法&#xff1f;二、阅读论文的11步方法三、实践示例四、常见问题解答五、结语 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2024每日百字篆刻时光&#xff0c;感谢你的陪伴与支持…

Java 基础面试 题(Java Basic Interview Questions)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…

HCIE IGP双栈综合实验

实验拓扑 实验需求及解法 本实验模拟ISP网络结构&#xff0c;R1/2组成国家骨干网&#xff0c;R3/4组成省级网络&#xff0c;R5/6/7组成数据中 心网络。 配置所有ipv4地址&#xff0c;请自行测试直连。 R1 sysname R1 interface GigabitEthernet0/0/0ip address 12.1.1.1 255.…

利用阿里云镜像仓库和 Github Action 同步镜像

利用阿里云镜像仓库和 Github Action 同步镜像 由于某些未知原因,国内无法直接从 DockerHub 拉取镜像,在不使用 VPN 等违法工具的情况下,可以利用 GitHub 的 Action 流水线功能,将镜像推送到阿里云的个人镜像仓库中。 这种方式相较于其他方式虽然相对麻烦,但好在免费,且实…

HarmonyOS4+NEXT星河版入门与项目实战(22)------动画(属性动画与显示动画)

文章目录 1、属性动画图解2、案例实现-小鱼移动游戏1、代码实现2、代码解释3、资源图片4、实现效果3、显示动画4、案例修改-显示动画5、总结1、属性动画图解 这里我们用一张完整的图来汇整属性动画的用法格式和使用的主要属性范围,如下所示: 2、案例实现-小鱼移动游戏 1、代…

csp-j初赛模拟试题(解析)

题目&#xff1a; 在 C中&#xff0c;以下哪个关键字用于实现多态性&#xff1f; A. virtualB. staticC. externD. const 以下数据结构中&#xff0c;不属于线性结构的是&#xff08; &#xff09;。 A. 栈B. 队列C. 二叉树D. 链表 一个有 8 个顶点的无向图&#xff0c;若每个…

使用R的数据包快速获取、调用各种地理数据

数据一直是科学研究绕不开的话题&#xff0c;为了方便快捷的获取各种地理数据&#xff0c;许多R包被开发出来&#xff0c;今天介绍一些方便快捷的数据R包。 rnaturalearth 包使 Natural Earth 数据可用。自然地球特征包括 1&#xff1a;10m、1&#xff1a;50m 和 1&#xff1a…

C语言——指针初阶(一)

目录 一.什么是指针&#xff1f;&#xff1f;&#xff1f; 指针是什么&#xff1f; 指针变量&#xff1a; 总结&#xff1a; 总结&#xff1a; 二.指针和指针类型 指针-整数&#xff1a; 总结&#xff1a; 指针的解引用 总结&#xff1a; 三.野指针 如何规避野指针 往期…

游戏引擎学习第22天

移除 DllMain() 并成功重新编译 以下是对内容的详细复述与总结&#xff1a; 问题和解决方案&#xff1a; 在编译过程中遇到了一些问题&#xff0c;特别是如何告知编译器不要退出程序&#xff0c;而是继续处理。问题的根源在于编译过程中传递给链接器的参数设置不正确。原本尝试…

Paper -- 建筑物高度估计 -- 使用街景图像、深度学习、轮廓处理和地理空间数据的建筑高度估计

基本信息 论文题目: Building Height Estimation using Street-View Images, Deep-Learning, Contour Processing, and Geospatial Data 中文题目: 使用街景图像、深度学习、轮廓处理和地理空间数据的建筑高度估计 作者: Ala’a Al-Habashna 作者单位: 加拿大统计局特别商业项…

如何为 ext2/ext3/ext4 文件系统的 /dev/centos/root 增加 800G 空间

如何为 ext2/ext3/ext4 文件系统的 /dev/centos/root 增加 800G 空间 一、引言二、检查当前磁盘和分区状态1. 使用 `df` 命令检查磁盘使用情况2. 使用 `lsblk` 命令查看分区结构3. 使用 `fdisk` 或 `parted` 命令查看详细的分区信息三、扩展逻辑卷(如果使用 LVM)1. 检查 LVM …

命令行使用ssh隧道连接远程mysql

本地电脑A 跳板机B 主机2.2.2.2 用户名 B ssh端口号22 登录密码bbb 远程mysql C 地址 3.3.3.3 端口号3306 用户名C 密码ccc A需要通过跳板机B才能访问C; navicat中配置ssh可以实现在A电脑上访问C 如何实现本地代码中访问C呢? # 假设本地使…

Vatee万腾平台:以数字之名,筑企业未来之路

在当今这个瞬息万变的数字化时代&#xff0c;企业若想在激烈的市场竞争中脱颖而出&#xff0c;就必须紧跟时代步伐&#xff0c;实现全面数字化转型。而Vatee万腾平台&#xff0c;正是这样一款能够帮助企业迈向数字化未来的强大工具。 Vatee万腾平台&#xff0c;作为数字化转型…

深度学习——损失函数与BP算法

一、损失函数 1. 线性回归损失函数 1.1 MAE损失 MAE&#xff08;Mean Absolute Error&#xff0c;平均绝对误差&#xff09;通常也被称为 L1-Loss&#xff0c;通过对预测值和真实值之间的绝对差取平均值来衡量他们之间的差异。MAE的公式如下&#xff1a; 其中&#xff1a; 是…