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 | |
24 | namespace dmlc { |
25 | /*! |
26 | * \brief exception class that will be thrown by |
27 | * default logger if DMLC_LOG_FATAL_THROW == 1 |
28 | */ |
29 | struct 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. |
39 | inline 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 | |
49 | inline 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 |
76 | inline 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 | |
100 | inline size_t LogStackTraceLevel() { |
101 | return 0; |
102 | } |
103 | |
104 | inline std::string demangle(char const* msg_str) { |
105 | return std::string(); |
106 | } |
107 | |
108 | inline 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 | |
120 | namespace dmlc { |
121 | /*! |
122 | * \brief optionally redirect to google's init log |
123 | * \param argv0 The arguments. |
124 | */ |
125 | inline 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 |
133 | namespace dmlc { |
134 | inline 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 | |
151 | namespace dmlc { |
152 | inline void InitLogging(const char*) { |
153 | // DO NOTHING |
154 | } |
155 | |
156 | // get debug option from env variable. |
157 | inline 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 | |
176 | template <typename X, typename Y> |
177 | std::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" |
197 | DEFINE_CHECK_FUNC(_LT, <) |
198 | DEFINE_CHECK_FUNC(_GT, >) |
199 | DEFINE_CHECK_FUNC(_LE, <=) |
200 | DEFINE_CHECK_FUNC(_GE, >=) |
201 | DEFINE_CHECK_FUNC(_EQ, ==) |
202 | DEFINE_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 | |
289 | class 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 |
323 | class 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. |
349 | class 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 |
370 | class DummyOStream { |
371 | public: |
372 | template <typename T> |
373 | DummyOStream& operator<<(T _) { return *this; } |
374 | inline std::string str() { return "" ; } |
375 | }; |
376 | class 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) |
392 | class 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 |
403 | class 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 |
416 | class 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". |
477 | class 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 | |