2 * Copyright 2004-present Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 #include <folly/experimental/logging/LogCategory.h>
17 #include <folly/experimental/logging/LogHandler.h>
18 #include <folly/experimental/logging/LogMessage.h>
19 #include <folly/experimental/logging/Logger.h>
20 #include <folly/experimental/logging/LoggerDB.h>
21 #include <folly/experimental/logging/test/TestLogHandler.h>
22 #include <folly/portability/GTest.h>
24 using namespace folly;
25 using std::make_shared;
27 class LoggerTest : public ::testing::Test {
29 void SetUp() override {
30 auto* category = logger_.getCategory();
32 handler_ = make_shared<TestLogHandler>();
33 category->addHandler(handler_);
34 category->setLevel(LogLevel::DEBUG, true);
37 LoggerDB db_{LoggerDB::TESTING};
38 Logger logger_{&db_, "test"};
39 std::shared_ptr<TestLogHandler> handler_;
42 TEST_F(LoggerTest, basic) {
44 logger_.log(LogLevel::WARN, "src/myproject/myfile.cpp", 1234, "hello world");
46 auto& messages = handler_->getMessages();
47 ASSERT_EQ(1, messages.size());
48 EXPECT_EQ("hello world", messages[0].first.getMessage());
49 EXPECT_EQ("src/myproject/myfile.cpp", messages[0].first.getFileName());
50 EXPECT_EQ(1234, messages[0].first.getLineNumber());
51 EXPECT_EQ(LogLevel::WARN, messages[0].first.getLevel());
52 EXPECT_FALSE(messages[0].first.containsNewlines());
53 EXPECT_EQ(logger_.getCategory(), messages[0].first.getCategory());
54 EXPECT_EQ(logger_.getCategory(), messages[0].second);
57 TEST_F(LoggerTest, subCategory) {
58 // Log from a sub-category.
59 Logger subLogger{&db_, "test.foo.bar"};
60 subLogger.log(LogLevel::ERROR, "myfile.cpp", 99, "sub-category\nlog message");
62 auto& messages = handler_->getMessages();
63 ASSERT_EQ(1, messages.size());
64 EXPECT_EQ("sub-category\nlog message", messages[0].first.getMessage());
65 EXPECT_EQ("myfile.cpp", messages[0].first.getFileName());
66 EXPECT_EQ(99, messages[0].first.getLineNumber());
67 EXPECT_EQ(LogLevel::ERROR, messages[0].first.getLevel());
68 EXPECT_TRUE(messages[0].first.containsNewlines());
69 EXPECT_EQ(subLogger.getCategory(), messages[0].first.getCategory());
70 EXPECT_EQ(logger_.getCategory(), messages[0].second);
73 TEST_F(LoggerTest, formatMessage) {
78 "num events: {:06d}, duration: {:6.3f}",
82 auto& messages = handler_->getMessages();
83 ASSERT_EQ(1, messages.size());
85 "num events: 001234, duration: 5.679", messages[0].first.getMessage());
86 EXPECT_EQ("log.cpp", messages[0].first.getFileName());
87 EXPECT_EQ(9, messages[0].first.getLineNumber());
88 EXPECT_EQ(LogLevel::WARN, messages[0].first.getLevel());
89 EXPECT_FALSE(messages[0].first.containsNewlines());
90 EXPECT_EQ(logger_.getCategory(), messages[0].first.getCategory());
91 EXPECT_EQ(logger_.getCategory(), messages[0].second);
94 TEST_F(LoggerTest, follyFormatError) {
95 // If we pass in a bogus format string, logf() should not throw.
96 // It should instead log a message, just complaining about the format error.
101 "param1: {:06d}, param2: {:6.3f}",
105 auto& messages = handler_->getMessages();
106 ASSERT_EQ(1, messages.size());
108 "error formatting log message: "
109 "invalid format argument {:6.3f}: invalid specifier 'f'; "
110 "format string: param1: {:06d}, param2: {:6.3f}",
111 messages[0].first.getMessage());
112 EXPECT_EQ("log.cpp", messages[0].first.getFileName());
113 EXPECT_EQ(9, messages[0].first.getLineNumber());
114 EXPECT_EQ(LogLevel::WARN, messages[0].first.getLevel());
115 EXPECT_FALSE(messages[0].first.containsNewlines());
116 EXPECT_EQ(logger_.getCategory(), messages[0].first.getCategory());
117 EXPECT_EQ(logger_.getCategory(), messages[0].second);
120 TEST_F(LoggerTest, toString) {
121 // Use the log API that calls folly::to<string>
122 logger_.log(LogLevel::DBG5, "log.cpp", 3, "status=", 5, " name=", "foobar");
124 auto& messages = handler_->getMessages();
125 ASSERT_EQ(1, messages.size());
126 EXPECT_EQ("status=5 name=foobar", messages[0].first.getMessage());
127 EXPECT_EQ("log.cpp", messages[0].first.getFileName());
128 EXPECT_EQ(3, messages[0].first.getLineNumber());
129 EXPECT_EQ(LogLevel::DBG5, messages[0].first.getLevel());
130 EXPECT_FALSE(messages[0].first.containsNewlines());
131 EXPECT_EQ(logger_.getCategory(), messages[0].first.getCategory());
132 EXPECT_EQ(logger_.getCategory(), messages[0].second);
135 class ToStringFailure {};
137 [[noreturn]] void toAppend(
138 const ToStringFailure& /* arg */,
139 std::string* /* result */) {
140 throw std::runtime_error(
141 "error converting ToStringFailure object to a string");
144 TEST_F(LoggerTest, toStringError) {
145 // Use the folly::to<string> log API, with an object that will throw
146 // an exception when we try to convert it to a string.
148 // The logging code should not throw, but should instead log a message
149 // with some detail about the failure.
151 logger_.log(LogLevel::DBG1, "log.cpp", 3, "status=", obj, " name=", "foobar");
153 auto& messages = handler_->getMessages();
154 ASSERT_EQ(1, messages.size());
156 "error constructing log message: "
157 "error converting ToStringFailure object to a string",
158 messages[0].first.getMessage());
159 EXPECT_EQ("log.cpp", messages[0].first.getFileName());
160 EXPECT_EQ(3, messages[0].first.getLineNumber());
161 EXPECT_EQ(LogLevel::DBG1, messages[0].first.getLevel());
162 EXPECT_FALSE(messages[0].first.containsNewlines());
163 EXPECT_EQ(logger_.getCategory(), messages[0].first.getCategory());
164 EXPECT_EQ(logger_.getCategory(), messages[0].second);
167 TEST_F(LoggerTest, escapeSequences) {
168 // Escape characters (and any other unprintable characters) in the log
169 // message should be escaped when logged.
170 logger_.log(LogLevel::WARN, "termcap.cpp", 34, "hello \033[34mworld\033[0m!");
172 auto& messages = handler_->getMessages();
173 ASSERT_EQ(1, messages.size());
174 EXPECT_EQ("hello \\x1b[34mworld\\x1b[0m!", messages[0].first.getMessage());
175 EXPECT_EQ("termcap.cpp", messages[0].first.getFileName());
176 EXPECT_EQ(34, messages[0].first.getLineNumber());
177 EXPECT_EQ(LogLevel::WARN, messages[0].first.getLevel());
178 EXPECT_FALSE(messages[0].first.containsNewlines());
179 EXPECT_EQ(logger_.getCategory(), messages[0].first.getCategory());
180 EXPECT_EQ(logger_.getCategory(), messages[0].second);
183 TEST_F(LoggerTest, logMacros) {
184 Logger foo{&db_, "test.foo.bar"};
185 Logger foobar{&db_, "test.foo.bar"};
186 Logger footest{&db_, "test.foo.test"};
187 Logger footest1234{&db_, "test.foo.test.1234"};
188 Logger other{&db_, "test.other"};
189 db_.setLevel("test", LogLevel::ERROR);
190 db_.setLevel("test.foo", LogLevel::DBG2);
191 db_.setLevel("test.foo.test", LogLevel::DBG7);
193 auto& messages = handler_->getMessages();
195 // test.other's effective level should be ERROR, so a warning
196 // message to it should be discarded
197 FB_LOG(other, WARN, "this should be discarded");
198 ASSERT_EQ(0, messages.size());
200 // Disabled log messages should not evaluate their arguments
201 bool argumentEvaluated = false;
202 auto getValue = [&] {
203 argumentEvaluated = true;
206 FB_LOG(foobar, DBG3, "discarded message: ", getValue());
207 EXPECT_FALSE(argumentEvaluated);
209 FB_LOG(foobar, DBG1, "this message should pass: ", getValue());
210 ASSERT_EQ(1, messages.size());
211 EXPECT_EQ("this message should pass: 5", messages[0].first.getMessage());
212 EXPECT_TRUE(argumentEvaluated);
215 // Similar checks with FB_LOGF()
216 argumentEvaluated = false;
217 FB_LOGF(footest1234, DBG9, "failing log check: {}", getValue());
218 EXPECT_FALSE(argumentEvaluated);
220 FB_LOGF(footest1234, DBG5, "passing log: {:03}", getValue());
221 ASSERT_EQ(1, messages.size());
222 EXPECT_EQ("passing log: 005", messages[0].first.getMessage());
223 EXPECT_TRUE(argumentEvaluated);
226 // Bad format arguments should not throw
227 FB_LOGF(footest1234, ERROR, "whoops: {}, {}", getValue());
228 ASSERT_EQ(1, messages.size());
230 "error formatting log message: "
231 "invalid format argument {}: argument index out of range, max=1; "
232 "format string: whoops: {}, {}",
233 messages[0].first.getMessage());