Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support async easylog #333

Merged
merged 15 commits into from
Jun 27, 2023
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
package(default_visibility = ["//visibility:public"])

cc_library(
name = "struct_pack",
Expand Down
149 changes: 130 additions & 19 deletions include/easylog/appender.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
#pragma once
#include <charconv>
#include <condition_variable>
#include <filesystem>
#include <fstream>
#include <iostream>
Expand All @@ -23,51 +24,140 @@
#include <string_view>

#include "record.hpp"
#include "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;
};

class appender {
public:
appender(const std::string &filename, size_t max_file_size, size_t max_files,
bool flush_every_time)
: flush_every_time_(flush_every_time), max_file_size_(max_file_size) {
appender(const std::string &filename, bool async, bool enable_console,
size_t max_file_size, size_t max_files, bool flush_every_time)
: flush_every_time_(flush_every_time),
enable_console_(enable_console),
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<std::mutex> lock(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);
}
}
}
}
});
}
}

void write_record(record_t &record) {
char buf[32];
size_t len = get_time_str(buf, record.get_time_point());

write_str({buf, len});
write_str({" ", 1});
write_str(severity_str(record.get_severity()));
write_str({" ", 1});

auto [ptr, ec] = std::to_chars(buf, buf + 32, record.get_tid());

write_str({"[", 1});
write_str(std::string_view(buf, ptr - buf));
write_str({"] ", 2});
write_str(record.get_file_str());
write_str(record.get_message());
write_str({"\n", 1});

if (enable_console_) {
std::cout << std::flush;
}
}

void write(record_t &&r) {
queue_.enqueue(std::move(r));
cnd_.notify_all();
}

