1// 2// Copyright(c) 2015 Gabi Melman. 3// Distributed under the MIT License (http://opensource.org/licenses/MIT) 4// 5 6/* -*- MODE: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 7/* 8 * Copyright 2017 Couchbase, Inc 9 * 10 * Licensed under the Apache License, Version 2.0 (the "License"); 11 * you may not use this file except in compliance with the License. 12 * You may obtain a copy of the License at 13 * 14 * http://www.apache.org/licenses/LICENSE-2.0 15 * 16 * Unless required by applicable law or agreed to in writing, software 17 * distributed under the License is distributed on an "AS IS" BASIS, 18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 * See the License for the specific language governing permissions and 20 * limitations under the License. 21 */ 22 23#include "custom_rotating_file_sink.h" 24 25#include <platform/dirutils.h> 26#include <spdlog/details/file_helper.h> 27#include <spdlog/details/fmt_helper.h> 28 29#include <memory> 30 31static unsigned long find_first_logfile_id(const std::string& basename) { 32 unsigned long id = 0; 33 34 auto files = cb::io::findFilesWithPrefix(basename); 35 for (auto& file : files) { 36 // the format of the name should be: 37 // fnm.number.txt 38 auto index = file.rfind(".txt"); 39 if (index == std::string::npos) { 40 continue; 41 } 42 43 file.resize(index); 44 index = file.rfind('.'); 45 if (index != std::string::npos) { 46 try { 47 unsigned long value = std::stoul(file.substr(index + 1)); 48 if (value > id) { 49 id = value + 1; 50 } 51 } catch (...) { 52 // Ignore 53 } 54 } 55 } 56 57 return id; 58} 59 60template <class Mutex> 61custom_rotating_file_sink<Mutex>::custom_rotating_file_sink( 62 const spdlog::filename_t& base_filename, 63 std::size_t max_size, 64 const std::string& log_pattern) 65 : _base_filename(base_filename), 66 _max_size(max_size), 67 _current_size(0), 68 _file_helper(std::make_unique<spdlog::details::file_helper>()), 69 _next_file_id(find_first_logfile_id(base_filename)) { 70 formatter = std::make_unique<spdlog::pattern_formatter>( 71 log_pattern, spdlog::pattern_time_type::local); 72 _file_helper->open(calc_filename()); 73 _current_size = _file_helper->size(); // expensive. called only once 74 addHook(openingLogfile); 75} 76 77/* In addition to the functionality of spdlog's rotating_file_sink, 78 * this class adds hooks marking the start and end of a logfile. 79 */ 80template <class Mutex> 81void custom_rotating_file_sink<Mutex>::sink_it_( 82 const spdlog::details::log_msg& msg) { 83 _current_size += msg.raw.size(); 84 if (_current_size > _max_size) { 85 std::unique_ptr<spdlog::details::file_helper> next = 86 std::make_unique<spdlog::details::file_helper>(); 87 try { 88 next->open(calc_filename(), true); 89 addHook(closingLogfile); 90 std::swap(_file_helper, next); 91 _current_size = msg.raw.size(); 92 addHook(openingLogfile); 93 } catch (...) { 94 // Keep on logging to the this file, but try swap at the next 95 // insert of data (didn't use the next file we need to 96 // roll back the next_file_id to avoid getting a hole ;-) 97 _next_file_id--; 98 } 99 } 100 fmt::memory_buffer formatted; 101 formatter->format(msg, formatted); 102 103 _file_helper->write(formatted); 104} 105 106template <class Mutex> 107void custom_rotating_file_sink<Mutex>::flush_() { 108 _file_helper->flush(); 109} 110 111/* Takes a message, formats it and writes it to file */ 112template <class Mutex> 113void custom_rotating_file_sink<Mutex>::addHook(const std::string& hook) { 114 spdlog::details::log_msg msg; 115 msg.time = spdlog::details::os::now(); 116 msg.level = spdlog::level::info; 117 118 // Append the hook to the msg 119 spdlog::details::fmt_helper::append_str(hook, msg.raw); 120 121 if (hook == openingLogfile) { 122 spdlog::details::fmt_helper::append_str( 123 std::string(_file_helper->filename().data(), 124 _file_helper->filename().size()), 125 msg.raw); 126 } 127 fmt::memory_buffer formatted; 128 formatter->format(msg, formatted); 129 _current_size += formatted.size(); 130 131 _file_helper->write(formatted); 132} 133 134/** 135 * Create a new filename. If this breaks when updating spdlog then compare 136 * functionality to the calc_filename() method in the spdlog rotating_file_sink 137 * @tparam Mutex 138 * @return An spdlog filename 139 */ 140template <class Mutex> 141spdlog::filename_t custom_rotating_file_sink<Mutex>::calc_filename() { 142 char fname[1024]; 143 unsigned long try_id = _next_file_id; 144 do { 145 sprintf(fname, "%s.%06lu.txt", _base_filename.c_str(), try_id++); 146 } while (cb::io::isFile(fname)); 147 148 _next_file_id = try_id; 149#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) 150 return fmt::to_wstring(fname); 151#else 152 return fmt::to_string(fname); 153#endif 154} 155 156template <class Mutex> 157custom_rotating_file_sink<Mutex>::~custom_rotating_file_sink() { 158 addHook(closingLogfile); 159} 160 161template class custom_rotating_file_sink<std::mutex>; 162template class custom_rotating_file_sink<spdlog::details::null_mutex>; 163