logging.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2013-2022 Regents of the University of California.
4  *
5  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
6  *
7  * ndn-cxx library is free software: you can redistribute it and/or modify it under the
8  * terms of the GNU Lesser General Public License as published by the Free Software
9  * Foundation, either version 3 of the License, or (at your option) any later version.
10  *
11  * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13  * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
14  *
15  * You should have received copies of the GNU General Public License and GNU Lesser
16  * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
17  * <http://www.gnu.org/licenses/>.
18  *
19  * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
20  */
21 
22 #include "ndn-cxx/util/logging.hpp"
23 #include "ndn-cxx/util/logger.hpp"
24 #include "ndn-cxx/util/time.hpp"
25 
26 #ifdef __ANDROID__
27 #include "ndn-cxx/util/impl/logger-android.hpp"
28 #endif
29 
30 #include <boost/log/attributes/function.hpp>
31 #include <boost/log/expressions.hpp>
32 #include <boost/log/expressions/attr.hpp>
33 #include <boost/log/expressions/formatters/date_time.hpp>
34 #include <boost/log/support/date_time.hpp>
35 #include <boost/range/adaptor/map.hpp>
36 #include <boost/range/algorithm/copy.hpp>
37 #include <boost/range/iterator_range.hpp>
38 
39 #include <cinttypes> // for PRIdLEAST64
40 #include <cstdio> // for std::snprintf()
41 #include <cstdlib> // for std::abs()
42 #include <iostream>
43 #include <sstream>
44 
45 namespace ndn {
46 namespace util {
47 namespace log {
48 
49 static std::string
50 makeTimestamp()
51 {
52  using namespace ndn::time;
53 
54  const auto sinceEpoch = system_clock::now().time_since_epoch();
55  BOOST_ASSERT(sinceEpoch.count() >= 0);
56  // use abs() to silence truncation warning in snprintf(), see #4365
57  const auto usecs = std::abs(duration_cast<microseconds>(sinceEpoch).count());
58  const auto usecsPerSec = microseconds::period::den;
59 
60  // 10 (whole seconds) + '.' + 6 (fraction) + '\0'
61  std::string buffer(10 + 1 + 6 + 1, '\0'); // note 1 extra byte still needed for snprintf
62  BOOST_ASSERT_MSG(usecs / usecsPerSec <= 9999999999, "whole seconds cannot fit in 10 characters");
63 
64  static_assert(std::is_same<microseconds::rep, int_least64_t>::value,
65  "PRIdLEAST64 is incompatible with microseconds::rep");
66  std::snprintf(&buffer.front(), buffer.size(), "%" PRIdLEAST64 ".%06" PRIdLEAST64,
67  usecs / usecsPerSec, usecs % usecsPerSec);
68 
69  // need to remove extra 1 byte ('\0')
70  buffer.pop_back();
71  return buffer;
72 }
73 
74 BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "Timestamp", std::string)
75 
76 } // namespace log
77 
78 static const LogLevel INITIAL_DEFAULT_LEVEL = LogLevel::NONE;
79 
80 Logging&
81 Logging::get()
82 {
83  // Initialization of block-scope variables with static storage duration is thread-safe.
84  // See ISO C++ standard [stmt.dcl]/4
85  static Logging instance;
86  return instance;
87 }
88 
89 Logging::Logging()
90 {
91 #ifndef __ANDROID__
92  bool wantAutoFlush = std::getenv("NDN_LOG_NOFLUSH") == nullptr;
93  auto destination = makeDefaultStreamDestination(shared_ptr<std::ostream>(&std::clog, [] (auto&&) {}),
94  wantAutoFlush);
95 #else
96  auto destination = detail::makeAndroidLogger();
97 #endif // __ANDROID__
98 
99  // cannot call the static setDestination(), as the singleton object is not yet constructed
100  this->setDestinationImpl(std::move(destination));
101 
102  const char* env = std::getenv("NDN_LOG");
103  if (env != nullptr) {
104  this->setLevelImpl(env);
105  }
106 
107  boost::log::core::get()->add_global_attribute("Timestamp",
108  boost::log::attributes::make_function(&log::makeTimestamp));
109 }
110 
111 void
112 Logging::addLoggerImpl(Logger& logger)
113 {
114  std::lock_guard<std::mutex> lock(m_mutex);
115 
116  const std::string& moduleName = logger.getModuleName();
117  m_loggers.emplace(moduleName, &logger);
118 
119  logger.setLevel(findLevel(moduleName));
120 }
121 
122 void
123 Logging::registerLoggerNameImpl(std::string name)
124 {
125  std::lock_guard<std::mutex> lock(m_mutex);
126  m_loggers.emplace(std::move(name), nullptr);
127 }
128 
129 std::set<std::string>
130 Logging::getLoggerNamesImpl() const
131 {
132  std::lock_guard<std::mutex> lock(m_mutex);
133 
134  std::set<std::string> loggerNames;
135  boost::copy(m_loggers | boost::adaptors::map_keys, std::inserter(loggerNames, loggerNames.end()));
136  return loggerNames;
137 }
138 
139 LogLevel
140 Logging::findLevel(std::string mn) const
141 {
142  while (!mn.empty()) {
143  auto it = m_enabledLevel.find(mn);
144  if (it != m_enabledLevel.end()) {
145  return it->second;
146  }
147  size_t pos = mn.find_last_of('.');
148  if (pos < mn.size() - 1) {
149  mn = mn.substr(0, pos + 1);
150  }
151  else if (pos == mn.size() - 1) {
152  mn.pop_back();
153  pos = mn.find_last_of('.');
154  if (pos != std::string::npos) {
155  mn = mn.substr(0, pos + 1);
156  }
157  else {
158  mn = "";
159  }
160  }
161  else {
162  mn = "";
163  }
164  }
165 
166  auto it = m_enabledLevel.find(mn);
167  return it != m_enabledLevel.end() ? it->second : INITIAL_DEFAULT_LEVEL;
168 }
169 
170 #ifdef NDN_CXX_HAVE_TESTS
171 bool
172 Logging::removeLogger(Logger& logger)
173 {
174  const std::string& moduleName = logger.getModuleName();
175  auto range = m_loggers.equal_range(moduleName);
176  for (auto i = range.first; i != range.second; ++i) {
177  if (i->second == &logger) {
178  m_loggers.erase(i);
179  return true;
180  }
181  }
182  return false;
183 }
184 #endif // NDN_CXX_HAVE_TESTS
185 
186 void
187 Logging::setLevelImpl(const std::string& prefix, LogLevel level)
188 {
189  std::lock_guard<std::mutex> lock(m_mutex);
190 
191  if (prefix.empty() || prefix.back() == '*') {
192  std::string p = prefix;
193  if (!p.empty()) {
194  p.pop_back();
195  }
196 
197  for (auto i = m_enabledLevel.begin(); i != m_enabledLevel.end();) {
198  if (i->first.compare(0, p.size(), p) == 0) {
199  i = m_enabledLevel.erase(i);
200  }
201  else {
202  ++i;
203  }
204  }
205  m_enabledLevel[p] = level;
206 
207  for (const auto& pair : m_loggers) {
208  if (pair.first.compare(0, p.size(), p) == 0 && pair.second != nullptr) {
209  pair.second->setLevel(level);
210  }
211  }
212  }
213  else {
214  m_enabledLevel[prefix] = level;
215  auto range = boost::make_iterator_range(m_loggers.equal_range(prefix));
216  for (const auto& pair : range) {
217  if (pair.second != nullptr) {
218  pair.second->setLevel(level);
219  }
220  }
221  }
222 }
223 
224 void
225 Logging::setLevelImpl(const std::string& config)
226 {
227  std::stringstream ss(config);
228  std::string configModule;
229  while (std::getline(ss, configModule, ':')) {
230  size_t ind = configModule.find('=');
231  if (ind == std::string::npos) {
232  NDN_THROW(std::invalid_argument("malformed logging config: '=' is missing"));
233  }
234 
235  std::string moduleName = configModule.substr(0, ind);
236  LogLevel level = parseLogLevel(configModule.substr(ind + 1));
237  this->setLevelImpl(moduleName, level);
238  }
239 }
240 
241 #ifdef NDN_CXX_HAVE_TESTS
242 void
243 Logging::resetLevels()
244 {
245  this->setLevelImpl("*", INITIAL_DEFAULT_LEVEL);
246  m_enabledLevel.clear();
247 }
248 #endif // NDN_CXX_HAVE_TESTS
249 
250 void
251 Logging::setDestination(std::ostream& os, bool wantAutoFlush)
252 {
253  auto destination = makeDefaultStreamDestination(shared_ptr<std::ostream>(&os, [] (auto&&) {}),
254  wantAutoFlush);
255  setDestination(std::move(destination));
256 }
257 
258 class TextOstreamBackend : public boost::log::sinks::text_ostream_backend
259 {
260 public:
261  TextOstreamBackend(std::shared_ptr<std::ostream> os, bool wantAutoFlush)
262  : m_stdPtr(std::move(os))
263  {
264  auto_flush(wantAutoFlush);
265  add_stream(boost::shared_ptr<std::ostream>(m_stdPtr.get(), [] (auto&&) {}));
266  }
267 
268 private:
269  // Quite a mess right now because Boost.Log uses boost::shared_ptr and we are using
270  // std::shared_ptr. When it is finally fixed, we can remove this mess.
271  std::shared_ptr<std::ostream> m_stdPtr;
272 };
273 
274 boost::shared_ptr<boost::log::sinks::sink>
275 Logging::makeDefaultStreamDestination(shared_ptr<std::ostream> os, bool wantAutoFlush)
276 {
277  auto backend = boost::make_shared<TextOstreamBackend>(std::move(os), wantAutoFlush);
278  auto destination = boost::make_shared<boost::log::sinks::asynchronous_sink<TextOstreamBackend>>(backend);
279 
280  namespace expr = boost::log::expressions;
281  destination->set_formatter(expr::stream
282  << expr::attr<std::string>(log::timestamp.get_name())
283  << " " << std::setw(5) << expr::attr<LogLevel>(log::severity.get_name()) << ": "
284  << "[" << expr::attr<std::string>(log::module.get_name()) << "] "
285  << expr::smessage);
286  return destination;
287 }
288 
289 void
290 Logging::setDestinationImpl(boost::shared_ptr<boost::log::sinks::sink> destination)
291 {
292  std::lock_guard<std::mutex> lock(m_mutex);
293 
294  if (destination == m_destination) {
295  return;
296  }
297 
298  if (m_destination != nullptr) {
299  boost::log::core::get()->remove_sink(m_destination);
300  m_destination->flush();
301  }
302 
303  m_destination = std::move(destination);
304 
305  if (m_destination != nullptr) {
306  boost::log::core::get()->add_sink(m_destination);
307  }
308 }
309 
310 #ifdef NDN_CXX_HAVE_TESTS
311 boost::shared_ptr<boost::log::sinks::sink>
312 Logging::getDestination() const
313 {
314  return m_destination;
315 }
316 
317 void
318 Logging::setLevelImpl(const std::unordered_map<std::string, LogLevel>& prefixRules)
319 {
320  resetLevels();
321  for (const auto& rule : prefixRules) {
322  setLevelImpl(rule.first, rule.second);
323  }
324 }
325 
326 const std::unordered_map<std::string, LogLevel>&
327 Logging::getLevels() const
328 {
329  return m_enabledLevel;
330 }
331 #endif // NDN_CXX_HAVE_TESTS
332 
333 void
334 Logging::flushImpl()
335 {
336  std::lock_guard<std::mutex> lock(m_mutex);
337 
338  if (m_destination != nullptr) {
339  m_destination->flush();
340  }
341 }
342 
343 } // namespace util
344 } // namespace ndn
static void setDestination(boost::shared_ptr< boost::log::sinks::sink > destination)
Set or replace log destination.
Definition: logging.hpp:212
static boost::shared_ptr< boost::log::sinks::sink > makeDefaultStreamDestination(shared_ptr< std::ostream > os, bool wantAutoFlush=true)
Create stream log destination using default formatting.
Definition: logging.cpp:275
#define NDN_THROW(e)
Definition: exception.hpp:61
constexpr duration< Rep, Period > abs(duration< Rep, Period > d)
Definition: time.hpp:58
LogLevel parseLogLevel(const std::string &s)
Parse LogLevel from a string.
Definition: logger.cpp:56
LogLevel
Indicates the severity level of a log message.
Definition: logger.hpp:42
@ NONE
no messages
Definition: data.cpp:25