1//
2// Copyright(c) 2015 Gabi Melman.
3// Distributed under the MIT License (http://opensource.org/licenses/MIT)
4//
5
6#pragma once
7#include "spdlog/details/file_helper.h"
8#include "spdlog/details/null_mutex.h"
9#include "spdlog/fmt/fmt.h"
10#include "spdlog/sinks/base_sink.h"
11#include "spdlog/spdlog.h"
12
13#include <chrono>
14#include <cstdio>
15#include <ctime>
16#include <mutex>
17#include <string>
18
19namespace spdlog {
20namespace sinks {
21
22/*
23 * Generator of daily log file names in format basename.YYYY-MM-DD.ext
24 */
25struct daily_filename_calculator
26{
27    // Create filename for the form basename.YYYY-MM-DD
28    static filename_t calc_filename(const filename_t &filename, const tm &now_tm)
29    {
30        filename_t basename, ext;
31        std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename);
32        std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w;
33        fmt::format_to(
34            w, SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext);
35        return fmt::to_string(w);
36    }
37};
38
39/*
40 * Rotating file sink based on date. rotates at midnight
41 */
42template<typename Mutex, typename FileNameCalc = daily_filename_calculator>
43class daily_file_sink SPDLOG_FINAL : public base_sink<Mutex>
44{
45public:
46    // create daily file sink which rotates on given time
47    daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false)
48        : base_filename_(std::move(base_filename))
49        , rotation_h_(rotation_hour)
50        , rotation_m_(rotation_minute)
51        , truncate_(truncate)
52    {
53        if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59)
54        {
55            throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor");
56        }
57        auto now = log_clock::now();
58        file_helper_.open(FileNameCalc::calc_filename(base_filename_, now_tm(now)), truncate_);
59        rotation_tp_ = next_rotation_tp_();
60    }
61
62protected:
63    void sink_it_(const details::log_msg &msg) override
64    {
65
66        if (msg.time >= rotation_tp_)
67        {
68            file_helper_.open(FileNameCalc::calc_filename(base_filename_, now_tm(msg.time)), truncate_);
69            rotation_tp_ = next_rotation_tp_();
70        }
71        fmt::memory_buffer formatted;
72        sink::formatter_->format(msg, formatted);
73        file_helper_.write(formatted);
74    }
75
76    void flush_() override
77    {
78        file_helper_.flush();
79    }
80
81private:
82    tm now_tm(log_clock::time_point tp)
83    {
84        time_t tnow = log_clock::to_time_t(tp);
85        return spdlog::details::os::localtime(tnow);
86    }
87
88    log_clock::time_point next_rotation_tp_()
89    {
90        auto now = log_clock::now();
91        tm date = now_tm(now);
92        date.tm_hour = rotation_h_;
93        date.tm_min = rotation_m_;
94        date.tm_sec = 0;
95        auto rotation_time = log_clock::from_time_t(std::mktime(&date));
96        if (rotation_time > now)
97        {
98            return rotation_time;
99        }
100        return {rotation_time + std::chrono::hours(24)};
101    }
102
103    filename_t base_filename_;
104    int rotation_h_;
105    int rotation_m_;
106    log_clock::time_point rotation_tp_;
107    details::file_helper file_helper_;
108    bool truncate_;
109};
110
111using daily_file_sink_mt = daily_file_sink<std::mutex>;
112using daily_file_sink_st = daily_file_sink<details::null_mutex>;
113
114} // namespace sinks
115
116//
117// factory functions
118//
119template<typename Factory = default_factory>
120inline std::shared_ptr<logger> daily_logger_mt(
121    const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false)
122{
123    return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate);
124}
125
126template<typename Factory = default_factory>
127inline std::shared_ptr<logger> daily_logger_st(
128    const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false)
129{
130    return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate);
131}
132} // namespace spdlog
133