Initial commit
* Logging ported from layover project with minor tweaks.
* Logging test ported over.
* Boost libraries are available.
This commit is contained in:
commit
b0ed20369f
2
.bazelrc
Normal file
2
.bazelrc
Normal file
@ -0,0 +1,2 @@
|
||||
build --action_env=BAZEL_CXXOPTS="-std=c++20:-g:-O2"
|
||||
run --action_env=BAZEL_CXXOPTS="-std=c++20:-g:-O2"
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
bazel-*
|
||||
11
BUILD
Normal file
11
BUILD
Normal file
@ -0,0 +1,11 @@
|
||||
load("@hedron_compile_commands//:refresh_compile_commands.bzl",
|
||||
"refresh_compile_commands"
|
||||
)
|
||||
|
||||
refresh_compile_commands(
|
||||
name = "refresh_compile_commands",
|
||||
targets = {
|
||||
"//source:parselinklog": "",
|
||||
"//tests/...": "",
|
||||
},
|
||||
)
|
||||
100
WORKSPACE
Normal file
100
WORKSPACE
Normal file
@ -0,0 +1,100 @@
|
||||
workspace(name = "parselink")
|
||||
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
#===============================================================================
|
||||
# Imported Bazel modules
|
||||
#===============================================================================
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Boost libraries, needed for asio + beast
|
||||
#-------------------------------------------------------------------------------
|
||||
rules_boost_commit = "49dc7d0e697c784f207fb1773b5b371c2511bfb8"
|
||||
rules_boost_url = "https://github.com/nelhage/rules_boost/archive/"
|
||||
|
||||
http_archive(
|
||||
name = "com_github_nelhage_rules_boost",
|
||||
url = rules_boost_url + rules_boost_commit + ".zip",
|
||||
strip_prefix = "rules_boost-" + rules_boost_commit
|
||||
)
|
||||
|
||||
load("@com_github_nelhage_rules_boost//:boost/boost.bzl", "boost_deps")
|
||||
boost_deps()
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# magic_enum: Used in logging implementation for enum names.
|
||||
#-------------------------------------------------------------------------------
|
||||
magic_enum_version = "0.8.2"
|
||||
magic_enum_base_url = \
|
||||
"https://github.com/Neargye/magic_enum/archive/refs/tags/v"
|
||||
magic_enum_sha256 = \
|
||||
"a10fa650307c60950b712a65a33fb46e9ac96ea72585cfa9fcf9ac9f502c01eb"
|
||||
|
||||
http_archive(
|
||||
name = "magic_enum",
|
||||
sha256 = magic_enum_sha256,
|
||||
url = magic_enum_base_url + magic_enum_version + ".zip",
|
||||
strip_prefix = "magic_enum-" + magic_enum_version,
|
||||
)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# fmt: Used in logging implementation.
|
||||
#-------------------------------------------------------------------------------
|
||||
fmt_version = "10.1.1"
|
||||
fmt_base_url = "https://github.com/fmtlib/fmt/archive/refs/tags/"
|
||||
|
||||
http_archive(
|
||||
name = "fmt",
|
||||
url = fmt_base_url + fmt_version + ".zip",
|
||||
patch_cmds = [
|
||||
"mv support/bazel/.bazelversion .bazelbersion",
|
||||
"mv support/bazel/BUILD.bazel BUILD.bazel",
|
||||
"mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel",
|
||||
],
|
||||
strip_prefix = "fmt-" + fmt_version,
|
||||
)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# ut: Unit test framework.
|
||||
# TODO(kss): Only if tests are needed?
|
||||
#-------------------------------------------------------------------------------
|
||||
ut_version = "1.1.9"
|
||||
ut_base_url = "https://github.com/boost-ext/ut/archive/refs/tags/v"
|
||||
ut_sha256 = "5811d993f88c5ba4916784cef60d1cb529917fb9a3f72236219cb9ee9c1974ca"
|
||||
|
||||
http_archive(
|
||||
name = "ut",
|
||||
url = ut_base_url + ut_version + ".zip",
|
||||
sha256 = ut_sha256,
|
||||
build_file_content =
|
||||
"""
|
||||
cc_library(
|
||||
name = "ut",
|
||||
includes = ["include"],
|
||||
hdrs = glob(["include/**/*.hpp"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
strip_prefix = "ut-" + ut_version,
|
||||
)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Support compile_commands.json generation for LSP.
|
||||
#-------------------------------------------------------------------------------
|
||||
hedron_commit = "3dddf205a1f5cde20faf2444c1757abe0564ff4c"
|
||||
hedron_sha256 = \
|
||||
"a4ce320769ba39a292ae0319eb534599d5114751ec9873e7eaa2aa5b7b7af1b2"
|
||||
hedron_base_url = \
|
||||
"https://github.com/hedronvision/bazel-compile-commands-extractor/archive/"
|
||||
|
||||
http_archive(
|
||||
name = "hedron_compile_commands",
|
||||
sha256 = hedron_sha256,
|
||||
strip_prefix = "bazel-compile-commands-extractor-" + hedron_commit,
|
||||
url = hedron_base_url + hedron_commit + ".zip",
|
||||
)
|
||||
|
||||
load("@hedron_compile_commands//:workspace_setup.bzl",
|
||||
"hedron_compile_commands_setup",
|
||||
)
|
||||
hedron_compile_commands_setup()
|
||||
15
source/BUILD
Normal file
15
source/BUILD
Normal file
@ -0,0 +1,15 @@
|
||||
cc_binary(
|
||||
name = "parselinkd",
|
||||
srcs = ["main.cpp"],
|
||||
deps = [
|
||||
"@fmt",
|
||||
"@boost//:beast",
|
||||
],
|
||||
)
|
||||
cc_binary(
|
||||
name = "parselinklog",
|
||||
srcs = ["log.cpp"],
|
||||
deps = [
|
||||
"//source/common"
|
||||
],
|
||||
)
|
||||
25
source/common/BUILD
Normal file
25
source/common/BUILD
Normal file
@ -0,0 +1,25 @@
|
||||
# parselink
|
||||
|
||||
cc_library(
|
||||
name = "lib",
|
||||
srcs = [
|
||||
"source/logging.cpp",
|
||||
],
|
||||
hdrs = [
|
||||
"include/logging.h",
|
||||
"include/logging/level.h",
|
||||
"include/logging/formatters.h",
|
||||
"include/logging/theme.h",
|
||||
"include/logging/traits.h",
|
||||
],
|
||||
linkstatic = True,
|
||||
includes = ["include"],
|
||||
deps = [
|
||||
"@fmt//:fmt",
|
||||
"@magic_enum//:magic_enum",
|
||||
],
|
||||
visibility = [
|
||||
# TODO: Fix visibility
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
130
source/common/include/logging.h
Normal file
130
source/common/include/logging.h
Normal file
@ -0,0 +1,130 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: Logging
|
||||
//
|
||||
// Logging infrastructure.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef logging_982a89e400976f59
|
||||
#define logging_982a89e400976f59
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "logging/formatters.h"
|
||||
#include "logging/theme.h"
|
||||
#include "logging/traits.h"
|
||||
|
||||
namespace parselink {
|
||||
namespace logging {
|
||||
|
||||
// Any log levels higher (lower severity) than static_threshold cannot not be
|
||||
// enabled in the library, but the compiler should be able to optimize away
|
||||
// some/most of the calls.
|
||||
constexpr inline auto static_threshold = level::verbose;
|
||||
constexpr inline auto default_threshold = level::info;
|
||||
|
||||
// Structure for holding a message. Note: message is a view over some buffer,
|
||||
// not it's own string. It will either be a static buffer supplied by the
|
||||
// endpoint implementation, or it will be dynamically created by the logger
|
||||
// before calling endpoint::write()
|
||||
struct message {
|
||||
level lvl;
|
||||
std::chrono::system_clock::time_point time;
|
||||
std::string_view name;
|
||||
std::string_view message;
|
||||
};
|
||||
|
||||
struct endpoint {
|
||||
virtual ~endpoint() = default;
|
||||
virtual std::span<char> buffer() { return {}; }
|
||||
virtual void write(message const& msg) = 0;
|
||||
virtual bool colored() const noexcept = 0;
|
||||
level threshold{default_threshold};
|
||||
};
|
||||
|
||||
class logger {
|
||||
public:
|
||||
// This constructor utilizes the default logging endpoint.
|
||||
logger(std::string_view name);
|
||||
|
||||
// This constructor allows for an arbitrary number of logging endpoints
|
||||
// to be passed in.
|
||||
logger(std::string_view name,
|
||||
std::vector<std::shared_ptr<endpoint>> eps);
|
||||
|
||||
template <typename... Endpoints>
|
||||
explicit logger(std::string_view name, Endpoints&&... eps)
|
||||
: logger(name, {std::forward<Endpoints>(eps)...}) {}
|
||||
|
||||
template<level Level, typename... Args>
|
||||
requires (Level <= static_threshold)
|
||||
void log(fmt::format_string<Args...> format, Args&&... args) const {
|
||||
try_write(Level, format.get(), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<level Level, typename... Args>
|
||||
requires (Level > static_threshold)
|
||||
[[gnu::flatten]] void log(fmt::format_string<Args...>, Args&&...) const {}
|
||||
|
||||
void set_threshold(level new_threshold) noexcept;
|
||||
|
||||
private:
|
||||
|
||||
template<typename... Args>
|
||||
void write_endpoint(std::shared_ptr<endpoint> const& endpoint, message msg,
|
||||
fmt::string_view format, Args&&... args) const {
|
||||
auto buff = endpoint->buffer();
|
||||
if (buff.empty()) {
|
||||
// Allocate memory.
|
||||
auto buff = fmt::memory_buffer();
|
||||
fmt::vformat_to(std::back_inserter(buff), format,
|
||||
fmt::make_format_args(args...));
|
||||
msg.message = std::string_view{buff.data(), buff.size()};
|
||||
endpoint->write(msg);
|
||||
} else {
|
||||
// Fill the static buffer.
|
||||
fmt::vformat_to(buff.begin(), format,
|
||||
fmt::make_format_args(args...));
|
||||
msg.message = std::string_view{buff.data(), buff.size()};
|
||||
endpoint->write(msg);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void try_write(level level, fmt::string_view format,
|
||||
Args&&... args) const {
|
||||
message msg{level, std::chrono::system_clock::now(), name_};
|
||||
for (auto const& ep : endpoints_) {
|
||||
if (level > ep->threshold) continue;
|
||||
if (ep->colored()) {
|
||||
write_endpoint(ep, msg, format,
|
||||
themed_arg<Args>{std::forward<Args>(args)}...);
|
||||
} else {
|
||||
write_endpoint(ep, msg, format,
|
||||
format_arg<Args>{std::forward<Args>(args)}...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string name_;
|
||||
std::vector<std::shared_ptr<endpoint>> endpoints_;
|
||||
};
|
||||
|
||||
} // namespace logging
|
||||
} // namespace parselink
|
||||
|
||||
#endif // logging_982a89e400976f59
|
||||
133
source/common/include/logging/formatters.h
Normal file
133
source/common/include/logging/formatters.h
Normal file
@ -0,0 +1,133 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: Logging
|
||||
//
|
||||
// {fmt} formatters for various types that are used throughout the codebase.
|
||||
// Some are specific, while others depend on traits.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef logging_formatters_d22a64b1645a8134
|
||||
#define logging_formatters_d22a64b1645a8134
|
||||
|
||||
#include "traits.h"
|
||||
#include "theme.h"
|
||||
|
||||
/* #include <parselink/util/expected.h> */
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
// Simple wrapper for error strings to be formatted like any other string_view.
|
||||
template <>
|
||||
struct fmt::formatter<parselink::logging::error_str>
|
||||
: fmt::formatter<std::string_view> {
|
||||
template<typename FormatContext>
|
||||
constexpr auto format(auto const& v, FormatContext& ctx) const {
|
||||
return fmt::formatter<std::string_view>::format(v.v, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
// Support enums. By default, enums print the enum type name as well as the
|
||||
// enum name for the given value. E.g.
|
||||
//
|
||||
// enum Color { Red, Green, Blue };
|
||||
// auto const color = Color::Green;
|
||||
//
|
||||
// Logging `color` will yield "Color::Green"
|
||||
template <typename E>
|
||||
requires std::is_enum_v<E>
|
||||
struct fmt::formatter<E> : fmt::formatter<std::string_view> {
|
||||
template<typename FormatContext>
|
||||
auto format(E const v, FormatContext& ctx) const {
|
||||
auto str = [v]{
|
||||
return fmt::format("{}::{}",
|
||||
magic_enum::enum_type_name<E>(),
|
||||
magic_enum::enum_name(v));
|
||||
}();
|
||||
return fmt::formatter<std::string_view>::format(str, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
// Support enums that _only_ print the string "name" of the enum value. Using
|
||||
// the same example above:
|
||||
//
|
||||
// enum Color { Red, Green, Blue };
|
||||
// auto const color = Color::Green;
|
||||
//
|
||||
// Logging `enum_name_only{color}` will yield "Green"
|
||||
template <typename E>
|
||||
requires std::is_enum_v<E>
|
||||
struct fmt::formatter<parselink::logging::enum_name_only<E>>
|
||||
: fmt::formatter<std::string_view> {
|
||||
using enum_name_only = parselink::logging::enum_name_only<E>;
|
||||
template<typename FormatContext>
|
||||
auto format(enum_name_only const v, FormatContext& ctx) const {
|
||||
return fmt::formatter<std::string_view>::format(
|
||||
magic_enum::enum_name(v.v), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
// Support conversion of typical standard error codes into a human-readable
|
||||
// string.
|
||||
template <>
|
||||
struct fmt::formatter<std::errc> : fmt::formatter<std::string_view> {
|
||||
template<typename FormatContext>
|
||||
auto format(std::errc const& v, FormatContext& ctx) const {
|
||||
return fmt::formatter<std::string_view>::format(
|
||||
std::make_error_code(v).message(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
// Support printing raw/smart pointers without needing to wrap them in fmt::ptr
|
||||
template <parselink::logging::detail::printable_pointer T>
|
||||
struct fmt::formatter<T> : fmt::formatter<void const*> {
|
||||
template<typename FormatContext>
|
||||
auto format(T const& v, FormatContext& ctx) const {
|
||||
return fmt::formatter<void const*>::format(fmt::ptr(v), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
#if 0
|
||||
// TODO(ksassenrath): Re-enable when expected has been integrated
|
||||
template <typename T, typename Err>
|
||||
struct fmt::formatter<parselink::expected<T, Err>> {
|
||||
template <typename ParseContext>
|
||||
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template<typename FormatContext>
|
||||
auto format(parselink::expected<T, Err> const& v, FormatContext& ctx) const {
|
||||
if (v.has_value()) {
|
||||
return fmt::format_to(ctx.out(), "{}", parselink::logging::format_arg<T>{v.value()});
|
||||
} else {
|
||||
return fmt::format_to(ctx.out(), "{}", parselink::logging::format_arg<Err>{v.error()});
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
// Support format_arg wrappers, which will be used to colorize output.
|
||||
template <typename T>
|
||||
struct fmt::formatter<parselink::logging::format_arg<T>> :
|
||||
fmt::formatter<typename parselink::logging::format_arg<T>::type> {
|
||||
using format_arg_type = parselink::logging::format_arg<T>;
|
||||
using resolved_type = typename format_arg_type::type;
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(format_arg_type const& v, FormatContext& ctx) const {
|
||||
return fmt::formatter<resolved_type>::format(v.v, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // logging_formatters_d22a64b1645a8134
|
||||
41
source/common/include/logging/level.h
Normal file
41
source/common/include/logging/level.h
Normal file
@ -0,0 +1,41 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: Logging
|
||||
//
|
||||
// Level definitions, for indicating the severity or intent of the log message.
|
||||
// Loggers and sinks can be configured to ignore logs not matching a certain
|
||||
// threshold.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef level_9f090ff308e53a57
|
||||
#define level_9f090ff308e53a57
|
||||
|
||||
namespace parselink {
|
||||
namespace logging {
|
||||
|
||||
enum class level {
|
||||
silent, // "Virtual" level used to suppress all logging output.
|
||||
critical, // Indicates a fatal error occurred. Crash likely.
|
||||
error, // Indicates a non-fatal error occurred.
|
||||
warning, // Indicates potentially incorrect/unintentional behavior.
|
||||
info, // Indicates general information.
|
||||
verbose, // Noisier/potentially unimportant information.
|
||||
debug, // Information intended for debugging purposes only.
|
||||
trace // Tracer-like levels of verbosity may impact performance.
|
||||
};
|
||||
|
||||
} // namespace logging
|
||||
} // namespace parselink
|
||||
|
||||
#endif // level_9f090ff308e53a57
|
||||
206
source/common/include/logging/theme.h
Normal file
206
source/common/include/logging/theme.h
Normal file
@ -0,0 +1,206 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: Logging
|
||||
//
|
||||
// The logging facilities within parselink colorize output by default. This
|
||||
// is achieved by wrapping each formatted argument with an empty structure
|
||||
// and specializing the theme structure for the type.
|
||||
//
|
||||
// If no theme is specified for a type, the text will be rendered with the
|
||||
// default color.
|
||||
//
|
||||
// TODO(ksassenrath):
|
||||
// 1. Make configurable with a text file.
|
||||
// 2. Defer colorization for logging endpoints which do not support/desire
|
||||
// colored output.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef log_theme_8e9601cc066b20bf
|
||||
#define log_theme_8e9601cc066b20bf
|
||||
|
||||
#include "level.h"
|
||||
#include "traits.h"
|
||||
|
||||
/* #include <parselink/util/expected.h> */
|
||||
|
||||
#include <fmt/color.h>
|
||||
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
|
||||
namespace parselink {
|
||||
namespace logging {
|
||||
|
||||
template <typename T>
|
||||
struct format_arg{
|
||||
using type = std::decay_t<T>;
|
||||
constexpr format_arg(type const& t) : v(t) {}
|
||||
type const& v;
|
||||
};
|
||||
|
||||
template <std::convertible_to<std::string_view> T>
|
||||
struct format_arg<T> {
|
||||
using type = std::string_view;
|
||||
constexpr format_arg(T&& t) noexcept : v(t) {}
|
||||
type v;
|
||||
};
|
||||
|
||||
template <detail::smart_pointer T>
|
||||
struct format_arg<T> {
|
||||
using type = void const*;
|
||||
constexpr format_arg(T const& t) noexcept : v(fmt::ptr(t)) {}
|
||||
void const* v;
|
||||
};
|
||||
|
||||
template <typename>
|
||||
struct theme {};
|
||||
|
||||
template <fmt::color Color>
|
||||
struct static_theme {
|
||||
constexpr static auto style = fmt::fg(Color);
|
||||
};
|
||||
|
||||
template <std::integral T>
|
||||
struct theme<T> : static_theme<fmt::color::light_sky_blue> {};
|
||||
|
||||
template <std::convertible_to<std::string_view> T>
|
||||
struct theme<T> : static_theme<fmt::color::pale_green> {};
|
||||
|
||||
template <detail::printable_pointer T>
|
||||
struct theme<T> : static_theme<fmt::color::pale_violet_red> {};
|
||||
|
||||
template <typename E>
|
||||
requires std::is_enum_v<E>
|
||||
struct theme<E> : static_theme<fmt::color::gray> {};
|
||||
|
||||
template <>
|
||||
struct theme<std::errc> : static_theme<fmt::color::fire_brick> {};
|
||||
|
||||
template <>
|
||||
struct theme<error_str> : static_theme<fmt::color::fire_brick> {};
|
||||
|
||||
template <>
|
||||
struct theme<enum_name_only<logging::level>> {
|
||||
constexpr static fmt::color colors[] = {
|
||||
fmt::color::black,
|
||||
fmt::color::red,
|
||||
fmt::color::fire_brick,
|
||||
fmt::color::golden_rod,
|
||||
fmt::color::light_sky_blue,
|
||||
fmt::color::lime_green,
|
||||
fmt::color::pink,
|
||||
fmt::color::slate_gray
|
||||
};
|
||||
static constexpr auto style(auto l) noexcept {
|
||||
return fmt::fg(*std::next(std::begin(colors), size_t(l.v)));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept has_static_theme =
|
||||
std::convertible_to<fmt::text_style,
|
||||
decltype(theme<std::remove_cvref_t<T>>::style)>;
|
||||
|
||||
template <typename T>
|
||||
concept has_dynamic_theme = requires (T const& t) {
|
||||
{ theme<std::remove_cvref_t<T>>::style(t) }
|
||||
-> std::convertible_to<fmt::text_style>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept has_theme = has_static_theme<T> || has_dynamic_theme<T>;
|
||||
|
||||
static_assert(has_static_theme<std::errc>);
|
||||
|
||||
template <typename T>
|
||||
constexpr auto get_theme(T const&) {
|
||||
return fmt::text_style{};
|
||||
}
|
||||
|
||||
template <has_static_theme T>
|
||||
constexpr auto get_theme(T const&) {
|
||||
return theme<T>::style;
|
||||
}
|
||||
|
||||
template <has_dynamic_theme T>
|
||||
constexpr auto get_theme(T const& value) {
|
||||
return theme<T>::style(value);
|
||||
}
|
||||
|
||||
[[gnu::always_inline]] constexpr auto styled(fmt::text_style const& style,
|
||||
auto out) {
|
||||
if (style.has_foreground()) {
|
||||
auto foreground =
|
||||
fmt::detail::make_foreground_color<char>(style.get_foreground());
|
||||
out = std::copy(foreground.begin(), foreground.end(), out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// TODO(ksassenrath): Enable when expected is supported
|
||||
template <typename T, typename Err>
|
||||
struct theme<expected<T, Err>> {
|
||||
static constexpr auto style(auto const& e) noexcept {
|
||||
if (e.has_value()) {
|
||||
return get_theme(e.value());
|
||||
} else {
|
||||
return get_theme(e.error());
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
struct themed_arg : format_arg<T> {};
|
||||
|
||||
template <typename T>
|
||||
themed_arg(T&&) -> themed_arg<T>;
|
||||
|
||||
template <typename... Args>
|
||||
fmt::format_args themed_args(Args&&... args) {
|
||||
return fmt::format_arg_store<fmt::format_context>(
|
||||
themed_arg<Args>{std::forward<Args>(args)}...);
|
||||
}
|
||||
|
||||
constexpr inline std::string_view reset_theme{"\x1b[0m"};
|
||||
|
||||
} // namespace logging
|
||||
} // namespace parselink
|
||||
|
||||
// Colorize the text but forward the actual formatting itself to the original
|
||||
// formatter.
|
||||
template <typename T>
|
||||
struct fmt::formatter<parselink::logging::themed_arg<T>>
|
||||
: fmt::formatter<typename parselink::logging::format_arg<T>::type> {
|
||||
using type = std::remove_cvref_t<T>;
|
||||
using themed_arg = parselink::logging::themed_arg<T>;
|
||||
using format_arg = parselink::logging::format_arg<T>;
|
||||
using resolved_type = typename format_arg::type;
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(themed_arg const& v, FormatContext& ctx) const {
|
||||
auto out = ctx.out();
|
||||
out = parselink::logging::styled(
|
||||
parselink::logging::get_theme(v.v), out);
|
||||
out = fmt::formatter<resolved_type>::format(v.v, ctx);
|
||||
if constexpr (parselink::logging::has_theme<type>) {
|
||||
auto reset_color = parselink::logging::reset_theme;
|
||||
out = std::copy(reset_color.begin(), reset_color.end(), out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // log_theme_8e9601cc066b20bf
|
||||
115
source/common/include/logging/traits.h
Normal file
115
source/common/include/logging/traits.h
Normal file
@ -0,0 +1,115 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: Logging
|
||||
//
|
||||
// Type traits for massaging various types into something fmt-printable.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef logging_traits_34e410874c0179c6
|
||||
#define logging_traits_34e410874c0179c6
|
||||
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
|
||||
namespace parselink {
|
||||
namespace logging {
|
||||
|
||||
// Customization point for printing out the enum name (value) and not the
|
||||
// full type. The default prints the type name. An example is provided below.
|
||||
//
|
||||
// enum class Foo { Bar, Cat };
|
||||
// Foo v{Foo::Bar};
|
||||
// logger.log<...>("value: {}", v); // logs "value: Foo::Bar"
|
||||
// logger.log<...>("value: {}", enum_name_only{v}); // logs "value: Bar"
|
||||
template <typename E>
|
||||
requires std::is_enum_v<E>
|
||||
struct enum_name_only {
|
||||
E v;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
enum_name_only(E) -> enum_name_only<E>;
|
||||
|
||||
// Wrapper for a string that will be colorized as if it's an error, instead of
|
||||
// a normal string.
|
||||
struct error_str {
|
||||
template <std::convertible_to<std::string_view> T>
|
||||
error_str(T&& t) : v(std::forward<T>(t)) {}
|
||||
std::string_view v;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// The following concepts aim to describe both raw and smart pointers in a
|
||||
// way that the log formatter can deduce theme (color/format) as well as
|
||||
// print the address without libformat's boilerplate (fmt::ptr).
|
||||
template <typename T>
|
||||
struct is_smart_pointer_type : std::false_type {};
|
||||
|
||||
template <typename T, typename D>
|
||||
struct is_smart_pointer_type<std::unique_ptr<T, D>> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_smart_pointer_type<std::shared_ptr<T>> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
concept smart_pointer = is_smart_pointer_type<std::remove_cvref_t<T>>::value;
|
||||
|
||||
template <typename T>
|
||||
using underlying_ptr_type = std::remove_cv_t<
|
||||
std::remove_pointer_t<std::decay_t<std::remove_cvref_t<T>>>>;
|
||||
|
||||
template <typename T>
|
||||
concept non_string_pointer =
|
||||
std::is_pointer_v<std::decay_t<std::remove_cvref_t<T>>>
|
||||
&& !std::is_same_v<underlying_ptr_type<T>, void>
|
||||
&& !std::is_same_v<underlying_ptr_type<T>, char>;
|
||||
|
||||
template <typename T>
|
||||
concept printable_pointer = smart_pointer<T> || non_string_pointer<T>;
|
||||
|
||||
// Some simple assertions to avoid breaking the code above.
|
||||
static_assert(printable_pointer<int*>);
|
||||
static_assert(printable_pointer<int*&>);
|
||||
static_assert(printable_pointer<int const*&>);
|
||||
static_assert(printable_pointer<int* const&>);
|
||||
static_assert(printable_pointer<int (&)[4]>);
|
||||
static_assert(printable_pointer<int const (&)[4]>);
|
||||
|
||||
static_assert(!printable_pointer<char*>);
|
||||
static_assert(!printable_pointer<char*&>);
|
||||
static_assert(!printable_pointer<char const*>);
|
||||
static_assert(!printable_pointer<char const*&>);
|
||||
static_assert(!printable_pointer<char (&)[4]>);
|
||||
static_assert(!printable_pointer<char const (&)[4]>);
|
||||
|
||||
static_assert(!printable_pointer<void*>);
|
||||
static_assert(!printable_pointer<void*&>);
|
||||
static_assert(!printable_pointer<void const*>);
|
||||
static_assert(!printable_pointer<void const*&>);
|
||||
|
||||
static_assert(printable_pointer<std::unique_ptr<int>>);
|
||||
static_assert(printable_pointer<std::unique_ptr<int>&>);
|
||||
static_assert(printable_pointer<std::unique_ptr<int> const&>);
|
||||
|
||||
static_assert(printable_pointer<std::unique_ptr<int const>>);
|
||||
static_assert(printable_pointer<std::unique_ptr<int const>&>);
|
||||
static_assert(printable_pointer<std::unique_ptr<int const> const&>);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace logging
|
||||
} // namespace parselink
|
||||
|
||||
#endif // logging_traits_34e410874c0179c6
|
||||
64
source/common/source/logging.cpp
Normal file
64
source/common/source/logging.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: Logging
|
||||
//
|
||||
// Logging implementation.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
using namespace parselink::logging;
|
||||
|
||||
namespace {
|
||||
|
||||
struct console_endpoint : public endpoint {
|
||||
static constexpr std::string_view format_string =
|
||||
"{:%Y-%m-%d %H:%M:%S}.{:03} [{:<8}] {:>20} | {}\n";
|
||||
|
||||
std::span<char> buffer() noexcept override { return buffer_; }
|
||||
|
||||
bool colored() const noexcept override { return true; }
|
||||
|
||||
void write(message const& msg) override {
|
||||
using namespace std::chrono_literals;
|
||||
fmt::print(format_string, fmt::gmtime(msg.time),
|
||||
(msg.time.time_since_epoch() % 1000ms) / 1ms,
|
||||
themed_arg{enum_name_only{msg.lvl}}, msg.name, msg.message);
|
||||
}
|
||||
|
||||
std::array<char, 4096> buffer_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
auto& console() {
|
||||
static auto console = std::make_shared<console_endpoint>();
|
||||
return console;
|
||||
}
|
||||
|
||||
logger::logger(std::string_view name) : name_{name} {
|
||||
endpoints_.emplace_back(console());
|
||||
}
|
||||
|
||||
logger::logger(std::string_view name, std::vector<std::shared_ptr<endpoint>> eps)
|
||||
: name_{name}, endpoints_{std::move(eps)} {}
|
||||
|
||||
void logger::set_threshold(level new_threshold) noexcept {
|
||||
for (auto& ep : endpoints_) {
|
||||
ep->threshold = new_threshold;
|
||||
}
|
||||
}
|
||||
|
||||
13
source/log.cpp
Normal file
13
source/log.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
#include <logging.h>
|
||||
|
||||
using namespace parselink;
|
||||
using level = parselink::logging::level;
|
||||
|
||||
namespace {
|
||||
logging::logger logger("parselog");
|
||||
}
|
||||
|
||||
int main(int argc, char**) {
|
||||
logger.log<level::critical>("Found {} arguments", argc);
|
||||
return 0;
|
||||
}
|
||||
444
source/main.cpp
Normal file
444
source/main.cpp
Normal file
@ -0,0 +1,444 @@
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace beast = boost::beast; // from <boost/beast.hpp>
|
||||
namespace http = beast::http; // from <boost/beast/http.hpp>
|
||||
namespace net = boost::asio; // from <boost/asio.hpp>
|
||||
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||
|
||||
// Return a reasonable mime type based on the extension of a file.
|
||||
beast::string_view
|
||||
mime_type(beast::string_view path)
|
||||
{
|
||||
using beast::iequals;
|
||||
auto const ext = [&path]
|
||||
{
|
||||
auto const pos = path.rfind(".");
|
||||
if(pos == beast::string_view::npos)
|
||||
return beast::string_view{};
|
||||
return path.substr(pos);
|
||||
}();
|
||||
if(iequals(ext, ".htm")) return "text/html";
|
||||
if(iequals(ext, ".html")) return "text/html";
|
||||
if(iequals(ext, ".php")) return "text/html";
|
||||
if(iequals(ext, ".css")) return "text/css";
|
||||
if(iequals(ext, ".txt")) return "text/plain";
|
||||
if(iequals(ext, ".js")) return "application/javascript";
|
||||
if(iequals(ext, ".json")) return "application/json";
|
||||
if(iequals(ext, ".xml")) return "application/xml";
|
||||
if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
|
||||
if(iequals(ext, ".flv")) return "video/x-flv";
|
||||
if(iequals(ext, ".png")) return "image/png";
|
||||
if(iequals(ext, ".jpe")) return "image/jpeg";
|
||||
if(iequals(ext, ".jpeg")) return "image/jpeg";
|
||||
if(iequals(ext, ".jpg")) return "image/jpeg";
|
||||
if(iequals(ext, ".gif")) return "image/gif";
|
||||
if(iequals(ext, ".bmp")) return "image/bmp";
|
||||
if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
|
||||
if(iequals(ext, ".tiff")) return "image/tiff";
|
||||
if(iequals(ext, ".tif")) return "image/tiff";
|
||||
if(iequals(ext, ".svg")) return "image/svg+xml";
|
||||
if(iequals(ext, ".svgz")) return "image/svg+xml";
|
||||
return "application/text";
|
||||
}
|
||||
|
||||
// Append an HTTP rel-path to a local filesystem path.
|
||||
// The returned path is normalized for the platform.
|
||||
std::string
|
||||
path_cat(
|
||||
beast::string_view base,
|
||||
beast::string_view path)
|
||||
{
|
||||
if(base.empty())
|
||||
return std::string(path);
|
||||
std::string result(base);
|
||||
#ifdef BOOST_MSVC
|
||||
char constexpr path_separator = '\\';
|
||||
if(result.back() == path_separator)
|
||||
result.resize(result.size() - 1);
|
||||
result.append(path.data(), path.size());
|
||||
for(auto& c : result)
|
||||
if(c == '/')
|
||||
c = path_separator;
|
||||
#else
|
||||
char constexpr path_separator = '/';
|
||||
if(result.back() == path_separator)
|
||||
result.resize(result.size() - 1);
|
||||
result.append(path.data(), path.size());
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
// Return a response for the given request.
|
||||
//
|
||||
// The concrete type of the response message (which depends on the
|
||||
// request), is type-erased in message_generator.
|
||||
template <class Body, class Allocator>
|
||||
http::message_generator
|
||||
handle_request(
|
||||
beast::string_view doc_root,
|
||||
http::request<Body, http::basic_fields<Allocator>>&& req)
|
||||
{
|
||||
// Returns a bad request response
|
||||
auto const bad_request =
|
||||
[&req](beast::string_view why)
|
||||
{
|
||||
http::response<http::string_body> res{http::status::bad_request, req.version()};
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "text/html");
|
||||
res.keep_alive(req.keep_alive());
|
||||
res.body() = std::string(why);
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
};
|
||||
|
||||
// Returns a not found response
|
||||
auto const not_found =
|
||||
[&req](beast::string_view target)
|
||||
{
|
||||
http::response<http::string_body> res{http::status::not_found, req.version()};
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "text/html");
|
||||
res.keep_alive(req.keep_alive());
|
||||
res.body() = "The resource '" + std::string(target) + "' was not found.";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
};
|
||||
|
||||
// Returns a server error response
|
||||
auto const server_error =
|
||||
[&req](beast::string_view what)
|
||||
{
|
||||
http::response<http::string_body> res{http::status::internal_server_error, req.version()};
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "text/html");
|
||||
res.keep_alive(req.keep_alive());
|
||||
res.body() = "An error occurred: '" + std::string(what) + "'";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
};
|
||||
|
||||
// Make sure we can handle the method
|
||||
if( req.method() != http::verb::get &&
|
||||
req.method() != http::verb::head)
|
||||
return bad_request("Unknown HTTP-method");
|
||||
|
||||
// Request path must be absolute and not contain "..".
|
||||
if( req.target().empty() ||
|
||||
req.target()[0] != '/' ||
|
||||
req.target().find("..") != beast::string_view::npos)
|
||||
return bad_request("Illegal request-target");
|
||||
|
||||
// Build the path to the requested file
|
||||
std::string path = path_cat(doc_root, req.target());
|
||||
if(req.target().back() == '/')
|
||||
path.append("index.html");
|
||||
|
||||
// Attempt to open the file
|
||||
beast::error_code ec;
|
||||
http::file_body::value_type body;
|
||||
body.open(path.c_str(), beast::file_mode::scan, ec);
|
||||
|
||||
// Handle the case where the file doesn't exist
|
||||
if(ec == beast::errc::no_such_file_or_directory)
|
||||
return not_found(req.target());
|
||||
|
||||
// Handle an unknown error
|
||||
if(ec)
|
||||
return server_error(ec.message());
|
||||
|
||||
// Cache the size since we need it after the move
|
||||
auto const size = body.size();
|
||||
|
||||
// Respond to HEAD request
|
||||
if(req.method() == http::verb::head)
|
||||
{
|
||||
http::response<http::empty_body> res{http::status::ok, req.version()};
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, mime_type(path));
|
||||
res.content_length(size);
|
||||
res.keep_alive(req.keep_alive());
|
||||
return res;
|
||||
}
|
||||
|
||||
// Respond to GET request
|
||||
http::response<http::file_body> res{
|
||||
std::piecewise_construct,
|
||||
std::make_tuple(std::move(body)),
|
||||
std::make_tuple(http::status::ok, req.version())};
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, mime_type(path));
|
||||
res.content_length(size);
|
||||
res.keep_alive(req.keep_alive());
|
||||
return res;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Report a failure
|
||||
void
|
||||
fail(beast::error_code ec, char const* what)
|
||||
{
|
||||
std::cerr << what << ": " << ec.message() << "\n";
|
||||
}
|
||||
|
||||
// Handles an HTTP server connection
|
||||
class session : public std::enable_shared_from_this<session>
|
||||
{
|
||||
beast::tcp_stream stream_;
|
||||
beast::flat_buffer buffer_;
|
||||
std::shared_ptr<std::string const> doc_root_;
|
||||
http::request<http::string_body> req_;
|
||||
|
||||
public:
|
||||
// Take ownership of the stream
|
||||
session(
|
||||
tcp::socket&& socket,
|
||||
std::shared_ptr<std::string const> const& doc_root)
|
||||
: stream_(std::move(socket))
|
||||
, doc_root_(doc_root)
|
||||
{
|
||||
}
|
||||
|
||||
// Start the asynchronous operation
|
||||
void
|
||||
run()
|
||||
{
|
||||
// We need to be executing within a strand to perform async operations
|
||||
// on the I/O objects in this session. Although not strictly necessary
|
||||
// for single-threaded contexts, this example code is written to be
|
||||
// thread-safe by default.
|
||||
net::dispatch(stream_.get_executor(),
|
||||
beast::bind_front_handler(
|
||||
&session::do_read,
|
||||
shared_from_this()));
|
||||
}
|
||||
|
||||
void
|
||||
do_read()
|
||||
{
|
||||
// Make the request empty before reading,
|
||||
// otherwise the operation behavior is undefined.
|
||||
req_ = {};
|
||||
|
||||
// Set the timeout.
|
||||
stream_.expires_after(std::chrono::seconds(30));
|
||||
|
||||
// Read a request
|
||||
http::async_read(stream_, buffer_, req_,
|
||||
beast::bind_front_handler(
|
||||
&session::on_read,
|
||||
shared_from_this()));
|
||||
}
|
||||
|
||||
void
|
||||
on_read(
|
||||
beast::error_code ec,
|
||||
std::size_t bytes_transferred)
|
||||
{
|
||||
boost::ignore_unused(bytes_transferred);
|
||||
|
||||
// This means they closed the connection
|
||||
if(ec == http::error::end_of_stream)
|
||||
return do_close();
|
||||
|
||||
if(ec)
|
||||
return fail(ec, "read");
|
||||
|
||||
// Send the response
|
||||
send_response(
|
||||
handle_request(*doc_root_, std::move(req_)));
|
||||
}
|
||||
|
||||
void
|
||||
send_response(http::message_generator&& msg)
|
||||
{
|
||||
bool keep_alive = msg.keep_alive();
|
||||
|
||||
// Write the response
|
||||
beast::async_write(
|
||||
stream_,
|
||||
std::move(msg),
|
||||
beast::bind_front_handler(
|
||||
&session::on_write, shared_from_this(), keep_alive));
|
||||
}
|
||||
|
||||
void
|
||||
on_write(
|
||||
bool keep_alive,
|
||||
beast::error_code ec,
|
||||
std::size_t bytes_transferred)
|
||||
{
|
||||
boost::ignore_unused(bytes_transferred);
|
||||
|
||||
if(ec)
|
||||
return fail(ec, "write");
|
||||
|
||||
if(! keep_alive)
|
||||
{
|
||||
// This means we should close the connection, usually because
|
||||
// the response indicated the "Connection: close" semantic.
|
||||
return do_close();
|
||||
}
|
||||
|
||||
// Read another request
|
||||
do_read();
|
||||
}
|
||||
|
||||
void
|
||||
do_close()
|
||||
{
|
||||
// Send a TCP shutdown
|
||||
beast::error_code ec;
|
||||
stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
|
||||
|
||||
// At this point the connection is closed gracefully
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Accepts incoming connections and launches the sessions
|
||||
class listener : public std::enable_shared_from_this<listener>
|
||||
{
|
||||
net::io_context& ioc_;
|
||||
tcp::acceptor acceptor_;
|
||||
std::shared_ptr<std::string const> doc_root_;
|
||||
|
||||
public:
|
||||
listener(
|
||||
net::io_context& ioc,
|
||||
tcp::endpoint endpoint,
|
||||
std::shared_ptr<std::string const> const& doc_root)
|
||||
: ioc_(ioc)
|
||||
, acceptor_(net::make_strand(ioc))
|
||||
, doc_root_(doc_root)
|
||||
{
|
||||
beast::error_code ec;
|
||||
|
||||
// Open the acceptor
|
||||
acceptor_.open(endpoint.protocol(), ec);
|
||||
if(ec)
|
||||
{
|
||||
fail(ec, "open");
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow address reuse
|
||||
acceptor_.set_option(net::socket_base::reuse_address(true), ec);
|
||||
if(ec)
|
||||
{
|
||||
fail(ec, "set_option");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind to the server address
|
||||
acceptor_.bind(endpoint, ec);
|
||||
if(ec)
|
||||
{
|
||||
fail(ec, "bind");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start listening for connections
|
||||
acceptor_.listen(
|
||||
net::socket_base::max_listen_connections, ec);
|
||||
if(ec)
|
||||
{
|
||||
fail(ec, "listen");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Start accepting incoming connections
|
||||
void
|
||||
run()
|
||||
{
|
||||
do_accept();
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
do_accept()
|
||||
{
|
||||
// The new connection gets its own strand
|
||||
acceptor_.async_accept(
|
||||
net::make_strand(ioc_),
|
||||
beast::bind_front_handler(
|
||||
&listener::on_accept,
|
||||
shared_from_this()));
|
||||
}
|
||||
|
||||
void
|
||||
on_accept(beast::error_code ec, tcp::socket socket)
|
||||
{
|
||||
if(ec)
|
||||
{
|
||||
fail(ec, "accept");
|
||||
return; // To avoid infinite loop
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create the session and run it
|
||||
std::make_shared<session>(
|
||||
std::move(socket),
|
||||
doc_root_)->run();
|
||||
}
|
||||
|
||||
// Accept another connection
|
||||
do_accept();
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
// Check command line arguments.
|
||||
if (argc != 5)
|
||||
{
|
||||
std::cerr <<
|
||||
"Usage: http-server-async <address> <port> <doc_root> <threads>\n" <<
|
||||
"Example:\n" <<
|
||||
" http-server-async 0.0.0.0 8080 . 1\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
auto const address = net::ip::make_address(argv[1]);
|
||||
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
|
||||
auto const doc_root = std::make_shared<std::string>(argv[3]);
|
||||
auto const threads = std::max<int>(1, std::atoi(argv[4]));
|
||||
|
||||
// The io_context is required for all I/O
|
||||
net::io_context ioc{threads};
|
||||
|
||||
// Create and launch a listening port
|
||||
std::make_shared<listener>(
|
||||
ioc,
|
||||
tcp::endpoint{address, port},
|
||||
doc_root)->run();
|
||||
|
||||
// Run the I/O service on the requested number of threads
|
||||
std::vector<std::thread> v;
|
||||
v.reserve(threads - 1);
|
||||
for(auto i = threads - 1; i > 0; --i)
|
||||
v.emplace_back(
|
||||
[&ioc]
|
||||
{
|
||||
ioc.run();
|
||||
});
|
||||
ioc.run();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
8
tests/common/BUILD
Normal file
8
tests/common/BUILD
Normal file
@ -0,0 +1,8 @@
|
||||
cc_test(
|
||||
name = "logging",
|
||||
srcs = ["logging.cpp"],
|
||||
deps = [
|
||||
"//source/common:lib",
|
||||
"@ut",
|
||||
],
|
||||
)
|
||||
380
tests/common/logging.cpp
Normal file
380
tests/common/logging.cpp
Normal file
@ -0,0 +1,380 @@
|
||||
#include <logging.h>
|
||||
|
||||
#include <boost/ut.hpp>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
using namespace parselink::logging;
|
||||
|
||||
namespace {
|
||||
|
||||
// Simple testing endpoint, writing to a std::string. Colors are not supported.
|
||||
template <bool Colored>
|
||||
struct test_endpoint_base : public endpoint {
|
||||
void write(message const& msg) override {
|
||||
if constexpr (Colored) {
|
||||
buffer_.append(fmt::format("{} {} | {}",
|
||||
themed_arg{enum_name_only{msg.lvl}}, msg.name, msg.message));
|
||||
} else {
|
||||
buffer_.append(fmt::format("{} {} | {}",
|
||||
enum_name_only{msg.lvl}, msg.name, msg.message));
|
||||
}
|
||||
}
|
||||
|
||||
bool colored() const noexcept override { return Colored; }
|
||||
|
||||
void clear() { buffer_.clear(); }
|
||||
std::string_view contents() const { return buffer_; }
|
||||
|
||||
std::string buffer_;
|
||||
};
|
||||
|
||||
template <typename Endpoint>
|
||||
auto make_test_logger(std::string_view name) {
|
||||
auto ep = std::make_shared<Endpoint>();
|
||||
return std::tuple{logger{name, ep}, ep};
|
||||
}
|
||||
|
||||
using test_endpoint = test_endpoint_base<false>;
|
||||
using colored_test_endpoint = test_endpoint_base<true>;
|
||||
|
||||
// In order to test themed (colorized) logging, we must be sure that theming
|
||||
// gets correctly-styled content.
|
||||
struct static_theme_test { int value; };
|
||||
|
||||
// In order to test themed (colorized) logging, we must be sure that theming
|
||||
// gets correctly-styled content.
|
||||
struct dynamic_theme_test { int value; };
|
||||
|
||||
template <has_theme T>
|
||||
auto styled(T&& v) {
|
||||
if constexpr(has_static_theme<T>) {
|
||||
return fmt::styled(v, theme<std::remove_cvref_t<T>>::style);
|
||||
} else if constexpr (has_dynamic_theme<T>) {
|
||||
return fmt::styled(v, theme<std::remove_cvref_t<T>>::style(v));
|
||||
} else {
|
||||
static_assert("Could not find theme, likely broken test!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<static_theme_test> : public fmt::formatter<int> {
|
||||
template <typename FormatContext>
|
||||
auto format(static_theme_test const& v, FormatContext& ctx) const {
|
||||
return fmt::formatter<int>::format(v.value, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<dynamic_theme_test> : public fmt::formatter<int> {
|
||||
template <typename FormatContext>
|
||||
auto format(dynamic_theme_test const& v, FormatContext& ctx) const {
|
||||
return fmt::formatter<int>::format(v.value, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct parselink::logging::theme<static_theme_test>
|
||||
: static_theme<fmt::color::black> {};
|
||||
|
||||
template <>
|
||||
struct parselink::logging::theme<dynamic_theme_test> {
|
||||
static constexpr auto style(auto const& dtt) noexcept {
|
||||
return fmt::fg(dtt.value % 2 ? fmt::color::red : fmt::color::green);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Begin tests!
|
||||
|
||||
using namespace boost::ut;
|
||||
|
||||
suite logging = [] {
|
||||
"log thresholds by default"_test = [] {
|
||||
auto [logger, ep] = make_test_logger<test_endpoint>("log level");
|
||||
// Must be handled explicitly as the level is a template parameter.
|
||||
logger.log<level::critical>("test");
|
||||
if (ep->threshold >= level::critical) {
|
||||
expect(ep->contents() == "critical log level | test");
|
||||
} else {
|
||||
expect(ep->contents().empty());
|
||||
}
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::error>("test");
|
||||
if (ep->threshold >= level::error) {
|
||||
expect(ep->contents() == "error log level | test");
|
||||
} else {
|
||||
expect(ep->contents().empty());
|
||||
}
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::warning>("test");
|
||||
if (ep->threshold >= level::warning) {
|
||||
expect(ep->contents() == "warning log level | test");
|
||||
} else {
|
||||
expect(ep->contents().empty());
|
||||
}
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::info>("test");
|
||||
if (ep->threshold >= level::info) {
|
||||
expect(ep->contents() == "info log level | test");
|
||||
} else {
|
||||
expect(ep->contents().empty());
|
||||
}
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::verbose>("test");
|
||||
if (ep->threshold >= level::verbose) {
|
||||
expect(ep->contents() == "verbose log level | test");
|
||||
} else {
|
||||
expect(ep->contents().empty());
|
||||
}
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::debug>("test");
|
||||
if (ep->threshold >= level::debug) {
|
||||
expect(ep->contents() == "debug log level | test");
|
||||
} else {
|
||||
expect(ep->contents().empty());
|
||||
}
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::trace>("test");
|
||||
if (ep->threshold >= level::trace) {
|
||||
expect(ep->contents() == "trace log level | test");
|
||||
} else {
|
||||
expect(ep->contents().empty());
|
||||
}
|
||||
ep->clear();
|
||||
};
|
||||
|
||||
"basic log formatting"_test = [] {
|
||||
auto [logger, ep] = make_test_logger<test_endpoint>("formatting");
|
||||
logger.log<level::info>("single int: {}", 5);
|
||||
expect(ep->contents() == "info formatting | single int: 5");
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::info>("two ints: {} {}", 32, 55);
|
||||
expect(ep->contents() == "info formatting | two ints: 32 55");
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::info>("string: {}", std::string{"foo"});
|
||||
expect(ep->contents() == "info formatting | string: foo");
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::info>("string_view: {}", std::string_view{"bar"});
|
||||
expect(ep->contents() == "info formatting | string_view: bar");
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::info>("c-string: {}", "brow");
|
||||
expect(ep->contents() == "info formatting | c-string: brow");
|
||||
ep->clear();
|
||||
|
||||
char const* cat = "cat";
|
||||
logger.log<level::info>("c-string: {}", cat);
|
||||
expect(ep->contents() == "info formatting | c-string: cat");
|
||||
ep->clear();
|
||||
};
|
||||
|
||||
"pointer formatting"_test = [] {
|
||||
auto [logger, ep] = make_test_logger<test_endpoint>("formatting");
|
||||
|
||||
// Yeah, this isn't kosher, but test progression is a bit tidier.
|
||||
int* ptr = new int(5);
|
||||
auto uniq_ptr = std::unique_ptr<int>(ptr);
|
||||
auto shr_ptr = std::shared_ptr<int>(new int(10));
|
||||
|
||||
{
|
||||
logger.log<level::info>("int pointer: {}", ptr);
|
||||
auto expected = fmt::format(
|
||||
"info formatting | int pointer: {}", fmt::ptr(ptr));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
}
|
||||
|
||||
{
|
||||
auto& ptrref = ptr;
|
||||
logger.log<level::info>("int pointer&: {}", ptrref);
|
||||
auto expected = fmt::format(
|
||||
"info formatting | int pointer&: {}", fmt::ptr(ptrref));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
}
|
||||
|
||||
{
|
||||
auto const& cptrref = ptr;
|
||||
logger.log<level::info>("int const pointer&: {}", cptrref);
|
||||
auto expected = fmt::format(
|
||||
"info formatting | int const pointer&: {}",
|
||||
fmt::ptr(cptrref));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
}
|
||||
|
||||
{
|
||||
logger.log<level::info>("std::unique_ptr<int>: {}", uniq_ptr);
|
||||
auto expected = fmt::format(
|
||||
"info formatting | std::unique_ptr<int>: {}",
|
||||
fmt::ptr(uniq_ptr));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
}
|
||||
|
||||
{
|
||||
auto& uniq_ptrref = uniq_ptr;
|
||||
logger.log<level::info>("std::unique_ptr<int>&: {}", uniq_ptrref);
|
||||
auto expected = fmt::format(
|
||||
"info formatting | std::unique_ptr<int>&: {}",
|
||||
fmt::ptr(uniq_ptrref));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
}
|
||||
|
||||
{
|
||||
auto const& cuniq_ptrref = uniq_ptr;
|
||||
logger.log<level::info>("std::unique_ptr<int> const&: {}",
|
||||
cuniq_ptrref);
|
||||
auto expected = fmt::format(
|
||||
"info formatting | std::unique_ptr<int> const&: {}",
|
||||
fmt::ptr(cuniq_ptrref));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
}
|
||||
|
||||
{
|
||||
logger.log<level::info>("std::shared_ptr<int>: {}", shr_ptr);
|
||||
auto expected = fmt::format(
|
||||
"info formatting | std::shared_ptr<int>: {}",
|
||||
fmt::ptr(shr_ptr));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
}
|
||||
|
||||
{
|
||||
auto& shr_ptrref = shr_ptr;
|
||||
logger.log<level::info>("std::shared_ptr<int>&: {}", shr_ptrref);
|
||||
auto expected = fmt::format(
|
||||
"info formatting | std::shared_ptr<int>&: {}",
|
||||
fmt::ptr(shr_ptrref));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
}
|
||||
|
||||
{
|
||||
auto const& cshr_ptrref = shr_ptr;
|
||||
logger.log<level::info>("std::shared_ptr<int> const&: {}",
|
||||
cshr_ptrref);
|
||||
auto expected = fmt::format(
|
||||
"info formatting | std::shared_ptr<int> const&: {}",
|
||||
fmt::ptr(cshr_ptrref));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
}
|
||||
};
|
||||
|
||||
"expected<T> formatting"_test = [] {
|
||||
auto [logger, ep] = make_test_logger<test_endpoint>("expected");
|
||||
{
|
||||
#if 0
|
||||
//TODO(ksassenrath): Enable when expected is added.
|
||||
layover::expected<int> x{5};
|
||||
logger.log<level::info>("{}", x);
|
||||
expect(ep->contents() == "info expected | 5");
|
||||
x = std::errc::invalid_argument;
|
||||
ep->clear();
|
||||
logger.log<level::info>("{}", x);
|
||||
expect(ep->contents() == "info expected | Invalid argument");
|
||||
ep->clear();
|
||||
#endif
|
||||
}
|
||||
{
|
||||
#if 0
|
||||
layover::expected<std::unique_ptr<int>> x{std::make_unique<int>(20)};
|
||||
logger.log<level::info>("{} {}", x, *(x.value()));
|
||||
auto expected = fmt::format(
|
||||
"info expected | {} 20", fmt::ptr(x.value()));
|
||||
expect(ep->contents() == expected);
|
||||
x = std::errc::invalid_argument;
|
||||
ep->clear();
|
||||
logger.log<level::info>("{}", x);
|
||||
expect(ep->contents() == "info expected | Invalid argument");
|
||||
ep->clear();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
"log theme"_test = [] {
|
||||
static_theme_test stt{42};
|
||||
dynamic_theme_test red{21}, green{202};
|
||||
|
||||
auto formatted = fmt::format("{}", styled(stt));
|
||||
auto expected = fmt::format("{}",
|
||||
fmt::styled(stt.value, fmt::fg(fmt::color::black)));
|
||||
expect(formatted == expected);
|
||||
formatted = fmt::format("{}", themed_arg{stt});
|
||||
expect(formatted == expected);
|
||||
|
||||
formatted = fmt::format("{} {}", styled(red), styled(green));
|
||||
expected = fmt::format("{} {}",
|
||||
fmt::styled(red.value, fmt::fg(fmt::color::red)),
|
||||
fmt::styled(green.value, fmt::fg(fmt::color::green)));
|
||||
expect(formatted == expected);
|
||||
formatted = fmt::format("{} {}", themed_arg{red}, themed_arg{green});
|
||||
expect(formatted == expected);
|
||||
};
|
||||
|
||||
"colored logging"_test = [] {
|
||||
// This unit test should not break if log colors are changed, but
|
||||
// it also relies on functionality
|
||||
auto [logger, ep] = make_test_logger<colored_test_endpoint>("colored");
|
||||
|
||||
logger.log<level::info>("");
|
||||
auto expected = fmt::format("{} colored | ",
|
||||
styled(enum_name_only{level::info}));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
|
||||
logger.log<level::info>("integral {}", 5);
|
||||
expected = fmt::format("{} colored | integral {}",
|
||||
styled(enum_name_only{level::info}), styled(5));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
|
||||
std::string_view sv = "hello";
|
||||
logger.log<level::info>("string_view {}", sv);
|
||||
expected = fmt::format("{} colored | string_view {}",
|
||||
styled(enum_name_only{level::info}), styled(sv));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
|
||||
std::string str = "hello";
|
||||
logger.log<level::info>("string {}", str);
|
||||
expected = fmt::format("{} colored | string {}",
|
||||
styled(enum_name_only{level::info}), styled(str));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
|
||||
auto c_str = "hello";
|
||||
logger.log<level::info>("c-string {}", c_str);
|
||||
expected = fmt::format("{} colored | c-string {}",
|
||||
styled(enum_name_only{level::info}), styled(c_str));
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
|
||||
int int_value = 42;
|
||||
auto* int_ptr = &int_value;
|
||||
logger.log<level::info>("pointer {} {}", int_ptr, *int_ptr);
|
||||
expected = fmt::format("{} colored | pointer {} {}",
|
||||
styled(enum_name_only{level::info}),
|
||||
themed_arg{int_ptr}, themed_arg{*int_ptr});
|
||||
expect(ep->contents() == expected);
|
||||
ep->clear();
|
||||
};
|
||||
};
|
||||
|
||||
int main(int, char**) {
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user