logging.cpp
1 /** 2 * This file is part of Darling. 3 * 4 * Copyright (C) 2021 Darling developers 5 * 6 * Darling is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * Darling is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with Darling. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include <darlingserver/logging.hpp> 21 #include <darlingserver/server.hpp> 22 #include <darlingserver/thread.hpp> 23 #include <darlingserver/process.hpp> 24 #include <filesystem> 25 #include <fcntl.h> 26 #include <unistd.h> 27 #include <sys/stat.h> 28 29 #define DEFAULT_LOG_CUTOFF DarlingServer::Log::Type::Error 30 31 static const char* alwaysLoggedCategories[] = { 32 //"kprintf", 33 }; 34 35 DarlingServer::Log::Log(std::string category): 36 _category(category) 37 { 38 for (size_t i = 0; i < sizeof(alwaysLoggedCategories) / sizeof(*alwaysLoggedCategories); ++i) { 39 if (strcmp(alwaysLoggedCategories[i], _category.c_str()) == 0) { 40 _alwaysLog = true; 41 break; 42 } 43 } 44 }; 45 46 DarlingServer::Log::Stream::Stream(Type type, const Log& log): 47 _type(type), 48 _log(log) 49 {}; 50 51 DarlingServer::Log::Stream::~Stream() { 52 *this << endLog; 53 }; 54 55 DarlingServer::Log::Stream& DarlingServer::Log::Stream::operator<<(EndLog value) { 56 auto str = _buffer.str(); 57 if (!str.empty()) { 58 _log._log(_type, str); 59 _buffer.str(std::string()); 60 _buffer.clear(); 61 } 62 return *this; 63 }; 64 65 DarlingServer::Log::Stream& DarlingServer::Log::Stream::operator<<(const Loggable& loggable) { 66 loggable.logToStream(*this); 67 return *this; 68 }; 69 70 DarlingServer::Log::Stream DarlingServer::Log::debug() const { 71 return Stream(Type::Debug, *this); 72 }; 73 74 DarlingServer::Log::Stream DarlingServer::Log::info() const { 75 return Stream(Type::Info, *this); 76 }; 77 78 DarlingServer::Log::Stream DarlingServer::Log::warning() const { 79 return Stream(Type::Warning, *this); 80 }; 81 82 DarlingServer::Log::Stream DarlingServer::Log::error() const { 83 return Stream(Type::Error, *this); 84 }; 85 86 std::string DarlingServer::Log::_typeToString(Type type) { 87 switch (type) { 88 case Type::Debug: 89 return "Debug"; 90 case Type::Info: 91 return "Info"; 92 case Type::Warning: 93 return "Warning"; 94 case Type::Error: 95 return "Error"; 96 default: 97 return "Unknown"; 98 } 99 }; 100 101 void DarlingServer::Log::_log(Type type, std::string message) const { 102 // NOTE: we use POSIX file APIs because we want to append each message to the log file atomically, 103 // and as far as i can tell, C++ fstreams provide no such guarantee (that they won't write in chunks). 104 static int logFile = []() { 105 std::filesystem::path path(Server::sharedInstance().prefix() + "/private/var/log/dserver.log"); 106 std::filesystem::create_directories(path.parent_path()); 107 return open(path.c_str(), O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 108 }(); 109 110 static bool logToStderr = []() { 111 auto val = getenv("DSERVER_LOG_STDERR"); 112 return val && strlen(val) >= 1 && (val[0] == 't' || val[0] == 'T' || val[0] == '1'); 113 }(); 114 115 static Type logMinLevel = []() { 116 auto val = getenv("DSERVER_LOG_LEVEL"); 117 Type level = DEFAULT_LOG_CUTOFF; 118 if (val) { 119 if (strncmp(val, "err", 3) == 0) { 120 level = Type::Error; 121 } else if (strncmp(val, "warn", 4) == 0) { 122 level = Type::Warning; 123 } else if (strncmp(val, "info", 4) == 0) { 124 level = Type::Info; 125 } else if (strncmp(val, "debug", 5) == 0) { 126 level = Type::Debug; 127 } 128 } 129 return level; 130 }(); 131 132 if ((type < logMinLevel && !_alwaysLog) || logFile < 0) { 133 return; 134 } 135 136 struct timespec time; 137 clock_gettime(CLOCK_REALTIME, &time); 138 double secs = (double)time.tv_sec + ((double)time.tv_nsec / 1.0e9); 139 auto currentProcess = Process::currentProcess(); 140 auto currentThread = Thread::currentThread(); 141 std::string pid = currentProcess ? (std::string("[P:") + std::to_string(currentProcess->id()) + "(" + std::to_string(currentProcess->nsid()) + ")]") : ""; 142 std::string tid = currentThread ? (std::string("[T:") + std::to_string(currentThread->id()) + "(" + std::to_string(currentThread->nsid()) + ")]") : ""; 143 std::string messageToLog = "[" + std::to_string(secs) + "](" + _category + ", " + _typeToString(type) + ")" + pid + tid + " " + message + "\n"; 144 145 write(logFile, messageToLog.c_str(), messageToLog.size()); 146 147 if (logToStderr) { 148 write(STDERR_FILENO, messageToLog.c_str(), messageToLog.size()); 149 } 150 };