file_sinks.h 5.2 KB
Newer Older
gabime's avatar
gabime committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
#pragma once

#include <fstream>
#include  <iomanip>
#include <mutex>
#include "base_sink.h"
#include "../details/flush_helper.h"

namespace c11log
{
namespace sinks
{

/*
* Thread safe, trivial file sink with single file as target
*/
class simple_file_sink : public base_sink
{
public:
    explicit simple_file_sink(const std::string &filename,
                              const std::string& extension,
                              const std::size_t flush_every=0)
        : _mutex(),
          _ofstream(filename + "." + extension, std::ofstream::binary|std::ofstream::app),
          _flush_helper(flush_every)
    {
    }
protected:
    void _sink_it(const bufpair_t& msg) override
    {
        std::lock_guard<std::mutex> lock(_mutex);
        _flush_helper.write(_ofstream, msg);
    }
private:
    std::mutex _mutex;
    std::ofstream _ofstream;
    details::file_flush_helper _flush_helper;
};


/*
 * Thread safe, rotating file sink based on size
*/
class rotating_file_sink : public base_sink
{
public:
    rotating_file_sink(const std::string &base_filename, const std::string &extension,
                       const std::size_t max_size, const std::size_t max_files,
                       const std::size_t flush_every=0):
        _base_filename(base_filename),
        _extension(extension),
        _max_size(max_size),
        _max_files(max_files),
        _current_size(0),
        _mutex(),
        _ofstream(_calc_filename(_base_filename, 0, _extension), std::ofstream::binary),
        _flush_helper(flush_every)
    {
    }

protected:
    void _sink_it(const bufpair_t& msg) override
    {
        std::lock_guard<std::mutex> lock(_mutex);
        _current_size += msg.second;
        if (_current_size  > _max_size)
        {
            _rotate();
            _current_size = msg.second;
        }
        _flush_helper.write(_ofstream, msg);
    }


private:
    static std::string _calc_filename(const std::string& filename, std::size_t index, const std::string& extension)
    {
        std::ostringstream oss;
        if (index)
            oss << filename << "." << index << "." << extension;
        else
            oss << filename << "." << extension;
        return oss.str();
    }


    // Rotate old files:
    // log.n-1.txt -> log.n.txt
    // log n-2.txt -> log.n-1.txt
    // ...
    // log.txt -> log.1.txt
    void _rotate()
    {
        _ofstream.close();
        //Remove oldest file
        for (auto i = _max_files; i > 0; --i)
        {
            auto src = _calc_filename(_base_filename, i - 1, _extension);
            auto target = _calc_filename(_base_filename, i, _extension);
            if (i == _max_files)
                std::remove(target.c_str());
            std::rename(src.c_str(), target.c_str());
        }
        _ofstream.open(_calc_filename(_base_filename, 0, _extension));
    }
    std::string _base_filename;
    std::string _extension;
    std::size_t _max_size;
    std::size_t _max_files;
    std::size_t _current_size;
    std::mutex _mutex;
    std::ofstream _ofstream;
    details::file_flush_helper _flush_helper;
};

/*
 * Thread safe, rotating file sink based on date. rotates at midnight
 */
class daily_file_sink:public base_sink
{
public:
    explicit daily_file_sink(const std::string& base_filename,
                             const std::string& extension,
                             const std::size_t flush_every=0):
        _base_filename(base_filename),
        _extension(extension),
        _midnight_tp (_calc_midnight_tp() ),
        _mutex(),
        _ofstream(_calc_filename(_base_filename, _extension), std::ofstream::binary|std::ofstream::app),
        _flush_helper(flush_every)
    {
    }

protected:
    void _sink_it(const bufpair_t& msg) override
    {
        std::lock_guard<std::mutex> lock(_mutex);
        if (std::chrono::system_clock::now() >= _midnight_tp)
        {
            _ofstream.close();
            _ofstream.open(_calc_filename(_base_filename, _extension));
            _midnight_tp = _calc_midnight_tp();
        }
        _flush_helper.write(_ofstream, msg);
    }

private:
    // Return next midnight's time_point
    static std::chrono::system_clock::time_point _calc_midnight_tp()
    {
        using namespace std::chrono;
        auto now = system_clock::now();
        time_t tnow = std::chrono::system_clock::to_time_t(now);
        tm date = c11log::details::os::localtime(tnow);
        date.tm_hour = date.tm_min = date.tm_sec = 0;
        auto midnight = std::chrono::system_clock::from_time_t(std::mktime(&date));
        return system_clock::time_point(midnight + hours(24));
    }

    //Create filename for the form basename.YYYY-MM-DD.extension
    static std::string _calc_filename(const std::string& basename, const std::string& extension)
    {
        std::tm tm = c11log::details::os::localtime();
        std::ostringstream oss;
        oss << basename << '.';
        oss << tm.tm_year + 1900 << '-' << std::setw(2) << std::setfill('0') << tm.tm_mon + 1 << '-' << tm.tm_mday;
        oss << '.' << extension;
        return oss.str();
    }

    std::string _base_filename;
    std::string _extension;
    std::chrono::system_clock::time_point _midnight_tp;
    std::mutex _mutex;
    std::ofstream _ofstream;
    details::file_flush_helper _flush_helper;

};
}
}