C++编程:实现简单的高精度时间日志记录小程序(单线程)

0. 概述

本文档描述了一个用C++实现的高精度时间日志记录程序,该程序旨在检查系统时间是否出现跳变。程序可以在指定的时间间隔内(默认20毫秒)将系统时间记录到日志文件中,并具备以下主要功能:

  1. 自定义时间间隔、文件名和运行时间:通过命令行参数设置时间间隔、输出文件名和总运行时间。默认值为20毫秒、timestamps_cpp.txt 和72000秒(20小时)。
  2. 高精度时间戳记录:使用gettimeofday获取系统时间,确保时间戳精确到微秒级。
  3. 日志轮转:当日志文件大小超过50MB时,自动进行日志轮转,将当前日志文件重命名为旧文件并生成新的日志文件。
  4. 时间跳变检测:在日志中标记时间跳变,检测逻辑包括:
    • 时间回退,即当前时间戳小于前一个时间戳。
    • 间隔异常,即实际间隔小于设定间隔的1.5倍*(实际应该是2倍,这里写1.5倍允许适当的误差)*。
      在默认设置下,程序仅记录跳变前后10个时间戳,以简化日志内容,突出异常信息。

1. 代码实现

1.1 概览

该程序按如下步骤实现:

  • 参数解析:从命令行解析时间间隔、文件名、运行时间和是否禁用跳变检测。
  • 时间记录与跳变检测:记录系统时间,检测时间跳变,进行相应日志输出。
  • 日志轮转:根据文件大小自动轮转日志文件。

流程图

在这里插入图片描述

graph TD
    A[开始] --> B[解析命令行参数]
    B --> C[初始化日志文件]
    C --> D[记录系统时间]
    D --> E[检测时间跳变]
    E -->|是| F[进入跳变模式,记录跳变前后时间戳]
    E -->|否| G[普通时间戳记录]
    F --> H[轮转日志文件(若需要)]
    G --> H
    H --> I[等待指定时间间隔]
    I -->|未到达运行时间| D
    I -->|到达运行时间| J[程序结束]

1.2 获取当前时间字符串

使用gettimeofdaystrftime将当前时间格式化为字符串,精确到微秒。

std::string getCurrentTimeStr() {
  struct timeval tv;
  gettimeofday(&tv, nullptr);
  struct tm tmInfo;
  localtime_r(&tv.tv_sec, &tmInfo);
  char buffer[30];
  strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tmInfo);

  // Append microseconds
  std::ostringstream oss;
  oss << buffer << "." << std::setw(6) << std::setfill('0') << tv.tv_usec;
  return oss.str();
}

1.3 文件大小检查与日志轮转

使用stat函数获取日志文件大小,并在超过50MB时进行轮转。轮转方式是将当前日志文件重命名为.old并创建新的日志文件。

void rotateLog(const std::string &filename, size_t maxSize, FILE **logFilePtr) {
  struct stat statBuf;
  if (stat(filename.c_str(), &statBuf) == 0) {
    if (static_cast<size_t>(statBuf.st_size) >= maxSize) {
      fclose(*logFilePtr);
      std::string oldFilename = filename + ".old";
      rename(filename.c_str(), oldFilename.c_str());
      *logFilePtr = fopen(filename.c_str(), "a");
    }
  }
}

1.4 时间跳变检测

程序使用时间戳微秒数进行跳变检测,包括两种跳变情况:

  • 时间回退:当前时间小于前一个时间。
  • 间隔异常:当前时间与上上个时间间隔过大。

当跳变检测到时,进入“跳变模式”,记录前后共20个时间戳,并在跳变处添加 [TIME_JUMP] 标记。

2. 编译与运行

2.1 编译

在终端中使用如下命令编译:

g++ -std=c++11 -o time_logger time_logger.cpp

2.2 运行

  • 使用默认设置(20毫秒间隔,输出文件为timestamps_cpp.txt,运行20小时):
./time_logger
  • 自定义设置(例如,100毫秒间隔,输出文件为output.txt,运行2小时):
./time_logger -i 100 -f output.txt -t 7200

2.3 示例日志文件

日志文件示例:

2024-04-27 12:34:56.789123
2024-04-27 12:34:56.829456
2024-04-27 12:34:56.869789
2024-04-27 12:34:56.809012 [TIME_JUMP]
2024-04-27 12:34:56.849345
...

3. 附完整代码

