/* PIP - Platform Independent Primitives High-level log Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include "pilog.h" #include "pidir.h" #include "piliterals_string.h" #include "piliterals_time.h" #include "pitime.h" //! \class PILog pilog.h //! \details //! \~english \section PILog_sec0 Synopsis //! \~russian \section PILog_sec0 Краткий обзор //! \~english //! This class provides log with optional file and console output. //! //! \~russian //! Этот класс предоставляет лог с опциональным выводом в файл и консоль. //! PILog::PILog(): PIThread(), log_ts(&log_file) { setName("PILog"); split_time = 8_h; timestamp_format = "yyyy-MM-dd hh:mm:ss.zzz"; setLineFormat("t - c: m"); id_by_cat[Level::Info] = PICout::registerExternalBufferID(); id_by_cat[Level::Debug] = PICout::registerExternalBufferID(); id_by_cat[Level::Warning] = PICout::registerExternalBufferID(); id_by_cat[Level::Error] = PICout::registerExternalBufferID(); CONNECTU(PICout::Notifier::object(), finished, this, coutDone); } PILog::~PILog() { stop(); } void PILog::setDir(const PIString & d) { stopAndWait(); log_dir = d; if (output[File]) { PIDir::make(log_dir); newFile(); } start(); } void PILog::setLineFormat(const PIString & f) { line_format = f; line_format_p = line_format; line_format_p.replace("t", "${t}").replace("c", "${c}").replace("m", "${m}"); } void PILog::setLevel(Level l) { max_level = l; } PICout PILog::error(PIObject * context) { return makePICout(context, Level::Error); } PICout PILog::warning(PIObject * context) { return makePICout(context, Level::Warning); } PICout PILog::info(PIObject * context) { return makePICout(context, Level::Info); } PICout PILog::debug(PIObject * context) { return makePICout(context, Level::Debug); } void PILog::stop() { while (true) { log_mutex.lock(); bool done = queue.isEmpty(); log_mutex.unlock(); if (done) break; piMinSleep(); } PIThread::stopAndWait(); } PIStringList PILog::readAllLogs() const { PIMap names; auto dir = PIDir(log_dir); auto fil = dir.entries(); for (auto fi: fil) { if (!fi.isFile()) continue; if (!fi.name().contains(".log.")) continue; names[PIDateTime::current().fromString(fi.baseName(), "yyyy_MM_dd__hh_mm_ss").toSystemTime()] = dir.relative(fi.path); } PIStringList ret; PIString cur_filename = dir.relative(log_file.path()); auto it = names.makeIterator(); bool was_own = false; auto readFile = [&ret](PIFile * f) { PIIOTextStream ts(f); PIString line; while (!ts.isEnd()) { line = ts.readLine().trim(); if (line.isNotEmpty()) ret << line; } }; while (it.next()) { PIFile * f = nullptr; bool own = true; if (it.value() == cur_filename) { log_mutex.lock(); f = &log_file; f->seekToBegin(); own = false; was_own = true; } else { f = new PIFile(log_dir + "/" + it.value(), PIIODevice::ReadOnly); } readFile(f); if (own) delete f; else { f->seekToEnd(); log_mutex.unlock(); } } if (!was_own) { log_mutex.lock(); log_file.seekToBegin(); readFile(&log_file); log_file.seekToEnd(); log_mutex.unlock(); } return ret; } void PILog::coutDone(int id, PIString * buffer) { if (!buffer) return; if (!id_by_cat.containsValue(id)) return; auto cat = id_by_cat.key(id, PILog::Level::Debug); if (cat > max_level) return; enqueue(*buffer, cat); delete buffer; } PICout PILog::makePICout(PIObject * context, Level cat) { auto buffer = new PIString(); if (context) { *buffer = "["_a + context->className(); if (context->name().isNotEmpty()) *buffer += " \"" + context->name() + "\""; *buffer += "] "; } return PICout::withExternalBufferAndID(buffer, id_by_cat.value(cat), PICoutManipulators::AddSpaces); } void PILog::enqueue(const PIString & msg, Level cat) { auto t = PIDateTime::fromSystemTime(PISystemTime::current()); PIMutexLocker ml(log_mutex); queue.enqueue({cat, t, msg}); } PIString PILog::entryToString(const Entry & e) const { static PIStringList categories{"error", "warn ", "info ", "debug"}; PIString t = e.time.toString(timestamp_format); PIString ret = line_format_p; ret.replace("${t}", t).replace("${c}", categories[static_cast(e.cat)]).replace("${m}", e.msg); return ret; } void PILog::newFile() { PIString aname = log_name; if (aname.isNotEmpty()) aname += "__"; log_file.open(log_dir + "/" + aname + PIDateTime::current().toString("yyyy_MM_dd__hh_mm_ss") + ".log." + PIString::fromNumber(++part_number), PIIODevice::ReadWrite); } void PILog::run() { if (output[File]) { if (split_tm.elapsed() >= split_time) { split_tm.reset(); newFile(); } } log_mutex.lock(); if (queue.isEmpty()) { log_mutex.unlock(); piMSleep(20); return; } log_mutex.unlock(); while (true) { log_mutex.lock(); if (queue.isEmpty()) { log_mutex.unlock(); return; } auto qi = queue.dequeue(); log_mutex.unlock(); auto str = entryToString(qi); if (log_file.isOpened()) log_ts << str << "\n"; if (output[Console]) { PICout out(qi.cat == Level::Error ? piCerr : piCout); if (color_console) { switch (qi.cat) { case Level::Error: out << PICoutManipulators::Red; break; case Level::Warning: out << PICoutManipulators::Yellow; break; default: break; } } out << str; } } }