Linux下程序启动之后的初始化---检查配置文件及读取日志配置项的值

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 在程序进行具体的操作之前,一些初始化操作是必不可少的。本文中的示例只是涉及到检查配置文件和读取日志配置项两个操作,在实际的应用中可能还会有初始化数据库参数、建立与其他模块的通信链路等操作,这要视不同的程序而定。

概述
最近,我对本开发组的几位新员工所编写的程序进行了代码走查,发现他们的代码都有一个共同的问题:缺少必要的初始化。在本文中,我将详细介绍程序在启动时所必须要进行的初始化操作,并用实际的C代码予以说明。

对于一般的程序来说,在启动时所必须要进行的初始化操作有两个:检查配置文件及读取日志配置项。对于检查配置文件,主要检查配置文件是否是规定的文件类型(后缀是否正确)及是否存放在了规定的目录下(一般存放在当前用户的etc目录下);对于读取日志配置项,主要将写日志文件相关的参数(如日志级别、日志文件最大长度、最大备份日志文件数量等)从配置文件的日志段中读取出来,放到全局变量中,以便后续流程使用。

可以用如下的流程图来形象地表示程序的整个流程:
liucheng

从上图可以看出,程序启动之后,如果检查配置文件和读取日志配置项两者之一不通过,那么程序就不会执行后续流程。由此也可以看到,初始化操作在程序中的重要地位。下面我们用实际的C代码来说明上图中所示的两个初始化操作。

程序代码
为了便于说明,假设我们的主程序文件名为InitEnv.c,配置文件名为InitEnv.ini。同时,因为要从配置文件中读取日志配置项的值,我们编写GetConfig.c和GetConfig.h文件来提供从文件中读取配置项值的操作(也就是API)。三个文件的代码内容如下:

InitEnv.c:

/**********************************************************************
* 版权所有 (C)2016, Zhou Zhaoxiong
*
* 文件名称:InitEnv.c
* 文件标识:无
* 内容摘要:程序运行之前检查配置文件和日志文件是否存在
* 其它说明:无
* 当前版本:V1.0
* 作   者:ZhouZhaoxiong
* 完成日期:20161213
*
**********************************************************************/
#include "GetConfig.h"

// 宏定义
#define INI_FILE_NAME  "InitEnv.ini"
#define LOG_FILE_NAME  "InitEnv.log"

// 日志模块全局参数结构
typedef struct
{
    UINT32    iLoglevel;                // 日志级别
    UINT32    iMaxLogSize;              // 日志文件最大长度
    UINT32    iMaxBakCount;             // 最大备份日志文件数量
    UINT32    iNewLogFileFlag;          // 启动时是否新建空日志文件, 否则追加到上次的日志文件中,1-Yes 0-No
    UINT32    iLogPositionFlag;         // 是否输出日志位置信息(文件名/行号),1-Yes 0-No
    UINT8     szLogFileFullName[256];   // 日志文件全路径名称
} T_LogInfo;
T_LogInfo t_loginfo = {0};


// 函数声明
INT32 AccessCfgFile(UINT8 *pszCfgFileName, UINT8 *pszCfgFileFullName);
INT32 InitLogInfo(UINT8 *pszCfgFileFullName);


/****************************************************************
* 功能描述: 主函数
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 0-执行完成
* 其他说明: 无
* 修改日期      版本号        修改人         修改内容
*-------------------------------------------------------------
* 20161213     V1.0     Zhou Zhaoxiong     创建
****************************************************************/
INT32 main(void)
{
    INT32 iRetVal                = 0;
    UINT8 szCfgFileFullName[256] = {0};

    // 首先检查配置文件是否存在,并获取带全路径的配置文件名
    iRetVal =AccessCfgFile(INI_FILE_NAME, szCfgFileFullName);
    if (iRetVal !=0)  // 配置文件不存在, 直接返回
    {
       printf("exec AccessCfgFile failed!\n");
        return -1;
    }

    // 打印获取到的带全路径的配置文件名
    printf("CfgFileFullName is %s!\n", szCfgFileFullName);

    // 然后读取配置文件, 初始化日志信息
    iRetVal =InitLogInfo(szCfgFileFullName);
    if (iRetVal !=0)  // 读取配置文件失败, 直接返回
    {
       printf("exec InitLogInfo failed!\n");
        return -1;
    }

    // 打印初始化的日志信息
    printf("Loglevel is %d, MaxLogSize is %d(MB), MaxBakCount is %d,NewLogFileFlag is %d, LogPositionFlag is %d, LogFileFullName is %s!\n",t_loginfo.iLoglevel, t_loginfo.iMaxLogSize, t_loginfo.iMaxBakCount,
           t_loginfo.iNewLogFileFlag, t_loginfo.iLogPositionFlag,t_loginfo.szLogFileFullName);

    return 0;
}


