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
20namespace spdlog {
21namespace details {
22
23SPDLOG_INLINE file_helper::file_helper(const file_event_handlers &event_handlers)
24 : event_handlers_(event_handlers)
25{}
26
27SPDLOG_INLINE file_helper::~file_helper()
28{
29 close();
30}
31
32SPDLOG_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
76SPDLOG_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
85SPDLOG_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
93SPDLOG_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
112SPDLOG_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
122SPDLOG_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
131SPDLOG_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")
149SPDLOG_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