与Log 文件操作接口类相关定义主要存在两个文件中,其中 env.h 定义了 Logger 抽象接口类,而 posix_logger.h 定义了在 POSIX 平台下 Logger 类的派生实现。
log 抽象类接口:
class Logger
{
public:
Logger() = default;
Logger(const Logger &) = delete;
Logger &operator=(const Logger &) = delete;
virtual ~Logger();
// Write an entry to the log file with the specified format.
virtual void Logv(const char *format, std::va_list ap) = 0;
};
class PosixLogger final : public Logger
{
private:
std::FILE *const fp_;
public:
explicit PosixLogger(std::FILE *fp) : fp_(fp) { assert(fp != nullptr); }
~PosixLogger() override { std::fclose(fp_); }
void Logv(const char *format, std::va_list arguments) override
{
// 时间
struct ::timeval now_timeval;
::gettimeofday(&now_timeval, nullptr);
const std::time_t now_seconds = now_timeval.tv_sec;
struct std::tm now_components;
::localtime_r(&now_seconds, &now_components);
// 线程ID
constexpr const int kMaxThreadIdSize = 32;
std::ostringstream thread_stream;
thread_stream << std::this_thread::get_id();
std::string thread_id = thread_stream.str();
if (thread_id.size() > kMaxThreadIdSize)
{
thread_id.resize(kMaxThreadIdSize);
}
constexpr const int kStackBufferSize = 512;
char stack_buffer[kStackBufferSize];
static_assert(sizeof(stack_buffer) == static_cast<size_t>(kStackBufferSize),
"sizeof(char) is expected to be 1 in C++");
int dynamic_buffer_size = 0; // Computed in the first iteration.
for (int iteration = 0; iteration < 2; ++iteration)
{
const int buffer_size = (iteration == 0) ? kStackBufferSize : dynamic_buffer_size;
char *const buffer = (iteration == 0) ? stack_buffer : new char[dynamic_buffer_size];
int buffer_offset = std::snprintf(
buffer, buffer_size, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %s ",
now_components.tm_year + 1900, now_components.tm_mon + 1,
now_components.tm_mday, now_components.tm_hour, now_components.tm_min,
now_components.tm_sec, static_cast<int>(now_timeval.tv_usec),
thread_id.c_str());
assert(buffer_offset <= 28 + kMaxThreadIdSize);
static_assert(28 + kMaxThreadIdSize < kStackBufferSize,
"stack-allocated buffer may not fit the message header");
assert(buffer_offset < buffer_size);
std::va_list arguments_copy;
va_copy(arguments_copy, arguments);
buffer_offset += std::vsnprintf(buffer + buffer_offset, buffer_size - buffer_offset, format, arguments_copy);
va_end(arguments_copy);
if (buffer_offset >= buffer_size - 1)
{
if (iteration == 0)
{
dynamic_buffer_size = buffer_offset + 2;
continue;
}
assert(false);
buffer_offset = buffer_size - 1;
}
if (buffer[buffer_offset - 1] != '\n')
{
buffer[buffer_offset] = '\n';
++buffer_offset;
}
assert(buffer_offset <= buffer_size);
std::fwrite(buffer, 1, buffer_offset, fp_);
std::fflush(fp_);
if (iteration != 0)
{
delete[] buffer;
}
break;
}
}
};
Logv 方法每写一条Log 信息,需要保存以下信息:
- 线程id
- 当前时间
- Log 文本信息
Logv 方法中定义了一个循环,该循环最多执行2次,区别在于:第一次缓冲是在栈中,大小为500字节,第二次缓冲区在堆中,大小为30000字节。如果500字节能保存所有信息,满足Log信息的写入要求,则循环体直接执行break,否则进入第二次循环,并动态申请一个30000字节的缓冲,以实现相应的功能。
使用这种方式的好处是:对不同的长度的Log信息可采取两种不同的操作模式,从而实现空间与时间资源消耗的平衡。
网友评论