logger.h
#pragma once

#include <atomic>
#include <chrono>
#include <fstream>
#include <mutex>
#include <sstream>
#include <string>
#include <string_view>

#include "fmt/chrono.h"
#include "fmt/format.h"
#include "singleton.h"

#define LOG_DEBUG(_fmt, ...)                                                                \
    flower::logger::instance().write(flower::logger::llt_debug,                             \
                                     fmt::format("{:%y-%m-%d %H:%M:%S} [DEBUG] " _fmt "\n", \
                                                 std::chrono::high_resolution_clock::now(), ##__VA_ARGS__))

#define LOG_INFO(_fmt, ...)                                                                \
    flower::logger::instance().write(flower::logger::llt_info,                             \
                                     fmt::format("{:%y-%m-%d %H:%M:%S} [INFO] " _fmt "\n", \
                                                 std::chrono::high_resolution_clock::now(), ##__VA_ARGS__))

#define LOG_WARN(_fmt, ...)                                                                \
    flower::logger::instance().write(flower::logger::llt_warn,                             \
                                     fmt::format("{:%y-%m-%d %H:%M:%S} [WARN] " _fmt "\n", \
                                                 std::chrono::high_resolution_clock::now(), ##__VA_ARGS__))

#define LOG_ERROR(_fmt, ...)                                                                \
    flower::logger::instance().write(flower::logger::llt_error,                             \
                                     fmt::format("{:%y-%m-%d %H:%M:%S} [ERROR] " _fmt "\n", \
                                                 std::chrono::high_resolution_clock::now(), ##__VA_ARGS__))

namespace flower {
class logger final : public singleton<logger> {
    friend class singleton<logger>;

public:
    enum log_level_type {
        llt_debug = 0,
        llt_info,
        llt_warn,
        llt_error,
        llt_off,
    };

    enum log_type {
        lt_none,
        lt_file,
        lt_console,
    };

private:
    logger();

public:
    ~logger() override {
        close_file();
    }

    bool start(std::string_view log_dir, std::string_view file_prefix, uint16_t file_level, uint16_t console_level);

    void set_log_level(log_type ty, log_level_type level);

    void write(log_level_type level, std::string_view log);

    void flush() {
        std::lock_guard lock(mutex_);
        file_.flush();
    }

private:
    void close_file();

    bool create_log_file();

    void check_file_name();

private:
    std::string file_prefix_{};
    std::atomic_uint16_t day_{};
    std::ofstream file_{};
    std::string log_dir_{};
    std::atomic_uint16_t file_level_{};
    std::atomic_uint16_t console_level_{};
    std::mutex mutex_{};
};
}  // namespace flower
logger.cpp
#include "logger.h"

#include "comm_func.h"

namespace flower {
logger::logger()
    : file_level_(log_level_type::llt_off)
    , console_level_(log_level_type::llt_debug) {
}

bool logger::start(std::string_view log_dir, std::string_view file_prefix, uint16_t file_level,
                   uint16_t console_level) {
    {
        std::lock_guard lock(mutex_);
        log_dir_ = log_dir;
        file_level_ = file_level;
        console_level_ = console_level;
        file_prefix_ = file_prefix;

        std::string path = comm_func::get_current_exe_dir();
        path.append("/").append(log_dir_);
        log_dir_ = path;

        if (!comm_func::create_dir(log_dir_)) {
            LOG_ERROR("logger::start create dir error");
            return false;
        }
    }

    return true;
}

void logger::set_log_level(log_type ty, log_level_type level) {
    if (ty == log_type::lt_file) {
        file_level_ = level;
        if (file_level_ == log_level_type::llt_off) {
            day_ = 0;
            close_file();
        }
    } else if (ty == log_type::lt_console) {
        console_level_ = level;
    } else {
    }
}

void logger::write(log_level_type level, std::string_view log) {
    if (file_level_ <= level) {
        check_file_name();
        std::lock_guard lock(mutex_);
        file_ << log;
    }

    if (console_level_ <= level) {
        std::cout << log.data();
    }
}

void logger::close_file() {
    std::lock_guard lock(mutex_);
    if (file_.is_open()) {
        file_.flush();
        file_.close();
    }
}

bool logger::create_log_file() {
    {
        std::lock_guard lock(mutex_);
        std::string log_file_name =
            fmt::format("{}/{}_{:%Y_%m_%d}.log", log_dir_, file_prefix_, std::chrono::high_resolution_clock::now());
        file_.open(log_file_name, std::ios::app);
        if (file_.fail()) {
            std::cout << "logger::create_log_file file:" << log_file_name << " open failed." << std::endl;
            return false;
        }
    }

    day_ = comm_func::get_month_day();
    return true;
}

void logger::check_file_name() {
    if (day_ != comm_func::get_month_day()) {
        close_file();
        create_log_file();
    }
}
}  // namespace flower