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/details/os.h> |
11 | #include <spdlog/common.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 | |
27 | SPDLOG_INLINE file_helper::~file_helper() |
28 | { |
29 | close(); |
30 | } |
31 | |
32 | SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) |
33 | { |
34 | close(); |
35 | filename_ = fname; |
36 | |
37 | auto *mode = SPDLOG_FILENAME_T("ab" ); |
38 | auto *trunc_mode = SPDLOG_FILENAME_T("wb" ); |
39 | |
40 | if (event_handlers_.before_open) |
41 | { |
42 | event_handlers_.before_open(filename_); |
43 | } |
44 | for (int tries = 0; tries < open_tries_; ++tries) |
45 | { |
46 | // create containing folder if not exists already. |
47 | os::create_dir(os::dir_name(fname)); |
48 | if (truncate) |
49 | { |
50 | // Truncate by opening-and-closing a tmp file in "wb" mode, always |
51 | // opening the actual log-we-write-to in "ab" mode, since that |
52 | // interacts more politely with eternal processes that might |
53 | // rotate/truncate the file underneath us. |
54 | std::FILE *tmp; |
55 | if (os::fopen_s(&tmp, fname, trunc_mode)) |
56 | { |
57 | continue; |
58 | } |
59 | std::fclose(tmp); |
60 | } |
61 | if (!os::fopen_s(&fd_, fname, mode)) |
62 | { |
63 | if (event_handlers_.after_open) |
64 | { |
65 | event_handlers_.after_open(filename_, fd_); |
66 | } |
67 | return; |
68 | } |
69 | |
70 | details::os::sleep_for_millis(open_interval_); |
71 | } |
72 | |
73 | throw_spdlog_ex("Failed opening file " + os::filename_to_str(filename_) + " for writing" , errno); |
74 | } |
75 | |
76 | SPDLOG_INLINE void file_helper::reopen(bool truncate) |
77 | { |
78 | if (filename_.empty()) |
79 | { |
80 | throw_spdlog_ex("Failed re opening file - was not opened before" ); |
81 | } |
82 | this->open(filename_, truncate); |
83 | } |
84 | |
85 | SPDLOG_INLINE void file_helper::flush() |
86 | { |
87 | if (std::fflush(fd_) != 0) |
88 | { |
89 | throw_spdlog_ex("Failed flush to file " + os::filename_to_str(filename_), errno); |
90 | } |
91 | } |
92 | |
93 | SPDLOG_INLINE void file_helper::close() |
94 | { |
95 | if (fd_ != nullptr) |
96 | { |
97 | if (event_handlers_.before_close) |
98 | { |
99 | event_handlers_.before_close(filename_, fd_); |
100 | } |
101 | |
102 | std::fclose(fd_); |
103 | fd_ = nullptr; |
104 | |
105 | if (event_handlers_.after_close) |
106 | { |
107 | event_handlers_.after_close(filename_); |
108 | } |
109 | } |
110 | } |
111 | |
112 | SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) |
113 | { |
114 | size_t msg_size = buf.size(); |
115 | auto data = buf.data(); |
116 | if (std::fwrite(data, 1, msg_size, fd_) != msg_size) |
117 | { |
118 | throw_spdlog_ex("Failed writing to file " + os::filename_to_str(filename_), errno); |
119 | } |
120 | } |
121 | |
122 | SPDLOG_INLINE size_t file_helper::size() const |
123 | { |
124 | if (fd_ == nullptr) |
125 | { |
126 | throw_spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(filename_)); |
127 | } |
128 | return os::filesize(fd_); |
129 | } |
130 | |
131 | SPDLOG_INLINE const filename_t &file_helper::filename() const |
132 | { |
133 | return filename_; |
134 | } |
135 | |
136 | // |
137 | // return file path and its extension: |
138 | // |
139 | // "mylog.txt" => ("mylog", ".txt") |
140 | // "mylog" => ("mylog", "") |
141 | // "mylog." => ("mylog.", "") |
142 | // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") |
143 | // |
144 | // the starting dot in filenames is ignored (hidden files): |
145 | // |
146 | // ".mylog" => (".mylog". "") |
147 | // "my_folder/.mylog" => ("my_folder/.mylog", "") |
148 | // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") |
149 | SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension(const filename_t &fname) |
150 | { |
151 | auto ext_index = fname.rfind('.'); |
152 | |
153 | // no valid extension found - return whole path and empty string as |
154 | // extension |
155 | if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) |
156 | { |
157 | return std::make_tuple(fname, filename_t()); |
158 | } |
159 | |
160 | // treat cases like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" |
161 | auto folder_index = fname.find_last_of(details::os::folder_seps_filename); |
162 | if (folder_index != filename_t::npos && folder_index >= ext_index - 1) |
163 | { |
164 | return std::make_tuple(fname, filename_t()); |
165 | } |
166 | |
167 | // finally - return a valid base and extension tuple |
168 | return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); |
169 | } |
170 | |
171 | } // namespace details |
172 | } // namespace spdlog |
173 | |