// g++ -std=c++11 -pthread -o time_logger time_logger.cpp
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <iomanip>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <vector>

// Struct to store command-line arguments
struct Arguments {
  int interval = 20; // milliseconds
  std::string file = "timestamps_cpp.txt";
  int time = 72000; // seconds
  bool disableSelectiveLogging = false;
};

// Function to parse command-line arguments
Arguments parseArguments(int argc, char *argv[]) {
  Arguments args;
  for (int i = 1; i < argc; ++i) {
    std::string arg = argv[i];
    if ((arg == "-i" || arg == "--interval") && i + 1 < argc) {
      args.interval = std::stoi(argv[++i]);
    } else if ((arg == "-f" || arg == "--file") && i + 1 < argc) {
      args.file = argv[++i];
    } else if ((arg == "-t" || arg == "--time") && i + 1 < argc) {
      args.time = std::stoi(argv[++i]);
    } else if (arg == "--disable_selective_logging") {
      args.disableSelectiveLogging = true;
    } else if (arg == "-h" || arg == "--help") {
      fprintf(stdout, "Usage: %s [options]\n", argv[0]);
      fprintf(stdout, "Options:\n");
      fprintf(stdout, "  -i, --interval <milliseconds>    Set the time "
                      "interval (default: 20 milliseconds)\n");
      fprintf(stdout, "  -f, --file <filename>            Set the output "
                      "filename (default: timestamps.txt)\n");
      fprintf(stdout, "  -t, --time <seconds>             Set the run time "
                      "(default: 72000 seconds)\n");
      fprintf(stdout,
              "  --disable_selective_logging      Disable selective logging\n");
      fprintf(stdout,
              "  -h, --help                       Show this help message\n");
      exit(0);
    } else {
      fprintf(stderr, "Unknown argument: %s\n", arg.c_str());
      fprintf(stderr, "Use -h or --help to see available options.\n");
      exit(EXIT_FAILURE);
    }
  }
  return args;
}

// Function to get the current time as a formatted string
std::string getCurrentTimeStr() {
  struct timeval tv;
  gettimeofday(&tv, nullptr);
  struct tm tmInfo;
  localtime_r(&tv.tv_sec, &tmInfo);
  char buffer[30];
  strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tmInfo);

  // Append microseconds
  std::ostringstream oss;
  oss << buffer << "." << std::setw(6) << std::setfill('0') << tv.tv_usec;
  return oss.str();
}

// Function to get the timestamp in microseconds since epoch
uint64_t getTimestampMicroseconds() {
  struct timeval tv;
  gettimeofday(&tv, nullptr);
  return static_cast<uint64_t>(tv.tv_sec) * 1000000 + tv.tv_usec;
}

// Function to rotate the log file, keeping only two log files (current and
// old), based on file size
void rotateLog(const std::string &filename, size_t maxSize, FILE **logFilePtr) {
  struct stat statBuf;

  // Check if the file exists and retrieve its size
  if (stat(filename.c_str(), &statBuf) == 0) {
    // If the file size exceeds the maximum allowed size
    if (static_cast<size_t>(statBuf.st_size) >= maxSize) {
      // Close the current log file
      fclose(*logFilePtr);

      // Rename the current log file to "filename.old"
      std::string oldFilename = filename + ".old";
      if (rename(filename.c_str(), oldFilename.c_str()) != 0) {
        fprintf(stderr, "Failed to rotate log file: %s\n", strerror(errno));
        // Attempt to reopen the original log file even if renaming failed
        *logFilePtr = fopen(filename.c_str(), "a");
        if (!*logFilePtr) {
          fprintf(stderr,
                  "Failed to reopen log file after failed rotation: %s\n",
                  strerror(errno));
          exit(EXIT_FAILURE);
        }
        return;
      }

      // Open a new log file for writing
      *logFilePtr = fopen(filename.c_str(), "a");
      if (!*logFilePtr) {
        fprintf(stderr, "Failed to open new log file after rotation: %s\n",
                strerror(errno));
        exit(EXIT_FAILURE);
      }

      fprintf(stdout, "Rotated log file: %s -> %s\n", filename.c_str(),
              oldFilename.c_str());
    }
  }
}

