f95e00e44e6168da5422bba255fee363a917441d
[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
24 namespace folly {
25
26 class LogStream;
27
28 /*
29  * Helper functions for fallback-formatting of arguments if folly::format()
30  * throws an exception.
31  *
32  * These are in a detail namespace so that we can include a using directive in
33  * order to do proper argument-dependent lookup of the correct toAppend()
34  * function to use.
35  */
36 namespace detail {
37 /* using override */
38 using folly::toAppend;
39 template <typename Arg>
40 auto fallbackFormatOneArg(std::string* str, const Arg* arg, int) -> decltype(
41     toAppend(std::declval<Arg>(), std::declval<std::string*>()),
42     std::declval<void>()) {
43   str->push_back('(');
44   try {
45 #ifdef FOLLY_HAS_RTTI
46     toAppend(folly::demangle(typeid(*arg)), str);
47     str->append(": ");
48 #endif
49     toAppend(*arg, str);
50   } catch (const std::exception&) {
51     str->append("<error_converting_to_string>");
52   }
53   str->push_back(')');
54 }
55
56 template <typename Arg>
57 inline void fallbackFormatOneArg(std::string* str, const Arg* arg, long) {
58   str->push_back('(');
59 #ifdef FOLLY_HAS_RTTI
60   try {
61     toAppend(folly::demangle(typeid(*arg)), str);
62     str->append(": ");
63   } catch (const std::exception&) {
64     // Ignore the error
65   }
66 #endif
67   str->append("<no_string_conversion>)");
68 }
69 }
70
71 /**
72  * LogStreamProcessor receives a LogStream and logs it.
73  *
74  * This class is primarily intended to be used through the FB_LOG*() macros.
75  * Its API is designed to support these macros, and is not designed for other
76  * use.
77  *
78  * The operator&() method is used to trigger the logging.
79  * This operator is used because it has lower precedence than <<, but higher
80  * precedence than the ? ternary operator, allowing it to bind with the correct
81  * precedence in the log macro implementations.
82  */
83 class LogStreamProcessor {
84  public:
85   enum AppendType { APPEND };
86   enum FormatType { FORMAT };
87
88   /**
89    * LogStreamProcessor constructor for use with a LOG() macro with no extra
90    * arguments.
91    *
92    * Note that the filename argument is not copied.  The caller should ensure
93    * that it points to storage that will remain valid for the lifetime of the
94    * LogStreamProcessor.  (This is always the case for the __FILE__
95    * preprocessor macro.)
96    */
97   LogStreamProcessor(
98       const LogCategory* category,
99       LogLevel level,
100       folly::StringPiece filename,
101       unsigned int lineNumber,
102       AppendType) noexcept
103       : category_{category},
104         level_{level},
105         filename_{filename},
106         lineNumber_{lineNumber} {}
107
108   /**
109    * LogStreamProcessor constructor for use with a LOG() macro with arguments
110    * to be concatenated with folly::to<std::string>()
111    *
112    * Note that the filename argument is not copied.  The caller should ensure
113    * that it points to storage that will remain valid for the lifetime of the
114    * LogStreamProcessor.  (This is always the case for the __FILE__
115    * preprocessor macro.)
116    */
117   template <typename... Args>
118   LogStreamProcessor(
119       const LogCategory* category,
120       LogLevel level,
121       const char* filename,
122       unsigned int lineNumber,
123       AppendType,
124       Args&&... args) noexcept
125       : category_{category},
126         level_{level},
127         filename_{filename},
128         lineNumber_{lineNumber},
129         message_{createLogString(std::forward<Args>(args)...)} {}
130
131   /**
132    * LogStreamProcessor constructor for use with a LOG() macro with arguments
133    * to be concatenated with folly::to<std::string>()
134    *
135    * Note that the filename argument is not copied.  The caller should ensure
136    * that it points to storage that will remain valid for the lifetime of the
137    * LogStreamProcessor.  (This is always the case for the __FILE__
138    * preprocessor macro.)
139    */
140   template <typename... Args>
141   LogStreamProcessor(
142       const LogCategory* category,
143       LogLevel level,
144       const char* filename,
145       unsigned int lineNumber,
146       FormatType,
147       folly::StringPiece fmt,
148       Args&&... args) noexcept
149       : category_{category},
150         level_{level},
151         filename_{filename},
152         lineNumber_{lineNumber},
153         message_{formatLogString(fmt, std::forward<Args>(args)...)} {}
154
155   /**
156    * This version of operator&() is typically used when the user specifies
157    * log arguments using the << stream operator.  The operator<<() generally
158    * returns a std::ostream&
159    */
160   void operator&(std::ostream& stream) noexcept;
161
162   /**
163    * This version of operator&() is used when no extra arguments are specified
164    * with the << operator.  In this case the & operator is applied directly to
165    * the temporary LogStream object.
166    */
167   void operator&(LogStream&& stream) noexcept;
168
169  private:
170   std::string extractMessageString(LogStream& stream) noexcept;
171
172   /**
173    * Construct a log message string using folly::to<std::string>()
174    *
175    * This function attempts to avoid throwing exceptions.  If an error occurs
176    * during formatting, a message including the error details is returned
177    * instead.  This is done to help ensure that log statements do not generate
178    * exceptions, but instead just log an error string when something goes wrong.
179    */
180   template <typename... Args>
181   std::string createLogString(Args&&... args) noexcept {
182     try {
183       return folly::to<std::string>(std::forward<Args>(args)...);
184     } catch (const std::exception& ex) {
185       // This most likely means there was some error converting the arguments
186       // to strings.  Handle the exception here, rather than letting it
187       // propagate up, since callers generally do not expect log statements to
188       // throw.
189       //
190       // Just log an error message letting indicating that something went wrong
191       // formatting the log message.
192       return folly::to<std::string>(
193           "error constructing log message: ", ex.what());
194     }
195   }
196
197   /**
198    * Construct a log message string using folly::sformat()
199    *
200    * This function attempts to avoid throwing exceptions.  If an error occurs
201    * during formatting, a message including the error details is returned
202    * instead.  This is done to help ensure that log statements do not generate
203    * exceptions, but instead just log an error string when something goes wrong.
204    */
205   template <typename... Args>
206   std::string formatLogString(
207       folly::StringPiece fmt,
208       const Args&... args) noexcept {
209     try {
210       return folly::sformat(fmt, args...);
211     } catch (const std::exception& ex) {
212       // This most likely means that the caller had a bug in their format
213       // string/arguments.  Handle the exception here, rather than letting it
214       // propagate up, since callers generally do not expect log statements to
215       // throw.
216       //
217       // Log the format string and as much of the arguments as we can convert,
218       // to aid debugging.
219       std::string result;
220       result.append("error formatting log message: ");
221       result.append(ex.what());
222       result.append("; format string: \"");
223       result.append(fmt.data(), fmt.size());
224       result.append("\", arguments: ");
225       fallbackFormat(&result, args...);
226       return result;
227     }
228   }
229
230   /**
231    * Helper function generate a fallback version of the arguments in case
232    * folly::sformat() throws an exception.
233    *
234    * This attempts to convert each argument to a string using a similar
235    * mechanism to folly::to<std::string>(), if supported.
236    */
237   template <typename Arg1, typename... Args>
238   void
239   fallbackFormat(std::string* str, const Arg1& arg1, const Args&... remainder) {
240     detail::fallbackFormatOneArg(str, &arg1, 0);
241     str->append(", ");
242     fallbackFormat(str, remainder...);
243   }
244
245   template <typename Arg>
246   void fallbackFormat(std::string* str, const Arg& arg) {
247     detail::fallbackFormatOneArg(str, &arg, 0);
248   }
249
250   const LogCategory* const category_;
251   LogLevel const level_;
252   folly::StringPiece filename_;
253   unsigned int lineNumber_;
254   std::string message_;
255 };
256 }