From f23fdf0639eacaeaa90656435e34d0112b405d39 Mon Sep 17 00:00:00 2001 From: faluco Date: Thu, 10 Dec 2020 18:13:06 +0100 Subject: [PATCH] Include missing files. --- lib/include/srslte/srslog/context.h | 250 +++++++++++++++ .../srslte/srslog/detail/log_entry_metadata.h | 47 +++ .../srslte/srslog/detail/support/tmp_utils.h | 70 +++++ lib/include/srslte/srslog/formatter.h | 185 +++++++++++ lib/src/srslog/formatters/CMakeLists.txt | 13 + lib/src/srslog/formatters/json_formatter.cpp | 149 +++++++++ lib/src/srslog/formatters/json_formatter.h | 137 ++++++++ lib/src/srslog/formatters/text_formatter.cpp | 160 ++++++++++ lib/src/srslog/formatters/text_formatter.h | 79 +++++ lib/test/srslog/context_test.cpp | 120 +++++++ lib/test/srslog/json_formatter_test.cpp | 295 ++++++++++++++++++ lib/test/srslog/test_dummies.h | 98 ++++++ lib/test/srslog/text_formatter_test.cpp | 195 ++++++++++++ 13 files changed, 1798 insertions(+) create mode 100644 lib/include/srslte/srslog/context.h create mode 100644 lib/include/srslte/srslog/detail/log_entry_metadata.h create mode 100644 lib/include/srslte/srslog/detail/support/tmp_utils.h create mode 100644 lib/include/srslte/srslog/formatter.h create mode 100644 lib/src/srslog/formatters/CMakeLists.txt create mode 100644 lib/src/srslog/formatters/json_formatter.cpp create mode 100644 lib/src/srslog/formatters/json_formatter.h create mode 100644 lib/src/srslog/formatters/text_formatter.cpp create mode 100644 lib/src/srslog/formatters/text_formatter.h create mode 100644 lib/test/srslog/context_test.cpp create mode 100644 lib/test/srslog/json_formatter_test.cpp create mode 100644 lib/test/srslog/test_dummies.h create mode 100644 lib/test/srslog/text_formatter_test.cpp diff --git a/lib/include/srslte/srslog/context.h b/lib/include/srslte/srslog/context.h new file mode 100644 index 000000000..02df5d49a --- /dev/null +++ b/lib/include/srslte/srslog/context.h @@ -0,0 +1,250 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSLOG_CONTEXT_H +#define SRSLOG_CONTEXT_H + +#include "srslte/srslog/detail/support/tmp_utils.h" +#include +#include + +namespace srslog { + +/// Metric formatting kinds for textual conversion. +enum class metric_kind { + numeric, /// Metric represents a numeric value. + string /// Metric represents a string. +}; + +namespace detail { + +/// This metrics container class is a wrapper to simplify access to the elements +/// of the underlying tuple that stores metrics and metric sets. +template +struct metrics_container { + /// Writes the arg value to metric T. + template + void write(Arg&& arg) + { + constexpr std::size_t index = detail::get_type_index_in_tuple(); + std::get(metrics).value = std::forward(arg); + } + + /// Returns the value of metric T. + template + auto read() const -> const decltype(T::value)& + { + constexpr std::size_t index = detail::get_type_index_in_tuple(); + return std::get(metrics).value; + } + + /// Returns the element of type T. + template + T& get() + { + constexpr std::size_t index = detail::get_type_index_in_tuple(); + return std::get(metrics); + } + + /// Returns the element of type T. + template + const T& get() const + { + constexpr std::size_t index = detail::get_type_index_in_tuple(); + return std::get(metrics); + } + + /// Returns the element in the specified index of list T. + /// NOTE: T must have implemented the T operator. + template + auto at(std::size_t i) -> typename T::value_type& + { + constexpr std::size_t index = detail::get_type_index_in_tuple(); + auto& elem = std::get(metrics); + assert(i < elem.size() && "Invalid index"); + return elem[i]; + } + + /// Returns the element in the specified index of list T. + /// NOTE: T must have implemented the T operator. + template + auto at(std::size_t i) const -> const typename T::value_type& + { + constexpr std::size_t index = detail::get_type_index_in_tuple(); + const auto& elem = std::get(metrics); + assert(i < elem.size() && "Invalid index"); + return elem[i]; + } + + /// Returns the raw contents of the metric set as a tuple. + const std::tuple& contents() const { return metrics; } + +private: + std::tuple metrics; +}; + +} // namespace detail + +/// A generic list to store metric sets of the same type. +template +struct metric_list : public T { + /// Returns the name of the list. + static const char* name() { return Name::name(); } +}; + +/// Template specializations of this struct allow configuring what formatting +/// kind should be used for a concrete metric. +/// By default treat all metrics as strings. +template +struct metric_kind_selector { + static const metric_kind kind = metric_kind::string; +}; + +/// A metric is the most basic object that composes a context. It is generally +/// used to represent any kind of state of a program. +/// It stores a value of type T associated with a name and the units. +template +struct metric { + /// Value of the metric. + Ty value{}; + + /// Returns the name of the metric. + static const char* name() { return Name::name(); } + + /// Returns the units of the metric. + static const char* units() { return Units::units(); } + + /// Returns the formatting kind of the metric. + static metric_kind kind() + { + return metric_kind_selector>::kind; + } +}; + +/// Template specialization that tags metrics with arithmetic values (integers +/// and floating point) as numeric. +template +struct metric_kind_selector< + metric, + typename std::enable_if::value>::type> { + static const metric_kind kind = metric_kind::numeric; +}; + +/// A metric set is a group of metrics that share a logical relation. Allows +/// storing and mixing other metric sets and metrics for building complex +/// structures. +template +struct metric_set : public detail::metrics_container { + /// Name of the metric set. + static const char* name() { return Name::name(); } +}; + +/// A context captures the state of different parts of a program grouping metric +/// sets. It is the root element of the metrics structure and allows mixing and +/// storing other metric sets and metrics. +template +struct context : public detail::metrics_container { + explicit context(std::string n) : name_str(std::move(n)) {} + + /// Name of the context. + const std::string& name() const { return name_str; } + +private: + const std::string name_str; +}; + +namespace detail { + +/// Builds a metric set type using a list of metric, metric sets or list types. +/// eg: using my_metric_t = srslog::build_metric_set_type; +/// NOTE: Adding duplicated types into the list is not allowed. +template +using build_metric_set_type = + metric_set::type...>; + +} // namespace detail + +/// Builds a context type using a list of metric set types. +/// eg: using my_context_t = srslog::build_context_type; +/// NOTE: Adding duplicated types into the list is not allowed. +template +using build_context_type = context::type...>; + +/// This macro defines a new metric type using the following attributes: +/// a) name: encoded as a string. +/// b) Metric type: type identifier to create objects for this metric. +/// c) Value type: type of the underlying metric value. +/// d) Units: encoded as a string, leave as empty string for no units. +/// +/// The following example declares a metric with the following attributes: +/// a) metric type: my_metric_t +/// b) metric value type: float +/// c) units: MB/s +/// d) name: Throughput +/// DECLARE_METRIC("Throughput", my_metric_t, float, "MB/s"); +#define DECLARE_METRIC(_name_rep, _type, _value_type, _units) \ + namespace metric_info { \ + struct _type##__units { \ + static const char* units() { return _units; } \ + }; \ + struct _type##__name_rep { \ + static const char* name() { return _name_rep; } \ + }; \ + } \ + using _type = srslog::metric::type, \ + metric_info::_type##__name_rep, \ + metric_info::_type##__units> + +/// This macro defines a new metric set type using the following attributes: +/// a) name: encoded as a string. +/// b) Metric set type: type identifier to create objects for this metric set. +/// c) Type list: list of types this set will hold (other sets, metrics, +/// lists). +/// +/// The following example declares a metric set of three elements (two metrics +/// and one set) with the following attributes: +/// a) metric type: my_set_t +/// b) name: my_set +/// b) type list: metric1_t, metric2_t, set2_t +/// DECLARE_METRIC_SET("my_set", my_set_t, metric1_t, metric2_t, set2_t); +#define DECLARE_METRIC_SET(_name_rep, _type, ...) \ + namespace metric_set_info { \ + struct _type##__name_rep { \ + static const char* name() { return _name_rep; } \ + }; \ + } \ + using _type = srslog::detail:: \ + build_metric_set_type + +/// This macro defines a list of metric sets of the same type: +/// a) name: encoded as a string. +/// b) List type: type identifier to create objects for this list. +/// c) Underlying type: type of the underlying list (vector, array, ...). +/// +/// The following example declares a list of metrics sets of type set1_t with +/// the following attributes: +/// a) list type: my_list_t +/// b) name: my_list +/// b) underlying type: std::vector +/// DECLARE_METRIC_LIST("my_list", my_list_t, std::vector); +#define DECLARE_METRIC_LIST(_name_rep, _type, _list_type) \ + namespace list_info { \ + struct _type##__name_rep { \ + static const char* name() { return _name_rep; } \ + }; \ + } \ + using _type = srslog::metric_list::type> + +} // namespace srslog + +#endif // SRSLOG_CONTEXT_H diff --git a/lib/include/srslte/srslog/detail/log_entry_metadata.h b/lib/include/srslte/srslog/detail/log_entry_metadata.h new file mode 100644 index 000000000..be40fa791 --- /dev/null +++ b/lib/include/srslte/srslog/detail/log_entry_metadata.h @@ -0,0 +1,47 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSLOG_DETAIL_LOG_ENTRY_METADATA_H +#define SRSLOG_DETAIL_LOG_ENTRY_METADATA_H + +#include "srslte/srslog/bundled/fmt/printf.h" +#include + +namespace srslog { + +namespace detail { + +/// This structure gives the user a way to log generic information as a context. +//:TODO: legacy struct, will get replaced by the new context framework. +struct log_context { + /// Generic context value. + uint32_t value; + /// When true, the context value will be printed in the log entry. + bool enabled; +}; + +/// Metadata fields carried for each log entry. +struct log_entry_metadata { + std::chrono::high_resolution_clock::time_point tp; + log_context context; + std::string fmtstring; + fmt::dynamic_format_arg_store store; + std::string log_name; + char log_tag; + std::vector hex_dump; +}; + +} // namespace detail + +} // namespace srslog + +#endif // SRSLOG_DETAIL_LOG_ENTRY_METADATA_H diff --git a/lib/include/srslte/srslog/detail/support/tmp_utils.h b/lib/include/srslte/srslog/detail/support/tmp_utils.h new file mode 100644 index 000000000..c21518942 --- /dev/null +++ b/lib/include/srslte/srslog/detail/support/tmp_utils.h @@ -0,0 +1,70 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSLOG_DETAIL_SUPPORT_TMP_UTILS_H +#define SRSLOG_DETAIL_SUPPORT_TMP_UTILS_H + +#include +#include + +namespace srslog { + +namespace detail { + +/// +/// Implementation of the std::index_sequence C++14 library utility. +/// + +template +struct index_sequence {}; + +template +struct index_sequence_helper + : public index_sequence_helper {}; + +template +struct index_sequence_helper<0U, Next...> { + using type = index_sequence; +}; + +template +using make_index_sequence = typename index_sequence_helper::type; + +/// +/// Implementation of the std::get C++14 library utility. +/// + +template +struct tuple_index; + +template +struct tuple_index> { + static constexpr std::size_t value = 0; +}; + +template +struct tuple_index> { + static constexpr std::size_t value = + 1 + tuple_index>::value; +}; + +template +constexpr std::size_t get_type_index_in_tuple() +{ + return tuple_index>::value; +} + +} // namespace detail + +} // namespace srslog + +#endif // SRSLOG_DETAIL_SUPPORT_TMP_UTILS_H diff --git a/lib/include/srslte/srslog/formatter.h b/lib/include/srslte/srslog/formatter.h new file mode 100644 index 000000000..551f6f426 --- /dev/null +++ b/lib/include/srslte/srslog/formatter.h @@ -0,0 +1,185 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSLOG_FORMATTER_H +#define SRSLOG_FORMATTER_H + +#include "srslte/srslog/bundled/fmt/format.h" +#include "srslte/srslog/context.h" + +namespace srslog { + +namespace detail { +struct log_entry_metadata; +} + +/// The generic metric value formatter. +template +struct metric_value_formatter { + metric_value_formatter() = delete; + /// All specializations should implement the following method with signature: + /// template + /// void format(const T& v, fmt::memory_buffer& buffer) +}; + +/// Default metric value formatter. Users that want to override this behaviour +/// should add an specialization of the metric they want to customize. +template +struct metric_value_formatter> { + template + void format(const T& v, fmt::memory_buffer& buffer) + { + fmt::format_to(buffer, "{}", v); + } +}; + +/// This is the base class that provides a common framework to format log +/// entries to different kinds of formats. User should implement two different +/// kinds of formats: +/// a) Basic log entry formatting. +/// b) Generic context formatting. +/// +/// For context formatting, callbacks are provided so that derived classes +/// handle specific formatting rules. +class log_formatter +{ +public: + virtual ~log_formatter() = default; + + /// Returns a copy of the formatter. + virtual std::unique_ptr clone() const = 0; + + /// Formats the log entry into the input buffer. + virtual void format(detail::log_entry_metadata&& metadata, + fmt::memory_buffer& buffer) = 0; + + /// Formats the context and log entry into the input buffer. + template + void format_ctx(const srslog::context& ctx, + detail::log_entry_metadata&& metadata, + fmt::memory_buffer& buffer) + { + format_context_begin(metadata, ctx.name(), sizeof...(Ts), buffer); + iterate_tuple(ctx.contents(), + 1, + buffer, + detail::make_index_sequence{}); + format_context_end(metadata, ctx.name(), buffer); + } + +private: + /// Processes all elements in a tuple. + template + void iterate_tuple(const std::tuple& t, + unsigned level, + fmt::memory_buffer& buffer, + detail::index_sequence) + { + (void)std::initializer_list{ + (process_element(std::get(t), level, buffer), 0)...}; + } + + /// Processes the input metric set. + template + void process_element(const metric_set& ms, + unsigned level, + fmt::memory_buffer& buffer) + { + format_metric_set_begin(ms.name(), sizeof...(Ts), level, buffer); + iterate_tuple(ms.contents(), + level + 1, + buffer, + detail::make_index_sequence{}); + format_metric_set_end(ms.name(), level, buffer); + } + + /// Processes the input metric list. + template + void process_element(const metric_list& list, + unsigned level, + fmt::memory_buffer& buffer) + { + format_list_begin(list.name(), list.size(), level, buffer); + for (const auto& elem : list) { + process_element(elem, level + 1, buffer); + } + format_list_end(list.name(), level, buffer); + } + + /// Processes the input metric. + template + void process_element(const metric& t, + unsigned level, + fmt::memory_buffer& buffer) + { + fmt::memory_buffer value; + metric_value_formatter::type>{}.format( + t.value, value); + + format_metric( + t.name(), fmt::to_string(value), t.units(), t.kind(), level, buffer); + } + +private: + /// Derived classes should implement the following callbacks to format metric + /// objects. Each callback is invoked at a different place of the formatting + /// algorithm. + + /// This callback gets called at the beginning of the context formatting + /// algorithm. + virtual void format_context_begin(const detail::log_entry_metadata& md, + const std::string& ctx_name, + unsigned size, + fmt::memory_buffer& buffer) = 0; + + /// This callback gets called at the end of the context formatting algorithm. + virtual void format_context_end(const detail::log_entry_metadata& md, + const std::string& ctx_name, + fmt::memory_buffer& buffer) = 0; + + /// This callback gets called at the beginning of a metric set formatting + /// procedure. + virtual void format_metric_set_begin(const std::string& set_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) = 0; + + /// This callback gets called at the beginning of a metric set formatting end. + virtual void format_metric_set_end(const std::string& set_name, + unsigned level, + fmt::memory_buffer& buffer) = 0; + + /// This callback gets called at the beginning of a metric list formatting + /// procedure. + virtual void format_list_begin(const std::string& list_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) = 0; + + /// This callback gets called at the end of a metric list formatting + /// procedure. + virtual void format_list_end(const std::string& list_name, + unsigned level, + fmt::memory_buffer& buffer) = 0; + + /// This callback gets called for each metric. + virtual void format_metric(const std::string& metric_name, + const std::string& metric_value, + const std::string& metric_units, + metric_kind kind, + unsigned level, + fmt::memory_buffer& buffer) = 0; +}; + +} // namespace srslog + +#endif // SRSLOG_FORMATTER_H diff --git a/lib/src/srslog/formatters/CMakeLists.txt b/lib/src/srslog/formatters/CMakeLists.txt new file mode 100644 index 000000000..20bc911e0 --- /dev/null +++ b/lib/src/srslog/formatters/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright 2013-2020 Software Radio Systems Limited +# +# By using this file, you agree to the terms and conditions set +# forth in the LICENSE file which can be found at the top level of +# the distribution. +# + +set(SOURCES + json_formatter.cpp + text_formatter.cpp) + +add_library(formatters STATIC ${SOURCES}) diff --git a/lib/src/srslog/formatters/json_formatter.cpp b/lib/src/srslog/formatters/json_formatter.cpp new file mode 100644 index 000000000..f5eb16bd6 --- /dev/null +++ b/lib/src/srslog/formatters/json_formatter.cpp @@ -0,0 +1,149 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "json_formatter.h" +#include "srslte/srslog/detail/log_entry_metadata.h" + +using namespace srslog; + +std::unique_ptr json_formatter::clone() const +{ + return std::unique_ptr(new json_formatter); +} + +void json_formatter::format(detail::log_entry_metadata&& metadata, + fmt::memory_buffer& buffer) +{ + fmt::format_to(buffer, + "{{\n" + " \"log_entry\": \"{}\"", + fmt::vsprintf(metadata.fmtstring, std::move(metadata.store))); + + if (!metadata.hex_dump.empty()) { + fmt::format_to( + buffer, + ",\n \"hex_dump\": \"{:02x}\"", + fmt::join(metadata.hex_dump.cbegin(), metadata.hex_dump.cend(), " ")); + } + + fmt::format_to(buffer, "\n}}\n"); +} + +void json_formatter::format_context_begin(const detail::log_entry_metadata& md, + const std::string& ctx_name, + unsigned size, + fmt::memory_buffer& buffer) +{ + assert(scope_stack.empty() && "Stack should be empty"); + assert(nest_level == 0 && "Nesting level should be 0"); + + fmt::format_to(buffer, "{{\n"); + push_scope(size); + + if (!md.fmtstring.empty()) { + fmt::format_to(buffer, + " \"log_entry\": \"{}\",\n", + fmt::vsprintf(md.fmtstring, std::move(md.store))); + } +} + +void json_formatter::format_context_end(const detail::log_entry_metadata& md, + const std::string& ctx_name, + fmt::memory_buffer& buffer) +{ + pop_scope(); + fmt::format_to(buffer, "}}\n"); + + assert(scope_stack.empty() && "Stack should be empty"); + assert(nest_level == 0 && "Nesting level should be 0"); +} + +void json_formatter::format_metric_set_begin(const std::string& set_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) +{ + // Arrays in JSON require an additional nesting level before inserting the + // object. + // array: [ + // { + // "obj: {} + // } + // ] + if (in_list_scope()) { + fmt::format_to(buffer, "{: <{}}{{\n", ' ', indents(level)); + increment_nest_level(); + } + + consume_element(); + + fmt::format_to(buffer, "{: <{}}\"{}\": {{\n", ' ', indents(level), set_name); + push_scope(size); +} + +void json_formatter::format_metric_set_end(const std::string& set_name, + unsigned level, + fmt::memory_buffer& buffer) +{ + pop_scope(); + fmt::format_to(buffer, + "{: <{}}}}{}\n", + ' ', + indents(level), + needs_comma() && !in_list_scope() ? "," : ""); + + if (in_list_scope()) { + decrement_nest_level(); + fmt::format_to( + buffer, "{: <{}}}}{}\n", ' ', indents(level), needs_comma() ? "," : ""); + } +} + +void json_formatter::format_metric(const std::string& metric_name, + const std::string& metric_value, + const std::string& metric_units, + metric_kind kind, + unsigned level, + fmt::memory_buffer& buffer) +{ + consume_element(); + + fmt::format_to(buffer, + "{: <{}}\"{}\": {}{}{}{}\n", + ' ', + indents(level), + metric_name, + kind == metric_kind::string ? "\"" : "", + metric_value, + kind == metric_kind::string ? "\"" : "", + needs_comma() ? "," : ""); +} + +void json_formatter::format_list_begin(const std::string& list_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) +{ + consume_element(); + + fmt::format_to(buffer, "{: <{}}\"{}\": [\n", ' ', indents(level), list_name); + push_list_scope(size); +} + +void json_formatter::format_list_end(const std::string& list_name, + unsigned level, + fmt::memory_buffer& buffer) +{ + pop_scope(); + fmt::format_to( + buffer, "{: <{}}]{}\n", ' ', indents(level), needs_comma() ? "," : ""); +} diff --git a/lib/src/srslog/formatters/json_formatter.h b/lib/src/srslog/formatters/json_formatter.h new file mode 100644 index 000000000..d7c886999 --- /dev/null +++ b/lib/src/srslog/formatters/json_formatter.h @@ -0,0 +1,137 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSLOG_JSON_FORMATTER_H +#define SRSLOG_JSON_FORMATTER_H + +#include "srslte/srslog/formatter.h" + +namespace srslog { + +/// JSON formatter class implementation. +/// Formats each log entry and context into its own JSON object making the +/// formatter stateless so that new entries do not depend on the state of +/// previous ones. The output is ready for JSON streaming following the +/// "Concatenated JSON" style. +class json_formatter : public log_formatter +{ +public: + json_formatter() { scope_stack.reserve(16); } + + std::unique_ptr clone() const override; + + void format(detail::log_entry_metadata&& metadata, + fmt::memory_buffer& buffer) override; + +private: + void format_context_begin(const detail::log_entry_metadata& md, + const std::string& ctx_name, + unsigned size, + fmt::memory_buffer& buffer) override; + + void format_context_end(const detail::log_entry_metadata& md, + const std::string& ctx_name, + fmt::memory_buffer& buffer) override; + + void format_metric_set_begin(const std::string& set_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) override; + + void format_metric_set_end(const std::string& set_name, + unsigned level, + fmt::memory_buffer& buffer) override; + + void format_list_begin(const std::string& list_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) override; + + void format_list_end(const std::string& list_name, + unsigned level, + fmt::memory_buffer& buffer) override; + + void format_metric(const std::string& metric_name, + const std::string& metric_value, + const std::string& metric_units, + metric_kind kind, + unsigned level, + fmt::memory_buffer& buffer) override; + + /// Pushes a new entry in the scope stack. + void push_scope(unsigned size) { scope_stack.emplace_back(size, false); } + + /// Pushes a new list entry in the scope stack. + void push_list_scope(unsigned size) { scope_stack.emplace_back(size, true); } + + /// Pops the topmost entry in the scope stack. + void pop_scope() + { + assert(!scope_stack.empty() && "Popping scope in empty stack"); + scope_stack.pop_back(); + } + + /// Consumes an element in the current scope. + void consume_element() + { + assert(!scope_stack.empty() && "Consuming element in void scope"); + assert(scope_stack.back().size && "No more elements to consume"); + --scope_stack.back().size; + } + + /// Returns true if the current element needs a comma. + bool needs_comma() const + { + assert(!scope_stack.empty() && "No scope exists"); + return scope_stack.back().size; + } + + /// Returns true if the current scope is a list. + bool in_list_scope() const + { + assert(!scope_stack.empty() && "No scope exists"); + return scope_stack.back().inside_list; + } + + /// Increments the nesting level by one. + void increment_nest_level() { ++nest_level; } + + /// Decrements the nesting level by one. + void decrement_nest_level() + { + assert(nest_level && "Expected the nesting level to greater than 0"); + --nest_level; + } + + /// Return the number of space chars to indent the specified level. + unsigned indents(unsigned level) const { return (nest_level + level) * 2; } + +private: + /// Keeps track of some information about a JSON scope. + struct scope { + scope(unsigned size, bool inside_list) : + size(size), inside_list(inside_list) + {} + /// Number of elements this scope holds. + unsigned size; + /// If true, indicates this scope belongs to a list. + const bool inside_list; + }; + +private: + unsigned nest_level = 0; + std::vector scope_stack; +}; + +} // namespace srslog + +#endif // SRSLOG_JSON_FORMATTER_H diff --git a/lib/src/srslog/formatters/text_formatter.cpp b/lib/src/srslog/formatters/text_formatter.cpp new file mode 100644 index 000000000..79ed995b4 --- /dev/null +++ b/lib/src/srslog/formatters/text_formatter.cpp @@ -0,0 +1,160 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "text_formatter.h" +#include "srslte/srslog/bundled/fmt/chrono.h" +#include "srslte/srslog/detail/log_entry_metadata.h" + +using namespace srslog; + +std::unique_ptr text_formatter::clone() const +{ + return std::unique_ptr(new text_formatter(*this)); +} + +/// Formats into a hex dump a range of elements, storing the result in the input +/// buffer. +static void format_hex_dump(const std::vector& v, + fmt::memory_buffer& buffer) +{ + if (v.empty()) { + return; + } + + const size_t elements_per_line = 16; + + for (auto i = v.cbegin(), e = v.cend(); i != e;) { + auto num_elements = + std::min(elements_per_line, std::distance(i, e)); + + fmt::format_to(buffer, + " {:04x}: {:02x}\n", + std::distance(v.cbegin(), i), + fmt::join(i, i + num_elements, " ")); + + std::advance(i, num_elements); + } +} + +/// Format the log metadata into the input buffer. +static void format_metadata(const detail::log_entry_metadata& metadata, + fmt::memory_buffer& buffer) +{ + // Time stamp data preparation. + std::tm current_time = + fmt::gmtime(std::chrono::high_resolution_clock::to_time_t(metadata.tp)); + auto us_fraction = std::chrono::duration_cast( + metadata.tp.time_since_epoch()) + .count() % + 1000000u; + fmt::format_to(buffer, "{:%H:%M:%S}.{:06} ", current_time, us_fraction); + + // Format optional fields if present. + if (!metadata.log_name.empty()) { + fmt::format_to(buffer, "[{: <4.4}] ", metadata.log_name); + } + if (metadata.log_tag != '\0') { + fmt::format_to(buffer, "[{}] ", metadata.log_tag); + } + if (metadata.context.enabled) { + fmt::format_to(buffer, "[{:5}] ", metadata.context.value); + } +} + +void text_formatter::format(detail::log_entry_metadata&& metadata, + fmt::memory_buffer& buffer) +{ + // Prefix first. + format_metadata(metadata, buffer); + + // Message formatting. + fmt::format_to(buffer, + "{}\n", + fmt::vsprintf(metadata.fmtstring, std::move(metadata.store))); + + // Optional hex dump formatting. + format_hex_dump(metadata.hex_dump, buffer); +} + +void text_formatter::format_context_begin(const detail::log_entry_metadata& md, + const std::string& ctx_name, + unsigned size, + fmt::memory_buffer& buffer) +{ + do_one_line_ctx_format = !md.fmtstring.empty(); + + format_metadata(md, buffer); + if (do_one_line_ctx_format) { + fmt::format_to(buffer, "["); + return; + } + fmt::format_to(buffer, "Context dump for \"{}\"\n", ctx_name); +} + +void text_formatter::format_context_end(const detail::log_entry_metadata& md, + const std::string& ctx_name, + fmt::memory_buffer& buffer) +{ + if (do_one_line_ctx_format) { + fmt::format_to(buffer, "]: {}\n", fmt::vsprintf(md.fmtstring, md.store)); + return; + } +} + +void text_formatter::format_metric_set_begin(const std::string& set_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) +{ + /*if (do_one_line_ctx_format) { + fmt::format_to(buffer, "{}", is_first ? "[" : " ["); + return; + } + fmt::format_to(buffer, " {}\n", set_name);*/ +} + +void text_formatter::format_metric_set_end(const std::string& set_name, + unsigned level, + fmt::memory_buffer& buffer) +{ + if (do_one_line_ctx_format) { + fmt::format_to(buffer, "]"); + return; + } +} + +void text_formatter::format_metric(const std::string& metric_name, + const std::string& metric_value, + const std::string& metric_units, + metric_kind kind, + unsigned level, + fmt::memory_buffer& buffer) +{ + //:TODO: re-enable + /*if (do_one_line_ctx_format) { + fmt::format_to(buffer, + "{}{}_{}: {}{}{}", + ctx.is_first_metric ? "" : ", ", + ctx.set_name, + ctx.metric_name, + ctx.metric_value, + ctx.metric_units.empty() ? "" : " ", + ctx.metric_units); + return; + } + fmt::format_to(buffer, + " {}: {}{}{}\n", + ctx.metric_name, + ctx.metric_value, + ctx.metric_units.empty() ? "" : " ", + ctx.metric_units);*/ +} diff --git a/lib/src/srslog/formatters/text_formatter.h b/lib/src/srslog/formatters/text_formatter.h new file mode 100644 index 000000000..b8dcf32cd --- /dev/null +++ b/lib/src/srslog/formatters/text_formatter.h @@ -0,0 +1,79 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSLOG_TEXT_FORMATTER_H +#define SRSLOG_TEXT_FORMATTER_H + +#include "srslte/srslog/formatter.h" + +namespace srslog { + +/// Plain text formatter implementation class. +//:TODO: this class needs refactoring to be compatible with multiple nesting of +// metrics. +class text_formatter : public log_formatter +{ +public: + std::unique_ptr clone() const override; + + void format(detail::log_entry_metadata&& metadata, + fmt::memory_buffer& buffer) override; + +private: + void format_context_begin(const detail::log_entry_metadata& md, + const std::string& ctx_name, + unsigned size, + fmt::memory_buffer& buffer) override; + + void format_context_end(const detail::log_entry_metadata& md, + const std::string& ctx_name, + fmt::memory_buffer& buffer) override; + + void format_metric_set_begin(const std::string& set_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) override; + + void format_metric_set_end(const std::string& set_name, + unsigned level, + fmt::memory_buffer& buffer) override; + + void format_list_begin(const std::string& list_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) override + { + //:TODO: implement me + } + + void format_list_end(const std::string& list_name, + unsigned level, + fmt::memory_buffer& buffer) override + { + //:TODO: implement me + } + + void format_metric(const std::string& metric_name, + const std::string& metric_value, + const std::string& metric_units, + metric_kind kind, + unsigned level, + fmt::memory_buffer& buffer) override; + +private: + /// Flags that the formatting should take place into a single line. + bool do_one_line_ctx_format = false; +}; + +} // namespace srslog + +#endif // SRSLOG_TEXT_FORMATTER_H diff --git a/lib/test/srslog/context_test.cpp b/lib/test/srslog/context_test.cpp new file mode 100644 index 000000000..096c5f1fd --- /dev/null +++ b/lib/test/srslog/context_test.cpp @@ -0,0 +1,120 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srslte/srslog/context.h" +#include "testing_helpers.h" +#include + +using namespace srslog; + +namespace { +DECLARE_METRIC("SNR", snr_t, float, "dB"); +DECLARE_METRIC("PWR", pwr_t, int, "dBm"); +DECLARE_METRIC("CenterFreq", cfreq_t, unsigned, "MHz"); +DECLARE_METRIC_SET("RF", myset1, snr_t, pwr_t, cfreq_t); + +DECLARE_METRIC("Throughput", thr_t, float, "MB/s"); +DECLARE_METRIC("Address", ip_addr_t, std::string, ""); +DECLARE_METRIC_SET("Network", myset2, thr_t, ip_addr_t); + +using ctx_t = srslog::build_context_type; +} // namespace + +/// Builds a context for testing. +static ctx_t build_context() +{ + ctx_t ctx("Ctx"); + return ctx; +} + +static bool when_context_is_built_then_context_name_is_valid() +{ + ctx_t ctx = build_context(); + + ASSERT_EQ(ctx.name(), "Ctx"); + + return true; +} + +static bool when_context_is_built_then_metric_sets_names_are_valid() +{ + ctx_t ctx = build_context(); + + ASSERT_EQ(ctx.get().name(), std::string("RF")); + ASSERT_EQ(ctx.get().name(), std::string("Network")); + + return true; +} + +static bool when_context_is_built_then_metric_names_are_valid() +{ + ctx_t ctx = build_context(); + + ASSERT_EQ(ctx.get().get().name(), std::string("SNR")); + ASSERT_EQ(ctx.get().get().name(), std::string("PWR")); + + return true; +} + +static bool when_context_is_built_then_metric_units_are_valid() +{ + ctx_t ctx = build_context(); + + ASSERT_EQ(ctx.get().get().units(), std::string("dB")); + ASSERT_EQ(ctx.get().get().units(), std::string("dBm")); + + return true; +} + +static bool when_metric_is_set_through_context_then_value_is_stored() +{ + ctx_t ctx = build_context(); + float value = 10; + + ctx.get().write(value); + + ASSERT_EQ(ctx.get().read(), value); + + return true; +} + +namespace { +DECLARE_METRIC("metric1", m1_t, float, ""); +DECLARE_METRIC_SET("test_set_t", test_set_t, m1_t); +DECLARE_METRIC_LIST("vector", vector_metrics, std::vector); +using ctx2_t = srslog::build_context_type; +} // namespace + +static bool when_context_with_list_is_set_value_is_retrieved_correctly() +{ + ctx2_t ctx("test"); + float val = 2; + + ctx.get().emplace_back(); + ctx.at(0).write(val); + + ASSERT_EQ(ctx.at(0).read(), val); + + return true; +} + +int main() +{ + TEST_FUNCTION(when_context_is_built_then_context_name_is_valid); + TEST_FUNCTION(when_context_is_built_then_metric_sets_names_are_valid); + TEST_FUNCTION(when_context_is_built_then_metric_names_are_valid); + TEST_FUNCTION(when_context_is_built_then_metric_units_are_valid); + TEST_FUNCTION(when_metric_is_set_through_context_then_value_is_stored); + TEST_FUNCTION(when_context_with_list_is_set_value_is_retrieved_correctly); + + return 0; +} diff --git a/lib/test/srslog/json_formatter_test.cpp b/lib/test/srslog/json_formatter_test.cpp new file mode 100644 index 000000000..33bbd84dc --- /dev/null +++ b/lib/test/srslog/json_formatter_test.cpp @@ -0,0 +1,295 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "src/srslog/formatters/json_formatter.h" +#include "srslte/srslog/detail/log_entry_metadata.h" +#include "testing_helpers.h" +#include + +using namespace srslog; + +/// Helper to build a log entry. +static detail::log_entry_metadata build_log_entry_metadata() +{ + // Create a time point 50000us from epoch. + using tp_ty = std::chrono::time_point; + tp_ty tp(std::chrono::microseconds(50000)); + + fmt::dynamic_format_arg_store store; + store.push_back(88); + + return {tp, {10, true}, "Text %d", std::move(store), "ABC", 'Z'}; +} + +static bool when_fully_filled_log_entry_then_everything_is_formatted() +{ + fmt::memory_buffer buffer; + json_formatter{}.format(build_log_entry_metadata(), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = "{\n" + " \"log_entry\": \"Text 88\"\n" + "}\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +static bool +when_fully_filled_log_entry_with_hex_dump_then_everything_is_formatted() +{ + auto entry = build_log_entry_metadata(); + entry.hex_dump.resize(12); + std::iota(entry.hex_dump.begin(), entry.hex_dump.end(), 0); + + fmt::memory_buffer buffer; + json_formatter{}.format(std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = + "{\n" + " \"log_entry\": \"Text 88\",\n" + " \"hex_dump\": \"00 01 02 03 04 05 06 07 08 09 0a 0b\"\n" + "}\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +namespace { +DECLARE_METRIC("SNR", snr_t, float, "dB"); +DECLARE_METRIC("PWR", pwr_t, int, "dBm"); +DECLARE_METRIC("CenterFreq", cfreq_t, unsigned, "MHz"); +DECLARE_METRIC_SET("RF", myset1, snr_t, pwr_t, cfreq_t); + +DECLARE_METRIC("Throughput", thr_t, float, "MB/s"); +DECLARE_METRIC("Address", ip_addr_t, std::string, ""); +DECLARE_METRIC_SET("Network", myset2, thr_t, ip_addr_t); + +using basic_ctx_t = srslog::build_context_type; +} // namespace + +static bool +when_log_entry_with_only_basic_context_is_passed_then_context_is_formatted() +{ + auto entry = build_log_entry_metadata(); + entry.fmtstring = ""; + basic_ctx_t ctx("UL Context"); + + ctx.get().write(-55.1); + ctx.get().write(-10); + ctx.get().write(1500); + ctx.get().write(150.01); + ctx.get().write("192.168.1.0"); + + fmt::memory_buffer buffer; + json_formatter{}.format_ctx(ctx, std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = "{\n" + " \"RF\": {\n" + " \"SNR\": -55.1,\n" + " \"PWR\": -10,\n" + " \"CenterFreq\": 1500\n" + " },\n" + " \"Network\": {\n" + " \"Throughput\": 150.01,\n" + " \"Address\": \"192.168.1.0\"\n" + " }\n" + "}\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +static bool +when_log_entry_with_message_and_basic_context_is_passed_then_context_is_formatted() +{ + auto entry = build_log_entry_metadata(); + basic_ctx_t ctx("UL Context"); + + ctx.get().write(-55.1); + ctx.get().write(-10); + ctx.get().write(1500); + ctx.get().write(150.01); + ctx.get().write("192.168.1.0"); + + fmt::memory_buffer buffer; + json_formatter{}.format_ctx(ctx, std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = "{\n" + " \"log_entry\": \"Text 88\",\n" + " \"RF\": {\n" + " \"SNR\": -55.1,\n" + " \"PWR\": -10,\n" + " \"CenterFreq\": 1500\n" + " },\n" + " \"Network\": {\n" + " \"Throughput\": 150.01,\n" + " \"Address\": \"192.168.1.0\"\n" + " }\n" + "}\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +namespace { +DECLARE_METRIC("bearer_id", bearer_id_t, unsigned, ""); +DECLARE_METRIC("qci", qci_t, unsigned, ""); +DECLARE_METRIC_SET("bearer_container", bearer_set, bearer_id_t, qci_t); + +DECLARE_METRIC("ue_rnti", ue_rnti_t, unsigned, ""); +DECLARE_METRIC("dl_cqi", dl_cqi_t, unsigned, ""); +DECLARE_METRIC_LIST("bearer_list", bearer_list_t, std::vector); +DECLARE_METRIC_SET("ue_container", ue_set, ue_rnti_t, dl_cqi_t, bearer_list_t); + +DECLARE_METRIC("type", entry_type_t, std::string, ""); +DECLARE_METRIC("sector_id", sector_id_t, unsigned, ""); +DECLARE_METRIC_LIST("ue_list", ue_list_t, std::vector); +DECLARE_METRIC_SET("sector_metrics", + sector_set, + entry_type_t, + sector_id_t, + ue_list_t); + +DECLARE_METRIC_LIST("sector_list", sector_list_t, std::vector); + +using complex_ctx_t = srslog::build_context_type; +} // namespace + +static bool +when_log_entry_with_only_complex_context_is_passed_then_context_is_formatted() +{ + complex_ctx_t ctx("UL Context"); + auto entry = build_log_entry_metadata(); + entry.fmtstring = ""; + + ctx.get().emplace_back(); + ctx.at(0).get().emplace_back(); + ctx.at(0).get().emplace_back(); + + ctx.at(0).at(0).get().emplace_back(); + ctx.at(0).at(0).get().emplace_back(); + + ctx.at(0).at(1).get().emplace_back(); + ctx.at(0).at(1).get().emplace_back(); + + fmt::memory_buffer buffer; + json_formatter{}.format_ctx(ctx, std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = "{\n" + " \"sector_list\": [\n" + " {\n" + " \"sector_metrics\": {\n" + " \"type\": \"\",\n" + " \"sector_id\": 0,\n" + " \"ue_list\": [\n" + " {\n" + " \"ue_container\": {\n" + " \"ue_rnti\": 0,\n" + " \"dl_cqi\": 0,\n" + " \"bearer_list\": [\n" + " {\n" + " \"bearer_container\": {\n" + " \"bearer_id\": 0,\n" + " \"qci\": 0\n" + " }\n" + " },\n" + " {\n" + " \"bearer_container\": {\n" + " \"bearer_id\": 0,\n" + " \"qci\": 0\n" + " }\n" + " }\n" + " ]\n" + " }\n" + " },\n" + " {\n" + " \"ue_container\": {\n" + " \"ue_rnti\": 0,\n" + " \"dl_cqi\": 0,\n" + " \"bearer_list\": [\n" + " {\n" + " \"bearer_container\": {\n" + " \"bearer_id\": 0,\n" + " \"qci\": 0\n" + " }\n" + " },\n" + " {\n" + " \"bearer_container\": {\n" + " \"bearer_id\": 0,\n" + " \"qci\": 0\n" + " }\n" + " }\n" + " ]\n" + " }\n" + " }\n" + " ]\n" + " }\n" + " }\n" + " ]\n" + "}\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +namespace { +DECLARE_METRIC("list_metric2", list_metric2, unsigned, ""); +DECLARE_METRIC_SET("metric_list_set", metric_list_set, list_metric2); +DECLARE_METRIC_LIST("metrics_list", metrics_list, std::vector); +DECLARE_METRIC("list_metric3", list_metric3, unsigned, ""); +DECLARE_METRIC("list_metric4", list_metric4, unsigned, ""); +using list_ctx_t = + srslog::build_context_type; +}; // namespace + +static bool when_context_with_empty_list_is_passed_then_list_object_is_empty() +{ + list_ctx_t ctx("UL Context"); + auto entry = build_log_entry_metadata(); + entry.fmtstring = ""; + + fmt::memory_buffer buffer; + json_formatter{}.format_ctx(ctx, std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = "{\n" + " \"list_metric3\": 0,\n" + " \"list_metric4\": 0,\n" + " \"metrics_list\": [\n" + " ]\n" + "}\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +int main() +{ + TEST_FUNCTION(when_fully_filled_log_entry_then_everything_is_formatted); + TEST_FUNCTION( + when_fully_filled_log_entry_with_hex_dump_then_everything_is_formatted); + TEST_FUNCTION( + when_log_entry_with_only_basic_context_is_passed_then_context_is_formatted); + TEST_FUNCTION( + when_log_entry_with_message_and_basic_context_is_passed_then_context_is_formatted); + TEST_FUNCTION( + when_log_entry_with_only_complex_context_is_passed_then_context_is_formatted); + TEST_FUNCTION( + when_context_with_empty_list_is_passed_then_list_object_is_empty); + + return 0; +} diff --git a/lib/test/srslog/test_dummies.h b/lib/test/srslog/test_dummies.h new file mode 100644 index 000000000..16474e3b2 --- /dev/null +++ b/lib/test/srslog/test_dummies.h @@ -0,0 +1,98 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef TEST_DUMMIES +#define TEST_DUMMIES + +#include "srslte/srslog/detail/log_backend.h" +#include "srslte/srslog/sink.h" + +namespace test_dummies { + +/// A Dummy implementation of a formatter. +class log_formatter_dummy : public srslog::log_formatter +{ +public: + void format(srslog::detail::log_entry_metadata&& metadata, + fmt::memory_buffer& buffer) override + {} + + std::unique_ptr clone() const override { return nullptr; } + +private: + void format_context_begin(const srslog::detail::log_entry_metadata& md, + const std::string& ctx_name, + unsigned size, + fmt::memory_buffer& buffer) override + {} + void format_context_end(const srslog::detail::log_entry_metadata& md, + const std::string& ctx_name, + fmt::memory_buffer& buffer) override + {} + void format_metric_set_begin(const std::string& set_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) override + {} + void format_metric_set_end(const std::string& set_name, + unsigned level, + fmt::memory_buffer& buffer) override + {} + void format_list_begin(const std::string& list_name, + unsigned size, + unsigned level, + fmt::memory_buffer& buffer) override + {} + void format_list_end(const std::string& list_name, + unsigned level, + fmt::memory_buffer& buffer) override + {} + void format_metric(const std::string& metric_name, + const std::string& metric_value, + const std::string& metric_units, + srslog::metric_kind kind, + unsigned level, + fmt::memory_buffer& buffer) override + {} +}; + +/// A Dummy implementation of a sink. +class sink_dummy : public srslog::sink +{ +public: + sink_dummy() : + sink(std::unique_ptr(new log_formatter_dummy)) + {} + + srslog::detail::error_string + write(srslog::detail::memory_buffer buffer) override + { + return {}; + } + + srslog::detail::error_string flush() override { return {}; } +}; + +/// A Dummy implementation of the log backend. +class backend_dummy : public srslog::detail::log_backend +{ +public: + void start() override {} + + void push(srslog::detail::log_entry&& entry) override {} + + bool is_running() const override { return true; } +}; + +} // namespace test_dummies + +#endif // TEST_DUMMIES diff --git a/lib/test/srslog/text_formatter_test.cpp b/lib/test/srslog/text_formatter_test.cpp new file mode 100644 index 000000000..fa20f56e1 --- /dev/null +++ b/lib/test/srslog/text_formatter_test.cpp @@ -0,0 +1,195 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "src/srslog/formatters/text_formatter.h" +#include "srslte/srslog/detail/log_entry_metadata.h" +#include "testing_helpers.h" +#include + +using namespace srslog; + +/// Helper to build a log entry. +static detail::log_entry_metadata build_log_entry_metadata() +{ + // Create a time point 50000us from epoch. + using tp_ty = std::chrono::time_point; + tp_ty tp(std::chrono::microseconds(50000)); + + fmt::dynamic_format_arg_store store; + store.push_back(88); + + return {tp, {10, true}, "Text %d", std::move(store), "ABC", 'Z'}; +} + +static bool when_fully_filled_log_entry_then_everything_is_formatted() +{ + fmt::memory_buffer buffer; + text_formatter{}.format(build_log_entry_metadata(), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = "00:00:00.050000 [ABC ] [Z] [ 10] Text 88\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +static bool when_log_entry_without_name_is_passed_then_name_is_not_formatted() +{ + auto entry = build_log_entry_metadata(); + entry.log_name = ""; + + fmt::memory_buffer buffer; + text_formatter{}.format(std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = "00:00:00.050000 [Z] [ 10] Text 88\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +static bool when_log_entry_without_tag_is_passed_then_tag_is_not_formatted() +{ + auto entry = build_log_entry_metadata(); + entry.log_tag = '\0'; + + fmt::memory_buffer buffer; + text_formatter{}.format(std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = "00:00:00.050000 [ABC ] [ 10] Text 88\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +static bool +when_log_entry_without_context_is_passed_then_context_is_not_formatted() +{ + auto entry = build_log_entry_metadata(); + entry.context.enabled = false; + + fmt::memory_buffer buffer; + text_formatter{}.format(std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = "00:00:00.050000 [ABC ] [Z] Text 88\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +static bool when_log_entry_with_hex_dump_is_passed_then_hex_dump_is_formatted() +{ + auto entry = build_log_entry_metadata(); + entry.hex_dump.resize(20); + std::iota(entry.hex_dump.begin(), entry.hex_dump.end(), 0); + + fmt::memory_buffer buffer; + text_formatter{}.format(std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = + "00:00:00.050000 [ABC ] [Z] [ 10] Text 88\n" + " 0000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n" + " 0010: 10 11 12 13\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +namespace { +DECLARE_METRIC("SNR", snr_t, float, "dB"); +DECLARE_METRIC("PWR", pwr_t, int, "dBm"); +DECLARE_METRIC("CenterFreq", cfreq_t, unsigned, "MHz"); +DECLARE_METRIC_SET("RF", myset1, snr_t, pwr_t, cfreq_t); + +DECLARE_METRIC("Throughput", thr_t, float, "MB/s"); +DECLARE_METRIC("Address", ip_addr_t, std::string, ""); +DECLARE_METRIC_SET("Network", myset2, thr_t, ip_addr_t); + +using ctx_t = srslog::build_context_type; +} // namespace + +static bool +when_log_entry_with_only_context_is_passed_then_context_is_formatted() +{ + auto entry = build_log_entry_metadata(); + entry.fmtstring = ""; + ctx_t ctx("UL Context"); + + ctx.get().write(-55.1); + ctx.get().write(-10); + ctx.get().write(1500); + ctx.get().write(150.01); + ctx.get().write("192.168.1.0"); + + fmt::memory_buffer buffer; + text_formatter{}.format_ctx(ctx, std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = + "00:00:00.050000 [ABC ] [Z] [ 10] Context dump for \"UL Context\"\n" + " RF\n" + " SNR: -55.1 dB\n" + " PWR: -10 dBm\n" + " CenterFreq: 1500 MHz\n" + " Network\n" + " Throughput: 150.01 MB/s\n" + " Address: 192.168.1.0\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +static bool +when_log_entry_with_context_and_message_is_passed_then_context_is_formatted() +{ + auto entry = build_log_entry_metadata(); + ctx_t ctx("UL Context"); + + ctx.get().write(-55.1); + ctx.get().write(-10); + ctx.get().write(1500); + ctx.get().write(150.01); + ctx.get().write("192.168.1.0"); + + fmt::memory_buffer buffer; + text_formatter{}.format_ctx(ctx, std::move(entry), buffer); + std::string result = fmt::to_string(buffer); + std::string expected = + "00:00:00.050000 [ABC ] [Z] [ 10] [[RF_SNR: -55.1 dB, RF_PWR: -10 dBm, " + "RF_CenterFreq: 1500 MHz] [Network_Throughput: 150.01 MB/s, " + "Network_Address: 192.168.1.0]]: Text 88\n"; + + ASSERT_EQ(result, expected); + + return true; +} + +int main() +{ + TEST_FUNCTION(when_fully_filled_log_entry_then_everything_is_formatted); + TEST_FUNCTION( + when_log_entry_without_name_is_passed_then_name_is_not_formatted); + TEST_FUNCTION(when_log_entry_without_tag_is_passed_then_tag_is_not_formatted); + TEST_FUNCTION( + when_log_entry_without_context_is_passed_then_context_is_not_formatted); + TEST_FUNCTION( + when_log_entry_with_hex_dump_is_passed_then_hex_dump_is_formatted); + TEST_FUNCTION( + when_log_entry_with_only_context_is_passed_then_context_is_formatted); + TEST_FUNCTION( + when_log_entry_with_context_and_message_is_passed_then_context_is_formatted); + + return 0; +}