// // Copyright(c) 2015 Gabi Melman. // Distributed under the MIT License (http://opensource.org/licenses/MIT) // /* -*- MODE: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * Copyright 2017 Couchbase, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "custom_rotating_file_sink.hxx" #include "core/platform/dirutils.h" #include #include #include static unsigned long find_first_logfile_id(const std::string& basename) { unsigned long id = 0; auto files = couchbase::core::platform::find_files_with_prefix(basename); for (auto& file : files) { // the format of the name should be: // fnm.number.txt auto index = file.rfind(".txt"); if (index == std::string::npos) { continue; } file.resize(index); index = file.rfind('.'); if (index != std::string::npos) { try { unsigned long value = std::stoul(file.substr(index + 1)); if (value > id) { id = value; } } catch (const std::invalid_argument&) { continue; /* ignore */ } catch (const std::out_of_range&) { continue; /* ignore */ } } } return id; } template custom_rotating_file_sink::custom_rotating_file_sink(const spdlog::filename_t& base_filename, std::size_t max_size, const std::string& log_pattern) : base_filename_(base_filename) , max_size_(max_size) , next_file_id_(find_first_logfile_id(base_filename)) { formatter = std::make_unique(log_pattern, spdlog::pattern_time_type::local); file_helper_ = open_file(); current_size_ = file_helper_->size(); // expensive. called only once add_hook(opening_log_file_); } /* In addition to the functionality of spdlog's rotating_file_sink, * this class adds hooks marking the start and end of a logfile. */ template void custom_rotating_file_sink::sink_it_(const spdlog::details::log_msg& msg) { current_size_ += msg.payload.size(); spdlog::memory_buf_t formatted; formatter->format(msg, formatted); file_helper_->write(formatted); // Is it time to wrap to the next file? if (current_size_ > max_size_) { try { auto next = open_file(); add_hook(closing_log_file_); std::swap(file_helper_, next); current_size_ = file_helper_->size(); add_hook(opening_log_file_); } catch (const spdlog::spdlog_ex&) { // Keep on logging to this file, but try swap at the next // insert of data (didn't use the next file we need to // roll back the next_file_id to avoid getting a hole ;-) next_file_id_--; } } } template void custom_rotating_file_sink::flush_() { file_helper_->flush(); } /* Takes a message, formats it and writes it to file */ template void custom_rotating_file_sink::add_hook(const std::string& hook) { spdlog::details::log_msg msg; msg.time = spdlog::details::os::now(); msg.level = spdlog::level::info; std::string hookToAdd = hook; if (hook == opening_log_file_) { hookToAdd.append(file_helper_->filename()); } // Payload shouldn't contain anything yet, overwrite it Expects(msg.payload.size() == 0); msg.payload = hook; spdlog::memory_buf_t formatted; formatter->format(msg, formatted); current_size_ += formatted.size(); file_helper_->write(formatted); } template std::unique_ptr custom_rotating_file_sink::open_file() { auto ret = std::make_unique(); do { ret->open(fmt::format("{}.{:06}.txt", base_filename_, next_file_id_++)); } while (ret->size() > max_size_); return ret; } template custom_rotating_file_sink::~custom_rotating_file_sink() { add_hook(closing_log_file_); } template class custom_rotating_file_sink; template class custom_rotating_file_sink;