324 lines
8.1 KiB
C++
324 lines
8.1 KiB
C++
/*
|
|
* Copyright (c) 2023, Alibaba Group Holding Limited;
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#pragma once
|
|
#include <charconv>
|
|
#include <condition_variable>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <shared_mutex>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include "record.hpp"
|
|
#include "ylt/util/concurrentqueue.h"
|
|
|
|
namespace easylog {
|
|
constexpr inline std::string_view BOM_STR = "\xEF\xBB\xBF";
|
|
struct QueueTraits : public moodycamel::ConcurrentQueueDefaultTraits {
|
|
static const size_t BLOCK_SIZE = 256;
|
|
};
|
|
|
|
constexpr char digits[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
|
|
|
|
template <size_t N, char c>
|
|
inline void to_int(int num, char *p, int &size) {
|
|
for (int i = 0; i < N; i++) {
|
|
p[--size] = digits[num % 10];
|
|
num = num / 10;
|
|
}
|
|
|
|
if constexpr (N != 4)
|
|
p[--size] = c;
|
|
}
|
|
|
|
inline char *get_time_str(const auto &now) {
|
|
static thread_local char buf[33];
|
|
static thread_local std::chrono::seconds last_sec_{};
|
|
|
|
std::chrono::system_clock::duration d = now.time_since_epoch();
|
|
std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(d);
|
|
auto mill_sec =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(d - s).count();
|
|
int size = 23;
|
|
if (last_sec_ == s) {
|
|
to_int<3, '.'>(mill_sec, buf, size);
|
|
return buf;
|
|
}
|
|
|
|
last_sec_ = s;
|
|
auto tm = std::chrono::system_clock::to_time_t(now);
|
|
auto gmt = localtime(&tm);
|
|
|
|
to_int<3, '.'>(mill_sec, buf, size);
|
|
to_int<2, ':'>(gmt->tm_sec, buf, size);
|
|
to_int<2, ':'>(gmt->tm_min, buf, size);
|
|
to_int<2, ' '>(gmt->tm_hour, buf, size);
|
|
|
|
to_int<2, '-'>(gmt->tm_mday, buf, size);
|
|
to_int<2, '-'>(gmt->tm_mon + 1, buf, size);
|
|
to_int<4, ' '>(gmt->tm_year + 1900, buf, size);
|
|
return buf;
|
|
}
|
|
|
|
class appender {
|
|
public:
|
|
appender() = default;
|
|
appender(const std::string &filename, bool async, size_t max_file_size,
|
|
size_t max_files, bool flush_every_time)
|
|
: has_init_(true),
|
|
flush_every_time_(flush_every_time),
|
|
max_file_size_(max_file_size) {
|
|
filename_ = filename;
|
|
max_files_ = (std::min)(max_files, static_cast<size_t>(1000));
|
|
open_log_file();
|
|
if (async) {
|
|
write_thd_ = std::thread([this] {
|
|
while (!stop_) {
|
|
if (max_files_ > 0 && file_size_ > max_file_size_ &&
|
|
static_cast<size_t>(-1) != file_size_) {
|
|
roll_log_files();
|
|
}
|
|
|
|
record_t record;
|
|
if (queue_.try_dequeue(record)) {
|
|
write_record(record);
|
|
}
|
|
|
|
if (queue_.size_approx() == 0) {
|
|
std::unique_lock lock(que_mtx_);
|
|
cnd_.wait(lock, [&]() {
|
|
return queue_.size_approx() > 0 || stop_;
|
|
});
|
|
}
|
|
|
|
if (stop_) {
|
|
if (queue_.size_approx() > 0) {
|
|
while (queue_.try_dequeue(record)) {
|
|
write_record(record);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
std::string_view get_tid_buf(unsigned int tid) {
|
|
static thread_local char buf[24];
|
|
static thread_local unsigned int last_tid;
|
|
static thread_local size_t last_len;
|
|
if (tid == last_tid) {
|
|
return {buf, last_len};
|
|
}
|
|
|
|
buf[0] = '[';
|
|
auto [ptr, ec] = std::to_chars(buf + 1, buf + 21, tid);
|
|
buf[22] = ']';
|
|
buf[23] = ' ';
|
|
last_len = ptr - buf;
|
|
buf[last_len++] = ']';
|
|
buf[last_len++] = ' ';
|
|
return {buf, last_len};
|
|
}
|
|
|
|
template <bool sync = false, bool enable_console = false>
|
|
void write_record(record_t &record) {
|
|
if constexpr (sync == true) {
|
|
std::shared_lock guard(mtx_);
|
|
if (max_files_ > 0 && file_size_ > max_file_size_ &&
|
|
static_cast<size_t>(-1) != file_size_) {
|
|
guard.unlock();
|
|
std::lock_guard lock{mtx_};
|
|
roll_log_files();
|
|
}
|
|
}
|
|
|
|
auto buf = get_time_str(record.get_time_point());
|
|
|
|
buf[23] = ' ';
|
|
memcpy(buf + 24, severity_str(record.get_severity()).data(), 8);
|
|
buf[32] = ' ';
|
|
|
|
if constexpr (enable_console) {
|
|
add_color(record.get_severity());
|
|
}
|
|
|
|
write_str<enable_console>(std::string_view(buf, 33));
|
|
|
|
if constexpr (enable_console) {
|
|
clean_color(record.get_severity());
|
|
}
|
|
|
|
write_str<enable_console>(get_tid_buf(record.get_tid()));
|
|
write_str<enable_console>(record.get_file_str());
|
|
write_str<enable_console>(record.get_message());
|
|
|
|
if constexpr (enable_console) {
|
|
std::cout << std::flush;
|
|
}
|
|
}
|
|
|
|
void add_color(Severity severity) {
|
|
#if defined(_WIN32)
|
|
#else
|
|
if (severity == Severity::WARN)
|
|
std::cout << "\x1B[93m";
|
|
if (severity == Severity::ERROR)
|
|
std::cout << "\x1B[91m";
|
|
if (severity == Severity::CRITICAL)
|
|
std::cout << "\x1B[97m\x1B[41m";
|
|
#endif
|
|
}
|
|
|
|
void clean_color(Severity severity) {
|
|
#if defined(_WIN32)
|
|
#else
|
|
if (severity >= Severity::WARN)
|
|
std::cout << "\x1B[0m\x1B[0K";
|
|
#endif
|
|
}
|
|
|
|
void write(record_t &&r) {
|
|
queue_.enqueue(std::move(r));
|
|
cnd_.notify_all();
|
|
}
|
|
|
|
void flush() {
|
|
std::lock_guard guard(mtx_);
|
|
if (file_.is_open()) {
|
|
file_.flush();
|
|
file_.sync_with_stdio();
|
|
}
|
|
}
|
|
|
|
void stop() {
|
|
std::lock_guard guard(mtx_);
|
|
if (!write_thd_.joinable()) {
|
|
return;
|
|
}
|
|
|
|
if (stop_) {
|
|
return;
|
|
}
|
|
stop_ = true;
|
|
cnd_.notify_one();
|
|
}
|
|
|
|
~appender() {
|
|
stop();
|
|
if (write_thd_.joinable())
|
|
write_thd_.join();
|
|
}
|
|
|
|
private:
|
|
void open_log_file() {
|
|
std::string filename = build_filename();
|
|
file_.open(filename, std::ios::binary | std::ios::out | std::ios::app);
|
|
if (file_) {
|
|
std::error_code ec;
|
|
size_t file_size = std::filesystem::file_size(filename, ec);
|
|
if (ec) {
|
|
std::cout << "get file size error" << std::flush;
|
|
abort();
|
|
}
|
|
|
|
if (file_size == 0) {
|
|
if (file_.write(BOM_STR.data(), BOM_STR.size())) {
|
|
file_size_ += BOM_STR.size();
|
|
is_first_write_ = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string build_filename(int file_number = 0) {
|
|
if (file_number == 0) {
|
|
return filename_;
|
|
}
|
|
|
|
auto file_path = std::filesystem::path(filename_);
|
|
std::string filename = file_path.stem().string();
|
|
|
|
if (file_number > 0) {
|
|
char buf[32];
|
|
auto [ptr, ec] = std::to_chars(buf, buf + 32, file_number);
|
|
filename.append(".").append(std::string_view(buf, ptr - buf));
|
|
}
|
|
|
|
if (file_path.has_extension()) {
|
|
filename.append(file_path.extension().string());
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
void roll_log_files() {
|
|
file_.close();
|
|
std::string last_filename = build_filename(max_files_ - 1);
|
|
|
|
std::error_code ec;
|
|
std::filesystem::remove(last_filename, ec);
|
|
|
|
for (int file_number = max_files_ - 2; file_number >= 0; --file_number) {
|
|
std::string current_fileName = build_filename(file_number);
|
|
std::string next_fileName = build_filename(file_number + 1);
|
|
|
|
std::filesystem::rename(current_fileName, next_fileName, ec);
|
|
}
|
|
|
|
open_log_file();
|
|
is_first_write_ = false;
|
|
}
|
|
|
|
template <bool enable_console>
|
|
void write_str(std::string_view str) {
|
|
if (has_init_) {
|
|
if (file_.write(str.data(), str.size())) {
|
|
if (flush_every_time_) {
|
|
file_.flush();
|
|
}
|
|
|
|
file_size_ += str.size();
|
|
}
|
|
}
|
|
|
|
if constexpr (enable_console) {
|
|
std::cout << str;
|
|
}
|
|
}
|
|
|
|
bool has_init_ = false;
|
|
std::string filename_;
|
|
|
|
bool flush_every_time_;
|
|
size_t file_size_ = 0;
|
|
size_t max_file_size_ = 0;
|
|
size_t max_files_ = 0;
|
|
bool is_first_write_ = true;
|
|
|
|
std::shared_mutex mtx_;
|
|
std::ofstream file_;
|
|
|
|
std::mutex que_mtx_;
|
|
|
|
moodycamel::ConcurrentQueue<record_t, QueueTraits> queue_;
|
|
std::thread write_thd_;
|
|
std::condition_variable cnd_;
|
|
std::atomic<bool> stop_ = false;
|
|
};
|
|
} // namespace easylog
|