1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
#ifndef INCLUDED_SRC_BUILDTOOL_LOGGING_LOG_SINK_FILE_HPP
#define INCLUDED_SRC_BUILDTOOL_LOGGING_LOG_SINK_FILE_HPP
#include <cstdio>
#include <filesystem>
#include <functional>
#include <iterator>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <unordered_map>
#ifdef __unix__
#include <sys/time.h>
#endif
#include "fmt/chrono.h"
#include "fmt/core.h"
#include "gsl-lite/gsl-lite.hpp"
#include "src/buildtool/logging/log_sink.hpp"
#include "src/buildtool/logging/logger.hpp"
/// \brief Thread-safe map of mutexes.
template <class T_Key>
class MutexMap {
public:
/// \brief Create mutex for key and run callback if successfully created.
/// Callback is executed while the internal map is still held exclusively.
void Create(T_Key const& key, std::function<void()> const& callback) {
std::lock_guard lock(mutex_);
if (not map_.contains(key)) {
[[maybe_unused]] auto& mutex = map_[key];
callback();
}
}
/// \brief Get mutex for key, creates mutex if key does not exist.
[[nodiscard]] auto Get(T_Key const& key) noexcept -> std::mutex& {
std::lock_guard lock(mutex_);
return map_[key];
}
private:
std::mutex mutex_{};
std::unordered_map<T_Key, std::mutex> map_{};
};
class LogSinkFile final : public ILogSink {
public:
enum class Mode {
Append, ///< Append if log file already exists.
Overwrite ///< Overwrite log file with each new program instantiation.
};
static auto CreateFactory(std::filesystem::path const& file_path,
Mode file_mode = Mode::Append) -> LogSinkFactory {
return
[=] { return std::make_shared<LogSinkFile>(file_path, file_mode); };
}
LogSinkFile(std::filesystem::path const& file_path, Mode file_mode)
: file_path_{std::filesystem::weakly_canonical(file_path).string()} {
// create file mutex for canonical path
file_mutexes_.Create(file_path_, [&] {
if (file_mode == Mode::Overwrite) {
// clear file contents
if (gsl::owner<FILE*> file =
std::fopen(file_path_.c_str(), "w")) {
std::fclose(file);
}
}
});
}
~LogSinkFile() noexcept final = default;
LogSinkFile(LogSinkFile const&) noexcept = delete;
LogSinkFile(LogSinkFile&&) noexcept = delete;
auto operator=(LogSinkFile const&) noexcept -> LogSinkFile& = delete;
auto operator=(LogSinkFile&&) noexcept -> LogSinkFile& = delete;
/// \brief Thread-safe emitting of log messages to file.
/// Race-conditions for file writes are resolved via a separate mutexes for
/// every canonical file path shared across all instances of this class.
void Emit(Logger const* logger,
LogLevel level,
std::string const& msg) const noexcept final {
#ifdef __unix__ // support nanoseconds for timestamp
timespec ts{};
clock_gettime(CLOCK_REALTIME, &ts);
auto timestamp = fmt::format(
"{:%Y-%m-%d %H:%M:%S}.{}", fmt::localtime(ts.tv_sec), ts.tv_nsec);
#else
auto timestamp = fmt::format(
"{:%Y-%m-%d %H:%M:%S}", fmt::localtime(std::time(nullptr));
#endif
std::ostringstream id{};
id << "thread:" << std::this_thread::get_id();
auto thread = id.str();
auto prefix = fmt::format(
"{}, [{}] {}", thread, timestamp, LogLevelToString(level));
if (logger != nullptr) {
// append logger name
prefix = fmt::format("{} ({})", prefix, logger->Name());
}
prefix = fmt::format("{}:", prefix);
auto cont_prefix = std::string(prefix.size(), ' ');
{
std::lock_guard lock{file_mutexes_.Get(file_path_)};
if (gsl::owner<FILE*> file = std::fopen(file_path_.c_str(), "a")) {
using it = std::istream_iterator<ILogSink::Line>;
std::istringstream iss{msg};
for_each(it{iss}, it{}, [&](auto const& line) {
fmt::print(file, "{} {}\n", prefix, line);
prefix = cont_prefix;
});
std::fclose(file);
}
}
}
private:
std::string file_path_{};
static inline MutexMap<std::string> file_mutexes_{};
};
#endif // INCLUDED_SRC_BUILDTOOL_LOGGING_LOG_SINK_FILE_HPP
|