/****************************************************************
* 功能描述: 检查配置文件是否存在,并获取带全路径的配置文件名
* 输入参数: pszCfgFileName-不带路径的配置文件名
* 输出参数: pszCfgFileFullName-带全路径的配置文件名
* 返 回 值: 0-存在 -1-不存在 -2-程序处理异常
* 其他说明: 无
* 修改日期      版本号         修改人       修改内容
*-------------------------------------------------------------
* 20161213     V1.0     Zhou Zhaoxiong     创建
****************************************************************/
INT32 AccessCfgFile(UINT8 *pszCfgFileName, UINT8*pszCfgFileFullName)
{
    UINT8  szTmpCfgFileName[256] = {0};
    UINT8 *pFindStr              = NULL;

    if (NULL ==pszCfgFileName || NULL == pszCfgFileFullName)
    {
       printf("AccessCfgFile: pszCfgFileName or pszCfgFileFullName isNULL!\n");
        return -2;
    }

    // 判断配置文件的后缀是否为ini
    pFindStr =strstr(pszCfgFileName, ".ini");
    if (pFindStr ==NULL)   // 配置文件后缀错误,直接返回
    {
       printf("AccessCfgFile: the suffix of %s is not ini, pleasecheck!\n", pszCfgFileName);
        return -2;
    }

    // 获取带全路径的配置文件名
   snprintf(szTmpCfgFileName, sizeof(szTmpCfgFileName)-1,"%s/etc/%s", getenv("HOME"), pszCfgFileName);

    // 判断配置文件是否存在
    if (0 == access(szTmpCfgFileName,F_OK))   // 配置文件存在
    {
       snprintf(pszCfgFileFullName, sizeof(szTmpCfgFileName)-1, "%s",szTmpCfgFileName);
    }
    else
    {
       printf("AccessCfgFile: %s has not existed!\n",szTmpCfgFileName);
        return -1;
    }

    return 0;
}


/****************************************************************
* 功能描述: 读取配置文件, 初始化日志信息
* 输入参数: pszCfgFileFullName-带全路径的配置文件名
* 输出参数: 无
* 返 回 值: 0-处理成功 -1-处理失败
* 其他说明: 无
* 修改日期       版本号       修改人        修改内容
* -------------------------------------------------------------
* 20161213     V1.0     Zhou Zhaoxiong     创建
****************************************************************/
INT32 InitLogInfo(UINT8 *pszCfgFileFullName)
{
    if (NULL ==pszCfgFileFullName)
    {
        printf("InitLogInfo:pszCfgFileFullName is NULL!\n");
        return -1;
    }

    // 日志级别0-Fatal 1-Error 2-Warn 3-Info 4-Trace 5-Debug 6-All
    t_loginfo.iLoglevel= GetConfigFileIntValue("LOG", "LogLevel", 3,pszCfgFileFullName);

    // 日志文件最大长度, 单位MB, 范围是[1,500]
   t_loginfo.iMaxLogSize = GetConfigFileIntValue("LOG","LogMaxSize", 5, pszCfgFileFullName);
    if(t_loginfo.iMaxLogSize < 1 || t_loginfo.iMaxLogSize > 500)
    {
       t_loginfo.iMaxLogSize = 10;          // 配置超出[1,500]范围默认10M
    }

    // 最大备份日志文件数量, 范围是[1,999]
    t_loginfo.iMaxBakCount = GetConfigFileIntValue("LOG","BackupCount", 10, pszCfgFileFullName);
    if(t_loginfo.iMaxBakCount < 1 || t_loginfo.iMaxBakCount > 999)
    {
       t_loginfo.iMaxBakCount = 10;    //配置超出[1,999]范围默认10个
    }

    // 每次启动是否新建空日志文件
    t_loginfo.iNewLogFileFlag = GetConfigFileIntValue("LOG","NewLogFileFlag", 1, pszCfgFileFullName);

    // 是否输出日志位置信息(文件名/行号)标志
    t_loginfo.iLogPositionFlag = GetConfigFileIntValue("LOG","LogPositionFlag", 1, pszCfgFileFullName);

    // 记录日志文件全路径名称
     snprintf(t_loginfo.szLogFileFullName,sizeof(t_loginfo.szLogFileFullName)-1, "%s/log/%s",getenv("HOME"), LOG_FILE_NAME);

    return 0;
}

