/ src / logging.cpp
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  };