hydrogen 1.2.6
Logger.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
23#include "core/Logger.h"
25#include <core/Version.h>
26
27#include <cstdio>
28#include <chrono>
29#include <thread>
30#include <QtCore/QDir>
31#include <QDateTime>
32#include <QFile>
33#include <QTextStream>
34
35#ifdef H2CORE_HAVE_QT6
36 #include <QStringConverter>
37#else
38 #include <QTextCodec>
39#endif
40
41#ifdef WIN32
42#include <windows.h>
43#endif
44
45namespace H2Core {
46
47unsigned Logger::__bit_msk = 0;
49const char* Logger::__levels[] = { "None", "Error", "Warning", "Info", "Debug", "Constructors", "Locks" };
50thread_local QString *Logger::pCrashContext = nullptr;
51
52pthread_t loggerThread;
53
54void* loggerThread_func( void* param ) {
55 if ( param == nullptr ) {
56 return nullptr;
57 }
58 Logger* pLogger = ( Logger* )param;
59#ifdef WIN32
60# ifdef H2CORE_HAVE_DEBUG
61 ::AllocConsole();
62// ::SetConsoleTitle( "Hydrogen debug log" );
63 SetConsoleOutputCP( CP_UTF8 );
64 freopen( "CONOUT$", "wt", stdout );
65# endif
66#endif
67
68 QTextStream stdoutStream( stdout );
69 QTextStream stderrStream( stderr );
70#ifdef H2CORE_HAVE_QT6
71 stdoutStream.setEncoding( QStringConverter::Utf8 );
72 stderrStream.setEncoding( QStringConverter::Utf8 );
73#else
74 stdoutStream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
75 stderrStream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
76#endif
77
78 bool bUseLogFile = true;
79 QFile logFile( pLogger->m_sLogFilePath );
80 QTextStream logFileStream = QTextStream();
81 if ( logFile.open( QIODevice::WriteOnly | QIODevice::Text ) ) {
82 logFileStream.setDevice( &logFile );
83#ifdef H2CORE_HAVE_QT6
84 logFileStream.setEncoding( QStringConverter::Utf8 );
85#else
86 logFileStream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
87#endif
88 }
89 else {
90 stderrStream <<
91 QString( "Error: can't open log file [%1] for writing...\n" )
92 .arg( pLogger->m_sLogFilePath );
93 stderrStream.flush();
94 bUseLogFile = false;
95 }
96 Logger::queue_t* queue = &pLogger->__msg_queue;
97 Logger::queue_t::iterator it, last;
98
99 while ( pLogger->__running ) {
100 pthread_mutex_lock( &pLogger->__mutex );
101 pthread_cond_wait( &pLogger->__messages_available, &pLogger->__mutex );
102 pthread_mutex_unlock( &pLogger->__mutex );
103 if ( !queue->empty() ) {
104 for ( it = last = queue->begin() ; it != queue->end() ; ++it ) {
105 last = it;
106 if ( pLogger->m_bUseStdout ) {
107 stdoutStream << *it;
108 stdoutStream.flush();
109 }
110 if ( bUseLogFile ) {
111 logFileStream << *it;
112 logFileStream.flush();
113 }
114 }
115 // remove all in front of last
116 pthread_mutex_lock( &pLogger->__mutex );
117 queue->erase( queue->begin(), last );
118 queue->pop_front();
119 pthread_mutex_unlock( &pLogger->__mutex );
120 }
121 }
122 if ( bUseLogFile ) {
123 logFileStream << "Stop logger";
124 }
125 logFile.close();
126#ifdef WIN32
127 ::FreeConsole();
128#endif
129
130 stderrStream.flush();
131 stdoutStream.flush();
132 pthread_exit( nullptr );
133 return nullptr;
134}
135
136Logger* Logger::bootstrap( unsigned msk, const QString& sLogFilePath,
137 bool bUseStdout, bool bLogTimestamps,
138 bool bLogColors ) {
140
141 // When starting Hydrogen after a fresh install with no user-level .hydrogen
142 // folder, opening the log file will fail as the .hydrogen folder as whole
143 // does not exist yet. It is created as part of the bootstrap of
144 // `Filesystem`.
145 QFileInfo logFileInfo;
146 if ( ! sLogFilePath.isEmpty() ) {
147 logFileInfo = QFileInfo( sLogFilePath );
148 }
149 else {
150 logFileInfo = QFileInfo( Filesystem::log_file_path() );
151 }
152 const auto dir = logFileInfo.absoluteDir();
153 if ( ! dir.exists() ) {
154 Filesystem::mkdir( dir.absolutePath() );
155 }
156
157 return Logger::create_instance( sLogFilePath, bUseStdout, bLogTimestamps,
158 bLogColors );
159}
160
161Logger* Logger::create_instance( const QString& sLogFilePath, bool bUseStdout,
162 bool bLogTimestamps, bool bLogColors ) {
163 if ( __instance == nullptr ) {
164 __instance = new Logger(
165 sLogFilePath, bUseStdout, bLogTimestamps, bLogColors );
166 }
167 return __instance;
168}
169
170Logger::Logger( const QString& sLogFilePath, bool bUseStdout,
171 bool bLogTimestamps, bool bLogColors )
172 : __running( true )
173 , m_sLogFilePath( sLogFilePath )
174 , m_bUseStdout( bUseStdout )
175 , m_bLogTimestamps( bLogTimestamps )
176 , m_bLogColors( bLogColors ) {
177 __instance = this;
178
179 m_prefixList << "" << "(E) " << "(W) " << "(I) " << "(D) " << "(C)" << "(L) ";
180
181 if ( ! m_bLogColors ) {
182 m_colorList << "" << "" << "" << "" << "" << "" << "";
183 m_sColorOff = "";
184 }
185 else {
186 m_colorList << "" << "\033[31m" << "\033[36m" << "\033[32m" << "\033[35m"
187 << "\033[35;1m" << "\033[35;1m";
188 m_sColorOff = "\033[0m";
189 }
190
191 // Sanity checks.
192 QFileInfo fiLogFile( m_sLogFilePath );
193 QFileInfo fiParentFolder( fiLogFile.absolutePath() );
194 if ( ( fiLogFile.exists() && ! fiLogFile.isWritable() ) ||
195 ( ! fiLogFile.exists() && ! fiParentFolder.isWritable() ) ) {
196 m_sLogFilePath = "";
197 }
198
199 if ( m_sLogFilePath.isEmpty() ) {
201 }
202
203 pthread_attr_t attr;
204 pthread_attr_init( &attr );
205 pthread_mutex_init( &__mutex, nullptr );
206 pthread_cond_init( &__messages_available, nullptr );
207 pthread_create( &loggerThread, &attr, loggerThread_func, this );
208
209 if ( should_log( Info ) ) {
210 log( Info, "Logger", "Logger", QString( "Starting Hydrogen version [%1]" )
211 .arg( QString::fromStdString( get_version() ) ) );
212 log( Info, "Logger", "Logger", QString( "Using log file [%1]" )
213 .arg( m_sLogFilePath ) );
214 }
215}
216
218 __running = false;
219 pthread_cond_broadcast ( &__messages_available );
220 pthread_join( loggerThread, nullptr );
221}
222
223void Logger::log( unsigned level, const QString& sClassName, const char* func_name,
224 const QString& sMsg, const QString& sColor ) {
225
226 if( level == None ){
227 return;
228 }
229
230 int i;
231 switch( level ) {
232 case Error:
233 i = 1;
234 break;
235 case Warning:
236 i = 2;
237 break;
238 case Info:
239 i = 3;
240 break;
241 case Debug:
242 i = 4;
243 break;
244 case Constructors:
245 i = 5;
246 break;
247 case Locks:
248 i = 6;
249 break;
250 default:
251 i = 0;
252 break;
253 }
254
255 QString sTimestampPrefix;
256 if ( m_bLogTimestamps ) {
257 sTimestampPrefix = QString( "[%1] " )
258 .arg( QDateTime::currentDateTime().toString( "hh:mm:ss.zzz" ) );
259 }
260
261 QString sCol = "";
262 if ( m_bLogColors ) {
263 sCol = sColor.isEmpty() ? m_colorList[ i ] : sColor;
264 }
265
266 const QString tmp = QString( "%1%2%3[%4::%5] %6%7\n" )
267 .arg( sCol ).arg( sTimestampPrefix ).arg( m_prefixList[i] )
268 .arg( sClassName ).arg( func_name ).arg( sMsg ).arg( m_sColorOff );
269
270 pthread_mutex_lock( &__mutex );
271 __msg_queue.push_back( tmp );
272 pthread_mutex_unlock( &__mutex );
273 pthread_cond_broadcast( &__messages_available );
274}
275
276void Logger::flush() const {
277
278 int nTimeout = 100;
279 for ( int ii = 0; ii < nTimeout; ++ii ) {
280 if ( __msg_queue.empty() ) {
281 break;
282 }
283
284 std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
285 }
286 return;
287}
288
289unsigned Logger::parse_log_level( const char* level ) {
290 unsigned log_level = Logger::None;
291 if( 0 == strncasecmp( level, __levels[0], strlen( __levels[0] ) ) ) {
292 log_level = Logger::None;
293 } else if ( 0 == strncasecmp( level, __levels[1], strlen( __levels[1] ) ) ) {
294 log_level = Logger::Error;
295 } else if ( 0 == strncasecmp( level, __levels[2], strlen( __levels[2] ) ) ) {
296 log_level = Logger::Error | Logger::Warning;
297 } else if ( 0 == strncasecmp( level, __levels[3], strlen( __levels[3] ) ) ) {
299 } else if ( 0 == strncasecmp( level, __levels[4], strlen( __levels[4] ) ) ) {
301 } else if ( 0 == strncasecmp( level, __levels[5], strlen( __levels[5] ) ) ) {
303 } else if ( 0 == strncasecmp( level, __levels[6], strlen( __levels[6] ) ) ) {
305 } else {
306#ifdef HAVE_SSCANF
307 int val = sscanf( level,"%x",&log_level );
308 if( val != 1 ) {
309 log_level = Logger::Error;
310 }
311#else
312 log_level = hextoi( level, -1 );
313 if( log_level==-1 ) {
314 log_level = Logger::Error;
315 }
316#endif
317 }
318 return log_level;
319}
320
321#ifndef HAVE_SSCANF
322int Logger::hextoi( const char* str, long len ) {
323 long pos = 0;
324 char c = 0;
325 int v = 0;
326 int res = 0;
327 bool leading_zero = false;
328
329 while( 1 ) {
330 if( ( len!=-1 ) && ( pos>=len ) ) {
331 break;
332 }
333 c = str[pos];
334 if( c==0 ) {
335 break;
336 } else if( c=='x' || c=='X' ) {
337 if ( ( pos==1 ) && leading_zero ) {
338 assert( res == 0 );
339 pos++;
340 continue;
341 } else {
342 return -1;
343 }
344 } else if( c>='a' ) {
345 v = c-'a'+10;
346 } else if( c>='A' ) {
347 v = c-'A'+10;
348 } else if( c>='0' ) {
349 if ( ( c=='0' ) && ( pos==0 ) ) {
350 leading_zero = true;
351 }
352 v = c-'0';
353 } else {
354 return -1;
355 }
356 if( v>15 ) {
357 return -1;
358 }
359 //assert( v == (v & 0xF) );
360 res = ( res << 4 ) | v;
361 assert( ( res & 0xF ) == ( v & 0xF ) );
362 pos++;
363 }
364 return res;
365}
366#endif // HAVE_SSCANF
367
368
371 Logger::pCrashContext = pContext;
372 pThisContext = nullptr;
373}
374
377 // Copy context string
378 pThisContext = new QString( sContext );
380}
381
388
389};
390
391/* vim: set softtabstop=4 noexpandtab: */
static bool mkdir(const QString &path)
create a path
static QString log_file_path()
returns the full path (including filename) of the logfile
CrashContext(QString *pContext)
Definition Logger.cpp:369
Class for writing logs to the console.
Definition Logger.h:41
static Logger * create_instance(const QString &sLogFilePath=QString(), bool bUseStdout=true, bool bLogTimestamps=false, bool bLogColors=true)
If __instance equals 0, a new H2Core::Logger singleton will be created and stored in it.
Definition Logger.cpp:161
~Logger()
destructor
Definition Logger.cpp:217
bool m_bLogColors
Definition Logger.h:182
pthread_cond_t __messages_available
Definition Logger.h:173
QString m_sColorOff
Definition Logger.h:178
std::list< QString > queue_t
message queue type
Definition Logger.h:55
static Logger * bootstrap(unsigned msk, const QString &sLogFilePath=QString(), bool bUseStdout=true, bool bLogTimestamps=false, bool bLogColors=true)
create the logger instance if not exists, set the log level and return the instance
Definition Logger.cpp:136
pthread_mutex_t __mutex
lock for adding or removing elements only
Definition Logger.h:169
void flush() const
Waits till the logger thread poped all remaining messages from __msg_queue.
Definition Logger.cpp:276
Logger(const QString &sLogFilePath=QString(), bool bUseStdout=true, bool bLogTimestamps=false, bool bLogColors=true)
constructor
Definition Logger.cpp:170
bool __running
set to true when the logger thread is running
Definition Logger.h:168
static Logger * __instance
Object holding the current H2Core::Logger singleton.
Definition Logger.h:167
static int hextoi(const char *str, long len)
convert an hex string to an integer.
Definition Logger.cpp:322
bool m_bUseStdout
Definition Logger.h:180
queue_t __msg_queue
the message queue
Definition Logger.h:170
static thread_local QString * pCrashContext
Definition Logger.h:184
QStringList m_colorList
Definition Logger.h:177
QStringList m_prefixList
Definition Logger.h:176
static const char * __levels[]
levels strings
Definition Logger.h:172
QString m_sLogFilePath
Definition Logger.h:174
void log(unsigned level, const QString &sClassName, const char *func_name, const QString &sMsg, const QString &sColor="")
the log function
Definition Logger.cpp:223
bool should_log(unsigned lvl) const
return true if the level is set in the bitmask
Definition Logger.h:96
static void set_bit_mask(unsigned msk)
set the bitmask
Definition Logger.h:101
static unsigned parse_log_level(const char *lvl)
parse a log level string and return the corresponding bit mask
Definition Logger.cpp:289
static unsigned __bit_msk
the bitmask of log_level_t
Definition Logger.h:171
bool m_bLogTimestamps
Definition Logger.h:181
friend void * loggerThread_func(void *param)
needed for being able to access logger internal
Definition Logger.cpp:54
std::string get_version()
Returns the current Hydrogen version string.
Definition Version.cpp:30
pthread_t loggerThread
Definition Logger.cpp:52
void * loggerThread_func(void *param)
Definition Logger.cpp:54