GetConfig.h:

/**********************************************************************
* 版权所有 (C)2016, Zhou Zhaoxiong。
*
* 文件名称:GetConfig.h
* 文件标识:无
* 内容摘要:Linux下配置文件的读取
* 其它说明:无
* 当前版本:V1.0
* 作   者:ZhouZhaoxiong
* 完成日期:20161213
*
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>


// 数据类型重定义
typedef unsigned char   UINT8;
typedef signed   int    INT32;
typedef unsigned int    UINT32;


// 函数声明
void GetStringContentValue(FILE *fp, UINT8 *pszSectionName,UINT8 *pszKeyName, UINT8 *pszOutput, UINT32 iOutputLen);
void GetConfigFileStringValue(UINT8 *pszSectionName, UINT8*pszKeyName, UINT8 *pDefaultVal, UINT8 *pszOutput, UINT32 iOutputLen, UINT8*pszConfigFileName);
INT32 GetConfigFileIntValue(UINT8 *pszSectionName, UINT8*pszKeyName, UINT32 iDefaultVal, UINT8 *pszConfigFileName);

GetConfig.c:

/**********************************************************************
* 版权所有 (C)2016, Zhou Zhaoxiong。
*
* 文件名称:GetConfig.c
* 文件标识:无
* 内容摘要:Linux下配置文件的读取
* 其它说明:无
* 当前版本:V1.0
* 作   者:ZhouZhaoxiong
* 完成日期:20161213
*
**********************************************************************/
#include "GetConfig.h"


/**********************************************************************
* 功能描述:获取具体的字符串值
* 输入参数: fp-配置文件指针
           pszSectionName-段名, 如: GENERAL
           pszKeyName-配置项名, 如:EmployeeName
           iOutputLen-输出缓存长度
* 输出参数: pszOutput-输出缓存
* 返 回 值:无
* 其它说明:无
* 修改日期        版本号         修改人        修改内容
*------------------------------------------------------------------
* 20161213       V1.0     Zhou Zhaoxiong     创建
********************************************************************/
void GetStringContentValue(FILE *fp, UINT8 *pszSectionName,UINT8 *pszKeyName, UINT8 *pszOutput, UINT32 iOutputLen)
{
    UINT8  szSectionName[100]    = {0};
    UINT8  szKeyName[100]        = {0};
    UINT8  szContentLine[256]    = {0};
    UINT8  szContentLineBak[256] = {0};
    UINT32 iContentLineLen       = 0;
    UINT32 iPositionFlag         = 0;

    // 先对输入参数进行异常判断
    if (fp == NULL ||pszSectionName == NULL || pszKeyName == NULL || pszOutput == NULL)
    {
       printf("GetStringContentValue: input parameter(s) isNULL!\n");
        return;
    }

    sprintf(szSectionName, "[%s]", pszSectionName);
    strcpy(szKeyName,pszKeyName);

    while (feof(fp) ==0)
    {
       memset(szContentLine, 0x00, sizeof(szContentLine));
       fgets(szContentLine, sizeof(szContentLine), fp);      // 获取段名

        // 判断是否是注释行(以;开头的行就是注释行)或以其他特殊字符开头的行
        if(szContentLine[0] == ';' || szContentLine[0] == '\r' || szContentLine[0] =='\n' || szContentLine[0] == '\0')
        {
            continue;
        }

        // 匹配段名
        if(strncasecmp(szSectionName, szContentLine, strlen(szSectionName)) == 0)    
        {
            while(feof(fp) == 0)
            {
               memset(szContentLine,    0x00,sizeof(szContentLine));
               memset(szContentLineBak, 0x00, sizeof(szContentLineBak));
               fgets(szContentLine, sizeof(szContentLine), fp);     // 获取字段值

                // 判断是否是注释行(以;开头的行就是注释行)
                if(szContentLine[0] == ';')
                {
                   continue;
                }

               memcpy(szContentLineBak, szContentLine, strlen(szContentLine));

                // 匹配配置项名
                if(strncasecmp(szKeyName, szContentLineBak, strlen(szKeyName)) == 0)    
                {
                   iContentLineLen = strlen(szContentLine);
                    for(iPositionFlag = strlen(szKeyName); iPositionFlag <= iContentLineLen;iPositionFlag ++)
                    {
                       if (szContentLine[iPositionFlag] == ' ')
                       {
                           continue;
                       }
                       if (szContentLine[iPositionFlag] == '=')
                       {
                           break;
                        }

                       iPositionFlag = iContentLineLen + 1;
                       break;
                    }

                   iPositionFlag = iPositionFlag + 1;   // 跳过=的位置

                    if(iPositionFlag > iContentLineLen)
                    {
                       continue;
                    }

                   memset(szContentLine, 0x00, sizeof(szContentLine));
                   strcpy(szContentLine, szContentLineBak + iPositionFlag);

                    // 去掉内容中的无关字符
                    for(iPositionFlag = 0; iPositionFlag < strlen(szContentLine); iPositionFlag ++)
                    {
                       if (szContentLine[iPositionFlag] == '\r' || szContentLine[iPositionFlag]== '\n' || szContentLine[iPositionFlag] == '\0')
                       {
                           szContentLine[iPositionFlag] = '\0';
                           break;
                       }
                    }

                    // 将配置项内容拷贝到输出缓存中
                   strncpy(pszOutput, szContentLine, iOutputLen-1);
                   break;
                }
                else if(szContentLine[0] == '[')
                {
                   break;
                }
            }
            break;
        }
    }
}


