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>
20 #include <folly/Conv.h>
21 #include <folly/FileUtil.h>
22 #include <folly/String.h>
23 #include <folly/experimental/logging/LogCategory.h>
24 #include <folly/experimental/logging/LogHandler.h>
25 #include <folly/experimental/logging/LogHandlerFactory.h>
26 #include <folly/experimental/logging/LogLevel.h>
27 #include <folly/experimental/logging/Logger.h>
28 #include <folly/experimental/logging/RateLimiter.h>
33 class LoggerDBSingleton {
35 explicit LoggerDBSingleton(LoggerDB* db) : db_{db} {}
36 ~LoggerDBSingleton() {
37 // We intentionally leak the LoggerDB object on destruction.
38 // We want Logger objects to remain valid for the entire lifetime of the
39 // program, without having to worry about destruction ordering issues, or
40 // making the Logger perform reference counting on the LoggerDB.
42 // Therefore the main LoggerDB object, and all of the LogCategory objects
43 // it contains, are always intentionally leaked.
45 // However, we do call db_->cleanupHandlers() to destroy any registered
46 // LogHandler objects. The LogHandlers can be user-defined objects and may
47 // hold resources that should be cleaned up. This also ensures that the
48 // LogHandlers flush all outstanding messages before we exit.
49 db_->cleanupHandlers();
52 LoggerDB* getDB() const {
61 LoggerDB* LoggerDB::get() {
62 // Intentionally leaky singleton
63 static LoggerDBSingleton singleton{new LoggerDB()};
64 return singleton.getDB();
67 LoggerDB::LoggerDB() {
68 // Create the root log category, and set the level to ERR by default
69 auto rootUptr = std::make_unique<LogCategory>(this);
70 LogCategory* root = rootUptr.get();
72 loggersByName_.wlock()->emplace(root->getName(), std::move(rootUptr));
75 root->setLevelLocked(LogLevel::ERR, false);
78 LoggerDB::LoggerDB(TestConstructorArg) : LoggerDB() {}
80 LoggerDB::~LoggerDB() {}
82 LogCategory* LoggerDB::getCategory(StringPiece name) {
83 return getOrCreateCategoryLocked(*loggersByName_.wlock(), name);
86 LogCategory* FOLLY_NULLABLE LoggerDB::getCategoryOrNull(StringPiece name) {
87 auto loggersByName = loggersByName_.rlock();
89 auto it = loggersByName->find(name);
90 if (it == loggersByName->end()) {
93 return it->second.get();
96 void LoggerDB::setLevel(folly::StringPiece name, LogLevel level, bool inherit) {
97 auto loggersByName = loggersByName_.wlock();
98 LogCategory* category = getOrCreateCategoryLocked(*loggersByName, name);
99 category->setLevelLocked(level, inherit);
102 void LoggerDB::setLevel(LogCategory* category, LogLevel level, bool inherit) {
103 auto loggersByName = loggersByName_.wlock();
104 category->setLevelLocked(level, inherit);
107 std::vector<std::string> LoggerDB::processConfigString(
108 folly::StringPiece config) {
109 std::vector<std::string> errors;
110 if (config.empty()) {
114 std::vector<StringPiece> pieces;
115 folly::split(",", config, pieces);
116 for (const auto& p : pieces) {
117 auto idx = p.rfind('=');
118 if (idx == folly::StringPiece::npos) {
120 folly::sformat("missing '=' in logger configuration: \"{}\"", p));
124 auto category = p.subpiece(0, idx);
125 auto level_str = p.subpiece(idx + 1);
128 level = stringToLogLevel(level_str);
129 } catch (const std::exception&) {
130 errors.emplace_back(folly::sformat(
131 "invalid log level \"{}\" for category \"{}\"", level_str, category));
135 setLevel(category, level);
141 LogCategory* LoggerDB::getOrCreateCategoryLocked(
142 LoggerNameMap& loggersByName,
144 auto it = loggersByName.find(name);
145 if (it != loggersByName.end()) {
146 return it->second.get();
149 StringPiece parentName = LogName::getParent(name);
150 LogCategory* parent = getOrCreateCategoryLocked(loggersByName, parentName);
151 return createCategoryLocked(loggersByName, name, parent);
154 LogCategory* LoggerDB::createCategoryLocked(
155 LoggerNameMap& loggersByName,
157 LogCategory* parent) {
158 auto uptr = std::make_unique<LogCategory>(name, parent);
159 LogCategory* logger = uptr.get();
160 auto ret = loggersByName.emplace(logger->getName(), std::move(uptr));
165 void LoggerDB::cleanupHandlers() {
166 // Get a copy of all categories, so we can call clearHandlers() without
167 // holding the loggersByName_ lock. We don't need to worry about LogCategory
168 // lifetime, since LogCategory objects always live for the lifetime of the
170 std::vector<LogCategory*> categories;
172 auto loggersByName = loggersByName_.wlock();
173 categories.reserve(loggersByName->size());
174 for (const auto& entry : *loggersByName) {
175 categories.push_back(entry.second.get());
179 // Also extract our HandlerFactoryMap and HandlerMap, so we can clear them
180 // later without holding the handlerInfo_ lock.
181 HandlerFactoryMap factories;
184 auto handlerInfo = handlerInfo_.wlock();
185 factories.swap(handlerInfo->factories);
186 handlers.swap(handlerInfo->handlers);
189 // Remove all of the LogHandlers from all log categories,
190 // to drop any shared_ptr references to the LogHandlers
191 for (auto* category : categories) {
192 category->clearHandlers();
196 size_t LoggerDB::flushAllHandlers() {
197 // Build a set of all LogHandlers. We use a set to avoid calling flush()
198 // more than once on the same handler if it is registered on multiple
199 // different categories.
200 std::set<std::shared_ptr<LogHandler>> handlers;
202 auto loggersByName = loggersByName_.wlock();
203 for (const auto& entry : *loggersByName) {
204 for (const auto& handler : entry.second->getHandlers()) {
205 handlers.emplace(handler);
210 // Call flush() on each handler
211 for (const auto& handler : handlers) {
214 return handlers.size();
217 void LoggerDB::registerHandlerFactory(
218 std::unique_ptr<LogHandlerFactory> factory,
219 bool replaceExisting) {
220 auto type = factory->getType();
221 auto handlerInfo = handlerInfo_.wlock();
222 if (replaceExisting) {
223 handlerInfo->factories[type.str()] = std::move(factory);
225 auto ret = handlerInfo->factories.emplace(type.str(), std::move(factory));
227 throw std::range_error(to<std::string>(
228 "a LogHandlerFactory for the type \"", type, "\" already exists"));
233 void LoggerDB::unregisterHandlerFactory(StringPiece type) {
234 auto handlerInfo = handlerInfo_.wlock();
235 auto numRemoved = handlerInfo->factories.erase(type.str());
236 if (numRemoved != 1) {
237 throw std::range_error(
238 to<std::string>("no LogHandlerFactory for type \"", type, "\" found"));
242 LogLevel LoggerDB::xlogInit(
243 StringPiece categoryName,
244 std::atomic<LogLevel>* xlogCategoryLevel,
245 LogCategory** xlogCategory) {
246 // Hold the lock for the duration of the operation
247 // xlogInit() may be called from multiple threads simultaneously.
248 // Only one needs to perform the initialization.
249 auto loggersByName = loggersByName_.wlock();
250 if (xlogCategory != nullptr && *xlogCategory != nullptr) {
251 // The xlogCategory was already initialized before we acquired the lock
252 return (*xlogCategory)->getEffectiveLevel();
255 auto* category = getOrCreateCategoryLocked(*loggersByName, categoryName);
257 // Set *xlogCategory before we update xlogCategoryLevel below.
258 // This is important, since the XLOG() macros check xlogCategoryLevel to
259 // tell if *xlogCategory has been initialized yet.
260 *xlogCategory = category;
262 auto level = category->getEffectiveLevel();
263 xlogCategoryLevel->store(level, std::memory_order_release);
264 category->registerXlogLevel(xlogCategoryLevel);
268 LogCategory* LoggerDB::xlogInitCategory(
269 StringPiece categoryName,
270 LogCategory** xlogCategory,
271 std::atomic<bool>* isInitialized) {
272 // Hold the lock for the duration of the operation
273 // xlogInitCategory() may be called from multiple threads simultaneously.
274 // Only one needs to perform the initialization.
275 auto loggersByName = loggersByName_.wlock();
276 if (isInitialized->load(std::memory_order_acquire)) {
277 // The xlogCategory was already initialized before we acquired the lock
278 return *xlogCategory;
281 auto* category = getOrCreateCategoryLocked(*loggersByName, categoryName);
282 *xlogCategory = category;
283 isInitialized->store(true, std::memory_order_release);
287 std::atomic<LoggerDB::InternalWarningHandler> LoggerDB::warningHandler_;
289 void LoggerDB::internalWarningImpl(
290 folly::StringPiece filename,
292 std::string&& msg) noexcept {
293 auto handler = warningHandler_.load();
295 handler(filename, lineNumber, std::move(msg));
297 defaultInternalWarningImpl(filename, lineNumber, std::move(msg));
301 void LoggerDB::setInternalWarningHandler(InternalWarningHandler handler) {
302 // This API is intentionally pretty basic. It has a number of limitations:
304 // - We only support plain function pointers, and not full std::function
305 // objects. This makes it possible to use std::atomic to access the
306 // handler pointer, and also makes it safe to store in a zero-initialized
307 // file-static pointer.
309 // - We don't support any void* argument to the handler. The caller is
310 // responsible for storing any callback state themselves.
312 // - When replacing or unsetting a handler we don't make any guarantees about
313 // when the old handler will stop being called. It may still be called
314 // from other threads briefly even after setInternalWarningHandler()
315 // returns. This is also a consequence of using std::atomic rather than a
318 // This provides the minimum capabilities needed to customize the handler,
319 // while still keeping the implementation simple and safe to use even before
321 warningHandler_.store(handler);
324 void LoggerDB::defaultInternalWarningImpl(
325 folly::StringPiece filename,
327 std::string&& msg) noexcept {
328 // Rate limit to 10 messages every 5 seconds.
330 // We intentonally use a leaky Meyer's singleton here over folly::Singleton:
331 // - We want this code to work even before main()
332 // - This singleton does not depend on any other singletons.
333 static auto* rateLimiter =
334 new logging::IntervalRateLimiter{10, std::chrono::seconds(5)};
335 if (!rateLimiter->check()) {
339 if (folly::kIsDebug) {
340 // Write directly to file descriptor 2.
342 // It's possible the application has closed fd 2 and is using it for
343 // something other than stderr. However we have no good way to detect
344 // this, which is the main reason we only write to stderr in debug build
345 // modes. assert() also writes directly to stderr on failure, which seems
346 // like a reasonable precedent.
348 // Another option would be to use openlog() and syslog(). However
349 // calling openlog() may inadvertently affect the behavior of other parts
350 // of the program also using syslog().
352 // We don't check for write errors here, since there's not much else we can
354 auto fullMsg = folly::to<std::string>(
355 "logging warning:", filename, ":", lineNumber, ": ", msg, "\n");
356 folly::writeFull(STDERR_FILENO, fullMsg.data(), fullMsg.size());