int main(int argc, char *argv[]) {
  Arguments args = parseArguments(argc, argv);
  double intervalSec = args.interval / 1000.0;
  std::string filename = args.file;
  int runTime = args.time;
  bool selectiveLogging = !args.disableSelectiveLogging;
  const size_t maxFileSize = 50 * 1024 * 1024; // 50MB

  fprintf(stdout, "Time Interval: %d milliseconds\n", args.interval);
  fprintf(stdout, "Output File: %s\n", filename.c_str());
  fprintf(stdout, "Run Time: %d seconds\n", runTime);
  fprintf(stdout, "Selective Logging: %s\n",
          (selectiveLogging ? "Enabled" : "Disabled"));

  // Initialize variables
  uint64_t lastTimestampUs = 0;
  uint64_t secondLastTimestampUs = 0;
  uint64_t totalTimestamps = 0;
  bool inJumpMode = false;
  int jumpRemaining = 0;
  std::deque<std::string> preJumpTimestamps;
  const size_t preJumpBufferSize = 10;

  // Open log file using fprintf
  FILE *logFile = fopen(filename.c_str(), "w");
  if (!logFile) {
    fprintf(stderr, "Failed to initialize log file.\n");
    return EXIT_FAILURE;
  }

  // Get start time
  struct timeval startTimeVal;
  gettimeofday(&startTimeVal, nullptr);
  time_t startTimeSec = startTimeVal.tv_sec;
  uint32_t timeJumpCount = 0;

  while (true) {
    // Check elapsed time
    struct timeval currentTimeVal;
    gettimeofday(&currentTimeVal, nullptr);
    double elapsedTime =
        currentTimeVal.tv_sec - startTimeSec + currentTimeVal.tv_usec / 1e6;
    if ((elapsedTime >= runTime) && (timeJumpCount > runTime)) {
      // if not jump too much, timeJumpCount should large than runTime
      break;
    } else {
      timeJumpCount = timeJumpCount + 1;
    }

    // Get current time string and timestamp
    std::string currentTimeStr = getCurrentTimeStr();
    uint64_t currentTimestampUs = getTimestampMicroseconds();

    bool timeJump = false;
    if (lastTimestampUs != 0 && secondLastTimestampUs != 0 &&
        selectiveLogging) {
      if (currentTimestampUs < lastTimestampUs) {
        // Time regression detected
        timeJump = true;
      } else {
        // Check if the interval is too large
        uint64_t expectedIntervalUs = args.interval * 1000;
        uint64_t actualIntervalUs = currentTimestampUs - secondLastTimestampUs;
        uint64_t thresholdUs = static_cast<uint64_t>(expectedIntervalUs * 1.5);
        if (actualIntervalUs < thresholdUs) {
          timeJump = true;
        }
      }
    }

    // Update timestamps
    secondLastTimestampUs = lastTimestampUs;
    lastTimestampUs = currentTimestampUs;

    // Add to pre-jump buffer
    if (preJumpTimestamps.size() >= preJumpBufferSize) {
      preJumpTimestamps.pop_front();
    }
    preJumpTimestamps.push_back(currentTimeStr);

    if (selectiveLogging && timeJump && !inJumpMode) {
      // Time jump detected, enter jump mode
      inJumpMode = true;
      jumpRemaining = 10; // Record the next 10 timestamps

      // Log pre-jump timestamps
      fprintf(logFile, "\n--- TIME JUMP DETECTED ---\n");
      for (const auto &ts : preJumpTimestamps) {
        fprintf(logFile, "%s\n", ts.c_str());
      }

      // Log current (jump) timestamp with [TIME_JUMP]
      fprintf(logFile, "%s [TIME_JUMP]\n", currentTimeStr.c_str());
      fflush(logFile);
    } else if (inJumpMode) {
      // In jump mode, record post-jump timestamps
      fprintf(logFile, "%s\n", currentTimeStr.c_str());
      fflush(logFile);
      jumpRemaining--;
      if (jumpRemaining <= 0) {
        inJumpMode = false;
      }
    } else {
      // Normal logging: every 1000 timestamps
      totalTimestamps++;
      if (totalTimestamps % 1000 == 0) {
        fprintf(logFile, "%s\n", currentTimeStr.c_str());
        fflush(logFile);
      }
    }

    // Rotate log file if needed
    rotateLog(filename, maxFileSize, &logFile);

    // Sleep for the specified interval
    usleep(static_cast<useconds_t>(intervalSec * 1e6));
  }

  // Close the log file
  fclose(logFile);
  fprintf(stdout, "Program has ended.\n");
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值