Line data Source code
1 :
2 : /* Copyright (c) 2005-2017, Stefan Eilemann <eile@equalizergraphics.com>
3 : * Daniel Nachbaur <danielnachbaur@gmail.com>
4 : *
5 : * This library is free software; you can redistribute it and/or modify it under
6 : * the terms of the GNU Lesser General Public License version 2.1 as published
7 : * by the Free Software Foundation.
8 : *
9 : * This library is distributed in the hope that it will be useful, but WITHOUT
10 : * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 : * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12 : * details.
13 : *
14 : * You should have received a copy of the GNU Lesser General Public License
15 : * along with this library; if not, write to the Free Software Foundation, Inc.,
16 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 : */
18 :
19 : #include <pthread.h>
20 :
21 : #include "log.h"
22 :
23 : #include "clock.h"
24 : #include "perThread.h"
25 : #include "scopedMutex.h"
26 : #include "spinLock.h"
27 : #include "thread.h"
28 :
29 : #include <cstdio>
30 : #include <cstdlib>
31 : #include <fstream>
32 :
33 : #ifdef _MSC_VER
34 : #include <process.h>
35 : #define atoll _atoi64
36 : #define snprintf _snprintf
37 : #define getpid _getpid
38 : #else
39 : #include <unistd.h>
40 : #endif
41 :
42 : namespace lunchbox
43 : {
44 : static unsigned getLogTopics();
45 : const size_t LENGTH_PID = 5;
46 : const size_t LENGTH_THREAD = 8;
47 : const size_t LENGTH_FILE = 29;
48 : const size_t LENGTH_TIME = 6;
49 :
50 : namespace
51 : {
52 132 : struct LogTable
53 : {
54 : LogLevel level;
55 : std::string name;
56 : };
57 : #define LOG_TABLE_ENTRY(name) \
58 : { \
59 : LOG_##name, std::string(#name) \
60 : }
61 :
62 22 : struct LogGlobals
63 : {
64 : // clang-format off
65 22 : LogGlobals()
66 22 : : levels{LOG_TABLE_ENTRY(ERROR),
67 : {LOG_ERROR, "WARN"},
68 : LOG_TABLE_ENTRY(INFO),
69 : LOG_TABLE_ENTRY(DEBUG),
70 : LOG_TABLE_ENTRY(VERB),
71 : LOG_TABLE_ENTRY(ALL)}
72 : #ifdef NDEBUG
73 : , stream(&std::cout)
74 : #else
75 : , stream(&std::cerr)
76 : #endif
77 : , file(nullptr)
78 22 : , clock(&defaultClock)
79 : {
80 22 : }
81 : // clang-format on
82 :
83 : LogTable levels[LOG_ALL];
84 : std::ostream* stream;
85 : std::ostream* file;
86 : Clock defaultClock;
87 : const Clock* clock;
88 : SpinLock lock; // The write lock
89 : PerThread<Log> log;
90 : };
91 :
92 19892 : LogGlobals& globals()
93 : {
94 19892 : static LogGlobals global;
95 19893 : return global;
96 : }
97 : }
98 :
99 : namespace detail
100 : {
101 : /** @internal The string buffer used for logging. */
102 : class Log : public std::streambuf
103 : {
104 : public:
105 1670 : explicit Log(std::ostream& stream)
106 1670 : : _indent(0)
107 : , _blocked(0)
108 : , _noHeader(0)
109 : , _newLine(true)
110 1670 : , _stream(stream)
111 : {
112 1670 : _file[0] = 0;
113 1670 : setThreadName("Unknown");
114 1670 : }
115 :
116 3340 : virtual ~Log() {}
117 0 : void indent() { ++_indent; }
118 0 : void exdent() { --_indent; }
119 22 : void disableFlush()
120 : {
121 22 : ++_blocked;
122 22 : assert(_blocked < 100);
123 22 : }
124 22 : void enableFlush()
125 : {
126 22 : assert(_blocked); // Too many enableFlush on log stream
127 22 : --_blocked;
128 22 : }
129 :
130 22 : void disableHeader() { ++_noHeader; } // use counted variable to allow
131 22 : void enableHeader() { --_noHeader; } // nested enable/disable calls
132 3354 : void setThreadName(const std::string& name)
133 : {
134 3354 : LBASSERT(!name.empty());
135 3354 : _thread = name.substr(0, LENGTH_THREAD);
136 3354 : }
137 :
138 0 : const std::string& getThreadName() const { return _thread; }
139 1677 : void setLogInfo(const char* f, const int line)
140 : {
141 1677 : LBASSERT(f);
142 3354 : std::string file(f);
143 1677 : const size_t length = file.length();
144 :
145 1677 : if (length > LENGTH_FILE)
146 1677 : file = file.substr(length - LENGTH_FILE, length);
147 :
148 1677 : snprintf(_file, LENGTH_FILE + 6, "%29s:%-4d", file.c_str(), line);
149 1677 : }
150 :
151 : protected:
152 86005 : int_type overflow(Log::int_type c) override
153 : {
154 86005 : if (c == EOF)
155 0 : return EOF;
156 :
157 86005 : if (_newLine)
158 : {
159 1699 : if (!_noHeader)
160 : {
161 1677 : if (lunchbox::Log::level > LOG_INFO)
162 1677 : _stringStream << std::right << std::setw(LENGTH_PID)
163 3354 : << getpid() << "." << std::left
164 : << std::setw(LENGTH_THREAD) << _thread << " "
165 3354 : << _file << " " << std::right
166 3354 : << std::setw(LENGTH_TIME)
167 3354 : << globals().clock->getTime64() << " ";
168 : else
169 0 : _stringStream << std::right << std::setw(LENGTH_TIME)
170 0 : << globals().clock->getTime64() << " ";
171 : }
172 :
173 1699 : for (int i = 0; i < _indent; ++i)
174 0 : _stringStream << " ";
175 1699 : _newLine = false;
176 : }
177 :
178 86005 : _stringStream << (char)c;
179 86005 : return c;
180 : }
181 :
182 5011 : int sync() override
183 : {
184 5011 : if (!_blocked)
185 : {
186 9978 : const std::string& string = _stringStream.str();
187 : {
188 9978 : ScopedFastWrite mutex(globals().lock);
189 4989 : _stream.write(string.c_str(), string.length());
190 4986 : _stream.rdbuf()->pubsync();
191 : }
192 4986 : _stringStream.str("");
193 : }
194 5008 : _newLine = true;
195 5008 : return 0;
196 : }
197 :
198 : private:
199 : Log(const Log&);
200 : Log& operator=(const Log&);
201 :
202 : /** Short thread name. */
203 : std::string _thread;
204 :
205 : /** The current file logging. */
206 : char _file[35];
207 :
208 : /** The current indentation level. */
209 : int _indent;
210 :
211 : /** Flush reference counter. */
212 : int _blocked;
213 :
214 : /** The header disable counter. */
215 : int _noHeader;
216 :
217 : /** The flag that a new line has started. */
218 : bool _newLine;
219 :
220 : /** The temporary buffer. */
221 : std::ostringstream _stringStream;
222 :
223 : /** The wrapped ostream. */
224 : std::ostream& _stream;
225 : };
226 : }
227 :
228 25 : int Log::level = Log::getLogLevel(getenv("LB_LOG_LEVEL"));
229 25 : unsigned Log::topics = getLogTopics();
230 :
231 1670 : Log::Log()
232 1670 : : std::ostream(new detail::Log(getOutput()))
233 3340 : , impl_(dynamic_cast<detail::Log*>(rdbuf()))
234 : {
235 1670 : }
236 :
237 5010 : Log::~Log()
238 : {
239 1670 : impl_->pubsync();
240 1670 : delete impl_;
241 3340 : }
242 :
243 0 : void Log::indent()
244 : {
245 0 : impl_->indent();
246 0 : }
247 :
248 0 : void Log::exdent()
249 : {
250 0 : impl_->exdent();
251 0 : }
252 :
253 22 : void Log::disableFlush()
254 : {
255 22 : impl_->disableFlush();
256 22 : }
257 :
258 22 : void Log::enableFlush()
259 : {
260 22 : impl_->enableFlush();
261 22 : }
262 :
263 1642 : void Log::forceFlush()
264 : {
265 1642 : impl_->pubsync();
266 1642 : }
267 :
268 22 : void Log::disableHeader()
269 : {
270 22 : impl_->disableHeader();
271 22 : }
272 :
273 22 : void Log::enableHeader()
274 : {
275 22 : impl_->enableHeader();
276 22 : }
277 :
278 1677 : void Log::setLogInfo(const char* file, const int line)
279 : {
280 1677 : impl_->setLogInfo(file, line);
281 1677 : }
282 :
283 1684 : void Log::setThreadName(const std::string& name)
284 : {
285 1684 : impl_->setThreadName(name);
286 1684 : }
287 :
288 0 : const std::string& Log::getThreadName() const
289 : {
290 0 : return impl_->getThreadName();
291 : }
292 :
293 50 : int Log::getLogLevel(const char* text)
294 : {
295 50 : if (text)
296 : {
297 0 : const int num = atoi(text);
298 0 : if (num > 0 && num <= LOG_ALL)
299 0 : return num;
300 :
301 0 : for (uint32_t i = 0; i < LOG_ALL; ++i)
302 0 : if (globals().levels[i].name == text)
303 0 : return globals().levels[i].level;
304 : }
305 :
306 : #ifdef NDEBUG
307 : return LOG_INFO;
308 : #else
309 50 : return LOG_DEBUG;
310 : #endif
311 : }
312 :
313 4 : std::string& Log::getLogLevelString()
314 : {
315 16 : for (uint32_t i = 0; i < LOG_ALL; ++i)
316 16 : if (globals().levels[i].level == level)
317 4 : return globals().levels[i].name;
318 :
319 0 : return globals().levels[0].name;
320 : }
321 :
322 25 : unsigned getLogTopics()
323 : {
324 25 : Log::level = Log::getLogLevel(getenv("LB_LOG_LEVEL"));
325 25 : const char* env = getenv("LB_LOG_TOPICS");
326 :
327 25 : if (env)
328 0 : return atoll(env);
329 :
330 25 : if (Log::level == LOG_ALL)
331 0 : return LOG_ANY;
332 :
333 : #ifdef NDEBUG
334 : return 0;
335 : #else
336 25 : return LOG_BUG;
337 : #endif
338 : }
339 :
340 6636 : Log& Log::instance()
341 : {
342 6636 : Log* log = globals().log.get();
343 6633 : if (!log)
344 : {
345 1670 : log = new Log();
346 1670 : globals().log = log;
347 : static bool first = true;
348 1670 : if (first && lunchbox::Log::level > LOG_INFO)
349 : {
350 22 : first = false;
351 22 : log->disableHeader();
352 22 : log->disableFlush();
353 22 : *log << std::setw(LENGTH_PID) << std::right << "PID"
354 44 : << "." << std::setw(LENGTH_THREAD) << std::left << "Thread "
355 : << "|" << std::setw(LENGTH_FILE + 5) << " Filename:line "
356 44 : << "|" << std::right << std::setw(LENGTH_TIME) << " ms "
357 : << "|"
358 44 : << " Message" << std::endl;
359 22 : log->enableFlush();
360 22 : log->enableHeader();
361 : }
362 : }
363 :
364 6633 : return *log;
365 : }
366 :
367 1677 : Log& Log::instance(const char* file, const int line)
368 : {
369 1677 : Log& log = instance();
370 1677 : log.setLogInfo(file, line);
371 1677 : return log;
372 : }
373 :
374 1647 : void Log::exit()
375 : {
376 1647 : Log* log = globals().log.get();
377 1647 : globals().log = nullptr;
378 1647 : delete log;
379 1647 : }
380 :
381 5 : void Log::reset()
382 : {
383 5 : exit();
384 :
385 5 : delete globals().file;
386 5 : globals().file = nullptr;
387 :
388 : #ifdef NDEBUG
389 : globals().stream = &std::cout;
390 : #else
391 5 : globals().stream = &std::cerr;
392 : #endif
393 5 : }
394 :
395 0 : void Log::setOutput(std::ostream& stream)
396 : {
397 0 : globals().stream = &stream;
398 0 : exit();
399 0 : }
400 :
401 0 : bool Log::setOutput(const std::string& file)
402 : {
403 0 : std::ofstream* newLog = new std::ofstream(file.c_str());
404 :
405 0 : if (!newLog->is_open())
406 : {
407 0 : LBERROR << "Can't open log file " << file << ": " << sysError
408 0 : << std::endl;
409 0 : delete newLog;
410 0 : return false;
411 : }
412 :
413 0 : LBDEBUG << "Redirect log to " << file << std::endl;
414 0 : setOutput(*newLog);
415 :
416 0 : delete globals().file;
417 0 : globals().file = newLog;
418 0 : return true;
419 : }
420 :
421 0 : void Log::setClock(Clock* clock)
422 : {
423 0 : if (clock)
424 0 : globals().clock = clock;
425 : else
426 0 : globals().clock = &globals().defaultClock;
427 0 : }
428 :
429 0 : const Clock& Log::getClock()
430 : {
431 0 : return *globals().clock;
432 : }
433 :
434 1670 : std::ostream& Log::getOutput()
435 : {
436 1670 : return *globals().stream;
437 : }
438 :
439 0 : std::ostream& indent(std::ostream& os)
440 : {
441 0 : Log* log = dynamic_cast<Log*>(&os);
442 0 : if (log)
443 0 : log->indent();
444 0 : return os;
445 : }
446 0 : std::ostream& exdent(std::ostream& os)
447 : {
448 0 : Log* log = dynamic_cast<Log*>(&os);
449 0 : if (log)
450 0 : log->exdent();
451 0 : return os;
452 : }
453 :
454 0 : std::ostream& disableFlush(std::ostream& os)
455 : {
456 0 : Log* log = dynamic_cast<Log*>(&os);
457 0 : if (log)
458 0 : log->disableFlush();
459 0 : return os;
460 : }
461 0 : std::ostream& enableFlush(std::ostream& os)
462 : {
463 0 : Log* log = dynamic_cast<Log*>(&os);
464 0 : if (log)
465 0 : log->enableFlush();
466 0 : return os;
467 : }
468 0 : std::ostream& forceFlush(std::ostream& os)
469 : {
470 0 : Log* log = dynamic_cast<Log*>(&os);
471 0 : if (log)
472 0 : log->forceFlush();
473 0 : return os;
474 : }
475 :
476 0 : std::ostream& disableHeader(std::ostream& os)
477 : {
478 0 : Log* log = dynamic_cast<Log*>(&os);
479 0 : if (log)
480 0 : log->disableHeader();
481 0 : return os;
482 : }
483 0 : std::ostream& enableHeader(std::ostream& os)
484 : {
485 0 : Log* log = dynamic_cast<Log*>(&os);
486 0 : if (log)
487 0 : log->enableHeader();
488 0 : return os;
489 : }
490 75 : }
|