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-2021 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 <cstdlib> // for std::abs()
41 #include <iostream>
42 #include <sstream>
43 #include <stdio.h> // for snprintf()
44 
45 // suppress warning caused by <boost/log/sinks/text_ostream_backend.hpp>
46 #ifdef __clang__
47 #pragma clang diagnostic ignored "-Wundefined-func-template"
48 #endif
49 
50 namespace ndn {
51 namespace util {
52 namespace log {
53 
54 static std::string
55 makeTimestamp()
56 {
57  using namespace ndn::time;
58 
59  const auto sinceEpoch = system_clock::now().time_since_epoch();
60  BOOST_ASSERT(sinceEpoch.count() >= 0);
61  // use abs() to silence truncation warning in snprintf(), see #4365
62  const auto usecs = std::abs(duration_cast<microseconds>(sinceEpoch).count());
63  const auto usecsPerSec = microseconds::period::den;
64 
65  // 10 (whole seconds) + '.' + 6 (fraction) + '\0'
66  std::string buffer(10 + 1 + 6 + 1, '\0'); // note 1 extra byte still needed for snprintf
67  BOOST_ASSERT_MSG(usecs / usecsPerSec <= 9999999999, "whole seconds cannot fit in 10 characters");
68 
69  static_assert(std::is_same<microseconds::rep, int_least64_t>::value,
70  "PRIdLEAST64 is incompatible with microseconds::rep");
71  // std::snprintf unavailable on some platforms, see #2299
72  ::snprintf(&buffer.front(), buffer.size(), "%" PRIdLEAST64 ".%06" PRIdLEAST64,
73  usecs / usecsPerSec, usecs % usecsPerSec);
74 
75  // need to remove extra 1 byte ('\0')
76  buffer.pop_back();
77  return buffer;
78 }
79 
80 BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "Timestamp", std::string)
81 
82 } // namespace log
83 
84 static const LogLevel INITIAL_DEFAULT_LEVEL = LogLevel::NONE;
85 
86 Logging&
87 Logging::get()
88 {
89  // Initialization of block-scope variables with static storage duration is thread-safe.
90  // See ISO C++ standard [stmt.dcl]/4
91  static Logging instance;
92  return instance;
93 }
94 
95 Logging::Logging()
96 {
97 #ifndef __ANDROID__
98  bool wantAutoFlush = std::getenv("NDN_LOG_NOFLUSH") == nullptr;
99  auto destination = makeDefaultStreamDestination(shared_ptr<std::ostream>(&std::clog, [] (auto&&) {}),
100  wantAutoFlush);
101 #else
102  auto destination = detail::makeAndroidLogger();
103 #endif // __ANDROID__
104 
105  // cannot call the static setDestination(), as the singleton object is not yet constructed
106  this->setDestinationImpl(std::move(destination));
107 
108  const char* env = std::getenv("NDN_LOG");
109  if (env != nullptr) {
110  this->setLevelImpl(env);
111  }
112 
113  boost::log::core::get()->add_global_attribute("Timestamp",
114  boost::log::attributes::make_function(&log::makeTimestamp));
115 }
116 
117 void
118 Logging::addLoggerImpl(Logger& logger)
119 {
120  std::lock_guard<std::mutex> lock(m_mutex);
121 
122  const std::string& moduleName = logger.getModuleName();
123  m_loggers.emplace(moduleName, &logger);
124 
125  logger.setLevel(findLevel(moduleName));
126 }
127 
128 void
129 Logging::registerLoggerNameImpl(std::string name)
130 {
131  std::lock_guard<std::mutex> lock(m_mutex);
132  m_loggers.emplace(std::move(name), nullptr);
133 }
134 
135 std::set<std::string>
136 Logging::getLoggerNamesImpl() const
137 {
138  std::lock_guard<std::mutex> lock(m_mutex);
139 
140  std::set<std::string> loggerNames;
141  boost::copy(m_loggers | boost::adaptors::map_keys, std::inserter(loggerNames, loggerNames.end()));
142  return loggerNames;
143 }
144 
145 LogLevel
146 Logging::findLevel(std::string mn) const
147 {
148  while (!mn.empty()) {
149  auto it = m_enabledLevel.find(mn);
150  if (it != m_enabledLevel.end()) {
151  return it->second;
152  }
153  size_t pos = mn.find_last_of('.');
154  if (pos < mn.size() - 1) {
155  mn = mn.substr(0, pos + 1);
156  }
157  else if (pos == mn.size() - 1) {
158  mn.pop_back();
159  pos = mn.find_last_of('.');
160  if (pos != std::string::npos) {
161  mn = mn.substr(0, pos + 1);
162  }
163  else {
164  mn = "";
165  }
166  }
167  else {
168  mn = "";
169  }
170  }
171 
172  auto it = m_enabledLevel.find(mn);
173  return it != m_enabledLevel.end() ? it->second : INITIAL_DEFAULT_LEVEL;
174 }
175 
176 #ifdef NDN_CXX_HAVE_TESTS
177 bool
178 Logging::removeLogger(Logger& logger)
179 {
180  const std::string& moduleName = logger.getModuleName();
181  auto range = m_loggers.equal_range(moduleName);
182  for (auto i = range.first; i != range.second; ++i) {
183  if (i->second == &logger) {
184  m_loggers.erase(i);
185  return true;
186  }
187  }
188  return false;
189 }
190 #endif // NDN_CXX_HAVE_TESTS
191 
192 void
193 Logging::setLevelImpl(const std::string& prefix, LogLevel level)
194 {
195  std::lock_guard<std::mutex> lock(m_mutex);
196 
197  if (prefix.empty() || prefix.back() == '*') {
198  std::string p = prefix;
199  if (!p.empty()) {
200  p.pop_back();
201  }
202 
203  for (auto i = m_enabledLevel.begin(); i != m_enabledLevel.end();) {
204  if (i->first.compare(0, p.size(), p) == 0) {
205  i = m_enabledLevel.erase(i);
206  }
207  else {
208  ++i;
209  }
210  }
211  m_enabledLevel[p] = level;
212 
213  for (const auto& pair : m_loggers) {
214  if (pair.first.compare(0, p.size(), p) == 0 && pair.second != nullptr) {
215  pair.second->setLevel(level);
216  }
217  }
218  }
219  else {
220  m_enabledLevel[prefix] = level;
221  auto range = boost::make_iterator_range(m_loggers.equal_range(prefix));
222  for (const auto& pair : range) {
223  if (pair.second != nullptr) {
224  pair.second->setLevel(level);
225  }
226  }
227  }
228 }
229 
230 void
231 Logging::setLevelImpl(const std::string& config)
232 {
233  std::stringstream ss(config);
234  std::string configModule;
235  while (std::getline(ss, configModule, ':')) {
236  size_t ind = configModule.find('=');
237  if (ind == std::string::npos) {
238  NDN_THROW(std::invalid_argument("malformed logging config: '=' is missing"));
239  }
240 
241  std::string moduleName = configModule.substr(0, ind);
242  LogLevel level = parseLogLevel(configModule.substr(ind + 1));
243  this->setLevelImpl(moduleName, level);
244  }
245 }
246 
247 #ifdef NDN_CXX_HAVE_TESTS
248 void
249 Logging::resetLevels()
250 {
251  this->setLevelImpl("*", INITIAL_DEFAULT_LEVEL);
252  m_enabledLevel.clear();
253 }
254 #endif // NDN_CXX_HAVE_TESTS
255 
256 void
257 Logging::setDestination(std::ostream& os, bool wantAutoFlush)
258 {
259  auto destination = makeDefaultStreamDestination(shared_ptr<std::ostream>(&os, [] (auto&&) {}),
260  wantAutoFlush);
261  setDestination(std::move(destination));
262 }
263 
264 class TextOstreamBackend : public boost::log::sinks::text_ostream_backend
265 {
266 public:
267  TextOstreamBackend(std::shared_ptr<std::ostream> os, bool wantAutoFlush)
268  : m_stdPtr(std::move(os))
269  {
270  auto_flush(wantAutoFlush);
271  add_stream(boost::shared_ptr<std::ostream>(m_stdPtr.get(), [] (auto&&) {}));
272  }
273 
274 private:
275  // Quite a mess right now because Boost.Log uses boost::shared_ptr and we are using
276  // std::shared_ptr. When it is finally fixed, we can remove this mess.
277  std::shared_ptr<std::ostream> m_stdPtr;
278 };
279 
280 boost::shared_ptr<boost::log::sinks::sink>
281 Logging::makeDefaultStreamDestination(shared_ptr<std::ostream> os, bool wantAutoFlush)
282 {
283  auto backend = boost::make_shared<TextOstreamBackend>(std::move(os), wantAutoFlush);
284  auto destination = boost::make_shared<boost::log::sinks::asynchronous_sink<TextOstreamBackend>>(backend);
285 
286  namespace expr = boost::log::expressions;
287  destination->set_formatter(expr::stream
288  << expr::attr<std::string>(log::timestamp.get_name())
289  << " " << std::setw(5) << expr::attr<LogLevel>(log::severity.get_name()) << ": "
290  << "[" << expr::attr<std::string>(log::module.get_name()) << "] "
291  << expr::smessage);
292  return destination;
293 }
294 
295 void
296 Logging::setDestinationImpl(boost::shared_ptr<boost::log::sinks::sink> destination)
297 {
298  std::lock_guard<std::mutex> lock(m_mutex);
299 
300  if (destination == m_destination) {
301  return;
302  }
303 
304  if (m_destination != nullptr) {
305  boost::log::core::get()->remove_sink(m_destination);
306  m_destination->flush();
307  }
308 
309  m_destination = std::move(destination);
310 
311  if (m_destination != nullptr) {
312  boost::log::core::get()->add_sink(m_destination);
313  }
314 }
315 
316 #ifdef NDN_CXX_HAVE_TESTS
317 boost::shared_ptr<boost::log::sinks::sink>
318 Logging::getDestination() const
319 {
320  return m_destination;
321 }
322 
323 void
324 Logging::setLevelImpl(const std::unordered_map<std::string, LogLevel>& prefixRules)
325 {
326  resetLevels();
327  for (const auto& rule : prefixRules) {
328  setLevelImpl(rule.first, rule.second);
329  }
330 }
331 
332 const std::unordered_map<std::string, LogLevel>&
333 Logging::getLevels() const
334 {
335  return m_enabledLevel;
336 }
337 #endif // NDN_CXX_HAVE_TESTS
338 
339 void
340 Logging::flushImpl()
341 {
342  std::lock_guard<std::mutex> lock(m_mutex);
343 
344  if (m_destination != nullptr) {
345  m_destination->flush();
346  }
347 }
348 
349 } // namespace util
350 } // namespace ndn
static void setDestination(boost::shared_ptr< boost::log::sinks::sink > destination)
Set or replace log destination.
Definition: logging.hpp:209
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:281
#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