开发工具-日志文件操作类

88 阅读3分钟

日志文件操作类

打开日志文件
如果日志文件大于m_MaxLogSize的值,切换日志文件
把内容写入日志文件
关闭日志文件

日志文件操作类声明

class CLogFile
{
public:
  FILE   *m_tracefp;           // 日志文件指针。
  char    m_filename[301];     // 日志文件名,建议采用绝对路径。
  char    m_openmode[11];      // 日志文件的打开方式,一般采用"a+"。
  bool    m_bEnBuffer;         // 写入日志时,是否启用操作系统的缓冲机制,缺省不启用。
  bool    m_bBackup;           // 是否自动切换,日志文件大小超过m_MaxLogSize将自动切换,缺省启用。
  long    m_MaxLogSize;        // 最大日志文件的大小,单位M,缺省100M。
  // pthread_spinlock_t spin;  // 初学暂时不要关心这行代码。

  // 构造函数。
  // MaxLogSize:最大日志文件的大小,单位M,缺省100M,最小为10M。
  CLogFile(const long MaxLogSize=100);  

  // 打开日志文件。
  // filename:日志文件名,建议采用绝对路径,如果文件名中的目录不存在,就先创建目录。
  // openmode:日志文件的打开方式,与fopen库函数打开文件的方式相同,缺省值是"a+"。
  // bBackup:是否自动切换,true-切换,false-不切换,在多进程的服务程序中,如果多个进程共用一个日志文件,bBackup必须为false。
  // bEnBuffer:是否启用文件缓冲机制,true-启用,false-不启用,如果启用缓冲区,那么写进日志文件中的内容不会立即写入文件,缺省是不启用。
  bool Open(const char *filename,const char *openmode=0,bool bBackup=true,bool bEnBuffer=false);

  // 如果日志文件大于m_MaxLogSize的值,就把当前的日志文件名改为历史日志文件名,再创建新的当前日志文件。
  // 备份后的文件会在日志文件名后加上日期时间,如/tmp/log/filetodb.log.20200101123025。
  // 注意,在多进程的程序中,日志文件不可切换,多线的程序中,日志文件可以切换。
  bool BackupLogFile();

  // 把内容写入日志文件,fmt是可变参数,使用方法与printf库函数相同。
  // Write方法会写入当前的时间,WriteEx方法不写时间。
  bool Write(const char *fmt,...);
  bool WriteEx(const char *fmt,...);

  // 关闭日志文件
  void Close();

  ~CLogFile();  // 析构函数会调用Close方法。
};

日志文件操作类实现

CLogFile::CLogFile(const long MaxLogSize)
{
  m_tracefp = 0;
  memset(m_filename,0,sizeof(m_filename));
  memset(m_openmode,0,sizeof(m_openmode));
  m_bBackup=true;
  m_bEnBuffer=false;
  m_MaxLogSize=MaxLogSize;
  if (m_MaxLogSize<10) m_MaxLogSize=10;

  // pthread_pin_init(&spin,0);  // 初学暂时不要关心这行代码.在多线程程序中,日志文件是共享资源
}

CLogFile::~CLogFile()
{
  Close();

  // pthread_spin_destroy(&spin);  // 初学暂时不要关心这行代码。
}

void CLogFile::Close()
{
  if (m_tracefp != 0) { fclose(m_tracefp); m_tracefp=0; }

  memset(m_filename,0,sizeof(m_filename));
  memset(m_openmode,0,sizeof(m_openmode));
  m_bBackup=true;
  m_bEnBuffer=false;
}

// 打开日志文件。
// filename:日志文件名,建议采用绝对路径,如果文件名中的目录不存在,就先创建目录。
// openmode:日志文件的打开方式,与fopen库函数打开文件的方式相同,缺省值是"a+"。
// bBackup:是否自动切换,true-切换,false-不切换,在多进程的服务程序中,如果多个进行共用一个日志文件,bBackup必须为false。
// bEnBuffer:是否启用文件缓冲机制,true-启用,false-不启用,如果启用缓冲区,那么写进日志文件中的内容不会立即写入文件,缺省是不启用。
bool CLogFile::Open(const char *filename,const char *openmode,bool bBackup,bool bEnBuffer)
{
  // 如果文件指针是打开的状态,先关闭它。
  Close();

  STRCPY(m_filename,sizeof(m_filename),filename);
  m_bEnBuffer=bEnBuffer;
  m_bBackup=bBackup;
  if (openmode==0) STRCPY(m_openmode,sizeof(m_openmode),"a+");
  else STRCPY(m_openmode,sizeof(m_openmode),openmode);

  if ((m_tracefp=FOPEN(m_filename,m_openmode)) == 0) return false;

  return true;
}

