6.5 KiB
logger
source_loaction
Since C++20 we got a nice functionality to perform easy and efficient logging.
void log(const std::string_view message,
const std::source_location location =
std::source_location::current())
{
std::cout << std::format("{}({}:{}) '{}':{}\n",
location.file_name(),
location.line(),
location.column(),
location.function_name(),
message);
}
template <typename T> void fun(T x)
{
log(x);
}
int main(int, char*[])
{
log("Hello world!");
fun("Hello C++20!");
}
prog.cc(24:8) 'int main(int, char**)': Hello world!
prog.cc(19:8) 'void fun(T) [with T = const char*]': Hello C++20!
It's easy to rebuild a little this solution and create own logger:
enum LogType {INF, WRN, ERR};
struct Logger {
Logger(LogType logType, std::source_location location = std::source_location::current()):
logType_{logType},
location_{location} {}
Logger& operator<<(std::string_view message) {
file_ << std::format("[{}]({}): {}({}:{}) `{}`: {}\n",
toString(logType_),
std::chrono::system_clock::now(),
location_.file_name(),
location_.line(),
location_.column(),
location_.function_name(),
message);
file_ << std::flush;
return *this;
}
private:
std::string_view toString(LogType logType) {
switch (logType) {
case INF: return "I";
case WRN: return "W";
case ERR: return "E";
default: return "UNKNOWN!";
}
}
LogType logType_;
std::source_location location_;
static std::ofstream file_;
};
std::ofstream Logger::file_("log.txt");
Ok now is time to log sth
int main() {
Logger(INF) << "Hello!" << "And Hi" << "And Dzien dobry!";
Logger(WRN) << "Ojej!";
Logger(ERR) << "Critical!!!" << "UPS!" << "Bad bad!";
}
And in file log.txt we got:
[I](2024-03-17 15:29:34.248747710): prog.cc(55:15) `int main()`: Hello!
[I](2024-03-17 15:29:34.248817593): prog.cc(55:15) `int main()`: And Hi
[I](2024-03-17 15:29:34.248832174): prog.cc(55:15) `int main()`: And Dzien dobry!
[W](2024-03-17 15:29:34.248843859): prog.cc(56:15) `int main()`: Ojej!
[E](2024-03-17 15:29:34.248855275): prog.cc(57:15) `int main()`: Critical!!!
[E](2024-03-17 15:29:34.248866476): prog.cc(57:15) `int main()`: UPS!
[E](2024-03-17 15:29:34.248877615): prog.cc(57:15) `int main()`: Bad bad!
Second version
We can also write a whole output when the logger will be destroyed, this allows to log only one line.
Logger::Logger(LogType logType, std::source_location location)
: logType_{logType},
location_{location} {
file_ << std::format("[{}]({}): {}({}:{}) `{}`:",
toString(logType_),
std::chrono::system_clock::now(),
location_.file_name(),
location_.line(),
location_.column(),
location_.function_name());
}
Logger::~Logger() {
file_ << std::endl; // flush and new line
}
Logger& Logger::operator<<(const std::string& str) {
file_ << " " << str;
return *this;
}
int main() {
Logger(INF) << "Hello!" << "And Hi" << "And Dzien dobry!";
Logger(WRN) << "Ojej!";
Logger(ERR) << "Critical!!!" << "UPS!" << "Bad bad!";
}
And in file log.txt we got:
[I](2024-03-17 15:29:34.248747710): prog.cc(55:15) `int main()`: Hello! And Hi And Dzien dobry!
[W](2024-03-17 15:29:34.248843859): prog.cc(56:15) `int main()`: Ojej!
[E](2024-03-17 15:29:34.248855275): prog.cc(57:15) `int main()`: Critical!!! UPS! Bad bad!
Ok but this is not thread safe!
Now we need to make the code a thread-safe and put the id of the thread. We need to lock a mutex before writing output to the file and keep it until we destroy a logger. Ofc we risk that someone will create a logger instance and don't destroy them in the following line, but we assume that we use it correctly. We can add a special function that wraps this for use (the most common is macro).
Logger::Logger(LogType logType, std::source_location location): logType_{logType}, location_{location} {
formatted_ = std::format("[{}][th:{}]({}): {}({}:{}) `{}`:",
toString(logType_),
std::this_thread::get_id(),
std::chrono::system_clock::now(),
location_.file_name(),
location_.line(),
location_.column(),
location_.function_name());
}
Logger& Logger::operator<<(const std::string& str) {
formatted_ = std::format("{} {}", std::move(formatted_), str);
return *this;
}
Logger::~Logger() {
std::lock_guard lg(loggMutex_);
loggFile_ << formatted_ << std::endl;
}
#define LOG_INF Logger(INF)
#define LOG_ERR Logger(ERR)
#define LOG_WRN Logger(WRN)
int main() {
LOG_INF << "This" << "is" << "an" << "info";
LOG_WRN << "This" << "is" << "a" << "warning";
LOG_ERR << "This" << "is" << "an" << "error";
}
[I][th:1](2024-03-17 15:53:04.743470912): prog.cc(86:5) `int main()`: This is an info
[W][th:1](2024-03-17 15:53:04.743541096): prog.cc(87:5) `int main()`: This is a warning
[E][th:1](2024-03-17 15:53:04.743558245): prog.cc(88:5) `int main()`: This is an error
Cleanup
At the beginning of the program, we should also clear all logged data, this is easy to achieve:
// Always clear file when program start
std::ofstream Logger::loggFile_("log.txt", std::ofstream::trunc);