Don't try to use _CrtDbgReport in the logging framework
[folly.git] / folly / experimental / logging / LoggerDB.cpp
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 #include <folly/experimental/logging/LoggerDB.h>
17
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>
25
26 namespace folly {
27
28 namespace {
29 class LoggerDBSingleton {
30  public:
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.
37     //
38     // Therefore the main LoggerDB object, and all of the LogCategory objects
39     // it contains, are always intentionally leaked.
40     //
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();
45   }
46
47   LoggerDB* getDB() const {
48     return db_;
49   }
50
51  private:
52   LoggerDB* db_;
53 };
54 }
55
56 LoggerDB* LoggerDB::get() {
57   // Intentionally leaky singleton
58   static LoggerDBSingleton singleton{new LoggerDB()};
59   return singleton.getDB();
60 }
61
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();
66   auto ret =
67       loggersByName_.wlock()->emplace(root->getName(), std::move(rootUptr));
68   DCHECK(ret.second);
69
70   root->setLevelLocked(LogLevel::ERROR, false);
71 }
72
73 LoggerDB::LoggerDB(TestConstructorArg) : LoggerDB() {}
74
75 LogCategory* LoggerDB::getCategory(StringPiece name) {
76   return getOrCreateCategoryLocked(*loggersByName_.wlock(), name);
77 }
78
79 LogCategory* FOLLY_NULLABLE LoggerDB::getCategoryOrNull(StringPiece name) {
80   auto loggersByName = loggersByName_.rlock();
81
82   auto it = loggersByName->find(name);
83   if (it == loggersByName->end()) {
84     return nullptr;
85   }
86   return it->second.get();
87 }
88
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);
93 }
94
95 void LoggerDB::setLevel(LogCategory* category, LogLevel level, bool inherit) {
96   auto loggersByName = loggersByName_.wlock();
97   category->setLevelLocked(level, inherit);
98 }
99
100 std::vector<std::string> LoggerDB::processConfigString(
101     folly::StringPiece config) {
102   std::vector<std::string> errors;
103   if (config.empty()) {
104     return errors;
105   }
106
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) {
112       errors.emplace_back(
113           folly::sformat("missing '=' in logger configuration: \"{}\"", p));
114       continue;
115     }
116
117     auto category = p.subpiece(0, idx);
118     auto level_str = p.subpiece(idx + 1);
119     LogLevel level;
120     try {
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));
125       continue;
126     }
127
128     setLevel(category, level);
129   }
130
131   return errors;
132 }
133
134 LogCategory* LoggerDB::getOrCreateCategoryLocked(
135     LoggerNameMap& loggersByName,
136     StringPiece name) {
137   auto it = loggersByName.find(name);
138   if (it != loggersByName.end()) {
139     return it->second.get();
140   }
141
142   StringPiece parentName = LogName::getParent(name);
143   LogCategory* parent = getOrCreateCategoryLocked(loggersByName, parentName);
144   return createCategoryLocked(loggersByName, name, parent);
145 }
146
147 LogCategory* LoggerDB::createCategoryLocked(
148     LoggerNameMap& loggersByName,
149     StringPiece name,
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));
154   DCHECK(ret.second);
155   return logger;
156 }
157
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
162   // LoggerDB.
163   std::vector<LogCategory*> categories;
164   {
165     auto loggersByName = loggersByName_.wlock();
166     categories.reserve(loggersByName->size());
167     for (const auto& entry : *loggersByName) {
168       categories.push_back(entry.second.get());
169     }
170   }
171
172   for (auto* category : categories) {
173     category->clearHandlers();
174   }
175 }
176
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();
188   }
189
190   auto* category = getOrCreateCategoryLocked(*loggersByName, categoryName);
191   if (xlogCategory) {
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;
196   }
197   auto level = category->getEffectiveLevel();
198   xlogCategoryLevel->store(level, std::memory_order_release);
199   category->registerXlogLevel(xlogCategoryLevel);
200   return level;
201 }
202
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;
214   }
215
216   auto* category = getOrCreateCategoryLocked(*loggersByName, categoryName);
217   *xlogCategory = category;
218   isInitialized->store(true, std::memory_order_release);
219   return category;
220 }
221
222 std::atomic<LoggerDB::InternalWarningHandler> LoggerDB::warningHandler_;
223
224 void LoggerDB::internalWarningImpl(
225     folly::StringPiece filename,
226     int lineNumber,
227     std::string&& msg) noexcept {
228   auto handler = warningHandler_.load();
229   if (handler) {
230     handler(filename, lineNumber, std::move(msg));
231   } else {
232     defaultInternalWarningImpl(filename, lineNumber, std::move(msg));
233   }
234 }
235
236 void LoggerDB::setInternalWarningHandler(InternalWarningHandler handler) {
237   // This API is intentionally pretty basic.  It has a number of limitations:
238   //
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.
243   //
244   // - We don't support any void* argument to the handler.  The caller is
245   //   responsible for storing any callback state themselves.
246   //
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
251   //   full lock.
252   //
253   // This provides the minimum capabilities needed to customize the handler,
254   // while still keeping the implementation simple and safe to use even before
255   // main().
256   warningHandler_.store(handler);
257 }
258
259 void LoggerDB::defaultInternalWarningImpl(
260     folly::StringPiece filename,
261     int lineNumber,
262     std::string&& msg) noexcept {
263   // Rate limit to 10 messages every 5 seconds.
264   //
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()) {
271     return;
272   }
273
274   if (folly::kIsDebug) {
275     // Write directly to file descriptor 2.
276     //
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.
282     //
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().
286     //
287     // We don't check for write errors here, since there's not much else we can
288     // do if it fails.
289     auto fullMsg = folly::to<std::string>(
290         "logging warning:", filename, ":", lineNumber, ": ", msg, "\n");
291     folly::writeFull(STDERR_FILENO, fullMsg.data(), fullMsg.size());
292   }
293 }
294 }