1/*!
2 * Copyright (c) 2015 by Contributors
3 * \file logging.h
4 * \brief defines logging macros of dmlc
5 * allows use of GLOG, fall back to internal
6 * implementation when disabled
7 */
8#ifndef DMLC_LOGGING_H_
9#define DMLC_LOGGING_H_
10#include <cstdio>
11#include <cstdlib>
12#include <string>
13#include <vector>
14#include <stdexcept>
15#include <memory>
16#include "./base.h"
17
18#if DMLC_LOG_STACK_TRACE
19#include <cxxabi.h>
20#include <sstream>
21#include DMLC_EXECINFO_H
22#endif
23
24namespace dmlc {
25/*!
26 * \brief exception class that will be thrown by
27 * default logger if DMLC_LOG_FATAL_THROW == 1
28 */
29struct Error : public std::runtime_error {
30 /*!
31 * \brief constructor
32 * \param s the error message
33 */
34 explicit Error(const std::string &s) : std::runtime_error(s) {}
35};
36
37#if DMLC_LOG_STACK_TRACE
38// get stack trace logging depth from env variable.
39inline size_t LogStackTraceLevel() {
40 size_t level;
41 if (auto var = std::getenv("DMLC_LOG_STACK_TRACE_DEPTH")) {
42 if (1 == sscanf(var, "%zu", &level)) {
43 return level + 1;
44 }
45 }
46 return DMLC_LOG_STACK_TRACE_SIZE;
47}
48
49inline std::string Demangle(char const *msg_str) {
50 using std::string;
51 string msg(msg_str);
52 size_t symbol_start = string::npos;
53 size_t symbol_end = string::npos;
54 if ( ((symbol_start = msg.find("_Z")) != string::npos)
55 && (symbol_end = msg.find_first_of(" +", symbol_start)) ) {
56 string left_of_symbol(msg, 0, symbol_start);
57 string symbol(msg, symbol_start, symbol_end - symbol_start);
58 string right_of_symbol(msg, symbol_end);
59
60 int status = 0;
61 size_t length = string::npos;
62 std::unique_ptr<char, void (*)(void *__ptr)> demangled_symbol =
63 {abi::__cxa_demangle(symbol.c_str(), 0, &length, &status), &std::free};
64 if (demangled_symbol && status == 0 && length > 0) {
65 string symbol_str(demangled_symbol.get());
66 std::ostringstream os;
67 os << left_of_symbol << symbol_str << right_of_symbol;
68 return os.str();
69 }
70 }
71 return string(msg_str);
72}
73
74// By default skip the first frame because
75// that belongs to ~LogMessageFatal
76inline std::string StackTrace(
77 size_t start_frame = 1,
78 const size_t stack_size = DMLC_LOG_STACK_TRACE_SIZE) {
79 using std::string;
80 std::ostringstream stacktrace_os;
81 std::vector<void*> stack(stack_size);
82 int nframes = backtrace(stack.data(), static_cast<int>(stack_size));
83 if (start_frame < static_cast<size_t>(nframes)) {
84 stacktrace_os << "Stack trace:\n";
85 }
86 char **msgs = backtrace_symbols(stack.data(), nframes);
87 if (msgs != nullptr) {
88 for (int frameno = start_frame; frameno < nframes; ++frameno) {
89 string msg = dmlc::Demangle(msgs[frameno]);
90 stacktrace_os << " [bt] (" << frameno - start_frame << ") " << msg << "\n";
91 }
92 }
93 free(msgs);
94 string stack_trace = stacktrace_os.str();
95 return stack_trace;
96}
97
98#else // DMLC_LOG_STACK_TRACE is off
99
100inline size_t LogStackTraceLevel() {
101 return 0;
102}
103
104inline std::string demangle(char const* msg_str) {
105 return std::string();
106}
107
108inline std::string StackTrace(size_t start_frame = 1,
109 const size_t stack_size = 0) {
110 return std::string("Stack trace not available when "
111 "DMLC_LOG_STACK_TRACE is disabled at compile time.");
112}
113
114#endif // DMLC_LOG_STACK_TRACE
115} // namespace dmlc
116
117#if DMLC_USE_GLOG
118#include <glog/logging.h>
119
120namespace dmlc {
121/*!
122 * \brief optionally redirect to google's init log
123 * \param argv0 The arguments.
124 */
125inline void InitLogging(const char* argv0) {
126 google::InitGoogleLogging(argv0);
127}
128} // namespace dmlc
129
130#elif defined DMLC_USE_LOGGING_LIBRARY
131
132#include DMLC_USE_LOGGING_LIBRARY
133namespace dmlc {
134inline void InitLogging(const char*) {
135 // DO NOTHING
136}
137}
138
139#else
140// use a light version of glog
141#include <assert.h>
142#include <iostream>
143#include <sstream>
144#include <ctime>
145
146#if defined(_MSC_VER)
147#pragma warning(disable : 4722)
148#pragma warning(disable : 4068)
149#endif
150
151namespace dmlc {
152inline void InitLogging(const char*) {
153 // DO NOTHING
154}
155
156// get debug option from env variable.
157inline bool DebugLoggingEnabled() {
158 static int state = 0;
159 if (state == 0) {
160 if (auto var = std::getenv("DMLC_LOG_DEBUG")) {
161 if (std::string(var) == "1") {
162 state = 1;
163 } else {
164 state = -1;
165 }
166 } else {
167 // by default hide debug logging.
168 state = -1;
169 }
170 }
171 return state == 1;
172}
173
174#ifndef DMLC_GLOG_DEFINED
175
176template <typename X, typename Y>
177std::unique_ptr<std::string> LogCheckFormat(const X& x, const Y& y) {
178 std::ostringstream os;
179 os << " (" << x << " vs. " << y << ") "; /* CHECK_XX(x, y) requires x and y can be serialized to string. Use CHECK(x OP y) otherwise. NOLINT(*) */
180 // no std::make_unique until c++14
181 return std::unique_ptr<std::string>(new std::string(os.str()));
182}
183
184// This function allows us to ignore sign comparison in the right scope.
185#define DEFINE_CHECK_FUNC(name, op) \
186 template <typename X, typename Y> \
187 DMLC_ALWAYS_INLINE std::unique_ptr<std::string> LogCheck##name(const X& x, const Y& y) { \
188 if (x op y) return nullptr; \
189 return LogCheckFormat(x, y); \
190 } \
191 DMLC_ALWAYS_INLINE std::unique_ptr<std::string> LogCheck##name(int x, int y) { \
192 return LogCheck##name<int, int>(x, y); \
193 }
194
195#pragma GCC diagnostic push
196#pragma GCC diagnostic ignored "-Wsign-compare"
197DEFINE_CHECK_FUNC(_LT, <)
198DEFINE_CHECK_FUNC(_GT, >)
199DEFINE_CHECK_FUNC(_LE, <=)
200DEFINE_CHECK_FUNC(_GE, >=)
201DEFINE_CHECK_FUNC(_EQ, ==)
202DEFINE_CHECK_FUNC(_NE, !=)
203#pragma GCC diagnostic pop
204
205#define CHECK_BINARY_OP(name, op, x, y) \
206 if (auto __dmlc__log__err = dmlc::LogCheck##name(x, y)) \
207 dmlc::LogMessageFatal(__FILE__, __LINE__).stream() \
208 << "Check failed: " << #x " " #op " " #y << *__dmlc__log__err << ": "
209
210// Always-on checking
211#define CHECK(x) \
212 if (!(x)) \
213 dmlc::LogMessageFatal(__FILE__, __LINE__).stream() \
214 << "Check failed: " #x << ": "
215#define CHECK_LT(x, y) CHECK_BINARY_OP(_LT, <, x, y)
216#define CHECK_GT(x, y) CHECK_BINARY_OP(_GT, >, x, y)
217#define CHECK_LE(x, y) CHECK_BINARY_OP(_LE, <=, x, y)
218#define CHECK_GE(x, y) CHECK_BINARY_OP(_GE, >=, x, y)
219#define CHECK_EQ(x, y) CHECK_BINARY_OP(_EQ, ==, x, y)
220#define CHECK_NE(x, y) CHECK_BINARY_OP(_NE, !=, x, y)
221#define CHECK_NOTNULL(x) \
222 ((x) == NULL ? dmlc::LogMessageFatal(__FILE__, __LINE__).stream() << "Check notnull: " #x << ' ', (x) : (x)) // NOLINT(*)
223
224// Debug-only checking.
225#if DMLC_LOG_DEBUG
226#define DCHECK(x) \
227 while (false) CHECK(x)
228#define DCHECK_LT(x, y) \
229 while (false) CHECK((x) < (y))
230#define DCHECK_GT(x, y) \
231 while (false) CHECK((x) > (y))
232#define DCHECK_LE(x, y) \
233 while (false) CHECK((x) <= (y))
234#define DCHECK_GE(x, y) \
235 while (false) CHECK((x) >= (y))
236#define DCHECK_EQ(x, y) \
237 while (false) CHECK((x) == (y))
238#define DCHECK_NE(x, y) \
239 while (false) CHECK((x) != (y))
240#else
241#define DCHECK(x) CHECK(x)
242#define DCHECK_LT(x, y) CHECK((x) < (y))
243#define DCHECK_GT(x, y) CHECK((x) > (y))
244#define DCHECK_LE(x, y) CHECK((x) <= (y))
245#define DCHECK_GE(x, y) CHECK((x) >= (y))
246#define DCHECK_EQ(x, y) CHECK((x) == (y))
247#define DCHECK_NE(x, y) CHECK((x) != (y))
248#endif // DMLC_LOG_DEBUG
249
250#if DMLC_LOG_CUSTOMIZE
251#define LOG_INFO dmlc::CustomLogMessage(__FILE__, __LINE__)
252#else
253#define LOG_INFO dmlc::LogMessage(__FILE__, __LINE__)
254#endif
255#define LOG_ERROR LOG_INFO
256#define LOG_WARNING LOG_INFO
257#define LOG_FATAL dmlc::LogMessageFatal(__FILE__, __LINE__)
258#define LOG_QFATAL LOG_FATAL
259
260// Poor man version of VLOG
261#define VLOG(x) LOG_INFO.stream()
262
263#define LOG(severity) LOG_##severity.stream()
264#define LG LOG_INFO.stream()
265#define LOG_IF(severity, condition) \
266 !(condition) ? (void)0 : dmlc::LogMessageVoidify() & LOG(severity)
267
268#if DMLC_LOG_DEBUG
269
270#define LOG_DFATAL LOG_FATAL
271#define DFATAL FATAL
272#define DLOG(severity) LOG_IF(severity, ::dmlc::DebugLoggingEnabled())
273#define DLOG_IF(severity, condition) LOG_IF(severity, ::dmlc::DebugLoggingEnabled() && (condition))
274
275#else
276
277#define LOG_DFATAL LOG_ERROR
278#define DFATAL ERROR
279#define DLOG(severity) true ? (void)0 : dmlc::LogMessageVoidify() & LOG(severity)
280#define DLOG_IF(severity, condition) \
281 (true || !(condition)) ? (void)0 : dmlc::LogMessageVoidify() & LOG(severity)
282#endif
283
284// Poor man version of LOG_EVERY_N
285#define LOG_EVERY_N(severity, n) LOG(severity)
286
287#endif // DMLC_GLOG_DEFINED
288
289class DateLogger {
290 public:
291 DateLogger() {
292#if defined(_MSC_VER)
293 _tzset();
294#endif
295 }
296 const char* HumanDate() {
297#if !defined(_LIBCPP_SGX_CONFIG) && DMLC_LOG_NODATE == 0
298#if defined(_MSC_VER)
299 _strtime_s(buffer_, sizeof(buffer_));
300#else
301 time_t time_value = time(NULL);
302 struct tm *pnow;
303#if !defined(_WIN32)
304 struct tm now;
305 pnow = localtime_r(&time_value, &now);
306#else
307 pnow = localtime(&time_value); // NOLINT(*)
308#endif
309 snprintf(buffer_, sizeof(buffer_), "%02d:%02d:%02d",
310 pnow->tm_hour, pnow->tm_min, pnow->tm_sec);
311#endif
312 return buffer_;
313#else
314 return "";
315#endif // _LIBCPP_SGX_CONFIG
316 }
317
318 private:
319 char buffer_[9];
320};
321
322#ifndef _LIBCPP_SGX_NO_IOSTREAMS
323class LogMessage {
324 public:
325 LogMessage(const char* file, int line)
326 :
327#ifdef __ANDROID__
328 log_stream_(std::cout)
329#else
330 log_stream_(std::cerr)
331#endif
332 {
333 log_stream_ << "[" << pretty_date_.HumanDate() << "] " << file << ":"
334 << line << ": ";
335 }
336 ~LogMessage() { log_stream_ << '\n'; }
337 std::ostream& stream() { return log_stream_; }
338
339 protected:
340 std::ostream& log_stream_;
341
342 private:
343 DateLogger pretty_date_;
344 LogMessage(const LogMessage&);
345 void operator=(const LogMessage&);
346};
347
348// customized logger that can allow user to define where to log the message.
349class CustomLogMessage {
350 public:
351 CustomLogMessage(const char* file, int line) {
352 log_stream_ << "[" << DateLogger().HumanDate() << "] " << file << ":"
353 << line << ": ";
354 }
355 ~CustomLogMessage() {
356 Log(log_stream_.str());
357 }
358 std::ostream& stream() { return log_stream_; }
359 /*!
360 * \brief customized logging of the message.
361 * This function won't be implemented by libdmlc
362 * \param msg The message to be logged.
363 */
364 static void Log(const std::string& msg);
365
366 private:
367 std::ostringstream log_stream_;
368};
369#else
370class DummyOStream {
371 public:
372 template <typename T>
373 DummyOStream& operator<<(T _) { return *this; }
374 inline std::string str() { return ""; }
375};
376class LogMessage {
377 public:
378 LogMessage(const char* file, int line) : log_stream_() {}
379 DummyOStream& stream() { return log_stream_; }
380
381 protected:
382 DummyOStream log_stream_;
383
384 private:
385 LogMessage(const LogMessage&);
386 void operator=(const LogMessage&);
387};
388#endif
389
390
391#if defined(_LIBCPP_SGX_NO_IOSTREAMS)
392class LogMessageFatal : public LogMessage {
393 public:
394 LogMessageFatal(const char* file, int line) : LogMessage(file, line) {}
395 ~LogMessageFatal() {
396 abort();
397 }
398 private:
399 LogMessageFatal(const LogMessageFatal&);
400 void operator=(const LogMessageFatal&);
401};
402#elif DMLC_LOG_FATAL_THROW == 0
403class LogMessageFatal : public LogMessage {
404 public:
405 LogMessageFatal(const char* file, int line) : LogMessage(file, line) {}
406 ~LogMessageFatal() {
407 log_stream_ << "\n" << StackTrace(1, LogStackTraceLevel()) << "\n";
408 abort();
409 }
410
411 private:
412 LogMessageFatal(const LogMessageFatal&);
413 void operator=(const LogMessageFatal&);
414};
415#else
416class LogMessageFatal {
417 public:
418 LogMessageFatal(const char *file, int line) {
419 GetEntry().Init(file, line);
420 }
421 std::ostringstream &stream() { return GetEntry().log_stream; }
422 DMLC_NO_INLINE ~LogMessageFatal() DMLC_THROW_EXCEPTION {
423#if DMLC_LOG_STACK_TRACE
424 GetEntry().log_stream << "\n"
425 << StackTrace(1, LogStackTraceLevel())
426 << "\n";
427#endif
428 throw GetEntry().Finalize();
429 }
430
431 private:
432 struct Entry {
433 std::ostringstream log_stream;
434 DMLC_NO_INLINE void Init(const char *file, int line) {
435 DateLogger date;
436 log_stream.str("");
437 log_stream.clear();
438 log_stream << "[" << date.HumanDate() << "] " << file << ":" << line
439 << ": ";
440 }
441 dmlc::Error Finalize() {
442#if DMLC_LOG_BEFORE_THROW
443 LOG(ERROR) << log_stream.str();
444#endif
445 return dmlc::Error(log_stream.str());
446 }
447 // Due to a bug in MinGW, objects with non-trivial destructor cannot be thread-local.
448 // See https://sourceforge.net/p/mingw-w64/bugs/527/
449 // Hence, don't use thread-local for the log stream if the compiler is MinGW.
450#if !(defined(__MINGW32__) || defined(__MINGW64__))
451 DMLC_NO_INLINE static Entry& ThreadLocal() {
452 static thread_local Entry result;
453 return result;
454 }
455#endif
456 };
457 LogMessageFatal(const LogMessageFatal &);
458 void operator=(const LogMessageFatal &);
459
460#if defined(__MINGW32__) || defined(__MINGW64__)
461 DMLC_NO_INLINE Entry& GetEntry() {
462 return entry_;
463 }
464
465 Entry entry_;
466#else
467 DMLC_NO_INLINE Entry& GetEntry() {
468 return Entry::ThreadLocal();
469 }
470#endif
471};
472#endif
473
474// This class is used to explicitly ignore values in the conditional
475// logging macros. This avoids compiler warnings like "value computed
476// is not used" and "statement has no effect".
477class LogMessageVoidify {
478 public:
479 LogMessageVoidify() {}
480 // This has to be an operator with a precedence lower than << but
481 // higher than "?:". See its usage.
482#if !defined(_LIBCPP_SGX_NO_IOSTREAMS)
483 void operator&(std::ostream&) {}
484#endif
485};
486
487} // namespace dmlc
488
489#endif
490#endif // DMLC_LOGGING_H_
491