logging: implement FATAL and DFATAL log levels
[folly.git] / folly / experimental / logging / LogStreamProcessor.h
1 /*
2  * Copyright 2004-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17
18 #include <folly/Conv.h>
19 #include <folly/Demangle.h>
20 #include <folly/Format.h>
21 #include <folly/experimental/logging/LogCategory.h>
22 #include <folly/experimental/logging/LogMessage.h>
23 #include <cstdlib>
24
25 namespace folly {
26
27 class LogStream;
28
29 /*
30  * Helper functions for fallback-formatting of arguments if folly::format()
31  * throws an exception.
32  *
33  * These are in a detail namespace so that we can include a using directive in
34  * order to do proper argument-dependent lookup of the correct toAppend()
35  * function to use.
36  */
37 namespace detail {
38 /* using override */
39 using folly::toAppend;
40 template <typename Arg>
41 auto fallbackFormatOneArg(std::string* str, const Arg* arg, int) -> decltype(
42     toAppend(std::declval<Arg>(), std::declval<std::string*>()),
43     std::declval<void>()) {
44   str->push_back('(');
45   try {
46 #ifdef FOLLY_HAS_RTTI
47     toAppend(folly::demangle(typeid(*arg)), str);
48     str->append(": ");
49 #endif
50     toAppend(*arg, str);
51   } catch (const std::exception&) {
52     str->append("<error_converting_to_string>");
53   }
54   str->push_back(')');
55 }
56
57 template <typename Arg>
58 inline void fallbackFormatOneArg(std::string* str, const Arg* arg, long) {
59   str->push_back('(');
60 #ifdef FOLLY_HAS_RTTI
61   try {
62     toAppend(folly::demangle(typeid(*arg)), str);
63     str->append(": ");
64   } catch (const std::exception&) {
65     // Ignore the error
66   }
67 #endif
68   str->append("<no_string_conversion>)");
69 }
70 }
71
72 /**
73  * LogStreamProcessor receives a LogStream and logs it.
74  *
75  * This class is primarily intended to be used through the FB_LOG*() macros.
76  * Its API is designed to support these macros, and is not designed for other
77  * use.
78  *
79  * The operator&() method is used to trigger the logging.
80  * This operator is used because it has lower precedence than <<, but higher
81  * precedence than the ? ternary operator, allowing it to bind with the correct
82  * precedence in the log macro implementations.
83  */
84 class LogStreamProcessor {
85  public:
86   enum AppendType { APPEND };
87   enum FormatType { FORMAT };
88
89   /**
90    * LogStreamProcessor constructor for use with a LOG() macro with no extra
91    * arguments.
92    *
93    * Note that the filename argument is not copied.  The caller should ensure
94    * that it points to storage that will remain valid for the lifetime of the
95    * LogStreamProcessor.  (This is always the case for the __FILE__
96    * preprocessor macro.)
97    */
98   LogStreamProcessor(
99       const LogCategory* category,
100       LogLevel level,
101       folly::StringPiece filename,
102       unsigned int lineNumber,
103       AppendType) noexcept
104       : category_{category},
105         level_{level},
106         filename_{filename},
107         lineNumber_{lineNumber} {}
108
109   /**
110    * LogStreamProcessor constructor for use with a LOG() macro with arguments
111    * to be concatenated with folly::to<std::string>()
112    *
113    * Note that the filename argument is not copied.  The caller should ensure
114    * that it points to storage that will remain valid for the lifetime of the
115    * LogStreamProcessor.  (This is always the case for the __FILE__
116    * preprocessor macro.)
117    */
118   template <typename... Args>
119   LogStreamProcessor(
120       const LogCategory* category,
121       LogLevel level,
122       const char* filename,
123       unsigned int lineNumber,
124       AppendType,
125       Args&&... args) noexcept
126       : category_{category},
127         level_{level},
128         filename_{filename},
129         lineNumber_{lineNumber},
130         message_{createLogString(std::forward<Args>(args)...)} {}
131
132   /**
133    * LogStreamProcessor constructor for use with a LOG() macro with arguments
134    * to be concatenated with folly::to<std::string>()
135    *
136    * Note that the filename argument is not copied.  The caller should ensure
137    * that it points to storage that will remain valid for the lifetime of the
138    * LogStreamProcessor.  (This is always the case for the __FILE__
139    * preprocessor macro.)
140    */
141   template <typename... Args>
142   LogStreamProcessor(
143       const LogCategory* category,
144       LogLevel level,
145       const char* filename,
146       unsigned int lineNumber,
147       FormatType,
148       folly::StringPiece fmt,
149       Args&&... args) noexcept
150       : category_{category},
151         level_{level},
152         filename_{filename},
153         lineNumber_{lineNumber},
154         message_{formatLogString(fmt, std::forward<Args>(args)...)} {}
155
156   /**
157    * This version of operator&() is typically used when the user specifies
158    * log arguments using the << stream operator.  The operator<<() generally
159    * returns a std::ostream&
160    */
161   void operator&(std::ostream& stream) noexcept;
162
163   /**
164    * This version of operator&() is used when no extra arguments are specified
165    * with the << operator.  In this case the & operator is applied directly to
166    * the temporary LogStream object.
167    */
168   void operator&(LogStream&& stream) noexcept;
169
170  private:
171   std::string extractMessageString(LogStream& stream) noexcept;
172
173   /**
174    * Construct a log message string using folly::to<std::string>()
175    *
176    * This function attempts to avoid throwing exceptions.  If an error occurs
177    * during formatting, a message including the error details is returned
178    * instead.  This is done to help ensure that log statements do not generate
179    * exceptions, but instead just log an error string when something goes wrong.
180    */
181   template <typename... Args>
182   std::string createLogString(Args&&... args) noexcept {
183     try {
184       return folly::to<std::string>(std::forward<Args>(args)...);
185     } catch (const std::exception& ex) {
186       // This most likely means there was some error converting the arguments
187       // to strings.  Handle the exception here, rather than letting it
188       // propagate up, since callers generally do not expect log statements to
189       // throw.
190       //
191       // Just log an error message letting indicating that something went wrong
192       // formatting the log message.
193       return folly::to<std::string>(
194           "error constructing log message: ", ex.what());
195     }
196   }
197
198   /**
199    * Construct a log message string using folly::sformat()
200    *
201    * This function attempts to avoid throwing exceptions.  If an error occurs
202    * during formatting, a message including the error details is returned
203    * instead.  This is done to help ensure that log statements do not generate
204    * exceptions, but instead just log an error string when something goes wrong.
205    */
206   template <typename... Args>
207   std::string formatLogString(
208       folly::StringPiece fmt,
209       const Args&... args) noexcept {
210     try {
211       return folly::sformat(fmt, args...);
212     } catch (const std::exception& ex) {
213       // This most likely means that the caller had a bug in their format
214       // string/arguments.  Handle the exception here, rather than letting it
215       // propagate up, since callers generally do not expect log statements to
216       // throw.
217       //
218       // Log the format string and as much of the arguments as we can convert,
219       // to aid debugging.
220       std::string result;
221       result.append("error formatting log message: ");
222       result.append(ex.what());
223       result.append("; format string: \"");
224       result.append(fmt.data(), fmt.size());
225       result.append("\", arguments: ");
226       fallbackFormat(&result, args...);
227       return result;
228     }
229   }
230
231   /**
232    * Helper function generate a fallback version of the arguments in case
233    * folly::sformat() throws an exception.
234    *
235    * This attempts to convert each argument to a string using a similar
236    * mechanism to folly::to<std::string>(), if supported.
237    */
238   template <typename Arg1, typename... Args>
239   void
240   fallbackFormat(std::string* str, const Arg1& arg1, const Args&... remainder) {
241     detail::fallbackFormatOneArg(str, &arg1, 0);
242     str->append(", ");
243     fallbackFormat(str, remainder...);
244   }
245
246   template <typename Arg>
247   void fallbackFormat(std::string* str, const Arg& arg) {
248     detail::fallbackFormatOneArg(str, &arg, 0);
249   }
250
251   const LogCategory* const category_;
252   LogLevel const level_;
253   folly::StringPiece filename_;
254   unsigned int lineNumber_;
255   std::string message_;
256 };
257
258 /*
259  * This template subclass of LogStreamProcessor exists primarily so that
260  * we can specify the [[noreturn]] attribute correctly on operator&()
261  * This lets the compiler know that code after LOG(FATAL) is unreachable.
262  */
263 template <bool Fatal>
264 class LogStreamProcessorT : public LogStreamProcessor {
265  public:
266   using LogStreamProcessor::LogStreamProcessor;
267
268   void operator&(std::ostream& stream) noexcept {
269     LogStreamProcessor::operator&(stream);
270   }
271   void operator&(LogStream&& stream) noexcept {
272     LogStreamProcessor::operator&(std::move(stream));
273   }
274 };
275
276 template <>
277 class LogStreamProcessorT<true> : public LogStreamProcessor {
278  public:
279   using LogStreamProcessor::LogStreamProcessor;
280
281   [[noreturn]] void operator&(std::ostream& stream) noexcept {
282     LogStreamProcessor::operator&(stream);
283     // We'll never actually reach here: the LogCategory code is responsible for
284     // crashing on FATAL messages.  However, add an abort() call so the
285     // compiler knows we really cannot return here.
286     std::abort();
287   }
288   [[noreturn]] void operator&(LogStream&& stream) noexcept {
289     LogStreamProcessor::operator&(std::move(stream));
290     // We'll never actually reach here: the LogCategory code is responsible for
291     // crashing on FATAL messages.  However, add an abort() call so the
292     // compiler knows we really cannot return here.
293     std::abort();
294   }
295 };
296 }