/* * This file is part of the Flowee project * Copyright (C) 2017-2026 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "Logger.h" #include "LogChannels_p.h" #include #include namespace { std::string shortenMethod(const char *methodName) { assert(methodName); const char *start = strchr(methodName, ' '); const char *end = strchr(methodName, '('); if (end) { if (!start || start > end) start = methodName; else ++start; ++end; std::string copy(start, end - start); return copy; } return std::string(); } } Log::Channel::Channel(TimeStampFormat f) : m_timeStampFormat(f), m_logSubSecondPrecision(true) { } Log::Channel::TimeStampFormat Log::Channel::timeStampFormat() const { return m_timeStampFormat; } void Log::Channel::setTimeStampFormat(const TimeStampFormat &timeStampFormat) { m_timeStampFormat = timeStampFormat; } bool Log::Channel::logSubSecondPrecision() const { return m_logSubSecondPrecision; } void Log::Channel::setlogSubSecondPrecision(bool showSubSecondPrecision) { m_logSubSecondPrecision = showSubSecondPrecision; } // ------------------------------------------------------ ConfigurableChannel::ConfigurableChannel(TimeStampFormat f) : Channel(f), m_timeStampFormat(f), m_logSection(true), m_logLineNumber(false), m_logMethodName(true), m_logFilename(false) { } bool ConfigurableChannel::logSection() const { return m_logSection; } void ConfigurableChannel::setLogSection(bool printSection) { m_logSection = printSection; } bool ConfigurableChannel::logLineNumber() const { return m_logLineNumber; } void ConfigurableChannel::setLogLineNumber(bool printLineNumber) { m_logLineNumber = printLineNumber; } bool ConfigurableChannel::logMethodName() const { return m_logMethodName; } void ConfigurableChannel::setLogMethodName(bool printMethodName) { m_logMethodName = printMethodName; } bool ConfigurableChannel::logFilename() const { return m_logFilename; } void ConfigurableChannel::setLogFilename(bool printFilename) { m_logFilename = printFilename; } // ------------------------------------------------------ ConsoleLogChannel::ConsoleLogChannel() : ConfigurableChannel(TimeOnly) { } void ConsoleLogChannel::setPrefix(const char *prefix) { m_prefix = prefix; } void ConsoleLogChannel::pushLog(int64_t, std::string *timestamp, const std::string &line, const char *filename, int lineNumber, const char *methodName, short logSection, short logLevel) { std::ostream &out = (logLevel == Log::WarningLevel || logLevel == Log::FatalLevel) ? std::clog : std::cout; if (timestamp) out << *timestamp << ' '; if (m_logSection && logSection) { out << '['; const std::string section = Log::Manager::sectionString(logSection); if (!section.empty()) out << section; else out << logSection; out << "] "; } if (m_prefix) out << m_prefix << ' '; if (m_logFilename && filename) out << filename << (m_logLineNumber ? ':' : ' '); if (m_logLineNumber && lineNumber) out << lineNumber << ';'; if (m_logMethodName && methodName) { std::string m(shortenMethod(methodName)); if (!m.empty()) out << m << ") "; } out << line; if (line.empty() || line.back() != '\n') out << std::endl; out.flush(); } FileLogChannel::FileLogChannel(const boost::filesystem::path &logFilename) : ConfigurableChannel(DateTime), m_fileout(0), m_logFilename(logFilename) { } FileLogChannel::~FileLogChannel() { if (m_fileout) fclose(m_fileout); } static void FileWriteStr(const std::string &str, FILE *fp) { fwrite(str.data(), 1, str.size(), fp); } void FileLogChannel::pushLog(int64_t, std::string *timestamp, const std::string &line, const char*, int, const char *methodName, short logSection, short) { if (m_fileout) { if (timestamp) { FileWriteStr(*timestamp, m_fileout); FileWriteStr(" ", m_fileout); } if (m_logSection && logSection) { FileWriteStr("[", m_fileout); const std::string section = Log::Manager::sectionString(logSection); if (section.empty()) { std::ostringstream num; num << logSection; FileWriteStr(num.str() , m_fileout); } else { FileWriteStr(section , m_fileout); } FileWriteStr("] ", m_fileout); } if (m_logMethodName && methodName) { std::string m(shortenMethod(methodName)); if (!m.empty()) { FileWriteStr(m , m_fileout); FileWriteStr(") " , m_fileout); } } FileWriteStr(line, m_fileout); if (line.empty() || line.back() != '\n') FileWriteStr("\n", m_fileout); } } void FileLogChannel::reopenLogFiles() { if (m_fileout) fclose(m_fileout); if (m_logFilename.empty()) return; boost::system::error_code error; boost::filesystem::create_directories(m_logFilename.parent_path(), error); m_fileout = fopen(m_logFilename.string().c_str(), "a"); if (!m_fileout) throw std::runtime_error(std::string("Failed to open(append) file: ") + m_logFilename.string()); setbuf(m_fileout, NULL); // unbuffered } void FileLogChannel::setPath(const std::string &path) { if (!m_logFilename.empty() && boost::filesystem::is_directory(path)) // interpret as dir, append previous filename. m_logFilename = boost::filesystem::path(path) / m_logFilename.filename(); else m_logFilename = path; } // ------------------------------------------------------- // FastLogChannel has a method to be run in it's own thread. void writeHandler(FastLogChannel::Item *readPtr, const boost::filesystem::path &logFilename) { FILE *fileout = fopen(logFilename.string().c_str(), "a"); const char *prevFilename = nullptr; // std::locale takes ownership of the pointer const std::locale loc(std::locale::classic(), new boost::posix_time::time_facet("%Y-%m-%d %H:%M:%S.")); while (readPtr->timeMillis != 1) { if (readPtr->timeMillis > 0) { // write data to disk. if (readPtr->filename && prevFilename != readPtr->filename) { fprintf(fileout, "> %s\n", readPtr->filename); prevFilename = readPtr->filename; } std::stringstream ss; ss.imbue(loc); ss << boost::posix_time::from_time_t(readPtr->timeMillis / 1000); ss << std::setw(3) << readPtr->timeMillis % 1000; ss << " {" << readPtr->callingThread; FileWriteStr(ss.str(), fileout); fwrite("} [", 1, 3, fileout); const std::string section = Log::Manager::sectionString(readPtr->logSection); if (section.empty()) { std::ostringstream num; num << readPtr->logSection; FileWriteStr(num.str() , fileout); } else { FileWriteStr(section , fileout); } fwrite("] ", 1, 2, fileout); if (readPtr->methodname) { int firstBrace = 0, methodStart =0; // to avoid huge method names, we avoid the returnType, brace and arguments. int i = 0; while (true) { char k = readPtr->methodname[i]; if (!k) break; if (!methodStart && k == ' ') methodStart = i + 1; if (k == '(') { firstBrace = i; break; } ++i; } fwrite(readPtr->methodname + methodStart, 1, firstBrace - methodStart, fileout); fprintf(fileout, "()L%d ", readPtr->lineNumber); } FileWriteStr(readPtr->line, fileout); fwrite("\n", 1, 1, fileout); } while (true) { auto *next = readPtr->next.load(); if (next) { delete readPtr; readPtr = next; break; } // wait for the main thread to push a new item. std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } fclose(fileout); } FastLogChannel::FastLogChannel(const boost::filesystem::path &logFilename) : Log::Channel(NoTime) // avoid the calling thread doing any time-rendering work. { assert(!logFilename.empty()); Item *newItem = new Item(); newItem->timeMillis = 0; newItem->logSection = 0; newItem->logLevel = 0; m_writePtr.store(newItem); m_writeThread.reset(new std::thread(writeHandler, newItem, logFilename)); } FastLogChannel::~FastLogChannel() { // push the time stamp '1': the magic number to ask our worker thread to quit. pushLog(1, nullptr, std::string(), nullptr, 0, nullptr, 0, 0); m_writeThread->join(); } void FastLogChannel::pushLog(int64_t timeMillis, std::string * /*timestamp*/, const std::string &line, const char *filename, int lineNumber, const char *methodName, short logSection, short logLevel) { Item *newItem = new Item(); newItem->timeMillis = timeMillis; newItem->line = line; newItem->filename = filename; newItem->methodname = methodName; newItem->lineNumber = lineNumber; newItem->logSection = logSection; newItem->logLevel = logLevel; newItem->callingThread = std::this_thread::get_id(); Item *current = m_writePtr.exchange(newItem); current->next = newItem; }