Skip to content

Commit a572936

Browse files
committed
draft logger
1 parent b970990 commit a572936

3 files changed

Lines changed: 230 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ set(ODR_SOURCE_FILES
8282
"src/odr/global_params.cpp"
8383
"src/odr/html.cpp"
8484
"src/odr/http_server.cpp"
85+
"src/odr/logger.cpp"
8586
"src/odr/odr.cpp"
8687
"src/odr/quantity.cpp"
8788
"src/odr/style.cpp"

src/odr/logger.cpp

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#include <odr/logger.hpp>
2+
3+
#include <algorithm>
4+
#include <iomanip>
5+
#include <iostream>
6+
#include <ranges>
7+
#include <utility>
8+
9+
namespace odr {
10+
11+
namespace {
12+
13+
class StdioLogger : public Logger {
14+
public:
15+
StdioLogger(std::string name, LogLevel level, LogFormat format,
16+
std::unique_ptr<std::ostream> output)
17+
: m_name(std::move(name)), m_level(level), m_format(std::move(format)),
18+
m_output(std::move(output)) {
19+
if (m_output == nullptr) {
20+
m_output = std::make_unique<std::ostream>(std::cout.rdbuf());
21+
}
22+
}
23+
24+
[[nodiscard]] bool will_log(LogLevel level) const override {
25+
return level >= m_level;
26+
}
27+
28+
void log_impl(LogLevel level, const std::string &message,
29+
const std::source_location &location) override {
30+
*m_output << format(level, message, location, m_format) << std::endl;
31+
}
32+
33+
private:
34+
std::string m_name;
35+
LogLevel m_level;
36+
LogFormat m_format;
37+
std::unique_ptr<std::ostream> m_output;
38+
};
39+
40+
class TeeLogger : public Logger {
41+
public:
42+
explicit TeeLogger(const std::vector<std::shared_ptr<Logger>> &loggers)
43+
: m_loggers(loggers) {
44+
if (m_loggers.empty()) {
45+
throw std::invalid_argument("TeeLogger requires at least one logger");
46+
}
47+
}
48+
49+
[[nodiscard]] bool will_log(LogLevel level) const override {
50+
return std::ranges::any_of(m_loggers, [level](const auto &logger) {
51+
return logger->will_log(level);
52+
});
53+
}
54+
55+
void log_impl(LogLevel level, const std::string &message,
56+
const std::source_location &location) override {
57+
for (const auto &logger : m_loggers) {
58+
logger->log(level, message, location);
59+
}
60+
}
61+
62+
private:
63+
std::vector<std::shared_ptr<Logger>> m_loggers;
64+
};
65+
66+
std::string_view level_to_string(LogLevel level) {
67+
switch (level) {
68+
case LogLevel::verbose:
69+
return "VERBOSE";
70+
case LogLevel::debug:
71+
return "DEBUG";
72+
case LogLevel::info:
73+
return "INFO";
74+
case LogLevel::warning:
75+
return "WARNING";
76+
case LogLevel::error:
77+
return "ERROR";
78+
case LogLevel::fatal:
79+
return "FATAL";
80+
default:
81+
throw std::invalid_argument("Unknown log level");
82+
}
83+
}
84+
85+
} // namespace
86+
87+
std::unique_ptr<Logger>
88+
Logger::create_stdio(const std::string &name, LogLevel level,
89+
const LogFormat &format,
90+
std::unique_ptr<std::ostream> output) {
91+
return std::make_unique<StdioLogger>(name, level, format, std::move(output));
92+
}
93+
94+
std::unique_ptr<Logger>
95+
Logger::create_tee(const std::vector<std::shared_ptr<Logger>> &loggers) {
96+
return std::make_unique<TeeLogger>(loggers);
97+
}
98+
99+
std::string Logger::format(LogLevel level, const std::string &message,
100+
const std::source_location &location,
101+
const LogFormat &format) {
102+
std::stringstream ss;
103+
104+
if (!format.time_format.empty()) {
105+
auto now = std::chrono::system_clock::now();
106+
auto time = std::chrono::system_clock::to_time_t(now);
107+
ss << std::put_time(std::localtime(&time), format.time_format.c_str())
108+
<< " ";
109+
}
110+
111+
if (format.level_width > 0) {
112+
std::string_view level_str = level_to_string(level);
113+
std::stringstream level_ss;
114+
level_ss << level_str.substr(0, format.level_width);
115+
level_ss << std::string(
116+
std::max<std::size_t>(0, format.level_width - level_ss.str().size()),
117+
' ');
118+
ss << level_ss.str();
119+
}
120+
121+
if (format.name_width > 0) {
122+
std::string name = location.function_name();
123+
std::stringstream name_ss;
124+
name_ss << name.substr(0, format.name_width);
125+
name_ss << std::string(
126+
std::max<std::size_t>(0, format.name_width - name_ss.str().size()),
127+
' ');
128+
ss << name_ss.str();
129+
}
130+
131+
if (format.location_width > 0) {
132+
std::string file_name = location.file_name();
133+
std::string line_number = std::to_string(location.line());
134+
std::stringstream location_ss;
135+
if (file_name.size() + 1 + line_number.size() > format.location_width) {
136+
if (1 + line_number.size() < format.location_width) {
137+
location_ss << file_name.substr(0, format.location_width - 1 -
138+
line_number.size())
139+
<< ":" << line_number;
140+
} else {
141+
location_ss << file_name.substr(0, format.location_width);
142+
}
143+
} else {
144+
location_ss << file_name << ":" << line_number;
145+
}
146+
location_ss << std::string(
147+
std::max<std::size_t>(0,
148+
format.location_width - location_ss.str().size()),
149+
' ');
150+
ss << location_ss.str();
151+
}
152+
153+
ss << message;
154+
155+
return ss.str();
156+
}
157+
158+
} // namespace odr

src/odr/logger.hpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#pragma once
2+
3+
#include <memory>
4+
#include <source_location>
5+
#include <sstream>
6+
#include <string>
7+
8+
namespace odr {
9+
10+
#define ODR_LOG(level, message) \
11+
do { \
12+
if (logger().will_log(level)) { \
13+
std::stringstream ss; \
14+
ss << message; \
15+
logger()->log(level, ss.str()); \
16+
} \
17+
} while (0)
18+
#define ODR_VERBOSE(message) ODR_LOG(LogLevel::Debug, message)
19+
#define ODR_DEBUG(message) ODR_LOG(LogLevel::Debug, message)
20+
#define ODR_INFO(message) ODR_LOG(LogLevel::Info, message)
21+
#define ODR_WARNING(message) ODR_LOG(LogLevel::Warning, message)
22+
#define ODR_ERROR(message) ODR_LOG(LogLevel::Error, message)
23+
#define ODR_FATAL(message) ODR_LOG(LogLevel::Fatal, message)
24+
25+
enum class LogLevel {
26+
verbose = 0,
27+
debug,
28+
info,
29+
warning,
30+
error,
31+
fatal,
32+
};
33+
34+
struct LogFormat {
35+
std::string time_format{"%H:%M:%S"};
36+
std::size_t level_width{7};
37+
std::size_t name_width{10};
38+
std::size_t location_width{20};
39+
};
40+
41+
class Logger {
42+
public:
43+
static std::unique_ptr<Logger>
44+
create_stdio(const std::string &name, LogLevel level,
45+
const LogFormat &format = LogFormat(),
46+
std::unique_ptr<std::ostream> output = nullptr);
47+
static std::unique_ptr<Logger>
48+
create_tee(const std::vector<std::shared_ptr<Logger>> &loggers);
49+
50+
static std::string format(LogLevel level, const std::string &message,
51+
const std::source_location &location,
52+
const LogFormat &format);
53+
54+
virtual ~Logger() = default;
55+
56+
[[nodiscard]] virtual bool will_log(LogLevel level) const = 0;
57+
58+
inline void
59+
log(LogLevel level, const std::string &message,
60+
const std::source_location &location = std::source_location::current()) {
61+
if (will_log(level)) {
62+
log_impl(level, message, location);
63+
}
64+
}
65+
66+
protected:
67+
virtual void log_impl(LogLevel level, const std::string &message,
68+
const std::source_location &location) = 0;
69+
};
70+
71+
} // namespace odr

0 commit comments

Comments
 (0)