| 1 | // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. |
| 2 | // Distributed under the MIT License (http://opensource.org/licenses/MIT) |
| 3 | |
| 4 | #pragma once |
| 5 | |
| 6 | #ifndef SPDLOG_HEADER_ONLY |
| 7 | #include <spdlog/details/file_helper.h> |
| 8 | #endif |
| 9 | |
| 10 | #include <spdlog/common.h> |
| 11 | #include <spdlog/details/os.h> |
| 12 | |
| 13 | #include <cerrno> |
| 14 | #include <chrono> |
| 15 | #include <cstdio> |
| 16 | #include <string> |
| 17 | #include <thread> |
| 18 | #include <tuple> |
| 19 | |
| 20 | namespace spdlog { |
| 21 | namespace details { |
| 22 | |
| 23 | SPDLOG_INLINE file_helper::file_helper(const file_event_handlers &event_handlers) |
| 24 | : event_handlers_(event_handlers) {} |
| 25 | |
| 26 | SPDLOG_INLINE file_helper::~file_helper() { close(); } |
| 27 | |
| 28 | SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) { |
| 29 | close(); |
| 30 | filename_ = fname; |
| 31 | |
| 32 | auto *mode = SPDLOG_FILENAME_T("ab" ); |
| 33 | auto *trunc_mode = SPDLOG_FILENAME_T("wb" ); |
| 34 | |
| 35 | if (event_handlers_.before_open) { |
| 36 | event_handlers_.before_open(filename_); |
| 37 | } |
| 38 | for (int tries = 0; tries < open_tries_; ++tries) { |
| 39 | // create containing folder if not exists already. |
| 40 | os::create_dir(path: os::dir_name(path: fname)); |
| 41 | if (truncate) { |
| 42 | // Truncate by opening-and-closing a tmp file in "wb" mode, always |
| 43 | // opening the actual log-we-write-to in "ab" mode, since that |
| 44 | // interacts more politely with eternal processes that might |
| 45 | // rotate/truncate the file underneath us. |
| 46 | std::FILE *tmp; |
| 47 | if (os::fopen_s(fp: &tmp, filename: fname, mode: trunc_mode)) { |
| 48 | continue; |
| 49 | } |
| 50 | std::fclose(stream: tmp); |
| 51 | } |
| 52 | if (!os::fopen_s(fp: &fd_, filename: fname, mode)) { |
| 53 | if (event_handlers_.after_open) { |
| 54 | event_handlers_.after_open(filename_, fd_); |
| 55 | } |
| 56 | return; |
| 57 | } |
| 58 | |
| 59 | details::os::sleep_for_millis(milliseconds: open_interval_); |
| 60 | } |
| 61 | |
| 62 | throw_spdlog_ex(msg: "Failed opening file " + os::filename_to_str(filename: filename_) + " for writing" , |
| 63 | errno); |
| 64 | } |
| 65 | |
| 66 | SPDLOG_INLINE void file_helper::reopen(bool truncate) { |
| 67 | if (filename_.empty()) { |
| 68 | throw_spdlog_ex(msg: "Failed re opening file - was not opened before" ); |
| 69 | } |
| 70 | this->open(fname: filename_, truncate); |
| 71 | } |
| 72 | |
| 73 | SPDLOG_INLINE void file_helper::flush() { |
| 74 | if (std::fflush(stream: fd_) != 0) { |
| 75 | throw_spdlog_ex(msg: "Failed flush to file " + os::filename_to_str(filename: filename_), errno); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | SPDLOG_INLINE void file_helper::sync() { |
| 80 | if (!os::fsync(fp: fd_)) { |
| 81 | throw_spdlog_ex(msg: "Failed to fsync file " + os::filename_to_str(filename: filename_), errno); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | SPDLOG_INLINE void file_helper::close() { |
| 86 | if (fd_ != nullptr) { |
| 87 | if (event_handlers_.before_close) { |
| 88 | event_handlers_.before_close(filename_, fd_); |
| 89 | } |
| 90 | |
| 91 | std::fclose(stream: fd_); |
| 92 | fd_ = nullptr; |
| 93 | |
| 94 | if (event_handlers_.after_close) { |
| 95 | event_handlers_.after_close(filename_); |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) { |
| 101 | if (fd_ == nullptr) return; |
| 102 | size_t msg_size = buf.size(); |
| 103 | auto data = buf.data(); |
| 104 | if (std::fwrite(ptr: data, size: 1, n: msg_size, s: fd_) != msg_size) { |
| 105 | throw_spdlog_ex(msg: "Failed writing to file " + os::filename_to_str(filename: filename_), errno); |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | SPDLOG_INLINE size_t file_helper::size() const { |
| 110 | if (fd_ == nullptr) { |
| 111 | throw_spdlog_ex(msg: "Cannot use size() on closed file " + os::filename_to_str(filename: filename_)); |
| 112 | } |
| 113 | return os::filesize(f: fd_); |
| 114 | } |
| 115 | |
| 116 | SPDLOG_INLINE const filename_t &file_helper::filename() const { return filename_; } |
| 117 | |
| 118 | // |
| 119 | // return file path and its extension: |
| 120 | // |
| 121 | // "mylog.txt" => ("mylog", ".txt") |
| 122 | // "mylog" => ("mylog", "") |
| 123 | // "mylog." => ("mylog.", "") |
| 124 | // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") |
| 125 | // |
| 126 | // the starting dot in filenames is ignored (hidden files): |
| 127 | // |
| 128 | // ".mylog" => (".mylog". "") |
| 129 | // "my_folder/.mylog" => ("my_folder/.mylog", "") |
| 130 | // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") |
| 131 | SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension( |
| 132 | const filename_t &fname) { |
| 133 | auto ext_index = fname.rfind(c: '.'); |
| 134 | |
| 135 | // no valid extension found - return whole path and empty string as |
| 136 | // extension |
| 137 | if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) { |
| 138 | return std::make_tuple(args: fname, args: filename_t()); |
| 139 | } |
| 140 | |
| 141 | // treat cases like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" |
| 142 | auto folder_index = fname.find_last_of(s: details::os::folder_seps_filename); |
| 143 | if (folder_index != filename_t::npos && folder_index >= ext_index - 1) { |
| 144 | return std::make_tuple(args: fname, args: filename_t()); |
| 145 | } |
| 146 | |
| 147 | // finally - return a valid base and extension tuple |
| 148 | return std::make_tuple(args: fname.substr(pos: 0, n: ext_index), args: fname.substr(pos: ext_index)); |
| 149 | } |
| 150 | |
| 151 | } // namespace details |
| 152 | } // namespace spdlog |
| 153 | |