Branch data Line data Source code
1 : : /*
2 : : ** Copyright (C) 2020 Sylvain Fargier
3 : : **
4 : : ** This software is provided 'as-is', without any express or implied
5 : : ** warranty. In no event will the authors be held liable for any damages
6 : : ** arising from the use of this software.
7 : : **
8 : : ** Permission is granted to anyone to use this software for any purpose,
9 : : ** including commercial applications, and to alter it and redistribute it
10 : : ** freely, subject to the following restrictions:
11 : : **
12 : : ** 1. The origin of this software must not be misrepresented; you must not
13 : : ** claim that you wrote the original software. If you use this software
14 : : ** in a product, an acknowledgment in the product documentation would be
15 : : ** appreciated but is not required.
16 : : ** 2. Altered source versions must be plainly marked as such, and must not be
17 : : ** misrepresented as being the original software.
18 : : ** 3. This notice may not be removed or altered from any source distribution.
19 : : **
20 : : ** Created on: 2021-12-30T18:13:05+01:00
21 : : ** Author: Sylvain Fargier <fargie_s> <fargier.sylvain@gmail.com>
22 : : **
23 : : */
24 : :
25 : : #include "Logger.hpp"
26 : :
27 : : #include <algorithm>
28 : : #include <atomic>
29 : : #include <cstdlib>
30 : : #include <ctime>
31 : : #include <iomanip>
32 : : #include <iostream>
33 : : #include <list>
34 : : #include <map>
35 : : #include <memory>
36 : : #include <mutex>
37 : : #include <thread>
38 : : #include <tuple>
39 : :
40 : : #include "Regex.hpp"
41 : : #include "chalk.hpp"
42 : :
43 : : namespace logger {
44 : :
45 : : struct Filter
46 : : {
47 : : std::string name;
48 : : std::unique_ptr<Regex> regex;
49 : : bool enable;
50 : : };
51 : :
52 : : struct LogFacility
53 : : {
54 : : ~LogFacility();
55 : : void reset();
56 : :
57 : : bool isEnabled(const std::string &category) const;
58 : : bool isEnabled(Level level) const;
59 : : bool isEnabled(Feature level) const;
60 : :
61 : : void clearFilters();
62 : : void parseFilter(const std::string &filter);
63 : : void parseFeature(const std::string &feature);
64 : : void addFilter(const std::string &filter, bool value);
65 : : std::string getFilter() const;
66 : :
67 : : mutable std::mutex mutex;
68 : : std::list<Filter> filters;
69 : : std::atomic<unsigned int> filtersVersion;
70 : : std::atomic<uint8_t> level;
71 : : std::atomic<uint8_t> feature;
72 : : std::atomic<bool> isTTY;
73 : : write_func_t writeFunc;
74 : : };
75 : :
76 : : struct ThreadLocalInfo
77 : : {
78 : : ThreadLocalInfo();
79 : : ~ThreadLocalInfo();
80 : :
81 : 26 : const std::string &getThreadName()
82 : : {
83 : 26 : if (!tid)
84 : 24 : return _threadName;
85 : 2 : else if (_threadName.empty())
86 : 2 : _threadName = ("<" + std::to_string(tid) + "> ");
87 : 2 : return _threadName;
88 : : }
89 : :
90 : : static std::atomic<unsigned int> s_tid;
91 : : const unsigned int tid;
92 : :
93 : : Logger::Writer writer;
94 : : std::string _threadName;
95 : : std::map<std::string, bool> categories;
96 : : std::size_t maxCategoryLen;
97 : : unsigned int filtersVersion;
98 : : std::ostringstream tmpBuffer;
99 : :
100 : : std::string lastTimeStr;
101 : : std::time_t lastTimeStamp;
102 : : };
103 : :
104 : : template<typename T>
105 : 889 : inline constexpr typename std::underlying_type<T>::type enum_cast(T t)
106 : : {
107 : 889 : return static_cast<typename std::underlying_type<T>::type>(t);
108 : : }
109 : :
110 : : static const std::map<std::string, Level> s_levelNameMap = {
111 : : {"EMERG", Level::EMERG}, {"ALERT", Level::ALERT},
112 : : {"CRIT", Level::CRIT}, {"ERROR", Level::ERROR},
113 : : {"WARNING", Level::WARNING}, {"NOTICE", Level::NOTICE},
114 : : {"INFO", Level::INFO}, {"DEBUG", Level::DEBUG}};
115 : : static const std::map<std::string, Feature> s_featureNameMap = {
116 : : {"DATETIME", Feature::DATETIME}, {"TIMEDELTA", Feature::TIMEDELTA},
117 : : {"LEVEL", Feature::LEVEL}, {"CATEGORY", Feature::CATEGORY},
118 : : {"NOCOLOR", Feature::NOCOLOR}, {"THREAD", Feature::THREAD}};
119 : :
120 : : static LogFacility s_facility;
121 : :
122 : : static thread_local ThreadLocalInfo s_threadLocalInfo;
123 : :
124 : : /*
125 : : * when loading libraries, zero initialization is achieved before dynamic init
126 : : * this let us detect whereas library is properly loaded or not.
127 : : *
128 : : * stringstreams do require proper init and can't be zero-init, so null
129 : : * writer will be used until library is properly loaded.
130 : : */
131 : 1 : static int s_ready = ([]() {
132 : 1 : s_facility.reset(); // force faciliy creation from main
133 : 1 : s_threadLocalInfo.getThreadName(); // force local_thread creation for main
134 : 1 : return 1;
135 : : })();
136 : : static std::atomic<unsigned int> s_threadLocalCount{0};
137 : :
138 : 262 : static inline bool isReady()
139 : : {
140 : 523 : return s_ready && s_threadLocalCount.load();
141 : : }
142 : :
143 : : static const Regex s_starRe("\\*");
144 : : static const chalk::Color s_timeColor(chalk::faint.fd(2));
145 : : static const chalk::Color s_categoryColor(chalk::fg::blue.fd(2));
146 : : static const chalk::Color s_threadColor(chalk::fg::magenta.fd(2));
147 : : static const std::string s_empty;
148 : :
149 : : static constexpr Level INVALID_LEVEL = static_cast<Level>(
150 : : enum_cast(Level::DEBUG) + 1);
151 : : static constexpr std::size_t MAX_CATEGORY_LEN = 18;
152 : :
153 : : std::atomic<unsigned int> ThreadLocalInfo::s_tid(0);
154 : :
155 : 117 : static inline Logger::Writer &nullWriter()
156 : : {
157 : 117 : static Logger::Writer s_nullWriter;
158 : 117 : if (!isReady())
159 : : {
160 : : /* init not ran yet */
161 : 1 : s_nullWriter.buf.setstate(std::ios_base::eofbit);
162 : : }
163 : :
164 : 117 : return s_nullWriter;
165 : : }
166 : :
167 : 0 : static const char *levelIcon(Level level)
168 : : {
169 : 0 : switch (level)
170 : : {
171 : 0 : case Level::EMERG: return "\U0001f480";
172 : 0 : case Level::ALERT: return "\U0001f198";
173 : 0 : case Level::CRIT: return "\U0001f4a5";
174 : 0 : case Level::ERROR: return "\U0001f525";
175 : 0 : case Level::WARNING: return "\u2757";
176 : 0 : case Level::NOTICE: return "\U0001f440";
177 : 0 : case Level::INFO: return "\U0001F4AC";
178 : 0 : case Level::DEBUG: return "\U0001f41b";
179 : 0 : default: return "";
180 : : }
181 : : }
182 : :
183 : 15 : static const char *levelName(Level level)
184 : : {
185 : 15 : switch (level)
186 : : {
187 : 1 : case Level::EMERG: return "[EMERG] ";
188 : 0 : case Level::ALERT: return "[ALERT] ";
189 : 1 : case Level::CRIT: return "[CRIT] ";
190 : 1 : case Level::ERROR: return "[ERROR] ";
191 : 1 : case Level::WARNING: return "[WARNING]";
192 : 2 : case Level::NOTICE: return "[NOTICE] ";
193 : 1 : case Level::INFO: return "[INFO] ";
194 : 8 : case Level::DEBUG: return "[DEBUG] ";
195 : 0 : default: return "";
196 : : }
197 : : }
198 : :
199 : 16 : static std::string ms_to_string(const std::chrono::milliseconds &ms)
200 : : {
201 : 16 : std::ostringstream &logger(s_threadLocalInfo.tmpBuffer);
202 : 16 : logger.str(s_empty);
203 : :
204 : 16 : std::chrono::milliseconds::rep count(ms.count());
205 : 16 : if (count <= 99999)
206 : 15 : logger << count << "ms";
207 : : else
208 : : {
209 : : unsigned int reminder;
210 : 1 : if ((reminder = count / (24 * 3600 * 1000)))
211 : : {
212 : 1 : logger << reminder << "d";
213 : 1 : count %= 24 * 3600 * 1000;
214 : : }
215 : 1 : if ((reminder = count / (3600 * 1000)))
216 : : {
217 : 1 : logger << reminder << "h";
218 : 1 : count %= 3600 * 1000;
219 : : }
220 : 1 : if ((reminder = count / (60 * 1000)))
221 : : {
222 : 1 : logger << reminder << "m";
223 : 1 : count %= 60 * 1000;
224 : : }
225 : 1 : if ((reminder = count / (1000)))
226 : 1 : count %= 1000;
227 : 1 : logger << reminder << "." << std::setw(3) << std::setfill('0') << count
228 : 1 : << "s";
229 : : }
230 : 16 : return logger.str();
231 : : }
232 : :
233 : : static constexpr std::size_t TIMEDATE_LEN = 20;
234 : 21 : static const std::string <_to_string(const LocalTime &l)
235 : : {
236 : 21 : std::ostringstream &oss(s_threadLocalInfo.tmpBuffer);
237 : 21 : oss.str(s_empty);
238 : :
239 : : std::chrono::milliseconds::rep now =
240 : 21 : std::chrono::duration_cast<std::chrono::milliseconds>(
241 : 21 : l.time.time_since_epoch())
242 : 21 : .count();
243 : 21 : const std::time_t time = now / 1000;
244 : 21 : if (s_threadLocalInfo.lastTimeStamp == time)
245 : : {
246 : 20 : oss << std::setfill('0') << std::setw(3) << (now % 1000);
247 : 20 : s_threadLocalInfo.lastTimeStr.replace(TIMEDATE_LEN, 3, oss.str());
248 : : }
249 : : else
250 : : {
251 : : struct std::tm tm;
252 : :
253 : 1 : localtime_r(&time, &tm);
254 : 1 : oss << std::setfill('0');
255 : 1 : oss << (tm.tm_year + 1900) << "-";
256 : 1 : oss << std::setw(2) << (tm.tm_mon + 1) << "-";
257 : 1 : oss << std::setw(2) << tm.tm_mday << " ";
258 : 1 : oss << std::setw(2) << tm.tm_hour << ":";
259 : 1 : oss << std::setw(2) << tm.tm_min << ":";
260 : 1 : oss << std::setw(2) << tm.tm_sec << ".";
261 : 1 : oss << std::setw(3) << (now % 1000);
262 : 1 : s_threadLocalInfo.lastTimeStr = oss.str();
263 : 1 : s_threadLocalInfo.lastTimeStamp = time;
264 : : }
265 : 21 : return s_threadLocalInfo.lastTimeStr;
266 : : }
267 : :
268 : 1 : LogFacility::~LogFacility()
269 : : {
270 : 1 : s_ready = 0;
271 : 1 : }
272 : :
273 : 27 : void LogFacility::reset()
274 : : {
275 : : // turn the null writer in a real null writer
276 : 27 : nullWriter().buf.setstate(std::ios_base::eofbit);
277 : :
278 : 27 : const char *dbg = std::getenv("DEBUG");
279 : 27 : const char *logLevel = std::getenv("LOG_LEVEL");
280 : 27 : const char *logCategory = std::getenv("LOG_CATEGORY");
281 : 27 : const char *logFeature = std::getenv("LOG_FEATURE");
282 : :
283 : 27 : if (logLevel)
284 : : {
285 : 0 : std::string logLevelStr(logLevel);
286 : 0 : std::transform(logLevelStr.begin(), logLevelStr.end(),
287 : : logLevelStr.begin(),
288 : 0 : [](char c) { return std::toupper(c); });
289 : 0 : const auto it = s_levelNameMap.find(logLevelStr);
290 : 0 : if (it != s_levelNameMap.end())
291 : 0 : level.store(static_cast<int>(it->second));
292 : : else
293 : : {
294 : : try
295 : : {
296 : 0 : int l = std::stoi(logLevelStr);
297 : 0 : if (l >= static_cast<int>(Level::EMERG) &&
298 : : l <= static_cast<int>(Level::DEBUG))
299 : 0 : level.store(l);
300 : : else
301 : 0 : throw std::invalid_argument(std::string());
302 : : }
303 : 0 : catch (const std::exception &)
304 : : {
305 : 0 : std::cerr << "Invalid log level: " << logLevel << "\n";
306 : 0 : std::cerr << "Must be one of:";
307 : 0 : for (const auto &v : s_levelNameMap)
308 : 0 : std::cerr << " " << v.first;
309 : 0 : std::cerr << std::endl;
310 : 0 : level.store(
311 : : static_cast<int>(dbg ? Level::DEBUG : Level::NOTICE));
312 : 0 : }
313 : : }
314 : 0 : }
315 : : else
316 : 27 : level.store(static_cast<int>(dbg ? Level::DEBUG : Level::NOTICE));
317 : :
318 : 27 : clearFilters();
319 : 27 : if (logCategory)
320 : 0 : parseFilter(logCategory);
321 : 27 : else if (dbg)
322 : 0 : parseFilter(dbg);
323 : :
324 : 27 : feature.store(enum_cast(Feature::DATETIME) | enum_cast(Feature::TIMEDELTA) |
325 : 54 : enum_cast(Feature::LEVEL) | enum_cast(Feature::CATEGORY) |
326 : 27 : enum_cast(Feature::THREAD));
327 : 27 : if (logFeature)
328 : 2 : parseFeature(logFeature);
329 : :
330 : 27 : isTTY.store(isatty(2));
331 : 27 : writeFunc = nullptr;
332 : 27 : }
333 : :
334 : 49 : bool LogFacility::isEnabled(const std::string &category) const
335 : : {
336 : 49 : if (s_threadLocalInfo.filtersVersion != s_facility.filtersVersion)
337 : : {
338 : 15 : s_threadLocalInfo.filtersVersion = s_facility.filtersVersion;
339 : 15 : s_threadLocalInfo.categories.clear();
340 : 15 : s_threadLocalInfo.maxCategoryLen = 0;
341 : : }
342 : :
343 : : const std::map<std::string, bool>::const_iterator categoryIt =
344 : 49 : s_threadLocalInfo.categories.find(category);
345 : 49 : if (categoryIt == s_threadLocalInfo.categories.end())
346 : : {
347 : 25 : std::unique_lock<std::mutex> lock(mutex);
348 : 25 : auto it = std::find_if(filters.cbegin(), filters.cend(),
349 : 20 : [&category](const Filter &item) {
350 : 10 : return item.regex->search(category);
351 : : });
352 : :
353 : 25 : const bool state = (it != filters.cend()) ? it->enable :
354 : 21 : filters.empty();
355 : 25 : lock.unlock();
356 : :
357 : 25 : if (category.length() > s_threadLocalInfo.maxCategoryLen)
358 : 11 : s_threadLocalInfo.maxCategoryLen = std::min(category.length(),
359 : : MAX_CATEGORY_LEN);
360 : 25 : s_threadLocalInfo.categories.emplace(std::make_pair(category, state));
361 : 25 : return state;
362 : 25 : }
363 : : else
364 : 24 : return categoryIt->second;
365 : : }
366 : :
367 : 192 : bool LogFacility::isEnabled(Level l) const
368 : : {
369 : 192 : return enum_cast(l) <= level;
370 : : }
371 : :
372 : 79 : bool LogFacility::isEnabled(Feature f) const
373 : : {
374 : 158 : return feature.load() & enum_cast(f);
375 : : }
376 : :
377 : 54 : void LogFacility::clearFilters()
378 : : {
379 : 54 : std::lock_guard<std::mutex> lock(mutex);
380 : 54 : filters.clear();
381 : 54 : filtersVersion++;
382 : 54 : }
383 : :
384 : 6 : void LogFacility::addFilter(const std::string &category, bool value)
385 : : {
386 : : try
387 : : {
388 : 6 : std::lock_guard<std::mutex> lock(mutex);
389 : 12 : filters.emplace_back(Filter{
390 : 12 : category, std::unique_ptr<Regex>(new Regex("^" + category + "$")),
391 : : value});
392 : 6 : filtersVersion++;
393 : 6 : }
394 : 0 : catch (std::exception &ex)
395 : : {
396 : 0 : std::cerr << "Failed to add filter: " << ex.what() << std::endl;
397 : 0 : }
398 : 6 : }
399 : :
400 : 27 : void LogFacility::parseFilter(const std::string &filter)
401 : : {
402 : 27 : std::size_t start = 0;
403 : : std::size_t end;
404 : :
405 : : do
406 : : {
407 : 31 : end = filter.find(',', start);
408 : :
409 : : std::string category = filter.substr(
410 : 31 : start, (end == std::string::npos) ? end : (end - start));
411 : 52 : for (std::size_t i = 0; i < category.length(); ++i)
412 : : {
413 : 21 : if (category[i] == '*')
414 : 3 : category.replace(i++, 1, ".*");
415 : : }
416 : :
417 : 31 : if (category.empty()) {}
418 : 6 : else if (category[0] == '-')
419 : 3 : addFilter(category.substr(1), false);
420 : : else
421 : 3 : addFilter(category, true);
422 : 31 : start = end + 1;
423 : 31 : } while (end != std::string::npos);
424 : 27 : }
425 : :
426 : 1 : std::string LogFacility::getFilter() const
427 : : {
428 : 1 : std::lock_guard<std::mutex> lock(mutex);
429 : 1 : std::ostringstream &oss(s_threadLocalInfo.tmpBuffer);
430 : 1 : oss.str(s_empty);
431 : :
432 : 1 : bool first = true;
433 : 1 : std::for_each(filters.cbegin(), filters.cend(),
434 : 17 : [&oss, &first](const Filter &f) {
435 : 4 : if (!first)
436 : 3 : oss << ",";
437 : 4 : first = false;
438 : :
439 : 4 : if (!f.enable)
440 : 2 : oss << "-";
441 : 4 : oss << f.name;
442 : 4 : });
443 : 2 : return oss.str();
444 : 1 : }
445 : :
446 : 2 : void LogFacility::parseFeature(const std::string &featureList)
447 : : {
448 : 2 : std::size_t start = 0;
449 : : std::size_t end;
450 : :
451 : : do
452 : : {
453 : 13 : end = featureList.find(',', start);
454 : :
455 : : std::string featureStr = featureList.substr(
456 : 13 : start, (end == std::string::npos) ? end : (end - start));
457 : 13 : std::transform(featureStr.begin(), featureStr.end(), featureStr.begin(),
458 : 95 : [](char c) { return std::toupper(c); });
459 : :
460 : 13 : bool enable = true;
461 : 13 : if (featureStr.empty()) {}
462 : 13 : else if (featureStr[0] == '-')
463 : : {
464 : 6 : enable = false;
465 : 6 : featureStr = featureStr.substr(1);
466 : : }
467 : :
468 : 13 : const auto it = s_featureNameMap.find(featureStr);
469 : 13 : if (it != s_featureNameMap.end())
470 : : {
471 : 12 : if (enable)
472 : 12 : feature.store(feature.load() | enum_cast(it->second));
473 : : else
474 : 12 : feature.store(feature.load() & ~enum_cast(it->second));
475 : : }
476 : : else
477 : : {
478 : 1 : std::cerr << "Invalid feature: " << featureStr << "\n";
479 : 1 : std::cerr << "Must be one of:";
480 : 7 : for (const auto &v : s_featureNameMap)
481 : 6 : std::cerr << " " << v.first;
482 : 1 : std::cerr << std::endl;
483 : : }
484 : 13 : start = end + 1;
485 : 13 : } while (end != std::string::npos);
486 : 2 : }
487 : :
488 : 4 : ThreadLocalInfo::ThreadLocalInfo() :
489 : 4 : tid(s_tid++), maxCategoryLen(0), filtersVersion(0), lastTimeStamp(0)
490 : : {
491 : 4 : s_threadLocalCount += 1;
492 : 4 : }
493 : :
494 : 4 : ThreadLocalInfo::~ThreadLocalInfo()
495 : : {
496 : 4 : s_threadLocalCount -= 1;
497 : 4 : }
498 : :
499 : 23 : Level Config::getLevel() const
500 : : {
501 : 23 : return static_cast<Level>(s_facility.level.load());
502 : : }
503 : :
504 : 26 : Config &Config::reset()
505 : : {
506 : 26 : s_facility.reset();
507 : 26 : return *this;
508 : : }
509 : :
510 : 50 : Config &Config::setLevel(Level level)
511 : : {
512 : 50 : s_facility.level.store(static_cast<int>(level));
513 : 50 : return *this;
514 : : }
515 : :
516 : 27 : Config &Config::setFilter(const std::string &filter)
517 : : {
518 : 27 : s_facility.clearFilters();
519 : 27 : addFilter(filter);
520 : 27 : return *this;
521 : : }
522 : :
523 : 27 : Config &Config::addFilter(const std::string &filter)
524 : : {
525 : 27 : s_facility.parseFilter(filter);
526 : 27 : return *this;
527 : : }
528 : :
529 : 1 : std::string Config::getFilter() const
530 : : {
531 : 1 : return s_facility.getFilter();
532 : : }
533 : :
534 : 28 : Config &Config::enable(Feature feature)
535 : : {
536 : 28 : s_facility.feature |= enum_cast(feature);
537 : 28 : return *this;
538 : : }
539 : :
540 : 93 : Config &Config::disable(Feature feature)
541 : : {
542 : 93 : s_facility.feature &= ~enum_cast(feature);
543 : 93 : return *this;
544 : : }
545 : :
546 : 12 : bool Config::isEnabled(Feature feature) const
547 : : {
548 : 12 : return s_facility.isEnabled(feature);
549 : : }
550 : :
551 : 4 : Config &Config::setWriteFunc(write_func_t fun)
552 : : {
553 : 4 : s_facility.writeFunc = fun;
554 : 4 : return *this;
555 : : }
556 : :
557 : 73 : Logger::Logger(Level level, Writer &writer) : writer(writer), level(level)
558 : : {
559 : 73 : writer.prepare(level);
560 : 73 : }
561 : :
562 : 11 : Logger::Logger(Logger &&other) : writer(other.writer), level(other.level)
563 : : {
564 : 11 : const_cast<Level &>(other.level) = INVALID_LEVEL;
565 : 11 : }
566 : :
567 : 84 : Logger::~Logger()
568 : : {
569 : 84 : if (&writer != &nullWriter() && level != INVALID_LEVEL)
570 : 67 : writer.flush(level);
571 : 84 : }
572 : :
573 : 20 : Logger::Writer &Logger::defaultWriter()
574 : : {
575 : 20 : if (!isReady()) // unlikely
576 : 0 : return nullWriter();
577 : 20 : return s_threadLocalInfo.writer;
578 : : }
579 : :
580 : 73 : void Logger::Writer::prepare(Level level)
581 : : {
582 : 73 : if (!isReady() || !s_facility.isEnabled(level))
583 : 3 : return;
584 : :
585 : 70 : const uint8_t features = s_facility.feature;
586 : 70 : if (features & enum_cast(Feature::NOCOLOR))
587 : : {
588 : 56 : if (features & enum_cast(Feature::DATETIME))
589 : 0 : buf << lt_to_string(LocalTime()) << " ";
590 : :
591 : 56 : if (features & enum_cast(Feature::TIMEDELTA))
592 : : {
593 : 0 : std::string value = ms_to_string(last.tick());
594 : 0 : buf << "(" << std::setfill(' ') << std::setw(7) << value << ") ";
595 : 0 : }
596 : :
597 : 56 : if (features & enum_cast(Feature::LEVEL))
598 : 1 : buf << levelName(level) << " ";
599 : :
600 : : // s_tid contains next thread id, thus main gets 0 and increments
601 : 56 : if (features & enum_cast(Feature::THREAD) && ThreadLocalInfo::s_tid > 1)
602 : 25 : buf << s_threadLocalInfo.getThreadName();
603 : : }
604 : : else
605 : : {
606 : 14 : if (features & enum_cast(Feature::DATETIME))
607 : 14 : buf << s_timeColor(lt_to_string(LocalTime())) << " ";
608 : :
609 : 14 : if (features & enum_cast(Feature::TIMEDELTA))
610 : : {
611 : 14 : std::string value = ms_to_string(last.tick());
612 : 0 : buf << s_timeColor.begin() << "(" << std::setfill(' ')
613 : 14 : << std::setw(7) << value << ")" << s_timeColor.end() << " ";
614 : 14 : }
615 : :
616 : 14 : if (features & enum_cast(Feature::LEVEL))
617 : 14 : buf << (s_facility.isTTY ? levelIcon(level) : levelName(level))
618 : 28 : << " ";
619 : :
620 : : // s_tid contains next thread id, thus main gets 0 and increments
621 : 14 : if (features & enum_cast(Feature::THREAD) && ThreadLocalInfo::s_tid > 1)
622 : 0 : buf << s_threadColor.begin() << s_threadLocalInfo.getThreadName()
623 : 0 : << s_threadColor.end();
624 : : }
625 : 70 : prefixEnd = buf.tellp();
626 : : }
627 : :
628 : 68 : void Logger::Writer::flush(Level level)
629 : : {
630 : 68 : if (buf.tellp() <= prefixEnd) {}
631 : 67 : else if (s_facility.isEnabled(level))
632 : : {
633 : 67 : buf.put('\n');
634 : 67 : const std::string line(buf.str());
635 : 67 : if (!s_facility.writeFunc || !s_facility.writeFunc(level, line))
636 : : {
637 : : // not calling std::endl to dump everything at once
638 : : // no mutex needed since line is dumped in a single write
639 : 57 : std::cerr << line << std::flush;
640 : : }
641 : 67 : }
642 : :
643 : 68 : if (!buf.eof())
644 : : {
645 : 68 : buf.str(s_empty);
646 : : }
647 : 68 : }
648 : :
649 : 42 : std::string Logger::Writer::str()
650 : : {
651 : 42 : return std::move(buf.str());
652 : : }
653 : :
654 : 2 : LocalTime::LocalTime(const time_point &time) : time(time) {}
655 : 17 : LocalTime::LocalTime() : time(std::chrono::system_clock::now()) {}
656 : :
657 : 7 : std::string to_string(const LocalTime &l)
658 : : {
659 : 7 : return lt_to_string(l);
660 : : }
661 : :
662 : 6 : Elapsed::Elapsed() : start(std::chrono::steady_clock::now()) {}
663 : :
664 : 16 : std::chrono::milliseconds Elapsed::tick()
665 : : {
666 : 16 : decltype(start) now = std::chrono::steady_clock::now();
667 : : std::chrono::milliseconds ms =
668 : 16 : std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
669 : 16 : start = now;
670 : 16 : return ms;
671 : : }
672 : :
673 : 2 : const Logger &operator<<(const Logger &logger,
674 : : const std::chrono::milliseconds &ms)
675 : : {
676 : 2 : return (logger << ms_to_string(ms));
677 : : }
678 : :
679 : 1 : NullLogger::NullLogger(Level level) : Logger(level, nullWriter()) {}
680 : :
681 : 52 : Logger log(Level level, const std::string &category)
682 : : {
683 : 52 : if (!isReady()) // unlikely ()
684 : : {
685 : 0 : static Logger::Writer s_writer;
686 : 0 : s_writer.buf.setstate(std::ios_base::eofbit);
687 : 0 : return Logger(level, s_writer);
688 : : }
689 : 101 : const bool enabled = s_facility.isEnabled(level) &&
690 : 49 : s_facility.isEnabled(category);
691 : :
692 : 52 : if (enabled && s_facility.isEnabled(Feature::CATEGORY))
693 : : {
694 : 20 : const bool isColor = !s_facility.isEnabled(Feature::NOCOLOR);
695 : : // is zero if no category ever used
696 : 20 : const std::size_t maxLen = s_threadLocalInfo.maxCategoryLen;
697 : 20 : if (maxLen > 0)
698 : : {
699 : 11 : const std::size_t fill = (maxLen > category.length()) ?
700 : 6 : (maxLen - category.length() + 1) :
701 : 11 : 1;
702 : 11 : return std::move(const_cast<Logger &>(
703 : 22 : Logger(level, enabled ? s_threadLocalInfo.writer : nullWriter())
704 : 22 : << (isColor ? s_categoryColor.begin() : std::string())
705 : 30 : << (category.empty() ? std::string(" ") :
706 : 30 : ("{" + category + "}"))
707 : 22 : << (isColor ? s_categoryColor.end() : std::string())
708 : 22 : << std::string(fill, ' ')));
709 : : }
710 : : }
711 : 41 : return Logger(level, enabled ? s_threadLocalInfo.writer : nullWriter());
712 : : }
713 : :
714 : : } // namespace logger
|