template <typename String>
void write(const String &str) {
std::lock_guard guard(mtx_);
if (is_first_write_) {
open_log_file();
is_first_write_ = false;
}
else if (max_files_ > 0 && file_size_ > max_file_size_ &&
static_cast<size_t>(-1) != file_size_) {
if (max_files_ > 0 && file_size_ > max_file_size_ &&
static_cast<size_t>(-1) != file_size_) {
roll_log_files();
}

// It can be improved witch cache.
if (file_.write(str.data(), str.size())) {
// It can be improved: flush with some interval .
if (flush_every_time_) {
file_.flush();
}
write_str(str);
}

file_size_ += str.size();
void flush() {
std::lock_guard guard(mtx_);
if (file_.is_open()) {
file_.flush();
file_.sync_with_stdio();
}
}

void flush() {
void stop() {
std::lock_guard guard(mtx_);
file_.flush();
if (!write_thd_.joinable()) {
return;
}

if (stop_) {
return;
}
stop_ = true;
cnd_.notify_one();
}

~appender() {
stop();
if (write_thd_.joinable())
write_thd_.join();
}

template <size_t N>
size_t get_time_str(char (&buf)[N], const auto &now) {
const auto nowAsTimeT = std::chrono::system_clock::to_time_t(now);
const auto nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch())
.count() %
1000;

size_t endpos =
std::strftime(buf, N, "%Y-%m-%d %H:%M:%S", std::localtime(&nowAsTimeT));
snprintf(buf + endpos, N - endpos, ".%03d", (int)nowMs);
return endpos + 4;
}

private:
void open_log_file() {
std::string filename = build_filename();
file_.open(filename, std::ios::binary | std::ios::app);
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);
Expand All @@ -79,6 +169,7 @@ class appender {
if (file_size == 0) {
if (file_.write(BOM_STR.data(), BOM_STR.size())) {
file_size_ += BOM_STR.size();
is_first_write_ = false;
}
}
}
Expand Down Expand Up @@ -123,7 +214,22 @@ class appender {
is_first_write_ = false;
}

void write_str(std::string_view str) {
if (file_.write(str.data(), str.size())) {
if (flush_every_time_) {
file_.flush();
}

file_size_ += str.size();
}

if (enable_console_) {
std::cout << str;
}
}

std::string filename_;
bool enable_console_ = false;

bool flush_every_time_;
size_t file_size_ = 0;
Expand All @@ -133,5 +239,10 @@ class appender {

std::mutex mtx_;
std::ofstream file_;

moodycamel::ConcurrentQueue<record_t, QueueTraits> queue_;
std::thread write_thd_;
std::condition_variable cnd_;
std::atomic<bool> stop_ = false;
};
} // namespace easylog
49 changes: 26 additions & 23 deletions include/easylog/easylog.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@ class logger {
return instance;
}

void operator+=(const record_t &record) { write(record); }
void operator+=(record_t &record) { write(record); }

void write(const record_t &record) {
append_format(record);
void write(record_t &record) {
if (async_ && appender_) {
append_record(std::move(record));
}
else {
append_format(record);
}

if (record.get_severity() == Severity::CRITICAL) {
flush();
Expand All @@ -48,11 +53,12 @@ class logger {
}
}

void init(Severity min_severity, bool enable_console,
void init(Severity min_severity, bool async, bool enable_console,
const std::string &filename, size_t max_file_size, size_t max_files,
bool flush_every_time) {
static appender appender(filename, max_file_size, max_files,
flush_every_time);
static appender appender(filename, async, enable_console, max_file_size,
max_files, flush_every_time);
async_ = async;
appender_ = &appender;
min_severity_ = min_severity;
enable_console_ = enable_console;
Expand All @@ -64,27 +70,17 @@ class logger {
appenders_.emplace_back(std::move(fn));
}

void stop_async_log() { appender_->stop(); }

private:
logger() = default;
logger(const logger &) = default;

template <size_t N>
size_t get_time_str(char (&buf)[N], const auto &now) {
const auto nowAsTimeT = std::chrono::system_clock::to_time_t(now);
const auto nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch())
.count() %
1000;

size_t endpos =
std::strftime(buf, N, "%Y-%m-%d %H:%M:%S", std::localtime(&nowAsTimeT));
snprintf(buf + endpos, N - endpos, ".%03d", (int)nowMs);
return endpos + 4;
}
void append_record(record_t record) { appender_->write(std::move(record)); }

void append_format(const record_t &record) {
char buf[32];
size_t len = get_time_str(buf, record.get_time_point());
size_t len = appender_->get_time_str(buf, record.get_time_point());

#ifdef YLT_ENABLE_PMR
#if __has_include(<memory_resource>)
Expand Down Expand Up @@ -119,16 +115,18 @@ class logger {
}

Severity min_severity_;
bool async_ = true;
bool enable_console_ = true;
appender *appender_ = nullptr;
std::vector<std::function<void(std::string_view)>> appenders_;
};

template <size_t Id = 0>
inline void init_log(Severity min_severity, const std::string &filename = "",
bool enable_console = true, size_t max_file_size = 0,
size_t max_files = 0, bool flush_every_time = false) {
logger<Id>::instance().init(min_severity, enable_console, filename,
bool async = true, bool enable_console = true,
size_t max_file_size = 0, size_t max_files = 0,
bool flush_every_time = false) {
logger<Id>::instance().init(min_severity, async, enable_console, filename,
max_file_size, max_files, flush_every_time);
}

Expand All @@ -137,6 +135,11 @@ inline void flush() {
logger<Id>::instance().flush();
}

template <size_t Id = 0>
inline void stop_async_log() {
logger<Id>::instance().stop_async_log();
}

template <size_t Id = 0>
inline void add_appender(std::function<void(std::string_view)> fn) {
logger<Id>::instance().add_appender(std::move(fn));
Expand Down
18 changes: 10 additions & 8 deletions include/easylog/record.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,20 @@ inline std::string_view severity_str(Severity severity) {

class record_t {
public:
record_t(auto tm_point, Severity severity, auto str)
: tm_point_(tm_point), severity_(severity), tid_(get_tid_impl()) {
std::memcpy(buf_, str.data(), str.size());
buf_len_ = str.size();
}
record_t() = default;
record_t(auto tm_point, Severity severity, std::string_view str)
: tm_point_(tm_point),
severity_(severity),
tid_(get_tid_impl()),
file_str_(str) {}
record_t(record_t &&) = default;
record_t &operator=(record_t &&) = default;

Severity get_severity() const { return severity_; }

const char *get_message() const { return ss_.data(); }

std::string_view get_file_str() const { return {buf_, buf_len_}; }
std::string_view get_file_str() const { return file_str_; }

unsigned int get_tid() const { return tid_; }

Expand Down Expand Up @@ -195,8 +198,7 @@ class record_t {
std::chrono::system_clock::time_point tm_point_;
Severity severity_;
unsigned int tid_;
char buf_[64] = {};
size_t buf_len_ = 0;
std::string file_str_;

#ifdef YLT_ENABLE_PMR
#if __has_include(<memory_resource>)
Expand Down
4 changes: 4 additions & 0 deletions src/easylog/benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
find_package(glog QUIET)

if (UNIX)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()
#include_directories(spdlog/include)
add_executable(easylog_benchmark
main.cpp
)
Expand Down
Loading