proxygen
CustomLogFormatter.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017-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  */
17 
18 #include <folly/Format.h>
19 #include <folly/logging/LogLevel.h>
21 #include <folly/portability/Time.h>
22 #include <algorithm>
23 
24 namespace {
25 using folly::LogLevel;
26 using folly::StringPiece;
27 
28 StringPiece getGlogLevelName(LogLevel level) {
29  if (level < LogLevel::INFO) {
30  return "VERBOSE";
31  } else if (level < LogLevel::WARN) {
32  return "INFO";
33  } else if (level < LogLevel::ERR) {
34  return "WARNING";
35  } else if (level < LogLevel::CRITICAL) {
36  return "ERROR";
37  } else if (level < LogLevel::DFATAL) {
38  return "CRITICAL";
39  }
40  return "FATAL";
41 }
42 
43 StringPiece getResetSequence(LogLevel level) {
44  if (level < LogLevel::INFO || level >= LogLevel::WARN) {
45  return "\033[0m";
46  } else {
47  return "";
48  }
49 }
50 
51 StringPiece getColorSequence(LogLevel level) {
52  if (level < LogLevel::INFO) {
53  return "\033[1;30m"; // BOLD/BRIGHT BLACK ~ GREY
54  } else if (level < LogLevel::WARN) {
55  return ""; // NO COLOR
56  } else if (level < LogLevel::ERR) {
57  return "\033[33m"; // YELLOW
58  } else if (level < LogLevel::CRITICAL) {
59  return "\033[31m"; // RED
60  }
61  return "\033[1;41m"; // BOLD ON RED BACKGROUND
62 }
63 
64 struct FormatKeys {
65  const StringPiece key;
66  const std::size_t argIndex;
67  const std::size_t width;
68 
69  constexpr FormatKeys(
70  StringPiece key_,
71  std::size_t argIndex_,
72  std::size_t width_ = 0)
73  : key(key_), argIndex(argIndex_), width(width_) {}
74 };
75 
85 constexpr std::array<FormatKeys, 11> formatKeys{{
86  FormatKeys(/* key */ "D", /* argIndex */ 2, /* width */ 2),
87  FormatKeys(/* key */ "FILE", /* argIndex */ 8),
88  FormatKeys(/* key */ "FUN", /* argIndex */ 9),
89  FormatKeys(/* key */ "H", /* argIndex */ 3, /* width */ 2),
90  FormatKeys(/* key */ "L", /* argIndex */ 0, /* width */ 1),
91  FormatKeys(/* key */ "LINE", /* argIndex */ 10, /* width */ 4),
92  FormatKeys(/* key */ "M", /* argIndex */ 4, /* width */ 2),
93  FormatKeys(/* key */ "S", /* argIndex */ 5, /* width */ 2),
94  FormatKeys(/* key */ "THREAD", /* argIndex */ 7, /* width */ 5),
95  FormatKeys(/* key */ "USECS", /* argIndex */ 6, /* width */ 6),
96  FormatKeys(/* key */ "m", /* argIndex */ 1, /* width */ 2),
97 }};
98 constexpr int messageIndex = formatKeys.size();
99 
100 } // namespace
101 
102 namespace folly {
103 
104 CustomLogFormatter::CustomLogFormatter(StringPiece format, bool colored)
105  : colored_(colored) {
106  parseFormatString(format);
107 }
108 
110  std::size_t estimatedWidth = 0;
111  functionNameCount_ = 0;
112  fileNameCount_ = 0;
113  // Replace all format keys to numbers to improve performance and to use
114  // varying value types (which is not possible using folly::vformat()).
116  output.reserve(input.size());
117  const char* varNameStart = nullptr;
118 
119  enum StateEnum {
120  LITERAL,
121  FMT_NAME,
122  FMT_MODIFIERS,
123  } state = LITERAL;
124 
125  for (const char* p = input.begin(); p < input.end(); ++p) {
126  switch (state) {
127  case LITERAL:
128  output.append(p, 1);
129  // In case of `{{` or `}}`, copy it as it is and only increment the
130  // estimatedWidth once as it will result to a single character in
131  // output.
132  if ((p + 1) != input.end() /* ensure not last character */ &&
133  (0 == memcmp(p, "}}", 2) || 0 == memcmp(p, "{{", 2))) {
134  output.append(p + 1, 1);
135  estimatedWidth++;
136  p++;
137  }
138  // If we see a single open curly brace, it denotes a start of a format
139  // name and so we change the state to FMT_NAME and do not increment
140  // estimatedWidth as it won't be in the output.
141  else if (*p == '{') {
142  varNameStart = p + 1;
143  state = FMT_NAME;
144  }
145  // In case it is just a regular literal, just increment estimatedWidth
146  // by one and move on to the next character.
147  else {
148  estimatedWidth++;
149  }
150  break;
151  // In case we have started processing a format name/key
152  case FMT_NAME:
153  // Unless it is the end of the format name/key, do nothing and scan over
154  // the name/key. When it is the end of the format name/key, look up
155  // the argIndex for it and replace the name/key with that index.
156  if (*p == ':' || *p == '}') {
157  StringPiece varName(varNameStart, p);
158  auto item = std::lower_bound(
159  formatKeys.begin(),
160  formatKeys.end(),
161  varName,
162  [](const auto& a, const auto& b) { return a.key < b; });
163 
164  if (UNLIKELY(item == formatKeys.end() || item->key != varName)) {
165  throw std::runtime_error(folly::to<std::string>(
166  "unknown format argument \"", varName, "\""));
167  }
168  output.append(folly::to<std::string>(item->argIndex));
169  output.append(p, 1);
170 
171  // Based on the format key, increment estimatedWidth with the
172  // estimate of how many characters long the value of the format key
173  // will be. If it is a FILE or a FUN, the width will be variable
174  // depending on the values of those fields.
175  estimatedWidth += item->width;
176  if (item->key == "FILE") {
177  fileNameCount_++;
178  } else if (item->key == "FUN") {
180  }
181 
182  // Figure out if there are modifiers that follow the key or if we
183  // continue processing literals.
184  if (*p == ':') {
185  state = FMT_MODIFIERS;
186  } else {
187  state = LITERAL;
188  }
189  }
190  break;
191  // In case we have started processing a format modifier (after :)
192  case FMT_MODIFIERS:
193  // Modifiers are just copied as is and are not considered to determine
194  // the estimatedWidth.
195  output.append(p, 1);
196  if (*p == '}') {
197  state = LITERAL;
198  }
199  break;
200  }
201  }
202  if (state != LITERAL) {
203  throw std::runtime_error("unterminated format string");
204  }
205  // Append a single space after the header format if header is not empty.
206  if (!output.empty()) {
207  output.append(" ");
208  estimatedWidth++;
209  }
210  logFormat_ = output;
211  staticEstimatedWidth_ = estimatedWidth;
212 
213  // populate singleLineLogFormat_ with the padded line format.
214  if (colored_) {
215  singleLineLogFormat_ = folly::to<std::string>(
216  "{",
217  messageIndex + 1,
218  "}",
219  logFormat_,
220  "{",
221  messageIndex,
222  "}{",
223  messageIndex + 2,
224  "}\n");
225  } else {
227  folly::to<std::string>(logFormat_, "{", messageIndex, "}\n");
228  }
229 }
230 
232  const LogMessage& message,
233  const LogCategory* /* handlerCategory */) {
234  // Get the local time info
235  struct tm ltime;
236  auto timeSinceEpoch = message.getTimestamp().time_since_epoch();
237  auto epochSeconds =
238  std::chrono::duration_cast<std::chrono::seconds>(timeSinceEpoch);
239  std::chrono::microseconds usecs =
240  std::chrono::duration_cast<std::chrono::microseconds>(timeSinceEpoch) -
241  epochSeconds;
242  time_t unixTimestamp = epochSeconds.count();
243  if (!localtime_r(&unixTimestamp, &ltime)) {
244  memset(&ltime, 0, sizeof(ltime));
245  }
246 
247  auto basename = message.getFileBaseName();
248 
249  // Most common logs will be single line logs and so we can format the entire
250  // log string including the message at once.
251  if (!message.containsNewlines()) {
252  return folly::sformat(
254  getGlogLevelName(message.getLevel())[0],
255  ltime.tm_mon + 1,
256  ltime.tm_mday,
257  ltime.tm_hour,
258  ltime.tm_min,
259  ltime.tm_sec,
260  usecs.count(),
261  message.getThreadID(),
262  basename,
263  message.getFunctionName(),
264  message.getLineNumber(),
265  // NOTE: THE FOLLOWING ARGUMENTS ALWAYS NEED TO BE THE LAST 3:
266  message.getMessage(),
267  // If colored logs are enabled, the singleLineLogFormat_ will contain
268  // placeholders for the color and the reset sequences. If not, then
269  // the following params will just be ignored by the folly::sformat().
270  getColorSequence(message.getLevel()),
271  getResetSequence(message.getLevel()));
272  }
273  // If the message contains multiple lines, ensure that the log header is
274  // prepended before each message line.
275  else {
276  const auto headerFormatter = folly::format(
277  logFormat_,
278  getGlogLevelName(message.getLevel())[0],
279  ltime.tm_mon + 1,
280  ltime.tm_mday,
281  ltime.tm_hour,
282  ltime.tm_min,
283  ltime.tm_sec,
284  usecs.count(),
285  message.getThreadID(),
286  basename,
287  message.getFunctionName(),
288  message.getLineNumber());
289 
290  // Estimate header length. If this still isn't long enough the string will
291  // grow as necessary, so the code will still be correct, but just slightly
292  // less efficient than if we had allocated a large enough buffer the first
293  // time around.
294  size_t headerLengthGuess = staticEstimatedWidth_ +
295  (fileNameCount_ * basename.size()) +
296  (functionNameCount_ * message.getFunctionName().size());
297 
298  // Format the data into a buffer.
300  // If colored logging is supported, then process the color based on
301  // the level of the message.
302  if (colored_) {
303  buffer.append(getColorSequence(message.getLevel()).toString());
304  }
305  StringPiece msgData{message.getMessage()};
306 
307  // Make a guess at how many lines will be in the message, just to make an
308  // initial buffer allocation. If the guess is too small then the string
309  // will reallocate and grow as necessary, it will just be slightly less
310  // efficient than if we had guessed enough space.
311  size_t numLinesGuess = 4;
312  buffer.reserve((headerLengthGuess * numLinesGuess) + msgData.size());
313 
314  size_t idx = 0;
315  while (true) {
316  auto end = msgData.find('\n', idx);
317  if (end == StringPiece::npos) {
318  end = msgData.size();
319  }
320 
321  auto line = msgData.subpiece(idx, end - idx);
322  headerFormatter.appendTo(buffer);
323  buffer.append(line.data(), line.size());
324  buffer.push_back('\n');
325 
326  if (end == msgData.size()) {
327  break;
328  }
329  idx = end + 1;
330  }
331  // If colored logging is supported and the current message is a color other
332  // than the default, then RESET colors after printing message.
333  if (colored_) {
334  buffer.append(getResetSequence(message.getLevel()).toString());
335  }
336  return buffer;
337  }
338 }
339 } // namespace folly
std::vector< uint8_t > buffer(kBufferSize+16)
Definition: test.c:42
folly::StringPiece toString(StateEnum state)
Definition: State.cpp:16
folly::StringPiece getFunctionName() const
Definition: LogMessage.h:93
char b
Colored colored_
std::string sformat(StringPiece fmt, Args &&...args)
Definition: Format.h:280
void parseFormatString(StringPiece format)
constexpr size_type size() const
Definition: Range.h:431
std::string formatMessage(const LogMessage &message, const LogCategory *handlerCategory) override
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
const std::string & getMessage() const
Definition: LogMessage.h:105
uint64_t getThreadID() const
Definition: LogMessage.h:101
LogLevel getLevel() const
Definition: LogMessage.h:80
auto end(TestAdlIterable &instance)
Definition: ForeachTest.cpp:62
char a
LogLevel
Definition: LogLevel.h:38
constexpr Iter end() const
Definition: Range.h:455
constexpr Iter begin() const
Definition: Range.h:452
unsigned int getLineNumber() const
Definition: LogMessage.h:89
static const size_type npos
Definition: Range.h:197
const char * string
Definition: Conv.cpp:212
folly::StringPiece getFileBaseName() const
Definition: LogMessage.cpp:61
Formatter< false, Args... > format(StringPiece fmt, Args &&...args)
Definition: Format.h:271
const Instruction LITERAL
std::chrono::system_clock::time_point getTimestamp() const
Definition: LogMessage.h:97
Range< const char * > StringPiece
#define UNLIKELY(x)
Definition: Likely.h:48
state
Definition: http_parser.c:272
bool containsNewlines() const
Definition: LogMessage.h:117