1
////////////////////////////////////////////////////////////////////////////////
3
// Copyright (C) 2011-2014, Armory Technologies, Inc. //
4
// Distributed under the GNU Affero General Public License (AGPL v3) //
5
// See LICENSE or http://www.gnu.org/licenses/agpl.html //
7
////////////////////////////////////////////////////////////////////////////////
9
// This is a convenient little C++ logging class that was based on a Dr. Dobbs
10
// article on the subject. The logger was rewritten to include a DualStream
11
// that pushes the log data to both std output AND file. This could easily
12
// be extended to use an arbitrary number of streams, with a different log lvl
13
// set on each one. At the moment, it only supports stdout and one file
14
// simultaneously at the same loglvl, though you can use LOGDISABLESTDOUT()
15
// to turn off the cout portion but still write to the log file.
19
// If you do not initialize the logger, the default behavior is to log nothing.
20
// All LOGERR, LOGWARN, etc, calls wll execute without error, but they will be
21
// diverted to a NullStream object (which throws them away).
23
// To use the logger, all you need to do is make one call to STARTLOGGING with
24
// the file name and log level, and then all subsequent calls to LOGERR, etc,
25
// will work as expected.
27
// STARTLOGGING("logfile.txt", LogLvlWarn); // ignore anything below LOGWARN
29
// LOGERR << "This is an error message, pretty much always logged";
30
// LOGWARN << "This is a warning";
31
// LOGINFO << "Given the LogLvlWarn above, this message will be ignored";
32
// LOGDEBUG << "This one will also be ignored"
34
// FLUSHLOG(); // force-flush all write buffers
35
// LOGDISABLESTDOUT(); // Stop writing log msgs to cout, only write to file
36
// LOGENABLESTDOUT(); // Okay nevermind, use cout again
38
// All logged lines begin with the msg type (ERROR, WARNING, etc), the current
39
// time down to one second, and the file:line. Then the message is printed.
40
// Newlines are added automatically to the end of each line, so there is no
41
// need to use "<< endl" at the end of any log messages (in fact, it will
42
// croak if you try to). Here's what the messages look like:
44
// -ERROR - 22:16:26: (code.cpp:129) I am recording an error!
45
// -WARN - 22:16:26: (code.cpp:130) This is just a warning, don't be alarmed!
46
// -DEBUG4- 22:16:26: (code.cpp:131) A seriously low-level debug message.
48
// If you'd like to change the format of the messages, you can modify the
49
// #define'd FILEANDLINE just below the #include's, and/or modify the
50
// getLogStream() method in the LoggerObj class (just note, you cannot
51
// move the __FILE__ and/or __LINE__ commands into the getLogStream() method
52
// because then it will always print "log.h:282" for the file and line).
54
////////////////////////////////////////////////////////////////////////////////
64
#include "OS_TranslatePath.h"
66
#define FILEANDLINE "(" << __FILE__ << ":" << __LINE__ << ") "
67
#define LOGERR (LoggerObj(LogLvlError ).getLogStream() << FILEANDLINE )
68
#define LOGWARN (LoggerObj(LogLvlWarn ).getLogStream() << FILEANDLINE )
69
#define LOGINFO (LoggerObj(LogLvlInfo ).getLogStream() << FILEANDLINE )
70
#define LOGDEBUG (LoggerObj(LogLvlDebug ).getLogStream() << FILEANDLINE )
71
#define LOGDEBUG1 (LoggerObj(LogLvlDebug1).getLogStream() << FILEANDLINE )
72
#define LOGDEBUG2 (LoggerObj(LogLvlDebug2).getLogStream() << FILEANDLINE )
73
#define LOGDEBUG3 (LoggerObj(LogLvlDebug3).getLogStream() << FILEANDLINE )
74
#define LOGDEBUG4 (LoggerObj(LogLvlDebug4).getLogStream() << FILEANDLINE )
75
#define STARTLOGGING(LOGFILE, LOGLEVEL) \
76
Log::SetLogFile(LOGFILE); \
77
Log::SetLogLevel(LOGLEVEL);
78
#define LOGDISABLESTDOUT() Log::SuppressStdout(true)
79
#define LOGENABLESTDOUT() Log::SuppressStdout(false)
80
#define SETLOGLEVEL(LOGLVL) Log::SetLogLevel(LOGLVL)
81
#define FLUSHLOG() Log::FlushStreams()
84
#define MAX_LOG_FILE_SIZE (500*1024)
88
inline string NowTime();
89
inline unsigned long long int NowTimeInt();
105
////////////////////////////////////////////////////////////////////////////////
109
virtual LogStream& operator<<(const char * str) = 0;
110
virtual LogStream& operator<<(string const & str) = 0;
111
virtual LogStream& operator<<(int i) = 0;
112
virtual LogStream& operator<<(unsigned int i) = 0;
113
virtual LogStream& operator<<(unsigned long long int i) = 0;
114
virtual LogStream& operator<<(float f) = 0;
115
virtual LogStream& operator<<(double d) = 0;
116
#if !defined(_MSC_VER) && !defined(__MINGW32__) && defined(__LP64__)
117
virtual LogStream& operator<<(size_t i) = 0;
121
////////////////////////////////////////////////////////////////////////////////
122
class DualStream : public LogStream
125
DualStream(void) : noStdout_(false) {}
127
void enableStdOut(bool newbool) { noStdout_ = !newbool; }
129
void setLogFile(string logfile, unsigned long long maxSz=MAX_LOG_FILE_SIZE)
132
truncateFile(fname_, maxSz);
133
fout_.open(OS_TranslatePath(fname_.c_str()), ios::app);
134
fout_ << "\n\nLog file opened at " << NowTimeInt() << ": " << fname_.c_str() << endl;
138
void truncateFile(string logfile, unsigned long long int maxSizeInBytes)
140
ifstream is(OS_TranslatePath(logfile.c_str()), ios::in|ios::binary);
142
// If file does not exist, nothing to do
146
// Check the filesize
147
is.seekg(0, ios::end);
148
unsigned long long int fsize = (size_t)is.tellg();
151
if(fsize < maxSizeInBytes)
153
// If it's already smaller than max, we're done
158
// Otherwise, seek to <maxSize> before end of log file
159
ifstream is(OS_TranslatePath(logfile.c_str()), ios::in|ios::binary);
160
is.seekg(fsize - maxSizeInBytes);
162
// Allocate buffer to hold the rest of the file (about maxSizeInBytes)
163
unsigned long long int bytesToCopy = fsize - is.tellg();
164
char* lastBytes = new char[(unsigned int)bytesToCopy];
165
is.read(lastBytes, bytesToCopy);
168
// Create temporary file and dump the bytes there
169
string tempfile = logfile + string("temp");
170
ofstream os(OS_TranslatePath(tempfile.c_str()), ios::out|ios::binary);
171
os.write(lastBytes, bytesToCopy);
175
// Remove the original and rename the temp file to original
177
remove(logfile.c_str());
178
rename(tempfile.c_str(), logfile.c_str());
180
_wunlink(OS_TranslatePath(logfile).c_str());
181
_wrename(OS_TranslatePath(tempfile).c_str(), OS_TranslatePath(logfile).c_str());
186
LogStream& operator<<(const char * str) { if(!noStdout_) cout << str; if(fout_.is_open()) fout_ << str; return *this; }
187
LogStream& operator<<(string const & str) { if(!noStdout_) cout << str.c_str(); if(fout_.is_open()) fout_ << str.c_str(); return *this; }
188
LogStream& operator<<(int i) { if(!noStdout_) cout << i; if(fout_.is_open()) fout_ << i; return *this; }
189
LogStream& operator<<(unsigned int i) { if(!noStdout_) cout << i; if(fout_.is_open()) fout_ << i; return *this; }
190
LogStream& operator<<(unsigned long long int i) { if(!noStdout_) cout << i; if(fout_.is_open()) fout_ << i; return *this; }
191
LogStream& operator<<(float f) { if(!noStdout_) cout << f; if(fout_.is_open()) fout_ << f; return *this; }
192
LogStream& operator<<(double d) { if(!noStdout_) cout << d; if(fout_.is_open()) fout_ << d; return *this; }
193
#if !defined(_MSC_VER) && !defined(__MINGW32__) && defined(__LP64__)
194
LogStream& operator<<(size_t i) { if(!noStdout_) cout << i; if(fout_.is_open()) fout_ << i; return *this; }
197
void FlushStreams(void) {cout.flush(); fout_.flush();}
199
void newline(void) { *this << "\n"; }
200
void close(void) { fout_.close(); }
208
////////////////////////////////////////////////////////////////////////////////
209
class NullStream : public LogStream
212
LogStream& operator<<(const char * str) { return *this; }
213
LogStream& operator<<(string const & str) { return *this; }
214
LogStream& operator<<(int i) { return *this; }
215
LogStream& operator<<(unsigned int i) { return *this; }
216
LogStream& operator<<(unsigned long long int i) { return *this; }
217
LogStream& operator<<(float f) { return *this; }
218
LogStream& operator<<(double d) { return *this; }
219
#if !defined(_MSC_VER) && !defined(__MINGW32__) && defined(__LP64__)
220
LogStream& operator<<(size_t i) { return *this; }
223
void FlushStreams(void) {}
230
Log(void) : isInitialized_(false), disableStdout_(false) {}
232
static Log & GetInstance(const char * filename=NULL)
234
static Log* theOneLog=NULL;
235
if(theOneLog==NULL || filename!=NULL)
237
// Close and delete any existing Log object
238
if(theOneLog != NULL)
240
theOneLog->ds_.close();
244
// Create a Log object
247
// Open the filestream if it's open
250
theOneLog->ds_.setLogFile(string(filename));
251
theOneLog->isInitialized_ = true;
262
LogStream& Get(LogLevel level = LogLvlInfo)
264
if((int)level > logLevel_ || !isInitialized_)
270
static void SetLogFile(string logfile) { GetInstance(logfile.c_str()); }
271
static void CloseLogFile(void)
273
GetInstance().ds_.FlushStreams();
274
GetInstance().ds_ << "Closing logfile.\n";
275
GetInstance().ds_.close();
276
// This doesn't actually seem to stop the StdOut logging... not sure why yet
277
GetInstance().isInitialized_ = false;
278
GetInstance().logLevel_ = LogLvlDisabled;
281
static void SetLogLevel(LogLevel level) { GetInstance().logLevel_ = (int)level; }
282
static void SuppressStdout(bool b=true) { GetInstance().ds_.enableStdOut(!b);}
284
static string ToString(LogLevel level)
286
static const char* const buffer[] = {"DISABLED", "ERROR ", "WARN ", "INFO ", "DEBUG ", "DEBUG1", "DEBUG2", "DEBUG3", "DEBUG4"};
287
return buffer[level];
290
static bool isOpen(void) {return GetInstance().ds_.fout_.is_open();}
291
static string filename(void) {return GetInstance().ds_.fname_;}
292
static void FlushStreams(void) {GetInstance().ds_.FlushStreams();}
302
Log& operator =(const Log&);
308
// I missed the opportunity with the above class, to design it as a constantly
309
// constructing/destructing object that adds a newline on every destruct. So
310
// instead I create this little wrapper that does it for me.
314
LoggerObj(LogLevel lvl) : logLevel_(lvl) {}
316
LogStream & getLogStream(void)
318
LogStream & lg = Log::GetInstance().Get(logLevel_);
319
lg << "-" << Log::ToString(logLevel_);
320
lg << "- " << NowTimeInt() << ": ";
326
Log::GetInstance().Get(logLevel_) << "\n";
327
Log::GetInstance().FlushStreams();
338
//#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
339
//# if defined (BUILDING_FILELOG_DLL)
340
//# define FILELOG_DECLSPEC __declspec (dllexport)
341
//# elif defined (USING_FILELOG_DLL)
342
//# define FILELOG_DECLSPEC __declspec (dllimport)
344
//# define FILELOG_DECLSPEC
345
//# endif // BUILDING_DBSIMPLE_DLL
347
//# define FILELOG_DECLSPEC
351
//#ifndef FILELOG_MAX_LEVEL
352
//#define FILELOG_MAX_LEVEL LogLvlDEBUG4
355
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
359
inline string NowTime()
361
const int MAX_LEN = 200;
362
char buffer[MAX_LEN];
363
if (GetTimeFormatA(LOCALE_USER_DEFAULT, 0, 0,
364
"HH':'mm':'ss", buffer, MAX_LEN) == 0)
365
return "Error in NowTime()";
367
char result[100] = {0};
368
static DWORD first = GetTickCount();
369
sprintf(result, "%s.%03ld", buffer, (long)(GetTickCount() - first) % 1000);
373
inline unsigned long long int NowTimeInt(void)
377
return (unsigned long long int)t;
382
#include <sys/time.h>
384
inline string NowTime()
390
strftime(buffer, sizeof(buffer), "%X", localtime_r(&t, &r));
392
gettimeofday(&tv, 0);
393
char result[100] = {0};
394
sprintf(result, "%s", buffer);
398
inline unsigned long long int NowTimeInt(void)
402
return (unsigned long long int)t;