1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004-2021 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 //    * Redistributions of source code must retain the above copyright notice,
11 //      this list of conditions and the following disclaimer.
12 //
13 //    * Redistributions in binary form must reproduce the above copyright
14 //      notice, this list of conditions and the following disclaimer in the
15 //      documentation and/or other materials provided with the distribution.
16 //
17 //    * Neither the name of the author nor the names of other contributors may
18 //      be used to endorse or promote products derived from this software
19 //      without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34 
35 #include <pch.hpp>
36 #include <musikcore/debug.h>
37 #include <musikcore/support/Common.h>
38 #include <functional>
39 #include <string>
40 #include <queue>
41 #include <thread>
42 #include <mutex>
43 #include <condition_variable>
44 #include <memory>
45 #include <iostream>
46 #include <time.h>
47 
48 using namespace musik::core;
49 
50 class log_queue;
51 
52 static std::vector<std::unique_ptr<musik::debug::IBackend>> backends;
53 static std::thread* thread = nullptr;
54 static log_queue* queue = nullptr;
55 static std::recursive_mutex mutex;
56 static volatile bool cancel = true;
57 
58 enum class debug_level {
59     verbose = 0,
60     info = 1,
61     warning = 2,
62     error = 3
63 };
64 
65 class log_queue {
66     public:
67         struct log_entry {
log_entrylog_queue::log_entry68             log_entry(debug_level l, const std::string& t, const std::string& m) {
69                 level = l;
70                 tag = t;
71                 message = m;
72             }
73 
74             debug_level level;
75             std::string tag;
76             std::string message;
77         };
78 
79         class stopped_exception {
80         };
81 
log_queue()82         log_queue() {
83             active = true;
84         }
85 
pop_top()86         log_entry* pop_top() {
87             std::unique_lock<std::mutex> lock(queue_mutex);
88             while ((queue.size() == 0) && (active == true)) {
89                 wait_for_next_item_condition.wait(lock);
90             }
91 
92             if (!active) {
93                 return nullptr;
94             }
95 
96             log_entry* top = queue.front();
97             queue.pop();
98             return top;
99         }
100 
push(log_entry * f)101         bool push(log_entry* f) {
102             std::unique_lock<std::mutex> lock(queue_mutex);
103 
104             if (active) {
105                 bool was_empty = (queue.size() == 0);
106                 queue.push(f);
107 
108                 if (was_empty) {
109                     wait_for_next_item_condition.notify_one();
110                 }
111 
112                 return true;
113             }
114 
115             return false;
116         }
117 
stop()118         void stop() {
119             std::unique_lock<std::mutex> lock(queue_mutex);
120             active = false;
121 
122             while (queue.size() > 0) {
123                 log_entry* top = queue.front();
124                 queue.pop();
125                 delete top;
126             }
127 
128             wait_for_next_item_condition.notify_all();
129         }
130 
131     private:
132         std::queue<log_entry*> queue;
133         std::condition_variable wait_for_next_item_condition;
134         std::mutex queue_mutex;
135         volatile bool active;
136 };
137 
thread_proc()138 static void thread_proc() {
139     try {
140         while (!cancel) {
141             log_queue::log_entry* entry = queue->pop_top();
142             if (entry) {
143                 for (auto& backend : backends) {
144                     switch (entry->level) {
145                         case debug_level::verbose:
146                             backend->verbose(entry->tag, entry->message);
147                             break;
148                         case debug_level::info:
149                             backend->info(entry->tag, entry->message);
150                             break;
151                         case debug_level::warning:
152                             backend->warning(entry->tag, entry->message);
153                             break;
154                         case debug_level::error:
155                             backend->error(entry->tag, entry->message);
156                             break;
157                     }
158                 }
159                 delete entry;
160             }
161         }
162     }
163     catch (log_queue::stopped_exception&) {
164     }
165 }
166 
Start(std::vector<musik::debug::IBackend * > backends)167 void musik::debug::Start(std::vector<musik::debug::IBackend*> backends) {
168     std::unique_lock<std::recursive_mutex> lock(mutex);
169 
170     if (queue || thread) {
171         return;
172     }
173 
174     for (auto backend : backends) {
175         ::backends.push_back(std::unique_ptr<musik::debug::IBackend>(backend));
176     }
177 
178     cancel = false;
179     queue = new log_queue();
180     thread = new std::thread(std::bind(&thread_proc));
181 
182     info("LOG SESSION", "---------- START ----------");
183 }
184 
Stop()185 void musik::debug::Stop() {
186     std::unique_lock<std::recursive_mutex> lock(mutex);
187 
188     cancel = true;
189 
190     if (thread && queue) {
191         queue->stop();
192         thread->join();
193 
194         delete thread;
195         thread = nullptr;
196         delete queue;
197         queue = nullptr;
198     }
199 
200     backends.clear();
201 }
202 
enqueue(debug_level level,const std::string & tag,const std::string & string)203 static void enqueue(debug_level level, const std::string& tag, const std::string& string) noexcept {
204     try {
205         std::unique_lock<std::recursive_mutex> lock(mutex);
206 
207         if (queue) {
208             queue->push(new log_queue::log_entry(level, tag, string));
209         }
210     }
211     catch (...) {
212         fprintf(stderr, "[%d] [%s] %s", static_cast<int>(level), tag.c_str(), string.c_str());
213     }
214 }
215 
verbose(const std::string & tag,const std::string & string)216 void musik::debug::verbose(const std::string& tag, const std::string& string) noexcept {
217     enqueue(debug_level::verbose, tag, string);
218 }
219 
v(const std::string & tag,const std::string & string)220 void musik::debug::v(const std::string& tag, const std::string& string) noexcept {
221     enqueue(debug_level::verbose, tag, string);
222 }
223 
info(const std::string & tag,const std::string & string)224 void musik::debug::info(const std::string& tag, const std::string& string) noexcept {
225     enqueue(debug_level::info, tag, string);
226 }
227 
i(const std::string & tag,const std::string & string)228 void musik::debug::i(const std::string& tag, const std::string& string) noexcept {
229     enqueue(debug_level::info, tag, string);
230 }
231 
warning(const std::string & tag,const std::string & string)232 void musik::debug::warning(const std::string& tag, const std::string& string) noexcept {
233     enqueue(debug_level::warning, tag, string);
234 }
235 
w(const std::string & tag,const std::string & string)236 void musik::debug::w(const std::string& tag, const std::string& string) noexcept {
237     enqueue(debug_level::warning, tag, string);
238 }
239 
error(const std::string & tag,const std::string & string)240 void musik::debug::error(const std::string& tag, const std::string& string) noexcept {
241     enqueue(debug_level::error, tag, string);
242 }
243 
e(const std::string & tag,const std::string & string)244 void musik::debug::e(const std::string& tag, const std::string& string) noexcept {
245     enqueue(debug_level::error, tag, string);
246 }
247 
248 ////////// backend utils //////////
249 
250 namespace musik {
251 
timestamp()252     static std::string timestamp() {
253         time_t rawtime = { 0 };
254         struct tm * timeinfo;
255         char buffer [64];
256         time(&rawtime);
257         timeinfo = localtime (&rawtime);
258         strftime (buffer, sizeof(buffer), "%T", timeinfo);
259         return std::string(buffer);
260     }
261 
writeTo(std::ostream & out,const std::string & level,const std::string & tag,const std::string & message)262     static void writeTo(
263         std::ostream& out,
264         const std::string& level,
265         const std::string& tag,
266         const std::string& message)
267     {
268         out << timestamp() << " [" << level << "] [" << tag << "] " << message << "\n";
269         out.flush();
270     }
271 
272 }
273 
274 ////////// FileBackend //////////
275 
276 namespace musik {
277 
FileBackend(const std::string & fn)278     debug::FileBackend::FileBackend(const std::string& fn)
279     : out(fn.c_str()) {
280 
281     }
282 
FileBackend(FileBackend && fn)283     debug::FileBackend::FileBackend(FileBackend&& fn) {
284         this->out.swap(fn.out);
285     }
286 
~FileBackend()287     debug::FileBackend::~FileBackend() {
288     }
289 
verbose(const std::string & tag,const std::string & string)290     void debug::FileBackend::verbose(const std::string& tag, const std::string& string) {
291         writeTo(this->out, "verbose", tag, string);
292     }
293 
info(const std::string & tag,const std::string & string)294     void debug::FileBackend::info(const std::string& tag, const std::string& string) {
295         writeTo(this->out, "info", tag, string);
296     }
297 
warning(const std::string & tag,const std::string & string)298     void debug::FileBackend::warning(const std::string& tag, const std::string& string) {
299         writeTo(this->out, "warning", tag, string);
300     }
301 
error(const std::string & tag,const std::string & string)302     void debug::FileBackend::error(const std::string& tag, const std::string& string) {
303         writeTo(this->out, "error", tag, string);
304     }
305 
306 }
307 
308 ////////// SimpleFileBackend //////////
309 
310 namespace musik {
311 
SimpleFileBackend()312     debug::SimpleFileBackend::SimpleFileBackend()
313     : FileBackend(GetDataDirectory() + "log.txt") {
314     }
315 
316 }
317 
318 ////////// ConsoleBackend //////////
319 
320 namespace musik {
321 
ConsoleBackend()322     debug::ConsoleBackend::ConsoleBackend() {
323     }
324 
~ConsoleBackend()325     debug::ConsoleBackend::~ConsoleBackend() {
326     }
327 
verbose(const std::string & tag,const std::string & string)328     void debug::ConsoleBackend::verbose(const std::string& tag, const std::string& string) {
329         writeTo(std::cout, "verbose", tag, string);
330     }
331 
info(const std::string & tag,const std::string & string)332     void debug::ConsoleBackend::info(const std::string& tag, const std::string& string) {
333         writeTo(std::cout, "info", tag, string);
334     }
335 
warning(const std::string & tag,const std::string & string)336     void debug::ConsoleBackend::warning(const std::string& tag, const std::string& string) {
337         writeTo(std::cout, "warning", tag, string);
338     }
339 
error(const std::string & tag,const std::string & string)340     void debug::ConsoleBackend::error(const std::string& tag, const std::string& string) {
341         writeTo(std::cerr, "error", tag, string);
342     }
343 
344 }
345