Flow 1.0.0
Flow project: Full implementation reference.
serial_file_logger.cpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
20#include "flow/log/config.hpp"
21
22namespace flow::log
23{
24
25// Implementations.
26
28 Config* config, const fs::path& log_path) :
29 /* Set up the logging *about our real attempts at file logging, probably to a Simple_ostream_logger/cout;
30 * or to null (meaning no such logging). Either way we will still attempt to actually log user-requested messages
31 * to log_path! */
32 Log_context(backup_logger_ptr, Flow_log_component::S_LOG),
33 m_config(config), // Save pointer, not copy, of the config given to us. Hence thread safety is a thing.
34 m_log_path(log_path),
35 /* Initialize ostream m_ofs; but this is guaranteed to only mess with, at most, general ostream (not ofstream)
36 * state such as locales and formatting; no I/O operations (writing) occur here; if they tried it'd be a problem,
37 * as m_ofs is not open yet. */
38 m_ofs_writer(*m_config, m_ofs),
39 m_reopening(false)
40{
41 FLOW_LOG_INFO("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: These log messages are going to "
42 "associated backup logger [" << backup_logger_ptr << "]. The messages actually logged through "
43 "Logger API requests to `*this` will go to aforementioned log path, not to the present output.");
44
45 // Immediately open file in worker thread. Could be lazy about it but might as well log about any issue ASAP.
47 // ^-- Synchronous. Doesn't matter what thread this is: they can't call do_log() until we return.
48} // Serial_file_logger::Serial_file_logger()
49
51{
52 FLOW_LOG_INFO("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: Deleting. Will flush "
53 "output if possible; then we will proceed to shut down.");
54
55 if (m_ofs.is_open())
56 {
57 FLOW_LOG_INFO("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: Flushing before destruction "
58 "finishes in other thread.");
59 m_ofs.flush();
60 }
61}
62
64{
65 /* The current I/O error-handling cycle is set up as follows:
66 * At construction: Attempt open; bool(m_ofs) becomes T or F. Done.
67 * At do_log() 1: If last op resulted in bool(m_ofs)==F, attempt re-open. bool(m_ofs) becomes T or F.
68 * If bool(m_ofs)==T, attempt to write string to m_ofs. bool(m_ofs) becomes T or F.
69 * [If F, then string is probably lost forever.]
70 * At do_log() 2: If last op resulted in bool(m_ofs)==F, attempt re-open. bool(m_ofs) becomes T or F.
71 * If bool(m_ofs)==T, attempt to write string to m_ofs. bool(m_ofs) becomes T or F.
72 * [If F, then string is probably lost forever.]
73 * At do_log() 3: ...etc.
74 *
75 * The idea is, basically: If the I/O op (either open or write) results in error at the time, retrying is documented
76 * as unlikely or impossible to succeed at the time. So log about any such problems to the *backup* logger but then
77 * move on, until the next (later in time) operation is attempted; at which point attempt re-open for max
78 * possibility of success.
79 *
80 * A formal to-do in my doc header is to perhaps buffer any failed impl_do_log()s, so they can be output upon
81 * hypothetical successful reopen later. */
82
83 if (!m_ofs)
84 {
85 FLOW_LOG_WARNING("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: do_log() wants to log, but "
86 "file stream is in bad state, spuriously or due to a previous op; will attempt reopen first.");
88 if (!m_ofs)
89 {
90 FLOW_LOG_WARNING("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: "
91 "Stream still in bad state; giving up on that log message. "
92 "Will retry file stream for next message.");
93 return;
94 }
95 // else { Cool, re-open succeeded, so fall through to in fact log. }
96 }
97 // else { Was in good state after last op; no reason to suspect otherwise now. }
98 assert(m_ofs);
99
100 m_ofs_writer.log(*metadata, msg);
101
102 if (!m_ofs)
103 {
105 ("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: do_log() just tried to log "
106 "msg when file stream was in good state, but now file stream is in bad state, probably because the "
107 "write failed. Giving up on that log message. Will retry file stream for next message.");
108 }
109} // Serial_file_logger::do_log()
110
112{
113 // We are in m_async_worker thread. Hence we may access m_ofs (including through m_ofs_writer).
114
115 // See notes in do_log() about the error handling cycle.
116
117 /* Discussion of us logging through *this, meaning FLOW_LOG_*() to our own target file:
118 * It's easy to do: FLOW_LOG_SET_LOGGER(this); and then just do it. However, logging while enabling the user to
119 * log naturally raises fears of infinite loops/recursion:
120 * - do_log() can detect m_ofs bad state and trigger log_flush_and_reopen() which then FLOW_LOG_*()s which
121 * do_log()s which....
122 * - log_flush_and_reopen() may be called by user which FLOW_LOG_*()s (as it should) which do_log()s which
123 * can detect m_ofs bad state and trigger log_flush_and_reopen() which then....
124 *
125 * So we do this to simply never execute log_flush_and_reopen() logic if currently called from it in the first
126 * place. If necessary the on-error log_flush_and_reopen() will be re-attempted from the next user do_log().
127 *
128 * In addition, just to avoid entropy, we do not log to *this logger (the first and last lines per log file)
129 * if m_ofs is in bad state (is falsy). We could, and it would just fail, but it's best to just not.
130 * As of this writing, actually, that m_ofs check would be sufficient to prevent the recursion problem by itself;
131 * but this felt too brittle (easy to break with an inadvertent change). */
132 if (m_reopening)
133 {
134 FLOW_LOG_TRACE("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: Reopen method invoked "
135 "by internal code while already being asked to reopen file. Doing nothing.");
136 return;
137 }
138 // else
139 m_reopening = true;
140
141 // Use inline lambda to ensure no matter how the below returns, we execute `m_reopening = false;` at the end.
142 ([&]()
143 {
144 if (m_ofs.is_open())
145 {
146 FLOW_LOG_INFO("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: Closing before re-opening.");
147 if (m_ofs) // As noted above, avoid entropy if stream is in bad state anyway.
148 {
149 /* This shall be *the* last message logged before the close. If part of a rotation operation -- a HUP sent
150 * to us to cause log_flush_and_reopen() to execute -- then it'll be the last message in the file, which is
151 * renamed before the HUP is sent.
152 *
153 * This *will* invoke do_log() -- but that do_log() *will not* invoke us again/infinitely due to
154 * the m_reopening check at the top, at a minimum; plus as of this writing do_log() will not invoke us
155 * at all, as it only triggers log_flush_and_reopen() if !m_ofs... but we just guaranteed the opposite. */
157 FLOW_LOG_INFO("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: "
158 "Closing before re-opening.");
159 }
160
161 m_ofs.flush(); // m_ofs_writer `flush`es after each message... but just in case.
162 m_ofs.close();
163 }
164
165 FLOW_LOG_INFO("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: "
166 "Opening for append. Will log on error.");
167 m_ofs.open(m_log_path, std::ios_base::app);
168
169 if (m_ofs) // As noted above, avoid entropy if stream is in bad state anyway.
170 {
171 /* This shall be *the* first message logged after the re-open. If part of a rotation operation (see above),
172 * then it'll be the first message in the new file, as the preceding file has been renamed, while we were still
173 * writing to it. */
175 FLOW_LOG_INFO("detail/Serial_file_logger [" << this << "] @ [" << m_log_path << "]: "
176 "Opened successfully for further logging.");
177 m_ofs.flush(); // m_ofs_writer `flush`es after each message... but just in case.
178 }
179 else // if (!m_ofs)
180 {
181 FLOW_LOG_WARNING("Could not open. Will probably retry subsequently.");
182 }
183 })();
184
185 m_reopening = false;
186} // Serial_file_logger::log_flush_and_reopen()
187
188bool Serial_file_logger::should_log(Sev sev, const Component& component) const // Virtual.
189{
190 return m_config->output_whether_should_log(sev, component);
191}
192
194{
195 return false;
196}
197
198} // namespace flow::log
A light-weight class, each object storing a component payload encoding an enum value from enum type o...
Definition: log.hpp:840
Class used to configure the filtering and logging behavior of Loggers; its use in your custom Loggers...
Definition: config.hpp:200
Convenience class that simply stores a Logger and/or Component passed into a constructor; and returns...
Definition: log.hpp:1619
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1291
void log(const Msg_metadata &metadata, util::String_view msg)
Logs to the wrapped ostream the given message and associated metadata like severity and time stamp; p...
bool m_reopening
Starts at false, becomes true at entry to log_flush_and_reopen(), then becomes false again.
void do_log(Msg_metadata *metadata, util::String_view msg) override
Implements interface method by synchronously logging the message and some subset of the metadata in a...
bool logs_asynchronously() const override
Implements interface method by returning false, indicating that this Logger will not need the content...
~Serial_file_logger() override
Flushes out anything buffered, returns resources/closes output file(s); then returns.
void log_flush_and_reopen()
Causes the log at the file-system path to be flushed/closed (if needed) and re-opened; this will occu...
Ostream_log_msg_writer m_ofs_writer
Stream writer via which to log messages to m_ofs.
Serial_file_logger(Logger *backup_logger_ptr, Config *config, const fs::path &log_path)
Constructs logger to subsequently log to the given file-system path.
const fs::path m_log_path
File-system path to which to write subsequently.
fs::ofstream m_ofs
The file to which to write. Because only the worker thread ever accesses it, no mutex is needed.
bool should_log(Sev sev, const Component &component) const override
See Async_file_logger::should_log().
#define FLOW_LOG_INFO(ARG_stream_fragment)
Logs an INFO message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:197
#define FLOW_LOG_WARNING(ARG_stream_fragment)
Logs a WARNING message into flow::log::Logger *get_logger() with flow::log::Component get_log_compone...
Definition: log.hpp:152
#define FLOW_LOG_SET_LOGGER(ARG_logger_ptr)
Equivalent to FLOW_LOG_SET_CONTEXT() but sets the get_logger only.
Definition: log.hpp:415
#define FLOW_LOG_TRACE(ARG_stream_fragment)
Logs a TRACE message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:227
Flow module providing logging functionality.
Sev
Enumeration containing one of several message severity levels, ordered from highest to lowest.
Definition: log_fwd.hpp:224
Basic_string_view< char > String_view
Commonly used char-based Basic_string_view. See its doc header.
Flow_log_component
The flow::log::Component payload enumeration comprising various log components used by Flow's own int...
Definition: common.hpp:632
Simple data store containing all of the information generated at every logging call site by flow::log...
Definition: log.hpp:1048