/**********************************************************************
* 功能描述:从配置文件中获取字符串
* 输入参数: pszSectionName-段名, 如:GENERAL
           pszKeyName-配置项名, 如:EmployeeName
           pDefaultVal-默认值
           iOutputLen-输出缓存长度
           pszConfigFileName-配置文件名
* 输出参数: pszOutput-输出缓存
* 返 回 值:无
* 其它说明:无
* 修改日期       版本号         修改人       修改内容
*------------------------------------------------------------------
* 20161213      V1.0     Zhou Zhaoxiong     创建
********************************************************************/ 
void GetConfigFileStringValue(UINT8 *pszSectionName, UINT8*pszKeyName, UINT8 *pDefaultVal, UINT8 *pszOutput, UINT32 iOutputLen, UINT8*pszConfigFileName)
{
    FILE  *fp                    = NULL;
    UINT8  szWholePath[256]      = {0};

    // 先对输入参数进行异常判断
    if (pszSectionName== NULL || pszKeyName == NULL || pszOutput == NULL || pszConfigFileName ==NULL)
    {
        printf("GetConfigFileStringValue: input parameter(s) isNULL!\n");
        return;
    }

    // 获取默认值
    if (pDefaultVal ==NULL)
    {
       strcpy(pszOutput, "");
    }
    else
    {
       strcpy(pszOutput, pDefaultVal);
    }

    // 打开配置文件
    fp =fopen(pszConfigFileName, "r");
    if (fp == NULL)
    {
        printf("GetConfigFileStringValue: open %s failed!\n",szWholePath);
        return;
    }

    // 调用函数用于获取具体配置项的值
   GetStringContentValue(fp, pszSectionName, pszKeyName, pszOutput,iOutputLen);

    // 关闭文件
    fclose(fp);
    fp = NULL;
}


/**********************************************************************
* 功能描述:从配置文件中获取整型变量
* 输入参数:pszSectionName-段名, 如:GENERAL
          pszKeyName-配置项名, 如:EmployeeName
          iDefaultVal-默认值
          pszConfigFileName-配置文件名
* 输出参数:无
* 返 回 值:iGetValue-获取到的整数值  -1-获取失败
* 其它说明:无
* 修改日期        版本号        修改人       修改内容
*------------------------------------------------------------------
* 20161213      V1.0     Zhou Zhaoxiong     创建
********************************************************************/ 
INT32 GetConfigFileIntValue(UINT8 *pszSectionName, UINT8*pszKeyName, UINT32 iDefaultVal, UINT8 *pszConfigFileName)
{
    UINT8  szGetValue[512] = {0};
    INT32  iGetValue       = 0;

    // 先对输入参数进行异常判断
    if (pszSectionName== NULL || pszKeyName == NULL || pszConfigFileName == NULL)
    {
        printf("GetConfigFileIntValue: input parameter(s) isNULL!\n");
        return -1;
    }

   GetConfigFileStringValue(pszSectionName, pszKeyName, NULL, szGetValue,512-1, pszConfigFileName);    // 先将获取的值存放在字符型缓存中

    if (szGetValue[0] =='\0' || szGetValue[0] == ';')    // 如果是结束符或分号, 则使用默认值
    {
        iGetValue =iDefaultVal;
    }
    else
    {
        iGetValue =atoi(szGetValue);
    }

    return iGetValue;
}