// 如果日志文件大于100M,就把当前的日志文件备份成历史日志文件,切换成功后清空当前日志文件的内容。
// 备份后的文件会在日志文件名后加上日期时间。
// 注意,在多进程的程序中,日志文件不可切换,多线的程序中,日志文件可以切换。
bool CLogFile::BackupLogFile()
{
  if (m_tracefp == 0) return false;

  // 不备份
  if (m_bBackup == false) return true;

  //fseek(m_tracefp,0,2);

  if (ftell(m_tracefp) > m_MaxLogSize*1024*1024)
  {
    fclose(m_tracefp); m_tracefp=0;

    char strLocalTime[21];
    memset(strLocalTime,0,sizeof(strLocalTime));
    LocalTime(strLocalTime,"yyyymmddhh24miss");

    char bak_filename[301];
    SNPRINTF(bak_filename,sizeof(bak_filename),300,"%s.%s",m_filename,strLocalTime);
    rename(m_filename,bak_filename);

    if ((m_tracefp=FOPEN(m_filename,m_openmode)) == 0) return false;
  }

  return true;
}

// 把内容写入日志文件,fmt是可变参数,使用方法与printf库函数相同。
// Write方法会写入当前的时间,WriteEx方法不写时间。
bool CLogFile::Write(const char *fmt,...)
{
  if (m_tracefp == 0) return false;

  // pthread_spin_lock(&spin);  // 初学暂时不要关心这行代码。

  if (BackupLogFile() == false) return false;

  char strtime[20]; 
  LocalTime(strtime);
  va_list ap;
  va_start(ap,fmt);
  fprintf(m_tracefp,"%s ",strtime);
  vfprintf(m_tracefp,fmt,ap);
  va_end(ap);

  if (m_bEnBuffer==false) fflush(m_tracefp);

  // pthread_spin_unlock(&spin);  // 初学暂时不要关心这行代码。

  return true;
}

// 把内容写入日志文件,fmt是可变参数,使用方法与printf库函数相同。
// Write方法会写入当前的时间,WriteEx方法不写时间。
bool CLogFile::WriteEx(const char *fmt,...)
{
  if (m_tracefp == 0) return false;

  // pthread_spin_lock(&spin);  // 初学暂时不要关心这行代码。

  va_list ap;
  va_start(ap,fmt);
  vfprintf(m_tracefp,fmt,ap);
  va_end(ap);

  if (m_bEnBuffer==false) fflush(m_tracefp);

  // pthread_spin_unlock(&spin);  // 初学暂时不要关心这行代码。

  return true;
}

CLogFile类的日志文件的切换测试

int main()
{
  CLogFile logfile;

  // 打开日志文件,如果"/tmp/log"不存在,就创建它,但是要确保当前用户具备创建目录的权限。
  if (logfile.Open("/tmp/log/demo43.log")==false)
  { printf("logfile.Open(/tmp/log/demo43.log) failed.\n"); return -1; }

  logfile.Write("demo43程序开始运行。\n");

  // 让程序循环10000000,生成足够大的日志。
  for (int ii=0;ii<10000000;ii++)
  {
    logfile.Write("本程序演示日志文件的切换,这是第%010d条记录。\n",ii);
  }

  logfile.Write("demo43程序运行结束。\n");
}

CLogFile类记录程序的运行日志测试

int main()
{
  CLogFile logfile;

  // 打开日志文件,如果"/tmp/log"不存在,就创建它,但是要确保当前用户具备创建目录的权限。
  if (logfile.Open("/tmp/log/demo42.log")==false)
  { printf("logfile.Open(/tmp/log/demo42.log) failed.\n"); return -1; }

  logfile.Write("demo42程序开始运行。\n");
  CDir Dir;

  // 扫描/tmp/data目录下文件名匹配"surfdata_*.xml"的文件。
  if (Dir.OpenDir("/tmp/data","surfdata_*.xml")==false)
  { logfile.Write("Dir.OpenDir(/tmp/data) failed.\n"); return -1; }

  CFile File;

  while (Dir.ReadDir()==true)
  {
    logfile.Write("处理文件%s...",Dir.m_FullFileName);

    if (File.Open(Dir.m_FullFileName,"r")==false)
    { logfile.WriteEx("failed.File.Open(%s) failed.\n",Dir.m_FullFileName); return -1; }

    char strBuffer[301];
    while (true)
    {
      memset(strBuffer,0,sizeof(strBuffer));
      if (File.FFGETS(strBuffer,300,"<endl/>")==false) break; // 行内容以"<endl/>"结束。

      // logfile.Write("strBuffer=%s",strBuffer);
      // 这里可以插入解析xml字符串并把数据写入数据库的代码。
    }

    // 处理完文件中的数据后,关闭文件指针,并删除文件。
    File.CloseAndRemove();
    logfile.WriteEx("ok\n");
  }
  logfile.Write("demo42程序运行结束。\n");
}