diff --git a/src/projects/base/ovlibrary/AMS.mk b/src/projects/base/ovlibrary/AMS.mk index 843a3a44b..2bd636db9 100644 --- a/src/projects/base/ovlibrary/AMS.mk +++ b/src/projects/base/ovlibrary/AMS.mk @@ -3,9 +3,17 @@ include $(DEFAULT_VARIABLES) LOCAL_TARGET := ovlibrary -LOCAL_HEADER_FILES := $(LOCAL_HEADER_FILES) -LOCAL_SOURCE_FILES := $(LOCAL_SOURCE_FILES) +LOCAL_HEADER_FILES := $(LOCAL_HEADER_FILES) \ + $(call get_sub_header_list,logger) \ + $(call get_sub_header_list,logger/formatters) \ + $(call get_sub_header_list,logger/pattern_flags) + +LOCAL_SOURCE_FILES := $(LOCAL_SOURCE_FILES) \ + $(call get_sub_source_list,logger) \ + $(call get_sub_source_list,logger/formatters) \ + $(call get_sub_source_list,logger/pattern_flags) $(call add_pkg_config,libpcre2-8) +$(call add_pkg_config,spdlog) include $(BUILD_STATIC_LIBRARY) diff --git a/src/projects/base/ovlibrary/json.cpp b/src/projects/base/ovlibrary/json.cpp index 03b84a8a3..6759f9520 100644 --- a/src/projects/base/ovlibrary/json.cpp +++ b/src/projects/base/ovlibrary/json.cpp @@ -12,6 +12,36 @@ namespace ov { + constexpr const char *StringFromJsonValueType(::Json::ValueType value_type) + { + switch (value_type) + { + case ::Json::ValueType::nullValue: + return "null"; + case ::Json::ValueType::intValue: + return "int"; + case ::Json::ValueType::uintValue: + return "uint"; + case ::Json::ValueType::realValue: + return "real"; + case ::Json::ValueType::stringValue: + return "string"; + case ::Json::ValueType::booleanValue: + return "boolean"; + case ::Json::ValueType::arrayValue: + return "array"; + case ::Json::ValueType::objectValue: + return "object"; + } + + return "unknown"; + } + + const char *StringFromJsonValueType(const ::Json::Value &value) + { + return StringFromJsonValueType(value.type()); + } + ov::String Json::Stringify(const JsonObject &object) { return Stringify(object._value); @@ -74,35 +104,4 @@ namespace ov return JsonObject::NullObject(); } - - const char *StringFromJsonValueType(::Json::ValueType value_type) - { - switch (value_type) - { - case ::Json::ValueType::nullValue: - return "null"; - case ::Json::ValueType::intValue: - return "int"; - case ::Json::ValueType::uintValue: - return "uint"; - case ::Json::ValueType::realValue: - return "real"; - case ::Json::ValueType::stringValue: - return "string"; - case ::Json::ValueType::booleanValue: - return "boolean"; - case ::Json::ValueType::arrayValue: - return "array"; - case ::Json::ValueType::objectValue: - return "object"; - } - - return "unknown"; - } - - const char *StringFromJsonValueType(const ::Json::Value &value) - { - return StringFromJsonValueType(value.type()); - } - } // namespace ov diff --git a/src/projects/base/ovlibrary/json.h b/src/projects/base/ovlibrary/json.h index 7fb157632..39fb65cb3 100644 --- a/src/projects/base/ovlibrary/json.h +++ b/src/projects/base/ovlibrary/json.h @@ -8,11 +8,13 @@ //============================================================================== #pragma once -#include "./json_object.h" -#include "./string.h" +#include "./json_builder.h" namespace ov { + constexpr const char *StringFromJsonValueType(::Json::ValueType value_type); + const char *StringFromJsonValueType(const ::Json::Value &value); + class Json { public: @@ -26,7 +28,4 @@ namespace ov static JsonObject Parse(const ov::String &str); static JsonObject Parse(const std::shared_ptr &data); }; - - const char *StringFromJsonValueType(::Json::ValueType value_type); - const char *StringFromJsonValueType(const ::Json::Value &value); } // namespace ov diff --git a/src/projects/base/ovlibrary/json_builder.cpp b/src/projects/base/ovlibrary/json_builder.cpp new file mode 100644 index 000000000..afece732f --- /dev/null +++ b/src/projects/base/ovlibrary/json_builder.cpp @@ -0,0 +1,258 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#include "./json_builder.h" + +#include "./json.h" + +namespace ov +{ + JsonBuilder::JsonBuilder(const PrivateToken &token) + { + } + + std::shared_ptr JsonBuilder::Builder(JsonBuilderModifier modifier) + { + auto builder = std::make_shared(PrivateToken()); + + auto modified_builder = (modifier != nullptr) ? modifier(builder.get()) : builder.get(); + + if (modified_builder != nullptr) + { + return modified_builder->GetSharedPtrAs(); + } + + return nullptr; + } + + void JsonBuilder::PushBackInternal(const char *key, JsonValueType value) + { + if (_value_type == ::Json::ValueType::nullValue) + { + _value_type = ::Json::ValueType::objectValue; + } + + if (_value_type != ::Json::ValueType::objectValue) + { + OV_ASSERT(false, "The current JSON value is not an object"); + return; + } + + _value_map.PushBack(key, std::move(value)); + } + + JsonBuilder *JsonBuilder::PushBack(const char *key, ::Json::Value value) + { + PushBackInternal(key, std::move(value)); + + return this; + } + + JsonBuilder *JsonBuilder::PushBack(const char *key, std::shared_ptr builder) + { + if (builder == nullptr) + { + PushBackInternal(key, ::Json::Value::null); + } + else + { + PushBackInternal(key, builder); + } + + return this; + } + + JsonBuilder *JsonBuilder::PushBack(const char *key, JsonBuilderModifier modifier) + { + if (modifier == nullptr) + { + PushBackInternal(key, ::Json::Value::null); + } + else + { + PushBackInternal(key, Builder(modifier)); + } + + return this; + } + + void JsonBuilder::PushBackInternal(JsonValueType item) + { + if (_value_type == ::Json::ValueType::nullValue) + { + _value_type = ::Json::ValueType::arrayValue; + } + + if (_value_type != ::Json::ValueType::arrayValue) + { + OV_ASSERT(false, "The current JSON value is not an array"); + return; + } + + _value_list.push_back(std::move(item)); + } + + JsonBuilder *JsonBuilder::PushBack(::Json::Value value) + { + PushBackInternal(std::move(value)); + + return this; + } + + JsonBuilder *JsonBuilder::PushBack(std::shared_ptr builder) + { + if (builder == nullptr) + { + PushBackInternal(::Json::Value::null); + } + else + { + PushBackInternal(builder); + } + + return this; + } + + JsonBuilder *JsonBuilder::PushBack(JsonBuilderModifier modifier) + { + if (modifier == nullptr) + { + PushBackInternal(::Json::Value::null); + } + else + { + PushBackInternal(Builder(modifier)); + } + + return this; + } + + ov::String JsonBuilder::Stringify() const + { + ov::String output; + return Stringify(output); + } + + ov::String &JsonBuilder::Stringify(ov::String &output) const + { + switch (_value_type) + { + case ::Json::ValueType::nullValue: + output = ""; + break; + + case ::Json::ValueType::objectValue: + StringifyObject(output); + break; + + case ::Json::ValueType::arrayValue: + StringifyArray(output); + break; + + default: + OV_ASSERT(false, "Invalid JSON value type"); + break; + } + + return output; + } + + static std::string EscapeString(ov::String &output, const ov::String &value) + { + ::Json::StreamWriterBuilder builder; + + return ::Json::writeString(builder, value.CStr()).c_str(); + } + + ov::String &JsonBuilder::StringifyObject(ov::String &output) const + { + auto end = _value_map.end(); + + output.Append('{'); + + for (auto it = _value_map.begin(); it != end; ++it) + { + if (it != _value_map.begin()) + { + output.Append(','); + } + + auto &key = it->first; + auto &value = it->second; + + // Print key + output.AppendFormat("%s:", EscapeString(output, key).c_str()); + + // Print value + if (std::holds_alternative<::Json::Value>(value)) + { + auto json = std::get<::Json::Value>(value); + + output.Append(Json::Stringify(json, false)); + } + else if (std::holds_alternative>(value)) + { + auto builder = std::get>(value); + + builder->Stringify(output); + } + else + { + OV_ASSERT(false, "Invalid JSON value type: %d", value.index()); + } + } + + output.Append('}'); + + return output; + } + + ov::String &JsonBuilder::StringifyArray(ov::String &output) const + { + auto end = _value_list.end(); + + output.Append('['); + + for (auto it = _value_list.begin(); it != end; ++it) + { + if (it != _value_list.begin()) + { + output.Append(','); + } + + auto &value = *it; + + // Print value + if (std::holds_alternative<::Json::Value>(value)) + { + auto json = std::get<::Json::Value>(value); + + output.Append(Json::Stringify(json, false)); + } + else if (std::holds_alternative>(value)) + { + auto builder = std::get>(value); + + builder->Stringify(output); + } + else + { + OV_ASSERT(false, "Invalid JSON value type: %d", value.index()); + } + } + + output.Append(']'); + + return output; + } + + ::Json::Value JsonBuilder::ToJsonValue() const + { + return ::Json::Value(); + } +} // namespace ov diff --git a/src/projects/base/ovlibrary/json_builder.h b/src/projects/base/ovlibrary/json_builder.h new file mode 100644 index 000000000..fc24bb65e --- /dev/null +++ b/src/projects/base/ovlibrary/json_builder.h @@ -0,0 +1,109 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#pragma once + +#include + +#include "./enable_shared_from_this.h" +#include "./json_object.h" +#include "./sequencial_map.h" + +namespace ov +{ + class JsonBuilder; + + using JsonBuilderModifier = std::function; + + // According to the specification, JSON works regardless of order, but when visualizing it as a JSON format + // such as outputting logs, the order may be important, so we provide a JsonBuilder class that guarantees the order. + // + // Usage: + // + // ov::String json = ov::JsonBuilder::Builder() + // ->PushBack("key1", 100) + // ->PushBack("key2", "value") + // // If you don't need additional operations when set nested values, you can create a new builder and use it + // ->PushBack("sub_object", + // ov::JsonBuilder::Builder() + // ->PushBack("sub_key1", "sub_value1") + // ->PushBack("sub_key2", "sub_value2") + // ->PushBack("sub_key3", [](auto builder) -> auto { + // return builder + // ->PushBack("sub_sub_key1", "sub_sub_value1") + // ->PushBack("sub_sub_key2", "sub_sub_value2"); + // })) + // // If you need additional operations when set nested values, you can use the modifier function + // ->PushBack("sub_array", [obj](auto builder) -> auto { + // ov::String s = "some complex value"; + // s += "!"; + // + // return builder + // ->PushBack(s.CStr()) + // ->PushBack("sub_array") + // ->PushBack([](auto builder) -> auto { + // return builder + // ->PushBack("sub_sub_key1", "sub_array1") + // ->PushBack("sub_sub_key2", "sub_array2"); + // }); + // }) + // ->Stringify(); + // + // This will output: + // {"key1":100,"key2":"value","sub_object":true,"sub_array":["some complex value!","sub_array",{"sub_sub_key1":"sub_array1","sub_sub_key2":"sub_array2"}]} + class JsonBuilder : public ov::EnableSharedFromThis + { + private: + struct PrivateToken + { + }; + + using JsonValueType = std::variant< + ::Json::Value, + std::shared_ptr>; + + public: + // To ensure that the JsonBuilder object always uses shared_ptr + JsonBuilder() = delete; + JsonBuilder(const PrivateToken &token); + ~JsonBuilder() = default; + + // Create a new JsonBuilder object + static std::shared_ptr Builder(JsonBuilderModifier modifier = nullptr); + + // These APIs can be used when the JSON value currently being created is an object + JsonBuilder *PushBack(const char *key, ::Json::Value value); + JsonBuilder *PushBack(const char *key, std::shared_ptr builder); + JsonBuilder *PushBack(const char *key, JsonBuilderModifier modifier); + + // These APIs can be used when the JSON value currently being created is an array + JsonBuilder *PushBack(::Json::Value value); + JsonBuilder *PushBack(std::shared_ptr builder); + JsonBuilder *PushBack(JsonBuilderModifier modifier); + + // To JSON string + ov::String Stringify() const; + + // To JSON object/array + ::Json::Value ToJsonValue() const; + + protected: + void PushBackInternal(const char *key, JsonValueType item); + void PushBackInternal(JsonValueType item); + + ov::String &Stringify(ov::String &output) const; + ov::String &StringifyObject(ov::String &output) const; + ov::String &StringifyArray(ov::String &output) const; + + protected: + ::Json::ValueType _value_type = ::Json::ValueType::nullValue; + + SequencialMap _value_map; + std::vector _value_list; + }; +} // namespace ov diff --git a/src/projects/base/ovlibrary/json_object.h b/src/projects/base/ovlibrary/json_object.h index a5652fe9e..0f60b711e 100644 --- a/src/projects/base/ovlibrary/json_object.h +++ b/src/projects/base/ovlibrary/json_object.h @@ -102,10 +102,10 @@ namespace ov if (value.isString()) { - return ov::String( value.asString().c_str() ); + return ov::String(value.asString().c_str()); } - return nullptr; + return nullptr; } bool GetBoolValue(const ov::String &key) const diff --git a/src/projects/base/ovlibrary/logger/formatters/common.h b/src/projects/base/ovlibrary/logger/formatters/common.h new file mode 100644 index 000000000..8f8ad4a78 --- /dev/null +++ b/src/projects/base/ovlibrary/logger/formatters/common.h @@ -0,0 +1,133 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#pragma once + +#include + +#include +#include + +namespace ov +{ + namespace logger + { + // The following code is to delegate processing to `MyFormatter.Parse()` when `fmt::formatter::parse()` is called. + // If `MyFormatter` has a `Parse()` function, it calls `MyFormatter.Parse()`, otherwise it returns `ctx.begin()`. + using ParseContext = fmt::format_parse_context; + using ParseResult = ParseContext::iterator; + using Parser = std::function; + + template + constexpr auto HasParse(int) -> decltype(std::declval().Parse(std::declval()), std::true_type()) + { + return std::true_type(); + } + + template + constexpr auto HasParse(...) -> std::false_type + { + return std::false_type(); + } + + template (0))::value, int> = 0> + constexpr ParseResult CallParse(Tformatter &formatter, ParseContext &ctx) + { + return formatter.Parse(ctx); + } + + template (0))::value, int> = 0> + constexpr ParseResult CallParse(Tformatter const &formatter, ParseContext &ctx) + { + return ctx.begin(); + } + + // The following code is to delegate processing to `MyFormatter.Format()` when `fmt::formatter::format()` is called. + // If `MyFormatter` has a `Format()` function, it calls `MyFormatter.Format()`, otherwise it prints in the form of ``. + using FormatContext = fmt::format_context; + using FormatResult = FormatContext::iterator; + template + using Formatter = std::function; + + template + constexpr auto HasFormat(int) -> decltype(std::declval().Format( + std::declval(), + std::declval()), + std::true_type()) + { + return std::true_type(); + } + + template + constexpr auto HasFormat(...) -> std::false_type + { + return std::false_type(); + } + + template (0))::value, int> = 0> + constexpr FormatResult CallFormat( + const char *instance_name, + Tformatter const &formatter, + Tinstance const &instance, + FormatContext &ctx) + { + return formatter.Format(instance, ctx); + } + + template (0))::value, int> = 0> + constexpr FormatResult CallFormat( + const char *instance_name, + Tformatter const &formatter, + Tinstance const &instance, + FormatContext &ctx) + { + // Split by '::' and get the last element as the class name to exclude the namespace + auto class_name = instance_name; + + for (auto i = instance_name; *i; ++i) + { + if (*i == ':') + { + class_name = i + 1; + } + } + + if (class_name[0] == '\0') + { + class_name = instance_name; + } + + return fmt::format_to(ctx.out(), "<{}: 0x{:x}>", class_name, reinterpret_cast(&instance)); + } + } // namespace logger +} // namespace ov + +// To support custom class for logging +// https://github.com/gabime/spdlog/wiki/1.-QuickStart#log-user-defined-objects + +// If `DECLARE_FORMATTER(MyFormatter, MyClass)` is declared, when `MyClass` is output to the log, +// `MyFormatter`'s `Parse()` and `Format()` functions are called. +// If `MyFormatter` does not have `Parse()` and `Format()` functions, the default behavior is performed. +#define DECLARE_CUSTOM_FORMATTER(formatter_name, for_class) \ + template <> \ + class fmt::formatter \ + { \ + public: \ + constexpr auto parse(format_parse_context &ctx) \ + { \ + return ov::logger::CallParse(_formatter, ctx); \ + } \ + \ + template \ + constexpr auto format(for_class const &instance, FmtContext &ctx) const \ + { \ + return ov::logger::CallFormat(#for_class, _formatter, instance, ctx); \ + } \ + \ + formatter_name _formatter; \ + } diff --git a/src/projects/base/ovlibrary/logger/formatters/formatters.h b/src/projects/base/ovlibrary/logger/formatters/formatters.h new file mode 100644 index 000000000..665039748 --- /dev/null +++ b/src/projects/base/ovlibrary/logger/formatters/formatters.h @@ -0,0 +1,13 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#pragma once + +#include "./common.h" +#include "./json_formatter.h" +#include "./string_formatter.h" diff --git a/src/projects/base/ovlibrary/logger/formatters/json_formatter.cpp b/src/projects/base/ovlibrary/logger/formatters/json_formatter.cpp new file mode 100644 index 000000000..59deb1e93 --- /dev/null +++ b/src/projects/base/ovlibrary/logger/formatters/json_formatter.cpp @@ -0,0 +1,23 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#include "./json_formatter.h" + +namespace ov +{ + namespace logger + { + FormatResult JsonFormatter::Format(::Json::Value const &instance, FormatContext &ctx) const + { + auto json_str = ov::Json::Stringify(instance); + + return fmt::formatter::format( + std::string_view(json_str.CStr(), json_str.GetLength()), ctx); + } + } // namespace logger +} // namespace ov diff --git a/src/projects/base/ovlibrary/logger/formatters/json_formatter.h b/src/projects/base/ovlibrary/logger/formatters/json_formatter.h new file mode 100644 index 000000000..b5355edc5 --- /dev/null +++ b/src/projects/base/ovlibrary/logger/formatters/json_formatter.h @@ -0,0 +1,31 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#pragma once + +#include "../../json.h" +#include "./common.h" + +namespace ov +{ + namespace logger + { + class JsonFormatter : protected fmt::formatter + { + public: + constexpr ParseResult Parse(ParseContext &ctx) + { + return fmt::formatter::parse(ctx); + } + + FormatResult Format(::Json::Value const &instance, FormatContext &ctx) const; + }; + } // namespace logger +} // namespace ov + +DECLARE_CUSTOM_FORMATTER(ov::logger::JsonFormatter, ::Json::Value); diff --git a/src/projects/base/ovlibrary/logger/formatters/string_formatter.cpp b/src/projects/base/ovlibrary/logger/formatters/string_formatter.cpp new file mode 100644 index 000000000..af9a2d117 --- /dev/null +++ b/src/projects/base/ovlibrary/logger/formatters/string_formatter.cpp @@ -0,0 +1,21 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#include "./string_formatter.h" + +namespace ov +{ + namespace logger + { + FormatResult StringFormatter::Format(String const &instance, FormatContext &ctx) const + { + return fmt::formatter::format( + std::string_view(instance.CStr(), instance.GetLength()), ctx); + } + } // namespace logger +} // namespace ov diff --git a/src/projects/base/ovlibrary/logger/formatters/string_formatter.h b/src/projects/base/ovlibrary/logger/formatters/string_formatter.h new file mode 100644 index 000000000..f5f2969e9 --- /dev/null +++ b/src/projects/base/ovlibrary/logger/formatters/string_formatter.h @@ -0,0 +1,31 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#pragma once + +#include "../../string.h" +#include "./common.h" + +namespace ov +{ + namespace logger + { + class StringFormatter : protected fmt::formatter + { + public: + constexpr ParseResult Parse(ParseContext &ctx) + { + return fmt::formatter::parse(ctx); + } + + FormatResult Format(String const &instance, FormatContext &ctx) const; + }; + } // namespace logger +} // namespace ov + +DECLARE_CUSTOM_FORMATTER(ov::logger::StringFormatter, ov::String); diff --git a/src/projects/base/ovlibrary/logger/logger.cpp b/src/projects/base/ovlibrary/logger/logger.cpp new file mode 100644 index 000000000..6c0c0476d --- /dev/null +++ b/src/projects/base/ovlibrary/logger/logger.cpp @@ -0,0 +1,140 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#include "logger.h" + +#include +#include + +#include + +#define OV_LOG_COLOR_RESET "\x1B[0m" + +#define OV_LOG_COLOR_FG_BLACK "\x1B[30m" +#define OV_LOG_COLOR_FG_RED "\x1B[31m" +#define OV_LOG_COLOR_FG_GREEN "\x1B[32m" +#define OV_LOG_COLOR_FG_YELLOW "\x1B[33m" +#define OV_LOG_COLOR_FG_BLUE "\x1B[34m" +#define OV_LOG_COLOR_FG_MAGENTA "\x1B[35m" +#define OV_LOG_COLOR_FG_CYAN "\x1B[36m" +#define OV_LOG_COLOR_FG_WHITE "\x1B[37m" + +#define OV_LOG_COLOR_FG_BR_BLACK "\x1B[90m" +#define OV_LOG_COLOR_FG_BR_RED "\x1B[91m" +#define OV_LOG_COLOR_FG_BR_GREEN "\x1B[92m" +#define OV_LOG_COLOR_FG_BR_YELLOW "\x1B[93m" +#define OV_LOG_COLOR_FG_BR_BLUE "\x1B[94m" +#define OV_LOG_COLOR_FG_BR_MAGENTA "\x1B[95m" +#define OV_LOG_COLOR_FG_BR_CYAN "\x1B[96m" +#define OV_LOG_COLOR_FG_BR_WHITE "\x1B[97m" + +#define OV_LOG_COLOR_BG_BLACK "\x1B[40m" +#define OV_LOG_COLOR_BG_RED "\x1B[41m" +#define OV_LOG_COLOR_BG_GREEN "\x1B[42m" +#define OV_LOG_COLOR_BG_YELLOW "\x1B[43m" +#define OV_LOG_COLOR_BG_BLUE "\x1B[44m" +#define OV_LOG_COLOR_BG_MAGENTA "\x1B[45m" +#define OV_LOG_COLOR_BG_CYAN "\x1B[46m" +#define OV_LOG_COLOR_BG_WHITE "\x1B[47m" + +#define OV_LOG_COLOR_BG_BR_BLACK "\x1B[100m" +#define OV_LOG_COLOR_BG_BR_RED "\x1B[101m" +#define OV_LOG_COLOR_BG_BR_GREEN "\x1B[102m" +#define OV_LOG_COLOR_BG_BR_YELLOW "\x1B[103m" +#define OV_LOG_COLOR_BG_BR_BLUE "\x1B[104m" +#define OV_LOG_COLOR_BG_BR_MAGENTA "\x1B[105m" +#define OV_LOG_COLOR_BG_BR_CYAN "\x1B[106m" +#define OV_LOG_COLOR_BG_BR_WHITE "\x1B[107m" + +namespace ov +{ + namespace logger + { + constexpr spdlog::level::level_enum LogLevelToSpdlogLevel(LogLevel level) + { + switch (level) + { + case LogLevel::Trace: + return spdlog::level::trace; + + case LogLevel::Debug: + return spdlog::level::debug; + + case LogLevel::Info: + return spdlog::level::info; + + case LogLevel::Warn: + return spdlog::level::warn; + + case LogLevel::Error: + return spdlog::level::err; + + case LogLevel::Critical: + return spdlog::level::critical; + } + + return spdlog::level::off; + } + + std::shared_ptr GetLogger(const char *tag) + { + static std::mutex logger_map_mutex; + static std::map> logger_map; + + std::lock_guard lock_guard(logger_map_mutex); + + { + auto logger = logger_map.find(tag); + + if (logger != logger_map.end()) + { + return logger->second; + } + } + + // TODO(dimiden): Need to initialize logger according to the contents of Logger.xml here + auto internal_logger = spdlog::stdout_color_mt(tag); + + auto &sinks = internal_logger->sinks(); + auto sink = std::dynamic_pointer_cast(sinks[0]); + + sink->set_color(spdlog::level::trace, OV_LOG_COLOR_FG_CYAN); + sink->set_color(spdlog::level::debug, OV_LOG_COLOR_FG_CYAN); + sink->set_color(spdlog::level::info, OV_LOG_COLOR_FG_WHITE); + sink->set_color(spdlog::level::warn, OV_LOG_COLOR_FG_YELLOW); + sink->set_color(spdlog::level::err, OV_LOG_COLOR_FG_RED); + sink->set_color(spdlog::level::critical, OV_LOG_COLOR_FG_BR_WHITE OV_LOG_COLOR_BG_RED); + + // [2025-02-12 17:54:01.445] I [SPRTMP-t41935:423153] Publisher | stream.cpp:294 | [stream(1423176515)] LLHLS Publisher Application - All StreamWorker has been stopped + + // https://github.com/gabime/spdlog/wiki/3.-Custom-formatting#pattern-flags + auto formatter = std::make_unique(); + formatter + ->add_flag(ThreadNamePatternFlag::FLAG) + .set_pattern("%^[%Y-%m-%d %H:%M:%S.%e%z] %L [%N:%t] %n | %s:%-4# | %v%$"); + + internal_logger->set_formatter(std::move(formatter)); + + auto logger = std::make_shared(internal_logger); + + logger_map[tag] = logger; + + return logger; + } + + void Logger::SetLogPattern(const char *pattern) + { + _logger->set_pattern(pattern); + } + + void Logger::Log(LogLevel level, const char *file, int line, const char *function, const std::string &message) + { + _logger->log(spdlog::source_loc{file, line, function}, LogLevelToSpdlogLevel(level), message); + } + } // namespace logger +} // namespace ov diff --git a/src/projects/base/ovlibrary/logger/logger.h b/src/projects/base/ovlibrary/logger/logger.h new file mode 100644 index 000000000..3782f77e0 --- /dev/null +++ b/src/projects/base/ovlibrary/logger/logger.h @@ -0,0 +1,145 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#pragma once + +#include + +#include +#include +#include + +#include "./pattern_flags/pattern_flags.h" +#include "formatters/formatters.h" + +// Forward declaration of spdlog::logger +namespace spdlog +{ + class logger; +} // namespace spdlog + +namespace ov +{ + namespace logger + { + enum class LogLevel + { + Trace, + Debug, + Info, + Warn, + Error, + Critical, + }; + + class Logger + { + public: + Logger(std::shared_ptr logger) + : _logger(logger) + { + } + + // Since the Logger is configured with spdlog, please refer to spdlog's pattern flags for more details. + // + // https://github.com/gabime/spdlog/wiki/3.-Custom-formatting#pattern-flags + // + // | flag | meaning | example | + // +----------+--------------------------------------------------------------------------------------------+------------------------------------------------------------+ + // | %v | The actual text to log | some user text" | + // | %t | Thread id | 1232" | + // | %P | Process id | 3456" | + // | %n | Logger's name | some logger name" | + // | %l | The log level of the message | debug", "info", etc | + // | %L | Short log level of the message | D", "I", etc | + // | %a | Abbreviated weekday name | Thu" | + // | %A | Full weekday name | Thursday" | + // | %b | Abbreviated month name | Aug" | + // | %B | Full month name | August" | + // | %c | Date and time representation | Thu Aug 23 15:35:46 2014" | + // | %C | Year in 2 digits | 14" | + // | %Y | Year in 4 digits | 2014" | + // | %D or %x | Short MM/DD/YY date | 08/23/14" | + // | %m | Month 01-12 | 11" | + // | %d | Day of month 01-31 | 29" | + // | %H | Hours in 24 format 00-23 | 23" | + // | %I | Hours in 12 format 01-12 | 11" | + // | %M | Minutes 00-59 | 59" | + // | %S | Seconds 00-59 | 58" | + // | %e | Millisecond part of the current second 000-999 | 678" | + // | %f | Microsecond part of the current second 000000-999999 | 056789" | + // | %F | Nanosecond part of the current second 000000000-999999999 | 256789123" | + // | %p | AM/PM | AM" | + // | %r | 12 hour clock | 02:55:02 PM" | + // | %R | 24-hour HH:MM time, equivalent to %H:%M | 23:55" | + // | %T or %X | ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S | 23:55:59" | + // | %z | ISO 8601 offset from UTC in timezone ([+/-]HH:MM) | "+02:00" | + // | %E | Seconds since the epoch | 1528834770" | + // | %% | The % sign | "%" | + // | %+ | spdlog's default format | "[2014-10-31 23:46:59.678] [mylogger] [info] Some message" | + // | %^ | start color range (can be used only once) | "[mylogger] [info(green)] Some message" | + // | %$ | end color range (for example %^[+++]%$ %v) (can be used only once) | [+++] Some message | + // | %@ | Source file and line (use SPDLOG_TRACE(..), | some/dir/my_file.cpp:123 | + // | | SPDLOG_INFO(...) etc. instead of spdlog::trace(...)) Same as %g:%# | | + // | %s | Basename of the source file (use SPDLOG_TRACE(..), SPDLOG_INFO(...) etc.) | my_file.cpp | + // | %g | Full or relative path of the source file as appears in | some/dir/my_file.cpp | + // | | spdlog::source_loc (use SPDLOG_TRACE(..), SPDLOG_INFO(...) etc.) | | + // | %# | Source line (use SPDLOG_TRACE(..), SPDLOG_INFO(...) etc.) | 123 | + // | %! | Source function (use SPDLOG_TRACE(..), SPDLOG_INFO(...) etc. see tweakme for pretty-print) | my_func | + // | %o | Elapsed time in milliseconds since previous message | 456 | + // | %i | Elapsed time in microseconds since previous message | 456 | + // | %u | Elapsed time in nanoseconds since previous message | 11456 | + // | %O | Elapsed time in seconds since previous message | 4 | + void SetLogPattern(const char *pattern); + + template + inline void Trace(const char *file, int line, const char *function, const char *format, Targs &&...args) + { + return Log(LogLevel::Trace, file, line, function, fmt::vformat(format, fmt::make_format_args(args...))); + } + + template + inline void Debug(const char *file, int line, const char *function, const char *format, Targs &&...args) + { + return Log(LogLevel::Debug, file, line, function, fmt::vformat(format, fmt::make_format_args(args...))); + } + + template + inline void Info(const char *file, int line, const char *function, const char *format, Targs &&...args) + { + return Log(LogLevel::Info, file, line, function, fmt::vformat(format, fmt::make_format_args(args...))); + } + + template + inline void Warn(const char *file, int line, const char *function, const char *format, Targs &&...args) + { + return Log(LogLevel::Warn, file, line, function, fmt::vformat(format, fmt::make_format_args(args...))); + } + + template + inline void Error(const char *file, int line, const char *function, const char *format, Targs &&...args) + { + return Log(LogLevel::Error, file, line, function, fmt::vformat(format, fmt::make_format_args(args...))); + } + + template + inline void Critical(const char *file, int line, const char *function, const char *format, Targs &&...args) + { + return Log(LogLevel::Critical, file, line, function, fmt::vformat(format, fmt::make_format_args(args...))); + } + + protected: + void Log(LogLevel level, const char *file, int line, const char *function, const std::string &message); + + private: + std::shared_ptr _logger; + }; + + std::shared_ptr GetLogger(const char *tag); + } // namespace logger +} // namespace ov diff --git a/src/projects/base/ovlibrary/logger/pattern_flags/pattern_flags.h b/src/projects/base/ovlibrary/logger/pattern_flags/pattern_flags.h new file mode 100644 index 000000000..e8ed41f40 --- /dev/null +++ b/src/projects/base/ovlibrary/logger/pattern_flags/pattern_flags.h @@ -0,0 +1,11 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#pragma once + +#include "./thread_name_pattern_flag.h" diff --git a/src/projects/base/ovlibrary/logger/pattern_flags/thread_name_pattern_flag.h b/src/projects/base/ovlibrary/logger/pattern_flags/thread_name_pattern_flag.h new file mode 100644 index 000000000..2b8ae50e1 --- /dev/null +++ b/src/projects/base/ovlibrary/logger/pattern_flags/thread_name_pattern_flag.h @@ -0,0 +1,47 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#include + +namespace ov +{ + namespace logger + { + // https://github.com/gabime/spdlog/wiki/3.-Custom-formatting#extending-spdlog-with-your-own-flags + class ThreadNamePatternFlag : public spdlog::custom_flag_formatter + { + public: + constexpr static const char FLAG = 'N'; + + //-------------------------------------------------------------------- + // Overriding of spdlog::custom_flag_formatter + //-------------------------------------------------------------------- + void format(const spdlog::details::log_msg &msg, const std::tm &, spdlog::memory_buf_t &dest) override + { + AppendThreadName(msg.pthread_id, dest); + } + + std::unique_ptr clone() const override + { + return spdlog::details::make_unique(); + } + + protected: + void AppendThreadName(pthread_t thread_id, spdlog::memory_buf_t &dest) + { + char name[16]{0}; + size_t length = 0; + + ::pthread_getname_np(thread_id, name, 16); + length = ::strlen(name); + + dest.append(name, name + length); + } + }; + } // namespace logger +} // namespace ov \ No newline at end of file diff --git a/src/projects/base/ovlibrary/ovlibrary.h b/src/projects/base/ovlibrary/ovlibrary.h index 4e54e90f4..51b418a61 100644 --- a/src/projects/base/ovlibrary/ovlibrary.h +++ b/src/projects/base/ovlibrary/ovlibrary.h @@ -45,3 +45,6 @@ #include "./url.h" #include "./precise_timer.h" #include "./files.h" +#include "./sequencial_map.h" + +#include "./logger/logger.h" diff --git a/src/projects/base/ovlibrary/sequencial_map.h b/src/projects/base/ovlibrary/sequencial_map.h new file mode 100644 index 000000000..c5098cb57 --- /dev/null +++ b/src/projects/base/ovlibrary/sequencial_map.h @@ -0,0 +1,290 @@ +//============================================================================== +// +// OvenMediaEngine +// +// Created by Hyunjun Jang +// Copyright (c) 2025 AirenSoft. All rights reserved. +// +//============================================================================== +#pragma once + +#include +#include + +#include "./assert.h" + +namespace ov +{ + // This is a map that maintains the order in which `PushBack()` or `EmplaceBack()` was called + template + class SequencialMap + { + private: + using KeyRefList = std::vector>; + + public: + using SizeType = typename KeyRefList::size_type; + + template + class IteratorBase + { + public: + IteratorBase( + Titerator current, + Tvalue_map &value_map) + : _current(current), + _value_map(value_map) + { + } + + auto &operator*() + { + return *(_value_map.find(*_current)); + } + + auto operator->() + { + return _value_map.find(*_current).operator->(); + } + + IteratorBase &operator++() + { + ++_current; + return *this; + } + + bool operator!=(const IteratorBase &other) const + { + return _current != other._current; + } + + private: + Titerator _current; + + Tvalue_map &_value_map; + }; + + using Iterator = IteratorBase>; + using ConstIterator = IteratorBase>; + + public: + void PushBack(const Tkey &key, const Tvalue &value) + { + auto it = _value_map.find(key); + + if (it == _value_map.end()) + { + _value_map[key] = value; + it = _value_map.find(key); + _key_ref_list.emplace_back(std::ref(it->first)); + } + else + { + // Update + it->second = value; + } + } + + template + std::pair EmplaceBack(Tkey &&key, Targs &&...args) + { + auto it = _value_map.find(key); + bool inserted = false; + + if (it == _value_map.end()) + { + auto new_it = _value_map.emplace( + std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + it = new_it.first; + _key_ref_list.emplace_back(std::ref(it->first)); + + inserted = true; + } + else + { + // Update value if key exists + it->second = Tvalue(std::forward(args)...); + } + + return std::make_pair(Iterator(_key_ref_list.begin(), _value_map), inserted); + } + + // Throws std::out_of_range if key does not exist + const Tvalue &At(const Tkey &key) const + { + auto it = _value_map.find(key); + + if (it != _value_map.end()) + { + return it->second; + } + + throw std::out_of_range("Key does not exist"); + } + + // Throws std::out_of_range if key does not exist + Tvalue &At(const Tkey &key) + { + return const_cast(std::as_const(*this).At(key)); + } + + SizeType Size() const + { + return _key_ref_list.size(); + } + + bool Empty() const + { + return _key_ref_list.empty(); + } + + void Clear() + { + _key_ref_list.clear(); + _value_map.clear(); + } + + bool Contains(const Tkey &key) const + { + return _value_map.find(key) != _value_map.end(); + } + + void Erase(const Tkey &key) + { + auto it = _value_map.find(key); + + if (it == _value_map.end()) + { + return; + } + + auto key_it = std::find(_key_ref_list.begin(), _key_ref_list.end(), key); + if (key_it != _key_ref_list.end()) + { + _key_ref_list.erase(key_it); + } + else + { + // If the key exists in _value_map, it must also exist in _key_list + OV_ASSERT(false, "Key does not exist in _key_list"); + } + + _value_map.erase(it); + } + + Tvalue &operator[](const Tkey &key) + { + auto it = _value_map.find(key); + + if (it != _value_map.end()) + { + return it->second; + } + + return + // EmplaceBack() returns std::pair, + EmplaceBack(key).first-> + // and Iterator.second is the value + second; + } + + Tvalue &operator[](Tkey &&key) + { + auto it = _value_map.find(key); + + if (it != _value_map.end()) + { + return it->second; + } + + return + // EmplaceBack() returns std::pair, + EmplaceBack(std::move(key)).first-> + // and Iterator.second is the value + second; + } + + ConstIterator CBegin() const + { + return ConstIterator(_key_ref_list.cbegin(), _value_map); + } + + ConstIterator CEnd() const + { + return ConstIterator(_key_ref_list.cend(), _value_map); + } + + Iterator Begin() + { + return Iterator(_key_ref_list.begin(), _value_map); + } + + Iterator End() + { + return Iterator(_key_ref_list.end(), _value_map); + } + + ConstIterator Begin() const + { + return CBegin(); + } + + ConstIterator End() const + { + return CEnd(); + } + + // For STL + inline ConstIterator cbegin() const + { + return CBegin(); + } + + inline ConstIterator cend() const + { + return CEnd(); + } + + inline Iterator begin() + { + return Begin(); + } + + inline Iterator end() + { + return End(); + } + + inline ConstIterator begin() const + { + return Begin(); + } + + inline ConstIterator end() const + { + return End(); + } + + private: + template + typename std::unordered_map::iterator EmplaceBackInternal(const Tkey &key, Targs &&...args) + { + auto it = _value_map.find(key); + + if (it == _value_map.end()) + { + _value_map.emplace(key, Tvalue()); + it = _value_map.find(key); + _key_ref_list.emplace_back(std::ref(it->first)); + } + + return it; + } + + private: + KeyRefList _key_ref_list; + std::unordered_map _value_map; + }; +} // namespace ov