]> git.mxchange.org Git - simgear.git/blob - simgear/debug/logstream.cxx
Merge branch 'next' of git.mxchange.org:/var/cache/git/repos/simgear into next
[simgear.git] / simgear / debug / logstream.cxx
1 // Stream based logging mechanism.
2 //
3 // Written by Bernie Bright, 1998
4 //
5 // Copyright (C) 1998  Bernie Bright - bbright@c031.aone.net.au
6 //
7 // This library is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU Library General Public
9 // License as published by the Free Software Foundation; either
10 // version 2 of the License, or (at your option) any later version.
11 //
12 // This library is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // Library General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #include <simgear_config.h>
24
25 #include "logstream.hxx"
26
27 #include <iostream>
28 #include <fstream>
29 #include <sstream>
30 #include <algorithm>
31
32 #include <boost/foreach.hpp>
33
34 #include <simgear/sg_inlines.h>
35 #include <simgear/threads/SGThread.hxx>
36 #include <simgear/threads/SGQueue.hxx>
37 #include <simgear/threads/SGGuard.hxx>
38
39 #include <simgear/misc/sgstream.hxx>
40 #include <simgear/misc/sg_path.hxx>
41
42 #if defined (SG_WINDOWS)
43 // for AllocConsole, OutputDebugString
44     #include <windows.h>
45 #endif
46
47 const char* debugClassToString(sgDebugClass c)
48 {
49     switch (c) {
50     case SG_NONE:       return "none";
51     case SG_TERRAIN:    return "terrain";
52     case SG_ASTRO:      return "astro";
53     case SG_FLIGHT:     return "flight";
54     case SG_INPUT:      return "input";
55     case SG_GL:         return "opengl";
56     case SG_VIEW:       return "view";
57     case SG_COCKPIT:    return "cockpit";
58     case SG_GENERAL:    return "general";
59     case SG_MATH:       return "math";
60     case SG_EVENT:      return "event";
61     case SG_AIRCRAFT:   return "aircraft";
62     case SG_AUTOPILOT:  return "autopilot";
63     case SG_IO:         return "io";
64     case SG_CLIPPER:    return "clipper";
65     case SG_NETWORK:    return "network";
66     case SG_ATC:        return "atc";
67     case SG_NASAL:      return "nasal";
68     case SG_INSTR:      return "instruments";
69     case SG_SYSTEMS:    return "systems";
70     case SG_AI:         return "ai";
71     case SG_ENVIRONMENT:return "environment";
72     case SG_SOUND:      return "sound";
73     case SG_NAVAID:     return "navaid";
74     case SG_GUI:        return "gui";
75     case SG_TERRASYNC:  return "terrasync";
76     case SG_PARTICLES:  return "particles";
77     default:            return "unknown";
78     }
79 }
80
81 //////////////////////////////////////////////////////////////////////////////
82
83 namespace simgear
84 {
85
86 LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
87         m_class(c),
88         m_priority(p)
89 {
90 }
91
92 bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
93 {
94          return ((c & m_class) != 0 && p >= m_priority);
95 }
96
97 void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
98 {
99         m_priority = p;
100         m_class = c;
101 }
102
103 } // of namespace simgear
104
105 //////////////////////////////////////////////////////////////////////////////
106
107 class FileLogCallback : public simgear::LogCallback
108 {
109 public:
110     FileLogCallback(const SGPath& aPath, sgDebugClass c, sgDebugPriority p) :
111             simgear::LogCallback(c, p)
112     {
113         m_file.open(aPath, std::ios_base::out | std::ios_base::trunc);
114     }
115
116     virtual void operator()(sgDebugClass c, sgDebugPriority p,
117         const char* file, int line, const std::string& message)
118     {
119         if (!shouldLog(c, p)) return;
120         m_file << debugClassToString(c) << ":" << (int) p
121             << ":" << file << ":" << line << ":" << message << std::endl;
122     }
123 private:
124     sg_ofstream m_file;
125 };
126
127 class StderrLogCallback : public simgear::LogCallback
128 {
129 public:
130     StderrLogCallback(sgDebugClass c, sgDebugPriority p) :
131                 simgear::LogCallback(c, p)
132     {
133     }
134
135 #if defined (SG_WINDOWS)
136     ~StderrLogCallback()
137     {
138         FreeConsole();
139     }
140 #endif
141
142     virtual void operator()(sgDebugClass c, sgDebugPriority p,
143         const char* file, int line, const std::string& aMessage)
144     {
145         if (!shouldLog(c, p)) return;
146
147         fprintf(stderr, "%s\n", aMessage.c_str());
148         //fprintf(stderr, "%s:%d:%s:%d:%s\n", debugClassToString(c), p,
149         //    file, line, aMessage.c_str());
150         fflush(stderr);
151     }
152 };
153
154
155 #ifdef SG_WINDOWS
156
157 class WinDebugLogCallback : public simgear::LogCallback
158 {
159 public:
160     WinDebugLogCallback(sgDebugClass c, sgDebugPriority p) :
161                 simgear::LogCallback(c, p)
162     {
163     }
164
165     virtual void operator()(sgDebugClass c, sgDebugPriority p,
166         const char* file, int line, const std::string& aMessage)
167     {
168         if (!shouldLog(c, p)) return;
169
170         std::ostringstream os;
171                 os << debugClassToString(c) << ":" << aMessage << std::endl;
172                 OutputDebugStringA(os.str().c_str());
173     }
174 };
175
176 #endif
177
178 class LogStreamPrivate : public SGThread
179 {
180 private:
181     /**
182      * storage of a single log entry. Note this is not used for a persistent
183      * store, but rather for short term buffering between the submitting
184      * and output threads.
185      */
186     class LogEntry
187     {
188     public:
189         LogEntry(sgDebugClass c, sgDebugPriority p,
190             const char* f, int l, const std::string& msg) :
191             debugClass(c), debugPriority(p), file(f), line(l),
192                 message(msg)
193         {
194         }
195
196         sgDebugClass debugClass;
197         sgDebugPriority debugPriority;
198         const char* file;
199         int line;
200         std::string message;
201     };
202
203     class PauseThread
204     {
205     public:
206         PauseThread(LogStreamPrivate* parent) : m_parent(parent)
207         {
208             m_wasRunning = m_parent->stop();
209         }
210
211         ~PauseThread()
212         {
213             if (m_wasRunning) {
214                 m_parent->startLog();
215             }
216         }
217     private:
218         LogStreamPrivate* m_parent;
219         bool m_wasRunning;
220     };
221 public:
222     LogStreamPrivate() :
223         m_logClass(SG_ALL),
224         m_logPriority(SG_ALERT),
225         m_isRunning(false)
226     {
227         bool addStderr = true;
228 #if defined (SG_WINDOWS)
229         // Check for stream redirection, has to be done before we call
230         // Attach / AllocConsole
231         const bool isFile = (GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_DISK); // Redirect to file?
232         if (AttachConsole(ATTACH_PARENT_PROCESS) == 0) {
233             // attach failed, don't install the callback
234             addStderr = false;
235         }
236
237         if (!isFile) {
238             // No - OK! now set streams to attached console
239             freopen("conout$", "w", stdout);
240             freopen("conout$", "w", stderr);
241         }
242 #endif
243         if (addStderr) {
244             m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
245             m_consoleCallbacks.push_back(m_callbacks.back());
246         }
247 #if defined (SG_WINDOWS) && !defined(NDEBUG)
248                 m_callbacks.push_back(new WinDebugLogCallback(m_logClass, m_logPriority));
249                 m_consoleCallbacks.push_back(m_callbacks.back());
250 #endif
251     }
252
253     ~LogStreamPrivate()
254     {
255         BOOST_FOREACH(simgear::LogCallback* cb, m_callbacks) {
256             delete cb;
257         }
258     }
259
260     SGMutex m_lock;
261     SGBlockingQueue<LogEntry> m_entries;
262
263     typedef std::vector<simgear::LogCallback*> CallbackVec;
264     CallbackVec m_callbacks;
265     /// subset of callbacks which correspond to stdout / console,
266         /// and hence should dynamically reflect console logging settings
267         CallbackVec m_consoleCallbacks;
268
269     sgDebugClass m_logClass;
270     sgDebugPriority m_logPriority;
271     bool m_isRunning;
272
273     void startLog()
274     {
275         SGGuard<SGMutex> g(m_lock);
276         if (m_isRunning) return;
277         m_isRunning = true;
278         start();
279     }
280
281     virtual void run()
282     {
283         while (1) {
284             LogEntry entry(m_entries.pop());
285             // special marker entry detected, terminate the thread since we are
286             // making a configuration change or quitting the app
287             if ((entry.debugClass == SG_NONE) && !strcmp(entry.file, "done")) {
288                 return;
289             }
290
291             // submit to each installed callback in turn
292             BOOST_FOREACH(simgear::LogCallback* cb, m_callbacks) {
293                 (*cb)(entry.debugClass, entry.debugPriority,
294                     entry.file, entry.line, entry.message);
295             }
296         } // of main thread loop
297     }
298
299     bool stop()
300     {
301         SGGuard<SGMutex> g(m_lock);
302         if (!m_isRunning) {
303             return false;
304         }
305
306         // log a special marker value, which will cause the thread to wakeup,
307         // and then exit
308         log(SG_NONE, SG_ALERT, "done", -1, "");
309         join();
310
311         m_isRunning = false;
312         return true;
313     }
314
315     void addCallback(simgear::LogCallback* cb)
316     {
317         PauseThread pause(this);
318         m_callbacks.push_back(cb);
319     }
320
321     void removeCallback(simgear::LogCallback* cb)
322     {
323         PauseThread pause(this);
324         CallbackVec::iterator it = std::find(m_callbacks.begin(), m_callbacks.end(), cb);
325         if (it != m_callbacks.end()) {
326             m_callbacks.erase(it);
327         }
328     }
329
330     void setLogLevels( sgDebugClass c, sgDebugPriority p )
331     {
332         PauseThread pause(this);
333         m_logPriority = p;
334         m_logClass = c;
335                 BOOST_FOREACH(simgear::LogCallback* cb, m_consoleCallbacks) {
336                 cb->setLogLevels(c, p);
337                 }
338     }
339
340     bool would_log( sgDebugClass c, sgDebugPriority p ) const
341     {
342         if (p >= SG_INFO) return true;
343         return ((c & m_logClass) != 0 && p >= m_logPriority);
344     }
345
346     void log( sgDebugClass c, sgDebugPriority p,
347             const char* fileName, int line, const std::string& msg)
348     {
349         LogEntry entry(c, p, fileName, line, msg);
350         m_entries.push(entry);
351     }
352 };
353
354 /////////////////////////////////////////////////////////////////////////////
355
356 static logstream* global_logstream = NULL;
357 static LogStreamPrivate* global_privateLogstream = NULL;
358 static SGMutex global_logStreamLock;
359
360 logstream::logstream()
361 {
362     global_privateLogstream = new LogStreamPrivate;
363     global_privateLogstream->startLog();
364 }
365
366 logstream::~logstream()
367 {
368     popup_msgs.clear();
369     global_privateLogstream->stop();
370     delete global_privateLogstream;
371 }
372
373 void
374 logstream::setLogLevels( sgDebugClass c, sgDebugPriority p )
375 {
376     global_privateLogstream->setLogLevels(c, p);
377 }
378
379 void
380 logstream::addCallback(simgear::LogCallback* cb)
381 {
382     global_privateLogstream->addCallback(cb);
383 }
384
385 void
386 logstream::removeCallback(simgear::LogCallback* cb)
387 {
388     global_privateLogstream->removeCallback(cb);
389 }
390
391 void
392 logstream::log( sgDebugClass c, sgDebugPriority p,
393         const char* fileName, int line, const std::string& msg)
394 {
395     global_privateLogstream->log(c, p, fileName, line, msg);
396 }
397
398 void
399 logstream::popup( const std::string& msg)
400 {
401     popup_msgs.push_back(msg);
402 }
403
404 std::string
405 logstream::get_popup()
406 {
407     std::string rv = "";
408     if (!popup_msgs.empty())
409     {
410         rv = popup_msgs.front();
411         popup_msgs.erase(popup_msgs.begin());
412     }
413     return rv;
414 }
415
416 bool
417 logstream::has_popup()
418 {
419     return (popup_msgs.size() > 0) ? true : false;
420 }
421
422 bool
423 logstream::would_log( sgDebugClass c, sgDebugPriority p ) const
424 {
425     return global_privateLogstream->would_log(c,p);
426 }
427
428 sgDebugClass
429 logstream::get_log_classes() const
430 {
431     return global_privateLogstream->m_logClass;
432 }
433
434 sgDebugPriority
435 logstream::get_log_priority() const
436 {
437     return global_privateLogstream->m_logPriority;
438 }
439
440 void
441 logstream::set_log_priority( sgDebugPriority p)
442 {
443     global_privateLogstream->setLogLevels(global_privateLogstream->m_logClass, p);
444 }
445
446 void
447 logstream::set_log_classes( sgDebugClass c)
448 {
449     global_privateLogstream->setLogLevels(c, global_privateLogstream->m_logPriority);
450 }
451
452
453 logstream&
454 sglog()
455 {
456     // Force initialization of cerr.
457     static std::ios_base::Init initializer;
458
459     // http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
460     // in the absence of portable memory barrier ops in Simgear,
461     // let's keep this correct & safe
462     SGGuard<SGMutex> g(global_logStreamLock);
463
464     if( !global_logstream )
465         global_logstream = new logstream();
466     return *global_logstream;
467 }
468
469 void
470 logstream::logToFile( const SGPath& aPath, sgDebugClass c, sgDebugPriority p )
471 {
472     global_privateLogstream->addCallback(new FileLogCallback(aPath, c, p));
473 }
474
475 namespace simgear
476 {
477
478 void requestConsole()
479 {
480     // this is a no-op now, stub exists for compatability for the moment.
481 }
482
483 void shutdownLogging()
484 {
485     SGGuard<SGMutex> g(global_logStreamLock);
486     delete global_logstream;
487     global_logstream = 0;
488 }
489
490 } // of namespace simgear