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-2019 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  // cannot call the static setDestination that uses the singleton Logging object that is not yet constructed
94  auto destination = makeDefaultStreamDestination(shared_ptr<std::ostream>(&std::clog, [] (auto) {}));
95  this->setDestinationImpl(std::move(destination));
96 
97  const char* environ = std::getenv("NDN_LOG");
98  if (environ != nullptr) {
99  this->setLevelImpl(environ);
100  }
101 
102  boost::log::core::get()->add_global_attribute("Timestamp", boost::log::attributes::make_function(&log::makeTimestamp));
103 }
104 
105 void
106 Logging::addLoggerImpl(Logger& logger)
107 {
108  std::lock_guard<std::mutex> lock(m_mutex);
109 
110  const std::string& moduleName = logger.getModuleName();
111  m_loggers.emplace(moduleName, &logger);
112 
113  logger.setLevel(findLevel(moduleName));
114 }
115 
116 void
117 Logging::registerLoggerNameImpl(std::string name)
118 {
119  std::lock_guard<std::mutex> lock(m_mutex);
120  m_loggers.emplace(std::move(name), nullptr);
121 }
122 
123 std::set<std::string>
124 Logging::getLoggerNamesImpl() const
125 {
126  std::lock_guard<std::mutex> lock(m_mutex);
127 
128  std::set<std::string> loggerNames;
129  boost::copy(m_loggers | boost::adaptors::map_keys, std::inserter(loggerNames, loggerNames.end()));
130  return loggerNames;
131 }
132 
133 LogLevel
134 Logging::findLevel(std::string mn) const
135 {
136  while (!mn.empty()) {
137  auto it = m_enabledLevel.find(mn);
138  if (it != m_enabledLevel.end()) {
139  return it->second;
140  }
141  size_t pos = mn.find_last_of('.');
142  if (pos < mn.size() - 1) {
143  mn = mn.substr(0, pos + 1);
144  }
145  else if (pos == mn.size() - 1) {
146  mn.pop_back();
147  pos = mn.find_last_of('.');
148  if (pos != std::string::npos) {
149  mn = mn.substr(0, pos + 1);
150  }
151  else {
152  mn = "";
153  }
154  }
155  else {
156  mn = "";
157  }
158  }
159 
160  auto it = m_enabledLevel.find(mn);
161  return it != m_enabledLevel.end() ? it->second : INITIAL_DEFAULT_LEVEL;
162 }
163 
164 #ifdef NDN_CXX_HAVE_TESTS
165 bool
166 Logging::removeLogger(Logger& logger)
167 {
168  const std::string& moduleName = logger.getModuleName();
169  auto range = m_loggers.equal_range(moduleName);
170  for (auto i = range.first; i != range.second; ++i) {
171  if (i->second == &logger) {
172  m_loggers.erase(i);
173  return true;
174  }
175  }
176  return false;
177 }
178 #endif // NDN_CXX_HAVE_TESTS
179 
180 void
181 Logging::setLevelImpl(const std::string& prefix, LogLevel level)
182 {
183  std::lock_guard<std::mutex> lock(m_mutex);
184 
185  if (prefix.empty() || prefix.back() == '*') {
186  std::string p = prefix;
187  if (!p.empty()) {
188  p.pop_back();
189  }
190 
191  for (auto i = m_enabledLevel.begin(); i != m_enabledLevel.end();) {
192  if (i->first.compare(0, p.size(), p) == 0) {
193  i = m_enabledLevel.erase(i);
194  }
195  else {
196  ++i;
197  }
198  }
199  m_enabledLevel[p] = level;
200 
201  for (const auto& pair : m_loggers) {
202  if (pair.first.compare(0, p.size(), p) == 0 && pair.second != nullptr) {
203  pair.second->setLevel(level);
204  }
205  }
206  }
207  else {
208  m_enabledLevel[prefix] = level;
209  auto range = boost::make_iterator_range(m_loggers.equal_range(prefix));
210  for (const auto& pair : range) {
211  if (pair.second != nullptr) {
212  pair.second->setLevel(level);
213  }
214  }
215  }
216 }
217 
218 void
219 Logging::setLevelImpl(const std::string& config)
220 {
221  std::stringstream ss(config);
222  std::string configModule;
223  while (std::getline(ss, configModule, ':')) {
224  size_t ind = configModule.find('=');
225  if (ind == std::string::npos) {
226  NDN_THROW(std::invalid_argument("malformed logging config: '=' is missing"));
227  }
228 
229  std::string moduleName = configModule.substr(0, ind);
230  LogLevel level = parseLogLevel(configModule.substr(ind + 1));
231  this->setLevelImpl(moduleName, level);
232  }
233 }
234 
235 #ifdef NDN_CXX_HAVE_TESTS
236 void
237 Logging::resetLevels()
238 {
239  this->setLevelImpl("*", INITIAL_DEFAULT_LEVEL);
240  m_enabledLevel.clear();
241 }
242 #endif // NDN_CXX_HAVE_TESTS
243 
244 void
245 Logging::setDestination(std::ostream& os)
246 {
247  auto destination = makeDefaultStreamDestination(shared_ptr<std::ostream>(&os, [] (auto) {}));
248  setDestination(std::move(destination));
249 }
250 
251 class TextOstreamBackend : public boost::log::sinks::text_ostream_backend
252 {
253 public:
254  TextOstreamBackend(std::shared_ptr<std::ostream> os)
255  : m_stdPtr(std::move(os))
256  {
257  auto_flush(true);
258  add_stream(boost::shared_ptr<std::ostream>(m_stdPtr.get(), [] (auto) {}));
259  }
260 
261 private:
262  // Quite a mess right now because Boost.Log uses boost::shared_ptr and we are using
263  // std::shared_ptr. When it is finally fixed, we can remove this mess.
264  std::shared_ptr<std::ostream> m_stdPtr;
265 };
266 
267 boost::shared_ptr<boost::log::sinks::sink>
268 Logging::makeDefaultStreamDestination(shared_ptr<std::ostream> os)
269 {
270  auto backend = boost::make_shared<TextOstreamBackend>(std::move(os));
271  auto destination = boost::make_shared<boost::log::sinks::asynchronous_sink<TextOstreamBackend>>(backend);
272 
273  namespace expr = boost::log::expressions;
274  destination->set_formatter(expr::stream
275  << expr::attr<std::string>(log::timestamp.get_name())
276  << " " << std::setw(5) << expr::attr<LogLevel>(log::severity.get_name()) << ": "
277  << "[" << expr::attr<std::string>(log::module.get_name()) << "] "
278  << expr::smessage);
279  return destination;
280 }
281 
282 void
283 Logging::setDestinationImpl(boost::shared_ptr<boost::log::sinks::sink> destination)
284 {
285  std::lock_guard<std::mutex> lock(m_mutex);
286 
287  if (destination == m_destination) {
288  return;
289  }
290 
291  if (m_destination != nullptr) {
292  boost::log::core::get()->remove_sink(m_destination);
293  m_destination->flush();
294  }
295 
296  m_destination = std::move(destination);
297 
298  if (m_destination != nullptr) {
299  boost::log::core::get()->add_sink(m_destination);
300  }
301 }
302 
303 #ifdef NDN_CXX_HAVE_TESTS
304 boost::shared_ptr<boost::log::sinks::sink>
305 Logging::getDestination() const
306 {
307  return m_destination;
308 }
309 
310 void
311 Logging::setLevelImpl(const std::unordered_map<std::string, LogLevel>& prefixRules)
312 {
313  resetLevels();
314  for (const auto& rule : prefixRules) {
315  setLevelImpl(rule.first, rule.second);
316  }
317 }
318 
319 const std::unordered_map<std::string, LogLevel>&
320 Logging::getLevels() const
321 {
322  return m_enabledLevel;
323 }
324 #endif // NDN_CXX_HAVE_TESTS
325 
326 void
327 Logging::flushImpl()
328 {
329  std::lock_guard<std::mutex> lock(m_mutex);
330 
331  if (m_destination != nullptr) {
332  m_destination->flush();
333  }
334 }
335 
336 } // namespace util
337 } // 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
R & get(variant< T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 > &v, nonstd::in_place_t(&)(nonstd::detail::in_place_type_tag< R >)=nonstd::in_place_type< R >)
Definition: variant.hpp:1753
static std::string makeTimestamp()
Definition: logging.cpp:51
LogLevel parseLogLevel(const std::string &s)
Parse LogLevel from a string.
Definition: logger.cpp:56
static boost::shared_ptr< boost::log::sinks::sink > makeDefaultStreamDestination(shared_ptr< std::ostream > os)
Create stream log destination using default formatting.
Definition: logging.cpp:268
static const LogLevel INITIAL_DEFAULT_LEVEL
Definition: logging.cpp:80
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:208
void setLevel(LogLevel level)
Definition: logger.hpp:99