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-2020 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 #include <boost/log/attributes/function.hpp>
27 #include <boost/log/expressions.hpp>
28 #include <boost/log/expressions/attr.hpp>
29 #include <boost/log/expressions/formatters/date_time.hpp>
30 #include <boost/log/support/date_time.hpp>
31 #include <boost/range/adaptor/map.hpp>
32 #include <boost/range/algorithm/copy.hpp>
33 #include <boost/range/iterator_range.hpp>
34 
35 #include <cinttypes> // for PRIdLEAST64
36 #include <cstdlib> // for std::abs()
37 #include <iostream>
38 #include <sstream>
39 #include <stdio.h> // for snprintf()
40 
41 // suppress warning caused by <boost/log/sinks/text_ostream_backend.hpp>
42 #ifdef __clang__
43 #pragma clang diagnostic ignored "-Wundefined-func-template"
44 #endif
45 
46 namespace ndn {
47 namespace util {
48 namespace log {
49 
50 static std::string
52 {
53  using namespace ndn::time;
54 
55  const auto sinceEpoch = system_clock::now().time_since_epoch();
56  BOOST_ASSERT(sinceEpoch.count() >= 0);
57  // use abs() to silence truncation warning in snprintf(), see #4365
58  const auto usecs = std::abs(duration_cast<microseconds>(sinceEpoch).count());
59  const auto usecsPerSec = microseconds::period::den;
60 
61  // 10 (whole seconds) + '.' + 6 (fraction) + '\0'
62  std::string buffer(10 + 1 + 6 + 1, '\0'); // note 1 extra byte still needed for snprintf
63  BOOST_ASSERT_MSG(usecs / usecsPerSec <= 9999999999, "whole seconds cannot fit in 10 characters");
64 
65  static_assert(std::is_same<microseconds::rep, int_least64_t>::value,
66  "PRIdLEAST64 is incompatible with microseconds::rep");
67  // std::snprintf unavailable on some platforms, see #2299
68  ::snprintf(&buffer.front(), buffer.size(), "%" PRIdLEAST64 ".%06" PRIdLEAST64,
69  usecs / usecsPerSec, usecs % usecsPerSec);
70 
71  // need to remove extra 1 byte ('\0')
72  buffer.pop_back();
73  return buffer;
74 }
75 
76 BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "Timestamp", std::string)
77 
78 } // namespace log
79 
81 
82 Logging&
83 Logging::get()
84 {
85  // Initialization of block-scope variables with static storage duration is thread-safe.
86  // See ISO C++ standard [stmt.dcl]/4
87  static Logging instance;
88  return instance;
89 }
90 
91 Logging::Logging()
92 {
93  bool wantAutoFlush = true;
94  const char* environ = std::getenv("NDN_LOG_NOFLUSH");
95  if (environ != nullptr) {
96  wantAutoFlush = false;
97  }
98 
99  // cannot call the static setDestination that uses the singleton Logging object that is not yet constructed
100  auto destination = makeDefaultStreamDestination(shared_ptr<std::ostream>(&std::clog, [] (auto) {}), wantAutoFlush);
101  this->setDestinationImpl(std::move(destination));
102 
103  environ = std::getenv("NDN_LOG");
104  if (environ != nullptr) {
105  this->setLevelImpl(environ);
106  }
107 
108  boost::log::core::get()->add_global_attribute("Timestamp", 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) {}), wantAutoFlush);
254  setDestination(std::move(destination));
255 }
256 
257 class TextOstreamBackend : public boost::log::sinks::text_ostream_backend
258 {
259 public:
260  TextOstreamBackend(std::shared_ptr<std::ostream> os, bool wantAutoFlush)
261  : m_stdPtr(std::move(os))
262  {
263  auto_flush(wantAutoFlush);
264  add_stream(boost::shared_ptr<std::ostream>(m_stdPtr.get(), [] (auto) {}));
265  }
266 
267 private:
268  // Quite a mess right now because Boost.Log uses boost::shared_ptr and we are using
269  // std::shared_ptr. When it is finally fixed, we can remove this mess.
270  std::shared_ptr<std::ostream> m_stdPtr;
271 };
272 
273 boost::shared_ptr<boost::log::sinks::sink>
274 Logging::makeDefaultStreamDestination(shared_ptr<std::ostream> os, bool wantAutoFlush)
275 {
276  auto backend = boost::make_shared<TextOstreamBackend>(std::move(os), wantAutoFlush);
277  auto destination = boost::make_shared<boost::log::sinks::asynchronous_sink<TextOstreamBackend>>(backend);
278 
279  namespace expr = boost::log::expressions;
280  destination->set_formatter(expr::stream
281  << expr::attr<std::string>(log::timestamp.get_name())
282  << " " << std::setw(5) << expr::attr<LogLevel>(log::severity.get_name()) << ": "
283  << "[" << expr::attr<std::string>(log::module.get_name()) << "] "
284  << expr::smessage);
285  return destination;
286 }
287 
288 void
289 Logging::setDestinationImpl(boost::shared_ptr<boost::log::sinks::sink> destination)
290 {
291  std::lock_guard<std::mutex> lock(m_mutex);
292 
293  if (destination == m_destination) {
294  return;
295  }
296 
297  if (m_destination != nullptr) {
298  boost::log::core::get()->remove_sink(m_destination);
299  m_destination->flush();
300  }
301 
302  m_destination = std::move(destination);
303 
304  if (m_destination != nullptr) {
305  boost::log::core::get()->add_sink(m_destination);
306  }
307 }
308 
309 #ifdef NDN_CXX_HAVE_TESTS
310 boost::shared_ptr<boost::log::sinks::sink>
311 Logging::getDestination() const
312 {
313  return m_destination;
314 }
315 
316 void
317 Logging::setLevelImpl(const std::unordered_map<std::string, LogLevel>& prefixRules)
318 {
319  resetLevels();
320  for (const auto& rule : prefixRules) {
321  setLevelImpl(rule.first, rule.second);
322  }
323 }
324 
325 const std::unordered_map<std::string, LogLevel>&
326 Logging::getLevels() const
327 {
328  return m_enabledLevel;
329 }
330 #endif // NDN_CXX_HAVE_TESTS
331 
332 void
333 Logging::flushImpl()
334 {
335  std::lock_guard<std::mutex> lock(m_mutex);
336 
337  if (m_destination != nullptr) {
338  m_destination->flush();
339  }
340 }
341 
342 } // namespace util
343 } // namespace ndn
Controls the logging facility.
Definition: logging.hpp:46
Definition: data.cpp:26
constexpr duration< Rep, Period > abs(duration< Rep, Period > d)
Definition: time.hpp:50
#define NDN_THROW(e)
Definition: exception.hpp:61
LogLevel
Indicates the severity level of a log message.
Definition: logger.hpp:42
static std::string makeTimestamp()
Definition: logging.cpp:51
LogLevel parseLogLevel(const std::string &s)
Parse LogLevel from a string.
Definition: logger.cpp:56
static const LogLevel INITIAL_DEFAULT_LEVEL
Definition: logging.cpp:80
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:274
const std::string & getModuleName() const
Definition: logger.hpp:87
Represents a log module in the logging facility.
Definition: logger.hpp:77
static void setDestination(boost::shared_ptr< boost::log::sinks::sink > destination)
Set or replace log destination.
Definition: logging.hpp:209
void setLevel(LogLevel level)
Definition: logger.hpp:99