程序说明
我们主要对InitEnv.c文件进行说明:

第一,检查配置文件的操作是由AccessCfgFile函数完成的,该函数首先判断配置文件的后缀是否为ini,然后获取带全路径的配置文件名,最后判断配置文件是否存在。如果该函数执行失败(配置文件不存在或其他),那么直接停止程序的运行,不再执行后续流程。

第二,读取日志配置项的操作是由InitLogInfo函数完成的,该函数从配置文件InitEnv.ini的[LOG]段中将LogLevel、LogMaxSize、BackupCount、NewLogFileFlag和LogPositionFlag配置项的值读取出来。如果该函数执行失败,那么直接停止程序的运行,不再执行后续流程。

第三,如果AccessCfgFile和InitLogInfo函数都执行成功,那么就意味着程序初始化成功了,可以执行后续操作。本程序的后续操作便是打印读取到的配置项的值及日志文件全路径名称。

程序测试
我们将上述三个文件上传到Linux机器上,并使用“gcc -g -o InitEnvInitEnv.c GetConfig.c”命令编译之后,生成InitEnv文件。执行“InitEnv”命令,即可对程序进行测试。

1)当配置文件InitEnv.ini不存在或未被放置到规定目录时,程序运行结果如下:

AccessCfgFile: /home/zhou/etc/InitEnv.ini has not existed!
exec AccessCfgFile failed!

2)当配置文件InitEnv.ini内容如下:

[LOG]
;LogLevel, 0-Fatal 1-Error 2-Warn 3-Info4-Trace 5-Debug 6-All
LogLevel=4 

;Max log size (MB), [1,500]
LogMaxSize=10

;Max log backup count [1,999]
BackupCount=100

;If output log into new file when starting, 1-Yes 0-No
NewLogFileFlag=0

;If output position info(filename/linenum), 1-Yes 0-No
LogPositionFlag=1

程序运行结果为:

CfgFileFullName is /home/zhou/etc/InitEnv.ini!
Loglevel is 4, MaxLogSize is 10(MB), MaxBakCount is 100,NewLogFileFlag is 0, LogPositionFlag is 1, LogFileFullName is /home/zhou/log/InitEnv.log!

可见,在正常情况下,程序能够完成检查配置文件及读取日志配置项的操作,大家也可以对程序进行更多的测试。

总结
在程序进行具体的操作之前,一些初始化操作是必不可少的。本文中的示例只是涉及到检查配置文件和读取日志配置项两个操作,在实际的应用中可能还会有初始化数据库参数、建立与其他模块的通信链路等操作,这要视不同的程序而定。

“磨刀不误砍柴工”,当程序完成了必要的初始化操作之后,便可以进行正式的操作了。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
2月前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
179 6
|
3月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
640 2
|
10天前
|
监控 Linux 开发者
如何在 Linux 中优雅的使用 head 命令,用来看日志简直溜的不行
`head` 命令是 Linux 系统中一个非常实用的工具,用于快速查看文件的开头部分内容。本文介绍了 `head` 命令的基本用法、高级用法、实际应用案例及注意事项,帮助用户高效处理文件和日志,提升工作效率。
22 7
|
1月前
|
监控 网络协议 安全
Linux系统日志管理
Linux系统日志管理
42 3
|
1月前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
36 1
|
2月前
|
消息中间件 分布式计算 Java
Linux环境下 java程序提交spark任务到Yarn报错
Linux环境下 java程序提交spark任务到Yarn报错
42 5
|
1月前
|
监控 Linux 测试技术
Linux系统命令与网络,磁盘和日志监控总结
Linux系统命令与网络,磁盘和日志监控总结
55 0
|
1月前
|
监控 Linux 测试技术
Linux系统命令与网络,磁盘和日志监控三
Linux系统命令与网络,磁盘和日志监控三
38 0
|
3月前
|
存储 消息中间件 监控
Java日志详解:日志级别,优先级、配置文件、常见日志管理系统ELK、日志收集分析
Java日志详解:日志级别,优先级、配置文件、常见日志管理系统、日志收集分析。日志级别从小到大的关系(优先级从低到高): ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF 低级别的会输出高级别的信息,高级别的不会输出低级别的信息
|
14天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
125 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板