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/LoggerDB.h>
18 #include <folly/Conv.h>
19 #include <folly/FileUtil.h>
20 #include <folly/String.h>
21 #include <folly/experimental/logging/LogCategory.h>
22 #include <folly/experimental/logging/LogLevel.h>
23 #include <folly/experimental/logging/Logger.h>
24 #include <folly/experimental/logging/RateLimiter.h>
29 class LoggerDBSingleton {
31 explicit LoggerDBSingleton(LoggerDB* db) : db_{db} {}
32 ~LoggerDBSingleton() {
33 // We intentionally leak the LoggerDB object on destruction.
34 // We want Logger objects to remain valid for the entire lifetime of the
35 // program, without having to worry about destruction ordering issues, or
36 // making the Logger perform reference counting on the LoggerDB.
38 // Therefore the main LoggerDB object, and all of the LogCategory objects
39 // it contains, are always intentionally leaked.
41 // However, we do call db_->cleanupHandlers() to destroy any registered
42 // LogHandler objects. The LogHandlers can be user-defined objects and may
43 // hold resources that should be cleaned up.
44 db_->cleanupHandlers();
47 LoggerDB* getDB() const {
56 LoggerDB* LoggerDB::get() {
57 // Intentionally leaky singleton
58 static LoggerDBSingleton singleton{new LoggerDB()};
59 return singleton.getDB();
62 LoggerDB::LoggerDB() {
63 // Create the root log category, and set the level to ERROR by default
64 auto rootUptr = std::make_unique<LogCategory>(this);
65 LogCategory* root = rootUptr.get();
67 loggersByName_.wlock()->emplace(root->getName(), std::move(rootUptr));
70 root->setLevelLocked(LogLevel::ERROR, false);
73 LoggerDB::LoggerDB(TestConstructorArg) : LoggerDB() {}
75 LogCategory* LoggerDB::getCategory(StringPiece name) {
76 return getOrCreateCategoryLocked(*loggersByName_.wlock(), name);
79 LogCategory* FOLLY_NULLABLE LoggerDB::getCategoryOrNull(StringPiece name) {
80 auto loggersByName = loggersByName_.rlock();
82 auto it = loggersByName->find(name);
83 if (it == loggersByName->end()) {
86 return it->second.get();
89 void LoggerDB::setLevel(folly::StringPiece name, LogLevel level, bool inherit) {
90 auto loggersByName = loggersByName_.wlock();
91 LogCategory* category = getOrCreateCategoryLocked(*loggersByName, name);
92 category->setLevelLocked(level, inherit);
95 void LoggerDB::setLevel(LogCategory* category, LogLevel level, bool inherit) {
96 auto loggersByName = loggersByName_.wlock();
97 category->setLevelLocked(level, inherit);
100 std::vector<std::string> LoggerDB::processConfigString(
101 folly::StringPiece config) {
102 std::vector<std::string> errors;
103 if (config.empty()) {
107 std::vector<StringPiece> pieces;
108 folly::split(",", config, pieces);
109 for (const auto& p : pieces) {
110 auto idx = p.rfind('=');
111 if (idx == folly::StringPiece::npos) {
113 folly::sformat("missing '=' in logger configuration: \"{}\"", p));
117 auto category = p.subpiece(0, idx);
118 auto level_str = p.subpiece(idx + 1);
121 level = stringToLogLevel(level_str);
122 } catch (const std::exception& ex) {
123 errors.emplace_back(folly::sformat(
124 "invalid log level \"{}\" for category \"{}\"", level_str, category));
128 setLevel(category, level);
134 LogCategory* LoggerDB::getOrCreateCategoryLocked(
135 LoggerNameMap& loggersByName,
137 auto it = loggersByName.find(name);
138 if (it != loggersByName.end()) {
139 return it->second.get();
142 StringPiece parentName = LogName::getParent(name);
143 LogCategory* parent = getOrCreateCategoryLocked(loggersByName, parentName);
144 return createCategoryLocked(loggersByName, name, parent);
147 LogCategory* LoggerDB::createCategoryLocked(
148 LoggerNameMap& loggersByName,
150 LogCategory* parent) {
151 auto uptr = std::make_unique<LogCategory>(name, parent);
152 LogCategory* logger = uptr.get();
153 auto ret = loggersByName.emplace(logger->getName(), std::move(uptr));
158 void LoggerDB::cleanupHandlers() {
159 // Get a copy of all categories, so we can call clearHandlers() without
160 // holding the loggersByName_ lock. We don't need to worry about LogCategory
161 // lifetime, since LogCategory objects always live for the lifetime of the
163 std::vector<LogCategory*> categories;
165 auto loggersByName = loggersByName_.wlock();
166 categories.reserve(loggersByName->size());
167 for (const auto& entry : *loggersByName) {
168 categories.push_back(entry.second.get());
172 for (auto* category : categories) {
173 category->clearHandlers();
177 LogLevel LoggerDB::xlogInit(
178 StringPiece categoryName,
179 std::atomic<LogLevel>* xlogCategoryLevel,
180 LogCategory** xlogCategory) {
181 // Hold the lock for the duration of the operation
182 // xlogInit() may be called from multiple threads simultaneously.
183 // Only one needs to perform the initialization.
184 auto loggersByName = loggersByName_.wlock();
185 if (xlogCategory != nullptr && *xlogCategory != nullptr) {
186 // The xlogCategory was already initialized before we acquired the lock
187 return (*xlogCategory)->getEffectiveLevel();
190 auto* category = getOrCreateCategoryLocked(*loggersByName, categoryName);
192 // Set *xlogCategory before we update xlogCategoryLevel below.
193 // This is important, since the XLOG() macros check xlogCategoryLevel to
194 // tell if *xlogCategory has been initialized yet.
195 *xlogCategory = category;
197 auto level = category->getEffectiveLevel();
198 xlogCategoryLevel->store(level, std::memory_order_release);
199 category->registerXlogLevel(xlogCategoryLevel);
203 LogCategory* LoggerDB::xlogInitCategory(
204 StringPiece categoryName,
205 LogCategory** xlogCategory,
206 std::atomic<bool>* isInitialized) {
207 // Hold the lock for the duration of the operation
208 // xlogInitCategory() may be called from multiple threads simultaneously.
209 // Only one needs to perform the initialization.
210 auto loggersByName = loggersByName_.wlock();
211 if (isInitialized->load(std::memory_order_acquire)) {
212 // The xlogCategory was already initialized before we acquired the lock
213 return *xlogCategory;
216 auto* category = getOrCreateCategoryLocked(*loggersByName, categoryName);
217 *xlogCategory = category;
218 isInitialized->store(true, std::memory_order_release);
222 std::atomic<LoggerDB::InternalWarningHandler> LoggerDB::warningHandler_;
224 void LoggerDB::internalWarningImpl(
225 folly::StringPiece filename,
227 std::string&& msg) noexcept {
228 auto handler = warningHandler_.load();
230 handler(filename, lineNumber, std::move(msg));
232 defaultInternalWarningImpl(filename, lineNumber, std::move(msg));
236 void LoggerDB::setInternalWarningHandler(InternalWarningHandler handler) {
237 // This API is intentionally pretty basic. It has a number of limitations:
239 // - We only support plain function pointers, and not full std::function
240 // objects. This makes it possible to use std::atomic to access the
241 // handler pointer, and also makes it safe to store in a zero-initialized
242 // file-static pointer.
244 // - We don't support any void* argument to the handler. The caller is
245 // responsible for storing any callback state themselves.
247 // - When replacing or unsetting a handler we don't make any guarantees about
248 // when the old handler will stop being called. It may still be called
249 // from other threads briefly even after setInternalWarningHandler()
250 // returns. This is also a consequence of using std::atomic rather than a
253 // This provides the minimum capabilities needed to customize the handler,
254 // while still keeping the implementation simple and safe to use even before
256 warningHandler_.store(handler);
259 void LoggerDB::defaultInternalWarningImpl(
260 folly::StringPiece filename,
262 std::string&& msg) noexcept {
263 // Rate limit to 10 messages every 5 seconds.
265 // We intentonally use a leaky Meyer's singleton here over folly::Singleton:
266 // - We want this code to work even before main()
267 // - This singleton does not depend on any other singletons.
268 static auto* rateLimiter =
269 new logging::IntervalRateLimiter{10, std::chrono::seconds(5)};
270 if (!rateLimiter->check()) {
274 if (folly::kIsDebug) {
275 // Write directly to file descriptor 2.
277 // It's possible the application has closed fd 2 and is using it for
278 // something other than stderr. However we have no good way to detect
279 // this, which is the main reason we only write to stderr in debug build
280 // modes. assert() also writes directly to stderr on failure, which seems
281 // like a reasonable precedent.
283 // Another option would be to use openlog() and syslog(). However
284 // calling openlog() may inadvertently affect the behavior of other parts
285 // of the program also using syslog().
287 // We don't check for write errors here, since there's not much else we can
289 auto fullMsg = folly::to<std::string>(
290 "logging warning:", filename, ":", lineNumber, ": ", msg, "\n");
291 folly::writeFull(STDERR_FILENO, fullMsg.data(), fullMsg.size());