summaryrefslogtreecommitdiff
path: root/asl/logging
diff options
context:
space:
mode:
Diffstat (limited to 'asl/logging')
-rw-r--r--asl/logging/BUILD.bazel26
-rw-r--r--asl/logging/logging.cpp52
-rw-r--r--asl/logging/logging.hpp98
-rw-r--r--asl/logging/logging_tests.cpp23
4 files changed, 199 insertions, 0 deletions
diff --git a/asl/logging/BUILD.bazel b/asl/logging/BUILD.bazel
new file mode 100644
index 0000000..396da74
--- /dev/null
+++ b/asl/logging/BUILD.bazel
@@ -0,0 +1,26 @@
+cc_library(
+ name = "logging",
+ srcs = [
+ "logging.cpp",
+ ],
+ hdrs = [
+ "logging.hpp",
+ ],
+ deps = [
+ "//asl",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+cc_test(
+ name = "tests",
+ srcs = [
+ "logging_tests.cpp"
+ ],
+ deps = [
+ ":logging",
+ "//asl/testing",
+ ],
+ visibility = ["//visibility:public"],
+)
+
diff --git a/asl/logging/logging.cpp b/asl/logging/logging.cpp
new file mode 100644
index 0000000..aa630ac
--- /dev/null
+++ b/asl/logging/logging.cpp
@@ -0,0 +1,52 @@
+#include "asl/logging/logging.hpp"
+#include "asl/print.hpp"
+#include "asl/string_builder.hpp"
+
+// @Todo Don't use internal get_stdout_writer, make console module
+
+static asl::log::DefaultLogger<asl::Writer*> g_default_logger{asl::print_internals::get_stdout_writer()};
+static asl::log::Logger* g_head = &g_default_logger;
+
+static constexpr asl::string_view kLevelName[] = {
+ " DEBUG ",
+ " INFO ",
+ " WARNING ",
+ " ERROR ",
+};
+
+void asl::log::register_logger(box<Logger> logger_box)
+{
+ auto* logger = leak(ASL_MOVE(logger_box));
+ logger->m_next = exchange(g_head, logger);
+}
+
+void asl::log::DefaultLoggerBase::log_inner(Writer& writer, const message& msg)
+{
+ asl::format(&writer, "[{}] {}:{}: {}\n",
+ kLevelName[msg.level], // NOLINT
+ msg.location.file,
+ msg.location.line,
+ msg.message);
+}
+
+void asl::log::log_inner(
+ level l,
+ string_view fmt, span<const format_internals::type_erased_arg> args,
+ const source_location& sl)
+{
+ // @Todo Use temporary allocator
+ StringWriter msg_writer{};
+ asl::format_internals::format(&msg_writer, fmt, args);
+
+ message m{
+ .level = l,
+ .message = msg_writer.as_string_view(),
+ .location = sl,
+ };
+
+ for (auto* it = g_head; it != nullptr; it = it->next_logger())
+ {
+ it->log(m);
+ }
+}
+
diff --git a/asl/logging/logging.hpp b/asl/logging/logging.hpp
new file mode 100644
index 0000000..81f10e7
--- /dev/null
+++ b/asl/logging/logging.hpp
@@ -0,0 +1,98 @@
+#pragma once
+
+#include "asl/format.hpp"
+#include "asl/utility.hpp"
+#include "asl/box.hpp"
+
+namespace asl::log
+{
+
+enum level : uint8_t
+{
+ kDebug = 0,
+ kInfo,
+ kWarning,
+ kError,
+};
+
+struct message
+{
+ level level;
+ string_view message;
+ source_location location;
+};
+
+// @Todo Write and use an intrusive doubly-linked list
+class Logger
+{
+ Logger* m_next{};
+
+public:
+ Logger() = default;
+ ASL_DEFAULT_COPY_MOVE(Logger);
+ virtual ~Logger() = default;
+
+ virtual void log(const message&) = 0;
+
+ friend void register_logger(box<Logger>);
+
+ constexpr Logger* next_logger() const { return m_next; }
+};
+
+class DefaultLoggerBase : public Logger
+{
+protected:
+ static void log_inner(Writer&, const message&);
+};
+
+template<derefs_as<Writer> W>
+class DefaultLogger : public DefaultLoggerBase
+{
+ W m_writer;
+
+public:
+ explicit constexpr DefaultLogger(W&& writer) : m_writer{ASL_FWD(writer)} {}
+
+ constexpr void log(const message& m) override
+ {
+ log_inner(deref<Writer>(m_writer), m);
+ }
+};
+
+void register_logger(box<Logger>);
+
+// @Todo Add a way to remove loggers (including all)
+
+template<typename T, typename... Args>
+requires constructible_from<T, Args&&...> && convertible_from<Logger*, T*>
+void register_logger(Args&&... args)
+{
+ register_logger(make_box<T>(ASL_FWD(args)...));
+}
+
+void log_inner(level l, string_view fmt, span<const format_internals::type_erased_arg> args, const source_location& sl);
+
+template<formattable... Args>
+void log(level l, const source_location& sl, string_view fmt, const Args&... args)
+{
+ if constexpr (sizeof...(Args) == 0)
+ {
+ log_inner(l, fmt, {}, sl);
+ }
+ else
+ {
+ format_internals::type_erased_arg type_erased_args[] = {
+ format_internals::type_erased_arg(args)...
+ };
+ log_inner(l, fmt, type_erased_args, sl);
+ }
+}
+
+} // namespace asl::log
+
+// @Todo Compile-time configuration of logging
+
+#define ASL_LOG_DEBUG(...) ::asl::log::log(::asl::log::kDebug, ::asl::source_location{}, __VA_ARGS__)
+#define ASL_LOG_INFO(...) ::asl::log::log(::asl::log::kInfo, ::asl::source_location{}, __VA_ARGS__)
+#define ASL_LOG_WARNING(...) ::asl::log::log(::asl::log::kWarning, ::asl::source_location{}, __VA_ARGS__)
+#define ASL_LOG_ERROR(...) ::asl::log::log(::asl::log::kError, ::asl::source_location{}, __VA_ARGS__)
diff --git a/asl/logging/logging_tests.cpp b/asl/logging/logging_tests.cpp
new file mode 100644
index 0000000..ebbf800
--- /dev/null
+++ b/asl/logging/logging_tests.cpp
@@ -0,0 +1,23 @@
+#include "asl/logging/logging.hpp"
+#include "asl/testing/testing.hpp"
+#include "asl/string_builder.hpp"
+
+ASL_TEST(log)
+{
+ asl::log::log(asl::log::kInfo, asl::source_location{}, "Hello, {}!", "world"_sv);
+
+ ASL_LOG_ERROR("Oh no! {}", 42);
+}
+
+static asl::StringWriter g_string_writer{};
+
+ASL_TEST(custom_writer)
+{
+ asl::log::register_logger<asl::log::DefaultLogger<asl::StringWriter<>&>>(g_string_writer);
+
+ ASL_LOG_INFO("Hello");
+ auto sv = g_string_writer.as_string_view();
+
+ ASL_TEST_EXPECT(sv == "[ INFO ] asl/logging/logging_tests.cpp:18: Hello\n");
+}
+