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