Import latest srslog version (#1796)

* - Import latest srslog version.
- Adjusted the nas_test to create logs correctly.
- Remove timestamp formatting now that is provided by srslog.
master
faluco 4 years ago committed by GitHub
parent 924cc4f937
commit 51b27fc255
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -48,7 +48,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
// From fits in To without any problem.
} else {
// From does not always fit in To, resort to a dynamic check.
if (from < T::min() || from > T::max()) {
if (from < (T::min)() || from > (T::max)()) {
// outside range.
ec = 1;
return {};
@ -74,7 +74,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
if (F::is_signed && !T::is_signed) {
// From may be negative, not allowed!
if (fmt::internal::is_negative(from)) {
if (fmt::detail::is_negative(from)) {
ec = 1;
return {};
}
@ -84,7 +84,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>(T::max())) {
if (from > static_cast<From>((T::max)())) {
ec = 1;
return {};
}
@ -97,7 +97,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>(T::max())) {
if (from > static_cast<From>((T::max)())) {
// outside range.
ec = 1;
return {};
@ -141,7 +141,7 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
// catch the only happy case
if (std::isfinite(from)) {
if (from >= T::lowest() && from <= T::max()) {
if (from >= T::lowest() && from <= (T::max)()) {
return static_cast<To>(from);
}
// not within range.
@ -195,12 +195,13 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
}
// multiply with Factor::num without overflow or underflow
if (Factor::num != 1) {
const auto max1 = internal::max_value<IntermediateRep>() / Factor::num;
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
if (count > max1) {
ec = 1;
return {};
}
const auto min1 = std::numeric_limits<IntermediateRep>::min() / Factor::num;
const auto min1 =
(std::numeric_limits<IntermediateRep>::min)() / Factor::num;
if (count < min1) {
ec = 1;
return {};
@ -269,7 +270,7 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
// multiply with Factor::num without overflow or underflow
if (Factor::num != 1) {
constexpr auto max1 = internal::max_value<IntermediateRep>() /
constexpr auto max1 = detail::max_value<IntermediateRep>() /
static_cast<IntermediateRep>(Factor::num);
if (count > max1) {
ec = 1;
@ -306,12 +307,12 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
// Usage: f FMT_NOMACRO()
#define FMT_NOMACRO
namespace internal {
namespace detail {
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
inline null<> localtime_s(...) { return null<>(); }
inline null<> gmtime_r(...) { return null<>(); }
inline null<> gmtime_s(...) { return null<>(); }
} // namespace internal
} // namespace detail
// Thread-safe replacement for std::localtime
inline std::tm localtime(std::time_t time) {
@ -322,22 +323,22 @@ inline std::tm localtime(std::time_t time) {
dispatcher(std::time_t t) : time_(t) {}
bool run() {
using namespace fmt::internal;
using namespace fmt::detail;
return handle(localtime_r(&time_, &tm_));
}
bool handle(std::tm* tm) { return tm != nullptr; }
bool handle(internal::null<>) {
using namespace fmt::internal;
bool handle(detail::null<>) {
using namespace fmt::detail;
return fallback(localtime_s(&tm_, &time_));
}
bool fallback(int res) { return res == 0; }
#if !FMT_MSC_VER
bool fallback(internal::null<>) {
using namespace fmt::internal;
bool fallback(detail::null<>) {
using namespace fmt::detail;
std::tm* tm = std::localtime(&time_);
if (tm) tm_ = *tm;
return tm != nullptr;
@ -359,21 +360,21 @@ inline std::tm gmtime(std::time_t time) {
dispatcher(std::time_t t) : time_(t) {}
bool run() {
using namespace fmt::internal;
using namespace fmt::detail;
return handle(gmtime_r(&time_, &tm_));
}
bool handle(std::tm* tm) { return tm != nullptr; }
bool handle(internal::null<>) {
using namespace fmt::internal;
bool handle(detail::null<>) {
using namespace fmt::detail;
return fallback(gmtime_s(&tm_, &time_));
}
bool fallback(int res) { return res == 0; }
#if !FMT_MSC_VER
bool fallback(internal::null<>) {
bool fallback(detail::null<>) {
std::tm* tm = std::gmtime(&time_);
if (tm) tm_ = *tm;
return tm != nullptr;
@ -386,17 +387,17 @@ inline std::tm gmtime(std::time_t time) {
return gt.tm_;
}
namespace internal {
inline std::size_t strftime(char* str, std::size_t count, const char* format,
namespace detail {
inline size_t strftime(char* str, size_t count, const char* format,
const std::tm* time) {
return std::strftime(str, count, format, time);
}
inline std::size_t strftime(wchar_t* str, std::size_t count,
const wchar_t* format, const std::tm* time) {
inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
const std::tm* time) {
return std::wcsftime(str, count, format, time);
}
} // namespace internal
} // namespace detail
template <typename Char> struct formatter<std::tm, Char> {
template <typename ParseContext>
@ -405,7 +406,7 @@ template <typename Char> struct formatter<std::tm, Char> {
if (it != ctx.end() && *it == ':') ++it;
auto end = it;
while (end != ctx.end() && *end != '}') ++end;
tm_format.reserve(internal::to_unsigned(end - it + 1));
tm_format.reserve(detail::to_unsigned(end - it + 1));
tm_format.append(it, end);
tm_format.push_back('\0');
return end;
@ -414,11 +415,10 @@ template <typename Char> struct formatter<std::tm, Char> {
template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) {
basic_memory_buffer<Char> buf;
std::size_t start = buf.size();
size_t start = buf.size();
for (;;) {
std::size_t size = buf.capacity() - start;
std::size_t count =
internal::strftime(&buf[start], size, &tm_format[0], &tm);
size_t size = buf.capacity() - start;
size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm);
if (count != 0) {
buf.resize(start + count);
break;
@ -430,7 +430,7 @@ template <typename Char> struct formatter<std::tm, Char> {
// https://github.com/fmtlib/fmt/issues/367
break;
}
const std::size_t MIN_GROWTH = 10;
const size_t MIN_GROWTH = 10;
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
}
return std::copy(buf.begin(), buf.end(), ctx.out());
@ -439,7 +439,7 @@ template <typename Char> struct formatter<std::tm, Char> {
basic_memory_buffer<Char> tm_format;
};
namespace internal {
namespace detail {
template <typename Period> FMT_CONSTEXPR const char* get_units() {
return nullptr;
}
@ -768,19 +768,25 @@ OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
return format_to(out, std::is_floating_point<Rep>::value ? fp_f : format,
val);
}
template <typename Char, typename OutputIt>
OutputIt copy_unit(string_view unit, OutputIt out, Char) {
return std::copy(unit.begin(), unit.end(), out);
}
template <typename OutputIt>
OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
// This works when wchar_t is UTF-32 because units only contain characters
// that have the same representation in UTF-16 and UTF-32.
utf8_to_utf16 u(unit);
return std::copy(u.c_str(), u.c_str() + u.size(), out);
}
template <typename Char, typename Period, typename OutputIt>
OutputIt format_duration_unit(OutputIt out) {
if (const char* unit = get_units<Period>()) {
string_view s(unit);
if (const_check(std::is_same<Char, wchar_t>())) {
utf8_to_utf16 u(s);
return std::copy(u.c_str(), u.c_str() + u.size(), out);
}
return std::copy(s.begin(), s.end(), out);
}
if (const char* unit = get_units<Period>())
return copy_unit(string_view(unit), out, Char());
const Char num_f[] = {'[', '{', '}', ']', 's', 0};
if (Period::den == 1) return format_to(out, num_f, Period::num);
if (const_check(Period::den == 1)) return format_to(out, num_f, Period::num);
const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0};
return format_to(out, num_def_f, Period::num, Period::den);
}
@ -874,9 +880,9 @@ struct chrono_formatter {
if (isnan(value)) return write_nan();
uint32_or_64_or_128_t<int> n =
to_unsigned(to_nonnegative_int(value, max_value<int>()));
int num_digits = internal::count_digits(n);
int num_digits = detail::count_digits(n);
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
out = format_decimal<char_type>(out, n, num_digits);
out = format_decimal<char_type>(out, n, num_digits).end;
}
void write_nan() { std::copy_n("nan", 3, out); }
@ -1004,14 +1010,14 @@ struct chrono_formatter {
out = format_duration_unit<char_type, Period>(out);
}
};
} // namespace internal
} // namespace detail
template <typename Rep, typename Period, typename Char>
struct formatter<std::chrono::duration<Rep, Period>, Char> {
private:
basic_format_specs<Char> specs;
int precision;
using arg_ref_type = internal::arg_ref<Char>;
using arg_ref_type = detail::arg_ref<Char>;
arg_ref_type width_ref;
arg_ref_type precision_ref;
mutable basic_string_view<Char> format_str;
@ -1032,7 +1038,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
return arg_ref_type(arg_id);
}
FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) {
FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) {
return arg_ref_type(context.next_arg_id());
}
@ -1062,17 +1068,17 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
auto begin = ctx.begin(), end = ctx.end();
if (begin == end || *begin == '}') return {begin, begin};
spec_handler handler{*this, ctx, format_str};
begin = internal::parse_align(begin, end, handler);
begin = detail::parse_align(begin, end, handler);
if (begin == end) return {begin, begin};
begin = internal::parse_width(begin, end, handler);
begin = detail::parse_width(begin, end, handler);
if (begin == end) return {begin, begin};
if (*begin == '.') {
if (std::is_floating_point<Rep>::value)
begin = internal::parse_precision(begin, end, handler);
begin = detail::parse_precision(begin, end, handler);
else
handler.on_error("precision not allowed for this argument type");
}
end = parse_chrono_format(begin, end, internal::chrono_format_checker());
end = parse_chrono_format(begin, end, detail::chrono_format_checker());
return {begin, end};
}
@ -1083,7 +1089,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
-> decltype(ctx.begin()) {
auto range = do_parse(ctx);
format_str = basic_string_view<Char>(
&*range.begin, internal::to_unsigned(range.end - range.begin));
&*range.begin, detail::to_unsigned(range.end - range.begin));
return range.end;
}
@ -1094,23 +1100,21 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
// is not specified.
basic_memory_buffer<Char> buf;
auto out = std::back_inserter(buf);
using range = internal::output_range<decltype(ctx.out()), Char>;
internal::basic_writer<range> w(range(ctx.out()));
internal::handle_dynamic_spec<internal::width_checker>(specs.width,
width_ref, ctx);
internal::handle_dynamic_spec<internal::precision_checker>(
precision, precision_ref, ctx);
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref,
ctx);
detail::handle_dynamic_spec<detail::precision_checker>(precision,
precision_ref, ctx);
if (begin == end || *begin == '}') {
out = internal::format_duration_value<Char>(out, d.count(), precision);
internal::format_duration_unit<Char, Period>(out);
out = detail::format_duration_value<Char>(out, d.count(), precision);
detail::format_duration_unit<Char, Period>(out);
} else {
internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
ctx, out, d);
f.precision = precision;
parse_chrono_format(begin, end, f);
}
w.write(buf.data(), buf.size(), specs);
return w.out();
return detail::write(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}
};

@ -198,7 +198,7 @@ struct rgb {
uint8_t b;
};
namespace internal {
namespace detail {
// color is a struct of either a rgb color or a terminal color.
struct color_type {
@ -221,7 +221,7 @@ struct color_type {
uint32_t rgb_color;
} value;
};
} // namespace internal
} // namespace detail
// Experimental text formatting support.
class text_style {
@ -298,11 +298,11 @@ class text_style {
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
return static_cast<uint8_t>(ems) != 0;
}
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color;
}
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
FMT_ASSERT(has_background(), "no background specified for this style");
return background_color;
}
@ -313,7 +313,7 @@ class text_style {
private:
FMT_CONSTEXPR text_style(bool is_foreground,
internal::color_type text_color) FMT_NOEXCEPT
detail::color_type text_color) FMT_NOEXCEPT
: set_foreground_color(),
set_background_color(),
ems() {
@ -326,23 +326,23 @@ class text_style {
}
}
friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground)
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
FMT_NOEXCEPT;
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background)
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
FMT_NOEXCEPT;
internal::color_type foreground_color;
internal::color_type background_color;
detail::color_type foreground_color;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
};
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT {
FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/true, foreground);
}
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT {
FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/false, background);
}
@ -350,21 +350,21 @@ FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
return text_style(lhs) | rhs;
}
namespace internal {
namespace detail {
template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color,
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
const char* esc) FMT_NOEXCEPT {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
bool is_background = esc == internal::data::background_color;
bool is_background = esc == detail::data::background_color;
uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background) value += 10u;
std::size_t index = 0;
size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
@ -398,7 +398,7 @@ template <typename Char> struct ansi_color_escape {
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
em_codes[3] = 9;
std::size_t index = 0;
size_t index = 0;
for (int i = 0; i < 4; ++i) {
if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b');
@ -429,14 +429,14 @@ template <typename Char> struct ansi_color_escape {
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
internal::color_type foreground) FMT_NOEXCEPT {
return ansi_color_escape<Char>(foreground, internal::data::foreground_color);
detail::color_type foreground) FMT_NOEXCEPT {
return ansi_color_escape<Char>(foreground, detail::data::foreground_color);
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
internal::color_type background) FMT_NOEXCEPT {
return ansi_color_escape<Char>(background, internal::data::background_color);
detail::color_type background) FMT_NOEXCEPT {
return ansi_color_escape<Char>(background, detail::data::background_color);
}
template <typename Char>
@ -455,11 +455,11 @@ inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
}
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
fputs(internal::data::reset_color, stream);
fputs(detail::data::reset_color, stream);
}
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
fputs(internal::data::wreset_color, stream);
fputs(detail::data::wreset_color, stream);
}
template <typename Char>
@ -476,33 +476,31 @@ void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = internal::make_emphasis<Char>(ts.get_emphasis());
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
has_style = true;
auto foreground =
internal::make_foreground_color<Char>(ts.get_foreground());
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
has_style = true;
auto background =
internal::make_background_color<Char>(ts.get_background());
auto background = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
internal::vformat_to(buf, format_str, args);
if (has_style) internal::reset_color<Char>(buf);
detail::vformat_to(buf, format_str, args);
if (has_style) detail::reset_color<Char>(buf);
}
} // namespace internal
} // namespace detail
template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<Char>> args) {
basic_memory_buffer<Char> buf;
internal::vformat_to(buf, ts, to_string_view(format), args);
detail::vformat_to(buf, ts, to_string_view(format), args);
buf.push_back(Char(0));
internal::fputs(buf.data(), f);
detail::fputs(buf.data(), f);
}
/**
@ -513,10 +511,10 @@ void vprint(std::FILE* f, const text_style& ts, const S& format,
"Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(internal::is_string<S>::value)>
FMT_ENABLE_IF(detail::is_string<S>::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) {
internal::check_format_string<Args...>(format_str);
detail::check_format_string<Args...>(format_str);
using context = buffer_context<char_t<S>>;
format_arg_store<context, Args...> as{args...};
vprint(f, ts, format_str, basic_format_args<context>(as));
@ -530,7 +528,7 @@ void print(std::FILE* f, const text_style& ts, const S& format_str,
"Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(internal::is_string<S>::value)>
FMT_ENABLE_IF(detail::is_string<S>::value)>
void print(const text_style& ts, const S& format_str, const Args&... args) {
return print(stdout, ts, format_str, args...);
}
@ -540,7 +538,7 @@ inline std::basic_string<Char> vformat(
const text_style& ts, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf;
internal::vformat_to(buf, ts, to_string_view(format_str), args);
detail::vformat_to(buf, ts, to_string_view(format_str), args);
return fmt::to_string(buf);
}
@ -560,7 +558,7 @@ template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
const Args&... args) {
return vformat(ts, to_string_view(format_str),
internal::make_args_checked<Args...>(format_str, args...));
detail::make_args_checked<Args...>(format_str, args...));
}
FMT_END_NAMESPACE

@ -13,7 +13,33 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace internal {
namespace detail {
// A compile-time string which is compiled into fast formatting code.
class compiled_string {};
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
/**
\rst
Converts a string literal *s* into a format string that will be parsed at
compile time and converted into efficient formatting code. Requires C++17
``constexpr if`` compiler support.
**Example**::
// Converts 42 into std::string using the most efficient method and no
// runtime format string processing.
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
\endrst
*/
#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string)
template <typename T, typename... Tail>
const T& first(const T& value, const Tail&...) {
return value;
}
// Part of a compiled format string. It can be either literal text or a
// replacement field.
@ -62,13 +88,15 @@ template <typename Char> struct part_counter {
if (begin != end) ++num_parts;
}
FMT_CONSTEXPR void on_arg_id() { ++num_parts; }
FMT_CONSTEXPR void on_arg_id(int) { ++num_parts; }
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; }
FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; }
FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; }
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) {
return ++num_parts, 0;
}
FMT_CONSTEXPR void on_replacement_field(const Char*) {}
FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
const Char* end) {
// Find the matching brace.
unsigned brace_counter = 0;
@ -116,25 +144,28 @@ class format_string_compiler : public error_handler {
handler_(part::make_text({begin, to_unsigned(end - begin)}));
}
FMT_CONSTEXPR void on_arg_id() {
FMT_CONSTEXPR int on_arg_id() {
part_ = part::make_arg_index(parse_context_.next_arg_id());
return 0;
}
FMT_CONSTEXPR void on_arg_id(int id) {
FMT_CONSTEXPR int on_arg_id(int id) {
parse_context_.check_arg_id(id);
part_ = part::make_arg_index(id);
return 0;
}
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char> id) {
part_ = part::make_arg_name(id);
return 0;
}
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) {
part_.arg_id_end = ptr;
handler_(part_);
}
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
const Char* end) {
auto repl = typename part::replacement();
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
@ -160,23 +191,24 @@ FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
format_string_compiler<Char, PartHandler>(format_str, handler));
}
template <typename Range, typename Context, typename Id>
template <typename OutputIt, typename Context, typename Id>
void format_arg(
basic_format_parse_context<typename Range::value_type>& parse_ctx,
basic_format_parse_context<typename Context::char_type>& parse_ctx,
Context& ctx, Id arg_id) {
ctx.advance_to(
visit_format_arg(arg_formatter<Range>(ctx, &parse_ctx), ctx.arg(arg_id)));
ctx.advance_to(visit_format_arg(
arg_formatter<OutputIt, typename Context::char_type>(ctx, &parse_ctx),
ctx.arg(arg_id)));
}
// vformat_to is defined in a subnamespace to prevent ADL.
namespace cf {
template <typename Context, typename Range, typename CompiledFormat>
auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
-> typename Context::iterator {
template <typename Context, typename OutputIt, typename CompiledFormat>
auto vformat_to(OutputIt out, CompiledFormat& cf,
basic_format_args<Context> args) -> typename Context::iterator {
using char_type = typename Context::char_type;
basic_format_parse_context<char_type> parse_ctx(
to_string_view(cf.format_str_));
Context ctx(out.begin(), args);
Context ctx(out, args);
const auto& parts = cf.parts();
for (auto part_it = std::begin(parts); part_it != std::end(parts);
@ -197,12 +229,12 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
case format_part_t::kind::arg_index:
advance_to(parse_ctx, part.arg_id_end);
internal::format_arg<Range>(parse_ctx, ctx, value.arg_index);
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
break;
case format_part_t::kind::arg_name:
advance_to(parse_ctx, part.arg_id_end);
internal::format_arg<Range>(parse_ctx, ctx, value.str);
detail::format_arg<OutputIt>(parse_ctx, ctx, value.str);
break;
case format_part_t::kind::replacement: {
@ -226,7 +258,9 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
advance_to(parse_ctx, part.arg_id_end);
ctx.advance_to(
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
visit_format_arg(arg_formatter<OutputIt, typename Context::char_type>(
ctx, nullptr, &specs),
arg));
break;
}
}
@ -240,7 +274,7 @@ struct basic_compiled_format {};
template <typename S, typename = void>
struct compiled_format_base : basic_compiled_format {
using char_type = char_t<S>;
using parts_container = std::vector<internal::format_part<char_type>>;
using parts_container = std::vector<detail::format_part<char_type>>;
parts_container compiled_parts;
@ -305,7 +339,7 @@ struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
const parts_container& parts() const {
static FMT_CONSTEXPR_DECL const auto compiled_parts =
compile_to_parts<char_type, num_format_parts>(
internal::to_string_view(S()));
detail::to_string_view(S()));
return compiled_parts.data;
}
};
@ -318,8 +352,8 @@ class compiled_format : private compiled_format_base<S> {
private:
basic_string_view<char_type> format_str_;
template <typename Context, typename Range, typename CompiledFormat>
friend auto cf::vformat_to(Range out, CompiledFormat& cf,
template <typename Context, typename OutputIt, typename CompiledFormat>
friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf,
basic_format_args<Context> args) ->
typename Context::iterator;
@ -359,8 +393,7 @@ template <typename Char> struct text {
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const {
// TODO: reserve
return copy_str<Char>(data.begin(), data.end(), out);
return write<Char>(out, data);
}
};
@ -373,33 +406,6 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
return {{&s[pos], size}};
}
template <typename Char, typename OutputIt, typename T,
std::enable_if_t<std::is_integral_v<T>, int> = 0>
OutputIt format_default(OutputIt out, T value) {
// TODO: reserve
format_int fi(value);
return std::copy(fi.data(), fi.data() + fi.size(), out);
}
template <typename Char, typename OutputIt>
OutputIt format_default(OutputIt out, double value) {
writer w(out);
w.write(value);
return w.out();
}
template <typename Char, typename OutputIt>
OutputIt format_default(OutputIt out, Char value) {
*out++ = value;
return out;
}
template <typename Char, typename OutputIt>
OutputIt format_default(OutputIt out, const Char* value) {
auto length = std::char_traits<Char>::length(value);
return copy_str<Char>(value, value + length, out);
}
// A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field {
using char_type = Char;
@ -408,13 +414,30 @@ template <typename Char, typename T, int N> struct field {
OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
return format_default<Char>(out, arg);
return write<Char>(out, arg);
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field {
using char_type = Char;
mutable formatter<T, Char> fmt;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
basic_format_context<OutputIt, Char> ctx(out, {});
return fmt.format(arg, ctx);
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
template <typename L, typename R> struct concat {
L lhs;
R rhs;
@ -450,7 +473,8 @@ constexpr auto compile_format_string(S format_str);
template <typename Args, size_t POS, int ID, typename T, typename S>
constexpr auto parse_tail(T head, S format_str) {
if constexpr (POS != to_string_view(format_str).size()) {
if constexpr (POS !=
basic_string_view<typename S::char_type>(format_str).size()) {
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
unknown_format>())
@ -462,6 +486,21 @@ constexpr auto parse_tail(T head, S format_str) {
}
}
template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt;
size_t end;
};
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos) {
str.remove_prefix(pos);
auto ctx = basic_format_parse_context<Char>(str);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + (end - str.data()) + 1};
}
// Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S>
@ -475,12 +514,13 @@ constexpr auto compile_format_string(S format_str) {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}') {
using type = get_type<ID, Args>;
if constexpr (std::is_same<type, int>::value) {
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
format_str);
} else {
return unknown_format();
}
} else if constexpr (str[POS + 1] == ':') {
using type = get_type<ID, Args>;
constexpr auto result = parse_specs<type>(str, POS + 2);
return parse_tail<Args, result.end, ID + 1>(
spec_field<char_type, type, ID>{result.fmt}, format_str);
} else {
return unknown_format();
}
@ -494,100 +534,130 @@ constexpr auto compile_format_string(S format_str) {
format_str);
}
}
#endif // __cpp_if_constexpr
} // namespace internal
#if FMT_USE_CONSTEXPR
# ifdef __cpp_if_constexpr
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value)>
FMT_ENABLE_IF(is_compile_string<S>::value ||
detail::is_compiled_string<S>::value)>
constexpr auto compile(S format_str) {
constexpr basic_string_view<typename S::char_type> str = format_str;
if constexpr (str.size() == 0) {
return internal::make_text(str, 0, 0);
return detail::make_text(str, 0, 0);
} else {
constexpr auto result =
internal::compile_format_string<internal::type_list<Args...>, 0, 0>(
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
internal::unknown_format>()) {
return internal::compiled_format<S, Args...>(to_string_view(format_str));
detail::unknown_format>()) {
return detail::compiled_format<S, Args...>(to_string_view(format_str));
} else {
return result;
}
}
}
#else
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value)>
constexpr auto compile(S format_str) -> detail::compiled_format<S, Args...> {
return detail::compiled_format<S, Args...>(to_string_view(format_str));
}
#endif // __cpp_if_constexpr
// Compiles the format string which must be a string literal.
template <typename... Args, typename Char, size_t N>
auto compile(const Char (&format_str)[N])
-> detail::compiled_format<const Char*, Args...> {
return detail::compiled_format<const Char*, Args...>(
basic_string_view<Char>(format_str, N - 1));
}
} // namespace detail
// DEPRECATED! use FMT_COMPILE instead.
template <typename... Args>
FMT_DEPRECATED auto compile(const Args&... args)
-> decltype(detail::compile(args...)) {
return detail::compile(args...);
}
#if FMT_USE_CONSTEXPR
# ifdef __cpp_if_constexpr
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)>
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
basic_memory_buffer<Char> buffer;
cf.format(std::back_inserter(buffer), args...);
detail::buffer<Char>& base = buffer;
cf.format(std::back_inserter(base), args...);
return to_string(buffer);
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)>
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
return cf.format(out, args...);
}
# else
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value)>
constexpr auto compile(S format_str) -> internal::compiled_format<S, Args...> {
return internal::compiled_format<S, Args...>(to_string_view(format_str));
}
# endif // __cpp_if_constexpr
#endif // FMT_USE_CONSTEXPR
// Compiles the format string which must be a string literal.
template <typename... Args, typename Char, size_t N>
auto compile(const Char (&format_str)[N])
-> internal::compiled_format<const Char*, Args...> {
return internal::compiled_format<const Char*, Args...>(
basic_string_view<Char>(format_str, N - 1));
}
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
CompiledFormat>::value)>
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer<Char> buffer;
using range = buffer_range<Char>;
using context = buffer_context<Char>;
internal::cf::vformat_to<context>(range(buffer), cf,
detail::buffer<Char>& base = buffer;
detail::cf::vformat_to<context>(std::back_inserter(base), cf,
make_format_args<context>(args...));
return to_string(buffer);
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
constexpr basic_string_view<typename S::char_type> str = S();
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
return fmt::to_string(detail::first(args...));
constexpr auto compiled = detail::compile<Args...>(S());
return format(compiled, std::forward<Args>(args)...);
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
using char_type = typename CompiledFormat::char_type;
using range = internal::output_range<OutputIt, char_type>;
using context = format_context_t<OutputIt, char_type>;
return internal::cf::vformat_to<context>(range(out), cf,
return detail::cf::vformat_to<context>(out, cf,
make_format_args<context>(args...));
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)>
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
OutputIt format_to(OutputIt out, const S&, const Args&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
return format_to(out, compiled, args...);
}
template <
typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&& std::is_base_of<
detail::basic_compiled_format, CompiledFormat>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const CompiledFormat& cf,
const Args&... args) {
auto it =
format_to(internal::truncating_iterator<OutputIt>(out, n), cf, args...);
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
return {it.base(), it.count()};
}
template <typename CompiledFormat, typename... Args>
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
return format_to(internal::counting_iterator(), cf, args...).count();
size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
return format_to(detail::counting_iterator(), cf, args...).count();
}
FMT_END_NAMESPACE

File diff suppressed because it is too large Load Diff

@ -15,6 +15,7 @@
#include <cstdarg>
#include <cstring> // for std::memmove
#include <cwchar>
#include <exception>
#include "format.h"
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
@ -22,8 +23,16 @@
#endif
#ifdef _WIN32
# include <io.h>
# if !defined(NOMINMAX) && !defined(WIN32_LEAN_AND_MEAN)
# define NOMINMAX
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# undef WIN32_LEAN_AND_MEAN
# undef NOMINMAX
# else
# include <windows.h>
# endif
# include <io.h>
#endif
#ifdef _MSC_VER
@ -33,15 +42,19 @@
// Dummy implementations of strerror_r and strerror_s called if corresponding
// system functions are not available.
inline fmt::internal::null<> strerror_r(int, char*, ...) { return {}; }
inline fmt::internal::null<> strerror_s(char*, std::size_t, ...) { return {}; }
inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; }
inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; }
FMT_BEGIN_NAMESPACE
namespace internal {
namespace detail {
FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
print(stderr, "{}:{}: assertion failed: {}", file, line, message);
std::abort();
// Use unchecked std::fprintf to avoid triggering another assertion when
// writing to stderr fails
std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
// Chosen instead of std::abort to satisfy Clang in CUDA mode during device
// code pass.
std::terminate();
}
#ifndef _MSC_VER
@ -67,14 +80,14 @@ inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) {
// other - failure
// Buffer should be at least of size 1.
FMT_FUNC int safe_strerror(int error_code, char*& buffer,
std::size_t buffer_size) FMT_NOEXCEPT {
size_t buffer_size) FMT_NOEXCEPT {
FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer");
class dispatcher {
private:
int error_code_;
char*& buffer_;
std::size_t buffer_size_;
size_t buffer_size_;
// A noop assignment operator to avoid bogus warnings.
void operator=(const dispatcher&) {}
@ -97,7 +110,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer,
// Handle the case when strerror_r is not available.
FMT_MAYBE_UNUSED
int handle(internal::null<>) {
int handle(detail::null<>) {
return fallback(strerror_s(buffer_, buffer_size_, error_code_));
}
@ -111,7 +124,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer,
#if !FMT_MSC_VER
// Fallback to strerror if strerror_r and strerror_s are not available.
int fallback(internal::null<>) {
int fallback(detail::null<>) {
errno = 0;
buffer_ = strerror(error_code_);
return errno;
@ -119,7 +132,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer,
#endif
public:
dispatcher(int err_code, char*& buf, std::size_t buf_size)
dispatcher(int err_code, char*& buf, size_t buf_size)
: error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); }
@ -127,7 +140,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer,
return dispatcher(error_code, buffer, buffer_size).run();
}
FMT_FUNC void format_error_code(internal::buffer<char>& out, int error_code,
FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT {
// Report error code making sure that the output fits into
// inline_buffer_size to avoid dynamic memory allocation and potential
@ -136,20 +149,17 @@ FMT_FUNC void format_error_code(internal::buffer<char>& out, int error_code,
static const char SEP[] = ": ";
static const char ERROR_STR[] = "error ";
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
auto abs_value = static_cast<uint32_or_64_or_128_t<int>>(error_code);
if (internal::is_negative(error_code)) {
if (detail::is_negative(error_code)) {
abs_value = 0 - abs_value;
++error_code_size;
}
error_code_size += internal::to_unsigned(internal::count_digits(abs_value));
internal::writer w(out);
if (message.size() <= inline_buffer_size - error_code_size) {
w.write(message);
w.write(SEP);
}
w.write(ERROR_STR);
w.write(error_code);
error_code_size += detail::to_unsigned(detail::count_digits(abs_value));
auto it = std::back_inserter(out);
if (message.size() <= inline_buffer_size - error_code_size)
format_to(it, "{}{}", message, SEP);
format_to(it, "{}{}", ERROR_STR, error_code);
assert(out.size() <= inline_buffer_size);
}
@ -168,10 +178,10 @@ FMT_FUNC void fwrite_fully(const void* ptr, size_t size, size_t count,
size_t written = std::fwrite(ptr, size, count, stream);
if (written < count) FMT_THROW(system_error(errno, "cannot write to file"));
}
} // namespace internal
} // namespace detail
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
namespace internal {
namespace detail {
template <typename Locale>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
@ -194,18 +204,16 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) {
return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
.decimal_point();
}
} // namespace internal
} // namespace detail
#else
template <typename Char>
FMT_FUNC std::string internal::grouping_impl(locale_ref) {
FMT_FUNC std::string detail::grouping_impl(locale_ref) {
return "\03";
}
template <typename Char>
FMT_FUNC Char internal::thousands_sep_impl(locale_ref) {
template <typename Char> FMT_FUNC Char detail::thousands_sep_impl(locale_ref) {
return FMT_STATIC_THOUSANDS_SEPARATOR;
}
template <typename Char>
FMT_FUNC Char internal::decimal_point_impl(locale_ref) {
template <typename Char> FMT_FUNC Char detail::decimal_point_impl(locale_ref) {
return '.';
}
#endif
@ -222,9 +230,9 @@ FMT_FUNC void system_error::init(int err_code, string_view format_str,
base = std::runtime_error(to_string(buffer));
}
namespace internal {
namespace detail {
template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) {
template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
// fallback_uintptr is always stored in little endian.
int i = static_cast<int>(sizeof(void*)) - 1;
while (i > 0 && n.value[i] == 0) --i;
@ -233,12 +241,27 @@ template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) {
}
template <typename T>
const char basic_data<T>::digits[] =
"0001020304050607080910111213141516171819"
"2021222324252627282930313233343536373839"
"4041424344454647484950515253545556575859"
"6061626364656667686970717273747576777879"
"8081828384858687888990919293949596979899";
const typename basic_data<T>::digit_pair basic_data<T>::digits[] = {
{'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'},
{'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'},
{'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'},
{'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'},
{'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'},
{'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
{'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'},
{'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'},
{'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'},
{'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'},
{'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'},
{'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
{'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'},
{'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'},
{'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'},
{'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'},
{'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'},
{'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
{'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'},
{'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
template <typename T>
const char basic_data<T>::hex_digits[] = "0123456789abcdef";
@ -317,6 +340,10 @@ const char basic_data<T>::background_color[] = "\x1b[48;2;";
template <typename T> const char basic_data<T>::reset_color[] = "\x1b[0m";
template <typename T> const wchar_t basic_data<T>::wreset_color[] = L"\x1b[0m";
template <typename T> const char basic_data<T>::signs[] = {0, '-', '+', ' '};
template <typename T>
const char basic_data<T>::left_padding_shifts[] = {31, 31, 0, 1, 0};
template <typename T>
const char basic_data<T>::right_padding_shifts[] = {0, 31, 0, 1, 0};
template <typename T> struct bits {
static FMT_CONSTEXPR_DECL const int value =
@ -576,9 +603,10 @@ class bigint {
void operator=(const bigint&) = delete;
void assign(const bigint& other) {
bigits_.resize(other.bigits_.size());
auto size = other.bigits_.size();
bigits_.resize(size);
auto data = other.bigits_.data();
std::copy(data, data + other.bigits_.size(), bigits_.data());
std::copy(data, data + size, make_checked(bigits_.data(), size));
exp_ = other.exp_;
}
@ -594,7 +622,7 @@ class bigint {
int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
bigint& operator<<=(int shift) {
FMT_NOINLINE bigint& operator<<=(int shift) {
assert(shift >= 0);
exp_ += shift / bigit_bits;
shift %= bigit_bits;
@ -1125,7 +1153,7 @@ int snprintf_float(T value, int precision, float_specs specs,
precision = (precision >= 0 ? precision : 6) - 1;
// Build the format string.
enum { max_format_size = 7 }; // Ths longest format is "%#.*Le".
enum { max_format_size = 7 }; // The longest format is "%#.*Le".
char format[max_format_size];
char* format_ptr = format;
*format_ptr++ = '%';
@ -1145,13 +1173,13 @@ int snprintf_float(T value, int precision, float_specs specs,
for (;;) {
auto begin = buf.data() + offset;
auto capacity = buf.capacity() - offset;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
#ifdef FMT_FUZZ
if (precision > 100000)
throw std::runtime_error(
"fuzz mode - avoid large allocation inside snprintf");
#endif
// Suppress the warning about a nonliteral format string.
// Cannot use auto becase of a bug in MinGW (#1532).
// Cannot use auto because of a bug in MinGW (#1532).
int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
int result = precision >= 0
? snprintf_ptr(begin, capacity, format, precision, value)
@ -1268,14 +1296,14 @@ FMT_FUNC const char* utf8_decode(const char* buf, uint32_t* c, int* e) {
return next;
}
} // namespace internal
} // namespace detail
template <> struct formatter<internal::bigint> {
template <> struct formatter<detail::bigint> {
format_parse_context::iterator parse(format_parse_context& ctx) {
return ctx.begin();
}
format_context::iterator format(const internal::bigint& n,
format_context::iterator format(const detail::bigint& n,
format_context& ctx) {
auto out = ctx.out();
bool first = true;
@ -1289,12 +1317,12 @@ template <> struct formatter<internal::bigint> {
out = format_to(out, "{:08x}", value);
}
if (n.exp_ > 0)
out = format_to(out, "p{}", n.exp_ * internal::bigint::bigit_bits);
out = format_to(out, "p{}", n.exp_ * detail::bigint::bigit_bits);
return out;
}
};
FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
auto transcode = [this](const char* p) {
auto cp = uint32_t();
auto error = 0;
@ -1325,7 +1353,7 @@ FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
buffer_.push_back(0);
}
FMT_FUNC void format_system_error(internal::buffer<char>& out, int error_code,
FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT {
FMT_TRY {
memory_buffer buf;
@ -1333,12 +1361,9 @@ FMT_FUNC void format_system_error(internal::buffer<char>& out, int error_code,
for (;;) {
char* system_message = &buf[0];
int result =
internal::safe_strerror(error_code, system_message, buf.size());
detail::safe_strerror(error_code, system_message, buf.size());
if (result == 0) {
internal::writer w(out);
w.write(message);
w.write(": ");
w.write(system_message);
format_to(std::back_inserter(out), "{}: {}", message, system_message);
return;
}
if (result != ERANGE)
@ -1350,7 +1375,7 @@ FMT_FUNC void format_system_error(internal::buffer<char>& out, int error_code,
format_error_code(out, error_code, message);
}
FMT_FUNC void internal::error_handler::on_error(const char* message) {
FMT_FUNC void detail::error_handler::on_error(const char* message) {
FMT_THROW(format_error(message));
}
@ -1359,14 +1384,39 @@ FMT_FUNC void report_system_error(int error_code,
report_error(format_system_error, error_code, message);
}
struct stringifier {
template <typename T> FMT_INLINE std::string operator()(T value) const {
return to_string(value);
}
std::string operator()(basic_format_arg<format_context>::handle h) const {
memory_buffer buf;
detail::buffer<char>& base = buf;
format_parse_context parse_ctx({});
format_context format_ctx(std::back_inserter(base), {}, {});
h.format(parse_ctx, format_ctx);
return to_string(buf);
}
};
FMT_FUNC std::string detail::vformat(string_view format_str, format_args args) {
if (format_str.size() == 2 && equal2(format_str.data(), "{}")) {
auto arg = args.get(0);
if (!arg) error_handler().on_error("argument not found");
return visit_format_arg(stringifier(), arg);
}
memory_buffer buffer;
detail::vformat_to(buffer, format_str, args);
return to_string(buffer);
}
FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
memory_buffer buffer;
internal::vformat_to(buffer, format_str,
detail::vformat_to(buffer, format_str,
basic_format_args<buffer_context<char>>(args));
#ifdef _WIN32
auto fd = _fileno(f);
if (_isatty(fd)) {
internal::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size()));
detail::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size()));
auto written = DWORD();
if (!WriteConsoleW(reinterpret_cast<HANDLE>(_get_osfhandle(fd)),
u16.c_str(), static_cast<DWORD>(u16.size()), &written,
@ -1376,15 +1426,15 @@ FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
return;
}
#endif
internal::fwrite_fully(buffer.data(), 1, buffer.size(), f);
detail::fwrite_fully(buffer.data(), 1, buffer.size(), f);
}
#ifdef _WIN32
// Print assuming legacy (non-Unicode) encoding.
FMT_FUNC void internal::vprint_mojibake(std::FILE* f, string_view format_str,
FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str,
format_args args) {
memory_buffer buffer;
internal::vformat_to(buffer, format_str,
detail::vformat_to(buffer, format_str,
basic_format_args<buffer_context<char>>(args));
fwrite_fully(buffer.data(), 1, buffer.size(), f);
}

File diff suppressed because it is too large Load Diff

@ -14,15 +14,15 @@
FMT_BEGIN_NAMESPACE
namespace internal {
namespace detail {
template <typename Char>
typename buffer_context<Char>::iterator vformat_to(
const std::locale& loc, buffer<Char>& buf,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
using range = buffer_range<Char>;
return vformat_to<arg_formatter<range>>(buf, to_string_view(format_str), args,
internal::locale_ref(loc));
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
return vformat_to<af>(std::back_inserter(buf), to_string_view(format_str),
args, detail::locale_ref(loc));
}
template <typename Char>
@ -30,43 +30,43 @@ std::basic_string<Char> vformat(
const std::locale& loc, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
internal::vformat_to(loc, buffer, format_str, args);
detail::vformat_to(loc, buffer, format_str, args);
return fmt::to_string(buffer);
}
} // namespace internal
} // namespace detail
template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vformat(
const std::locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
return internal::vformat(loc, to_string_view(format_str), args);
return detail::vformat(loc, to_string_view(format_str), args);
}
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const std::locale& loc,
const S& format_str, Args&&... args) {
return internal::vformat(
return detail::vformat(
loc, to_string_view(format_str),
internal::make_args_checked<Args...>(format_str, args...));
detail::make_args_checked<Args...>(format_str, args...));
}
template <typename S, typename OutputIt, typename... Args,
typename Char = enable_if_t<
internal::is_output_iterator<OutputIt>::value, char_t<S>>>
detail::is_output_iterator<OutputIt>::value, char_t<S>>>
inline OutputIt vformat_to(
OutputIt out, const std::locale& loc, const S& format_str,
format_args_t<type_identity_t<OutputIt>, Char> args) {
using range = internal::output_range<OutputIt, Char>;
return vformat_to<arg_formatter<range>>(
range(out), to_string_view(format_str), args, internal::locale_ref(loc));
using af = detail::arg_formatter<OutputIt, Char>;
return vformat_to<af>(out, to_string_view(format_str), args,
detail::locale_ref(loc));
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value&&
internal::is_string<S>::value)>
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
detail::is_string<S>::value)>
inline OutputIt format_to(OutputIt out, const std::locale& loc,
const S& format_str, Args&&... args) {
internal::check_format_string<Args...>(format_str);
detail::check_format_string<Args...>(format_str);
using context = format_context_t<OutputIt, char_t<S>>;
format_arg_store<context, Args...> as{args...};
return vformat_to(out, loc, to_string_view(format_str),

@ -50,7 +50,7 @@
#ifdef FMT_SYSTEM
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
#else
# define FMT_SYSTEM(call) call
# define FMT_SYSTEM(call) ::call
# ifdef _WIN32
// Fix warnings about deprecated symbols.
# define FMT_POSIX_CALL(call) ::_##call
@ -133,7 +133,7 @@ class error_code {
};
#ifdef _WIN32
namespace internal {
namespace detail {
// A converter from UTF-16 to UTF-8.
// It is only provided for Windows since other systems support UTF-8 natively.
class utf16_to_utf8 {
@ -156,7 +156,7 @@ class utf16_to_utf8 {
FMT_API void format_windows_error(buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT;
} // namespace internal
} // namespace detail
/** A Windows error. */
class windows_error : public system_error {
@ -277,7 +277,8 @@ class file {
enum {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist.
};
// Constructs a file object which doesn't represent any file.
@ -313,10 +314,10 @@ class file {
FMT_API long long size() const;
// Attempts to read count bytes from the file into the specified buffer.
FMT_API std::size_t read(void* buffer, std::size_t count);
FMT_API size_t read(void* buffer, size_t count);
// Attempts to write count bytes from the specified buffer to the file.
FMT_API std::size_t write(const void* buffer, std::size_t count);
FMT_API size_t write(const void* buffer, size_t count);
// Duplicates a file descriptor with the dup function and returns
// the duplicate as a file object.
@ -341,6 +342,63 @@ class file {
// Returns the memory page size.
long getpagesize();
class direct_buffered_file;
template <typename S, typename... Args>
void print(direct_buffered_file& f, const S& format_str,
const Args&... args);
// A buffered file with a direct buffer access and no synchronization.
class direct_buffered_file {
private:
file file_;
enum { buffer_size = 4096 };
char buffer_[buffer_size];
int pos_;
void flush() {
if (pos_ == 0) return;
file_.write(buffer_, pos_);
pos_ = 0;
}
int free_capacity() const { return buffer_size - pos_; }
public:
direct_buffered_file(cstring_view path, int oflag)
: file_(path, oflag), pos_(0) {}
~direct_buffered_file() {
flush();
}
void close() {
flush();
file_.close();
}
template <typename S, typename... Args>
friend void print(direct_buffered_file& f, const S& format_str,
const Args&... args) {
// We could avoid double buffering.
auto buf = fmt::memory_buffer();
fmt::format_to(std::back_inserter(buf), format_str, args...);
auto remaining_pos = 0;
auto remaining_size = buf.size();
while (remaining_size > detail::to_unsigned(f.free_capacity())) {
auto size = f.free_capacity();
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size);
f.pos_ += size;
f.flush();
remaining_pos += size;
remaining_size -= size;
}
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size);
f.pos_ += static_cast<int>(remaining_size);
}
};
#endif // FMT_USE_FCNTL
#ifdef FMT_LOCALE

@ -14,10 +14,10 @@
FMT_BEGIN_NAMESPACE
template <typename CHar> class basic_printf_parse_context;
template <typename Char> class basic_printf_parse_context;
template <typename OutputIt, typename Char> class basic_printf_context;
namespace internal {
namespace detail {
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
private:
@ -80,7 +80,7 @@ template <typename T, typename Char> class is_streamable {
// Write the content of buf to os.
template <typename Char>
void write(std::basic_ostream<Char>& os, buffer<Char>& buf) {
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
unsigned_streamsize size = buf.size();
@ -101,8 +101,8 @@ void format_value(buffer<Char>& buf, const T& value,
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
if (loc) output.imbue(loc.get<std::locale>());
#endif
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
buf.resize(buf.size());
}
@ -110,7 +110,8 @@ void format_value(buffer<Char>& buf, const T& value,
template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: private formatter<basic_string_view<Char>, Char> {
auto parse(basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
return formatter<basic_string_view<Char>, Char>::parse(ctx);
}
template <typename ParseCtx,
@ -136,14 +137,14 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
return std::copy(buffer.begin(), buffer.end(), ctx.out());
}
};
} // namespace internal
} // namespace detail
template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
internal::vformat_to(buffer, format_str, args);
internal::write(os, buffer);
detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer);
}
/**
@ -156,10 +157,10 @@ void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
\endrst
*/
template <typename S, typename... Args,
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
vprint(os, to_string_view(format_str),
internal::make_args_checked<Args...>(format_str, args...));
detail::make_args_checked<Args...>(format_str, args...));
}
FMT_END_NAMESPACE

@ -14,7 +14,7 @@
#include "ostream.h"
FMT_BEGIN_NAMESPACE
namespace internal {
namespace detail {
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
@ -90,11 +90,11 @@ template <typename T, typename Context> class arg_converter {
if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings.
if (is_signed) {
arg_ = internal::make_arg<Context>(
arg_ = detail::make_arg<Context>(
static_cast<int>(static_cast<target_type>(value)));
} else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
arg_ = internal::make_arg<Context>(
arg_ = detail::make_arg<Context>(
static_cast<unsigned>(static_cast<unsigned_type>(value)));
}
} else {
@ -102,9 +102,9 @@ template <typename T, typename Context> class arg_converter {
// glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB.
arg_ = internal::make_arg<Context>(static_cast<long long>(value));
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
} else {
arg_ = internal::make_arg<Context>(
arg_ = detail::make_arg<Context>(
static_cast<typename make_unsigned_or_bool<U>::type>(value));
}
}
@ -133,7 +133,7 @@ template <typename Context> class char_converter {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) {
arg_ = internal::make_arg<Context>(
arg_ = detail::make_arg<Context>(
static_cast<typename Context::char_type>(value));
}
@ -141,6 +141,13 @@ template <typename Context> class char_converter {
void operator()(T) {} // No conversion needed for non-integral types.
};
// An argument visitor that return a pointer to a C string if argument is a
// string or null otherwise.
template <typename Char> struct get_cstring {
template <typename T> const Char* operator()(T) { return nullptr; }
const Char* operator()(const Char* s) { return s; }
};
// Checks if an argument is a valid printf width specifier and sets
// left alignment if it is negative.
template <typename Char> class printf_width_handler {
@ -155,7 +162,7 @@ template <typename Char> class printf_width_handler {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
unsigned operator()(T value) {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (internal::is_negative(value)) {
if (detail::is_negative(value)) {
specs_.align = align::left;
width = 0 - width;
}
@ -172,22 +179,20 @@ template <typename Char> class printf_width_handler {
};
template <typename Char, typename Context>
void printf(buffer<Char>& buf, basic_string_view<Char> format,
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
Context(std::back_inserter(buf), format, args).format();
}
} // namespace detail
template <typename OutputIt, typename Char, typename Context>
internal::truncating_iterator<OutputIt> printf(
internal::truncating_iterator<OutputIt> it, basic_string_view<Char> format,
// For printing into memory_buffer.
template <typename Char, typename Context>
FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
basic_string_view<Char> format,
basic_format_args<Context> args) {
return Context(it, format, args).format();
return detail::vprintf(buf, format, args);
}
} // namespace internal
using internal::printf; // For printing into memory_buffer.
template <typename Range> class printf_arg_formatter;
using detail::vprintf;
template <typename Char>
class basic_printf_parse_context : public basic_format_parse_context<Char> {
@ -200,15 +205,15 @@ template <typename OutputIt, typename Char> class basic_printf_context;
The ``printf`` argument formatter.
\endrst
*/
template <typename Range>
class printf_arg_formatter : public internal::arg_formatter_base<Range> {
template <typename OutputIt, typename Char>
class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
public:
using iterator = typename Range::iterator;
using iterator = OutputIt;
private:
using char_type = typename Range::value_type;
using base = internal::arg_formatter_base<Range>;
using context_type = basic_printf_context<iterator, char_type>;
using char_type = Char;
using base = detail::arg_formatter_base<OutputIt, Char>;
using context_type = basic_printf_context<OutputIt, Char>;
context_type& context_;
@ -233,9 +238,9 @@ class printf_arg_formatter : public internal::arg_formatter_base<Range> {
\endrst
*/
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
: base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {}
: base(iter, &specs, detail::locale_ref()), context_(ctx) {}
template <typename T, FMT_ENABLE_IF(fmt::internal::is_integral<T>::value)>
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)>
iterator operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and char_type so
// use std::is_same instead.
@ -250,6 +255,10 @@ class printf_arg_formatter : public internal::arg_formatter_base<Range> {
return (*this)(static_cast<int>(value));
fmt_specs.sign = sign::none;
fmt_specs.alt = false;
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right;
return base::operator()(value);
} else {
@ -316,12 +325,14 @@ template <typename T> struct printf_formatter {
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
internal::format_value(internal::get_container(ctx.out()), value);
detail::format_value(detail::get_container(ctx.out()), value);
return ctx.out();
}
};
/** This template formats data and writes the output to a writer. */
/**
This template formats data and writes the output through an output iterator.
*/
template <typename OutputIt, typename Char> class basic_printf_context {
public:
/** The character type for the output. */
@ -351,9 +362,8 @@ template <typename OutputIt, typename Char> class basic_printf_context {
public:
/**
\rst
Constructs a ``printf_context`` object. References to the arguments and
the writer are stored in the context object so make sure they have
appropriate lifetimes.
Constructs a ``printf_context`` object. References to the arguments are
stored in the context object so make sure they have appropriate lifetimes.
\endrst
*/
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
@ -363,7 +373,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
OutputIt out() { return out_; }
void advance_to(OutputIt it) { out_ = it; }
internal::locale_ref locale() { return {}; }
detail::locale_ref locale() { return {}; }
format_arg arg(int id) const { return args_.get(id); }
@ -374,7 +384,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
}
/** Formats stored arguments and writes the output to the range. */
template <typename ArgFormatter = printf_arg_formatter<buffer_range<Char>>>
template <typename ArgFormatter = printf_arg_formatter<OutputIt, Char>>
OutputIt format();
};
@ -394,7 +404,9 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
specs.fill[0] = '0';
break;
case ' ':
if (specs.sign != sign::plus) {
specs.sign = sign::space;
}
break;
case '#':
specs.alt = true;
@ -412,7 +424,7 @@ basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
arg_index = parse_ctx_.next_arg_id();
else
parse_ctx_.check_arg_id(--arg_index);
return internal::get_arg(*this, arg_index);
return detail::get_arg(*this, arg_index);
}
template <typename OutputIt, typename Char>
@ -424,7 +436,7 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
if (c >= '0' && c <= '9') {
// Parse an argument index (if followed by '$') or a width possibly
// preceded with '0' flag(s).
internal::error_handler eh;
detail::error_handler eh;
int value = parse_nonnegative_int(it, end, eh);
if (it != end && *it == '$') { // value is an argument index
++it;
@ -443,12 +455,12 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
// Parse width.
if (it != end) {
if (*it >= '0' && *it <= '9') {
internal::error_handler eh;
detail::error_handler eh;
specs.width = parse_nonnegative_int(it, end, eh);
} else if (*it == '*') {
++it;
specs.width = static_cast<int>(visit_format_arg(
internal::printf_width_handler<char_type>(specs), get_arg()));
detail::printf_width_handler<char_type>(specs), get_arg()));
}
}
return arg_index;
@ -476,38 +488,52 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs);
if (arg_index == 0) on_error("argument index out of range");
if (arg_index == 0) on_error("argument not found");
// Parse precision.
if (it != end && *it == '.') {
++it;
c = it != end ? *it : 0;
if ('0' <= c && c <= '9') {
internal::error_handler eh;
detail::error_handler eh;
specs.precision = parse_nonnegative_int(it, end, eh);
} else if (c == '*') {
++it;
specs.precision = static_cast<int>(
visit_format_arg(internal::printf_precision_handler(), get_arg()));
visit_format_arg(detail::printf_precision_handler(), get_arg()));
} else {
specs.precision = 0;
}
}
format_arg arg = get_arg(arg_index);
if (specs.alt && visit_format_arg(internal::is_zero_int(), arg))
// For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral())
specs.fill[0] =
' '; // Ignore '0' flag for non-numeric types or if '-' present.
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char());
arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>(
str,
detail::to_unsigned(nul != str_end ? nul - str : specs.precision)));
}
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
specs.alt = false;
if (specs.fill[0] == '0') {
if (arg.is_arithmetic())
if (arg.is_arithmetic() && specs.align != align::left)
specs.align = align::numeric;
else
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types.
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-'
// flag is also present.
}
// Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0;
char_type t = it != end ? *it : 0;
using internal::convert_arg;
using detail::convert_arg;
switch (c) {
case 'h':
if (t == 'h') {
@ -531,7 +557,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
convert_arg<intmax_t>(arg, t);
break;
case 'z':
convert_arg<std::size_t>(arg, t);
convert_arg<size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
@ -556,7 +582,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
specs.type = 'd';
break;
case 'c':
visit_format_arg(internal::char_converter<basic_printf_context>(arg),
visit_format_arg(detail::char_converter<basic_printf_context>(arg),
arg);
break;
}
@ -565,15 +591,14 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
start = it;
// Format argument.
visit_format_arg(ArgFormatter(out, specs, *this), arg);
out = visit_format_arg(ArgFormatter(out, specs, *this), arg);
}
return std::copy(start, it, out);
}
template <typename Char>
using basic_printf_context_t =
basic_printf_context<std::back_insert_iterator<internal::buffer<Char>>,
Char>;
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>;
@ -610,7 +635,7 @@ inline std::basic_string<Char> vsprintf(
const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
printf(buffer, to_string_view(format), args);
vprintf(buffer, to_string_view(format), args);
return to_string(buffer);
}
@ -624,7 +649,7 @@ inline std::basic_string<Char> vsprintf(
\endrst
*/
template <typename S, typename... Args,
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
using context = basic_printf_context_t<Char>;
return vsprintf(to_string_view(format), make_format_args<context>(args...));
@ -635,8 +660,8 @@ inline int vfprintf(
std::FILE* f, const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
printf(buffer, to_string_view(format), args);
std::size_t size = buffer.size();
vprintf(buffer, to_string_view(format), args);
size_t size = buffer.size();
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
? -1
: static_cast<int>(size);
@ -652,7 +677,7 @@ inline int vfprintf(
\endrst
*/
template <typename S, typename... Args,
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
using context = basic_printf_context_t<Char>;
return vfprintf(f, to_string_view(format),
@ -676,7 +701,7 @@ inline int vprintf(
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(internal::is_string<S>::value)>
FMT_ENABLE_IF(detail::is_string<S>::value)>
inline int printf(const S& format_str, const Args&... args) {
using context = basic_printf_context_t<char_t<S>>;
return vprintf(to_string_view(format_str),
@ -688,8 +713,8 @@ inline int vfprintf(
std::basic_ostream<Char>& os, const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
printf(buffer, to_string_view(format), args);
internal::write(os, buffer);
vprintf(buffer, to_string_view(format), args);
detail::write_buffer(os, buffer);
return static_cast<int>(buffer.size());
}
@ -698,7 +723,7 @@ template <typename ArgFormatter, typename Char,
typename Context =
basic_printf_context<typename ArgFormatter::iterator, Char>>
typename ArgFormatter::iterator vprintf(
internal::buffer<Char>& out, basic_string_view<Char> format_str,
detail::buffer<Char>& out, basic_string_view<Char> format_str,
basic_format_args<type_identity_t<Context>> args) {
typename ArgFormatter::iterator iter(out);
Context(iter, format_str, args).template format<ArgFormatter>();

@ -33,7 +33,7 @@ template <typename Char> struct formatting_base {
template <typename Char, typename Enable = void>
struct formatting_range : formatting_base<Char> {
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit =
static FMT_CONSTEXPR_DECL const size_t range_length_limit =
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
// range.
Char prefix;
@ -54,7 +54,7 @@ struct formatting_tuple : formatting_base<Char> {
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
};
namespace internal {
namespace detail {
template <typename RangeT, typename OutputIterator>
OutputIterator copy(const RangeT& range, OutputIterator out) {
@ -118,26 +118,24 @@ template <typename T> class is_tuple_like_ {
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
template <typename T, T... N>
using integer_sequence = std::integer_sequence<T, N...>;
template <std::size_t... N> using index_sequence = std::index_sequence<N...>;
template <std::size_t N>
using make_index_sequence = std::make_index_sequence<N>;
template <size_t... N> using index_sequence = std::index_sequence<N...>;
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
#else
template <typename T, T... N> struct integer_sequence {
using value_type = T;
static FMT_CONSTEXPR std::size_t size() { return sizeof...(N); }
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
};
template <std::size_t... N>
using index_sequence = integer_sequence<std::size_t, N...>;
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
template <typename T, std::size_t N, T... Ns>
template <typename T, size_t N, T... Ns>
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
template <typename T, T... Ns>
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
template <std::size_t N>
using make_index_sequence = make_integer_sequence<std::size_t, N>;
template <size_t N>
using make_index_sequence = make_integer_sequence<size_t, N>;
#endif
template <class Tuple, class F, size_t... Is>
@ -185,11 +183,11 @@ FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
return add_space ? L" '{}'" : L"'{}'";
}
} // namespace internal
} // namespace detail
template <typename T> struct is_tuple_like {
static FMT_CONSTEXPR_DECL const bool value =
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value;
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
};
template <typename TupleT, typename Char>
@ -202,17 +200,17 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
if (formatting.add_prepostfix_space) {
*out++ = ' ';
}
out = internal::copy(formatting.delimiter, out);
out = detail::copy(formatting.delimiter, out);
}
out = format_to(out,
internal::format_str_quoted(
detail::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), v),
v);
++i;
}
formatting_tuple<Char>& formatting;
std::size_t& i;
size_t& i;
typename std::add_lvalue_reference<decltype(
std::declval<FormatContext>().out())>::type out;
};
@ -228,14 +226,14 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
template <typename FormatContext = format_context>
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
auto out = ctx.out();
std::size_t i = 0;
internal::copy(formatting.prefix, out);
size_t i = 0;
detail::copy(formatting.prefix, out);
internal::for_each(values, format_each<FormatContext>{formatting, i, out});
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
if (formatting.add_prepostfix_space) {
*out++ = ' ';
}
internal::copy(formatting.postfix, out);
detail::copy(formatting.postfix, out);
return ctx.out();
}
@ -243,10 +241,9 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
template <typename T, typename Char> struct is_range {
static FMT_CONSTEXPR_DECL const bool value =
internal::is_range_<T>::value &&
!internal::is_like_std_string<T>::value &&
detail::is_range_<T>::value && !detail::is_like_std_string<T>::value &&
!std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_constructible<internal::std_string_view<Char>, T>::value;
!std::is_constructible<detail::std_string_view<Char>, T>::value;
};
template <typename RangeT, typename Char>
@ -262,15 +259,17 @@ struct formatter<RangeT, Char,
template <typename FormatContext>
typename FormatContext::iterator format(const RangeT& values,
FormatContext& ctx) {
auto out = internal::copy(formatting.prefix, ctx.out());
std::size_t i = 0;
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
auto out = detail::copy(formatting.prefix, ctx.out());
size_t i = 0;
auto it = values.begin();
auto end = values.end();
for (; it != end; ++it) {
if (i > 0) {
if (formatting.add_prepostfix_space) *out++ = ' ';
out = internal::copy(formatting.delimiter, out);
out = detail::copy(formatting.delimiter, out);
}
out = format_to(out,
internal::format_str_quoted(
detail::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), *it),
*it);
if (++i > formatting.range_length_limit) {
@ -279,11 +278,11 @@ struct formatter<RangeT, Char,
}
}
if (formatting.add_prepostfix_space) *out++ = ' ';
return internal::copy(formatting.postfix, out);
return detail::copy(formatting.postfix, out);
}
};
template <typename Char, typename... T> struct tuple_arg_join : internal::view {
template <typename Char, typename... T> struct tuple_arg_join : detail::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;
@ -301,14 +300,14 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
template <typename FormatContext>
typename FormatContext::iterator format(
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
return format(value, ctx, internal::make_index_sequence<sizeof...(T)>{});
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
}
private:
template <typename FormatContext, size_t... N>
typename FormatContext::iterator format(
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
internal::index_sequence<N...>) {
detail::index_sequence<N...>) {
return format_args(value, ctx, std::get<N>(value.tuple)...);
}
@ -371,14 +370,14 @@ FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
\endrst
*/
template <typename T>
arg_join<internal::iterator_t<const std::initializer_list<T>>, char> join(
std::initializer_list<T> list, string_view sep) {
arg_join<const T*, const T*, char> join(std::initializer_list<T> list,
string_view sep) {
return join(std::begin(list), std::end(list), sep);
}
template <typename T>
arg_join<internal::iterator_t<const std::initializer_list<T>>, wchar_t> join(
std::initializer_list<T> list, wstring_view sep) {
arg_join<const T*, const T*, wchar_t> join(std::initializer_list<T> list,
wstring_view sep) {
return join(std::begin(list), std::end(list), sep);
}

@ -42,6 +42,9 @@ public:
/// Pushes a log entry into the backend.
virtual void push(detail::log_entry&& entry) = 0;
/// Returns true when the backend has been started, otherwise false.
virtual bool is_running() const = 0;
};
} // namespace detail

@ -23,6 +23,8 @@
#define SRSLOG_DETAIL_LOG_ENTRY_H
#include "srslte/srslog/bundled/fmt/printf.h"
#include "srslte/srslog/detail/support/thread_utils.h"
#include <chrono>
namespace srslog {
@ -30,12 +32,33 @@ class sink;
namespace detail {
/// This structure gives the user a way to log generic information as a context.
struct log_context {
/// Generic contxt value.
uint32_t value;
/// When true, the context value will be printed in the log entry.
bool enabled;
};
/// This command flushes all the messages pending in the backend.
struct flush_backend_cmd {
shared_variable<bool>& completion_flag;
std::vector<sink*> sinks;
};
/// This structure packs all the required data required to create a log entry in
/// the backend.
//:TODO: provide proper command objects when we have custom formatting.
struct log_entry {
sink* s;
std::chrono::high_resolution_clock::time_point tp;
log_context context;
std::string fmtstring;
fmt::dynamic_format_arg_store<fmt::printf_context> store;
std::string log_name;
char log_tag;
std::vector<uint8_t> hex_dump;
std::unique_ptr<flush_backend_cmd> flush_cmd;
};
} // namespace detail

@ -48,7 +48,7 @@ struct is_in_place_type_t<in_place_type_t<T>> : std::true_type {};
class any
{
public:
//:FIXME: Clang 3.8 does not compile when default constructing a const object
//:TODO: Clang 3.8 does not compile when default constructing a const object
// due to DR253. Declare the defaulted constructor out of the class.
any();
@ -149,7 +149,7 @@ private:
std::unique_ptr<type_interface> storage;
};
//:FIXME: declared out of line, see FIXME above.
//:TODO: declared out of line, see TODO above.
inline any::any() = default;
/// Constructs an any object containing an object of type T, passing the

@ -26,7 +26,7 @@
#include <queue>
#ifndef SRSLOG_QUEUE_CAPACITY
#define SRSLOG_QUEUE_CAPACITY 2048
#define SRSLOG_QUEUE_CAPACITY 8192
#endif
namespace srslog {

@ -0,0 +1,93 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#ifndef SRSLOG_EVENT_TRACE_H
#define SRSLOG_EVENT_TRACE_H
#include <chrono>
#include <string>
namespace srslog {
class log_channel;
/// The event tracing framework allows to trace any kind of event inside an
/// application.
/// To enable event tracing the ENABLE_SRSLOG_EVENT_TRACE macro symbol should
/// be defined, otherwise calls to the tracing framework will be ignored. This
/// is important to avoid the overhead of tracing when it is not required.
/// For details about each event trace type please refer to:
/// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit
/// Initializes the event trace framework saving the trace events to a
/// "event_trace.log" file.
void event_trace_init();
/// Initializes the event trace framework using the specified log channel to log
/// all trace events.
void event_trace_init(log_channel& c);
#ifdef ENABLE_SRSLOG_EVENT_TRACE
/// Generates the begin phase of a duration event.
void trace_duration_begin(const std::string& category, const std::string& name);
/// Generates the end phase of a duration event.
void trace_duration_end(const std::string& category, const std::string& name);
/// Generates a complete event.
#define trace_complete_event(C, N) \
auto scoped_complete_event_variable = detail::scoped_complete_event(C, N)
#else
/// No-ops.
#define trace_duration_begin(C, N)
#define trace_duration_end(C, N)
#define trace_complete_event(C, N)
#endif
namespace detail {
/// Scoped type object for implementing a complete event.
class scoped_complete_event
{
public:
scoped_complete_event(std::string cat, std::string n) :
category(std::move(cat)),
name(std::move(n)),
start(std::chrono::steady_clock::now())
{}
~scoped_complete_event();
private:
const std::string category;
const std::string name;
std::chrono::time_point<std::chrono::steady_clock> start;
};
} // namespace detail
} // namespace srslog
#endif // SRSLOG_EVENT_TRACE_H

@ -23,10 +23,30 @@
#define SRSLOG_LOG_CHANNEL_H
#include "srslte/srslog/detail/log_backend.h"
#include "srslte/srslog/detail/support/thread_utils.h"
#include <cassert>
namespace srslog {
/// Log channel configuration settings.
struct log_channel_config {
log_channel_config() = default;
log_channel_config(std::string n, char tag, bool should_print_context) :
name(std::move(n)),
tag(tag),
should_print_context(should_print_context)
{}
/// Optional log channel name. If set, will get printed for each log entry.
/// Disabled by default.
std::string name;
/// Optional log channel tag. If set, will get printed for each log entry.
/// Disabled by default.
char tag = '\0';
/// When set to true, each log entry will get printed with the context value.
/// Disabled by default.
bool should_print_context = false;
};
/// A log channel is the entity used for logging messages.
///
/// It can deliver a log entry to one or more different sinks, for example a
@ -38,9 +58,21 @@ class log_channel
{
public:
log_channel(std::string id, sink& s, detail::log_backend& backend) :
log_channel(std::move(id), s, backend, {})
{}
log_channel(std::string id,
sink& s,
detail::log_backend& backend,
log_channel_config config) :
log_id(std::move(id)),
log_sink(s),
backend(backend),
log_name(std::move(config.name)),
log_tag(config.tag),
should_print_context(config.should_print_context),
ctx_value(0),
hex_max_size(0),
is_enabled(true)
{}
@ -57,21 +89,67 @@ public:
/// Returns the id string of the channel.
const std::string& id() const { return log_id; }
/// Set the log channel context to the specified value.
void set_context(uint32_t x) { ctx_value = x; }
/// Set the maximum number of bytes to can be printed in a hex dump.
/// Set to -1 to indicate no hex dump limit.
void set_hex_dump_max_size(int size) { hex_max_size = size; }
/// Builds the provided log entry and passes it to the backend. When the
/// channel is disabled the log entry will be discarded.
template <typename... Args>
void operator()(const std::string& fmtstr, Args&&... args)
{
if (!enabled()) {
return;
}
assert(&log_sink);
// Populate the store with all incoming arguments.
fmt::dynamic_format_arg_store<fmt::printf_context> store;
(void)std::initializer_list<int>{(store.push_back(args), 0)...};
// Send the log entry to the backend.
detail::log_entry entry = {&log_sink,
std::chrono::high_resolution_clock::now(),
{ctx_value, should_print_context},
fmtstr,
std::move(store),
log_name,
log_tag};
backend.push(std::move(entry));
}
/// Builds the provided log entry and passes it to the backend. When the
/// channel is disabled the log entry will be discarded.
template <typename... Args>
void operator()(const uint8_t* buffer,
size_t len,
const std::string& fmtstr,
Args&&... args)
{
if (!enabled()) {
return;
}
assert(&log_sink);
// Populate the store with all incoming arguments.
fmt::dynamic_format_arg_store<fmt::printf_context> store;
(void)std::initializer_list<int>{(store.push_back(args), 0)...};
// Calculate the length to capture in the buffer.
if (hex_max_size >= 0)
len = std::min<size_t>(len, hex_max_size);
// Send the log entry to the backend.
detail::log_entry entry = {&log_sink, fmtstr, std::move(store)};
detail::log_entry entry = {&log_sink,
std::chrono::high_resolution_clock::now(),
{ctx_value, should_print_context},
fmtstr,
std::move(store),
log_name,
log_tag,
std::vector<uint8_t>(buffer, buffer + len)};
backend.push(std::move(entry));
}
@ -79,6 +157,11 @@ private:
const std::string log_id;
sink& log_sink;
detail::log_backend& backend;
const std::string log_name;
const char log_tag;
const bool should_print_context;
detail::shared_variable<uint32_t> ctx_value;
detail::shared_variable<int> hex_max_size;
detail::shared_variable<bool> is_enabled;
};

@ -31,6 +31,7 @@ namespace detail {
/// The logger_impl template class contains the common functionality to loggers.
/// Its main responsibility is to control the logging level by individually
/// enabling or disabling the logging channels that form the logger.
/// NOTE: Thread safe class.
template <typename T, typename Enum>
class logger_impl : public T
{
@ -60,11 +61,31 @@ public:
/// Change the logging level.
void set_level(Enum lvl)
{
detail::scoped_lock lock(m);
for (unsigned i = 0, e = channels.size(); i != e; ++i) {
channels[i]->set_enabled(static_cast<Enum>(i) <= lvl);
}
}
/// Set the specified context value to all the channels of the logger.
void set_context(uint32_t x)
{
detail::scoped_lock lock(m);
for (auto channel : channels) {
channel->set_context(x);
}
}
/// Set the maximum number of bytes to can be printed in a hex dump to all the
/// channels of the logger.
void set_hex_dump_max_size(int x)
{
detail::scoped_lock lock(m);
for (auto channel : channels) {
channel->set_hex_dump_max_size(x);
}
}
private:
/// Comparison operator for enum types, used by the set_level method.
friend bool operator<=(Enum lhs, Enum rhs)
@ -75,6 +96,7 @@ private:
private:
const std::string logger_id;
const std::array<log_channel*, size> channels;
mutable detail::mutex m;
};
/// Type trait to detect if T is a logger.
@ -117,12 +139,13 @@ using build_logger_type = detail::logger_impl<T, Enum>;
/// Common logger types.
///
/// Basic logger with three levels.
enum class basic_levels { error, warning, info, LAST };
/// Basic logger with four levels.
enum class basic_levels { error, warning, info, debug, LAST };
struct basic_logger_channels {
log_channel& error;
log_channel& warning;
log_channel& info;
log_channel& debug;
};
using basic_logger = build_logger_type<basic_logger_channels, basic_levels>;

@ -28,32 +28,102 @@
namespace srslog {
///
/// NOTE: All functions are thread safe unless otherwise specified.
///
///
/// Log channel management functions.
///
/// Finds a log channel with the specified id string in the repository. On
/// success returns a pointer to the requested log channel, otherwise nullptr.
log_channel* find_log_channel(const std::string& id);
/// Returns an instance of a log_channel with the specified id that writes to
/// the default sink using the default log channel configuration.
/// NOTE: Any '#' characters in the id will get removed.
log_channel& fetch_log_channel(const std::string& id);
/// Returns an instance of a log_channel with the specified id that writes to
/// the specified sink.
/// NOTE: Any '#' characters in the id will get removed.
log_channel&
fetch_log_channel(const std::string& id, sink& s, log_channel_config config);
/// Creates a new log channel instance with the specified id string and sink,
/// then registers it in the log channel repository so that it can be later
/// retrieved in other parts of the application. Returns a pointer to the
/// newly created channel, otherwise when a channel is already registered with
/// the same id it returns nullptr.
/// NOTE: The input id string should not contain any '#' characters otherwise
/// nullptr is returned.
/// NOTE: Deprecated, use fetch_log_channel instead.
log_channel* create_log_channel(const std::string& id, sink& s);
/// Finds a log channel with the specified id string in the repository. On
/// success returns a pointer to the requested log channel, otherwise nullptr.
log_channel* find_log_channel(const std::string& id);
namespace detail {
/// Internal helper functions.
detail::any* fetch_logger(const std::string& id, detail::any&& logger);
detail::any* find_logger(const std::string& id);
} // namespace detail
///
/// Logger management functions.
///
namespace detail {
/// Finds a logger with the specified id string and type in the repository. On
/// success returns a pointer to the requested logger, otherwise nullptr.
/// NOTE: T should be a type that is a logger.
template <typename T>
inline T* find_logger(const std::string& id)
{
static_assert(detail::is_logger<T>::value, "T should be a logger type");
detail::any* p = detail::find_logger(id);
/// Internal helper functions.
detail::any* create_logger(const std::string& id, detail::any&& logger);
detail::any* find_logger(const std::string& id);
return detail::any_cast<T>(p);
}
} // namespace detail
/// Returns an instance of a basic logger (see basic_logger type) with the
/// specified id string. All logger channels will write into the default sink.
/// The context value of the logger can be printed on each log entry by setting
/// to true the should_print_context argument.
basic_logger& fetch_basic_logger(const std::string& id,
bool should_print_context = true);
/// Returns an instance of a basic logger (see basic_logger type) with the
/// specified id string. All logger channels will write into the specified sink.
/// The context value of the logger can be printed on each log entry by setting
/// to true the should_print_context argument.
basic_logger& fetch_basic_logger(const std::string& id,
sink& s,
bool should_print_context = true);
/// Returns a logger instance with the specified id string, type and channel
/// references.
/// NOTE: T should be a type that is a logger.
template <typename T, typename... Args>
inline T& fetch_logger(const std::string& id, Args&&... args)
{
static_assert(detail::is_logger<T>::value, "T should be a logger type");
auto logger = detail::make_any<T>(id, std::forward<Args>(args)...);
detail::any* p = detail::fetch_logger(id, std::move(logger));
return *detail::any_cast<T>(p);
}
/// Creates a new basic logger instance (see basic_logger type) with the
/// specified id string and sink, registering it into the logger repository so
/// that it can be later retrieved in other parts of the application. The
/// context value of the logger can be printed on each log entry by setting to
/// true the should_print_context argument. All logger channels will write into
/// the specified sink. Returns a pointer to the newly created logger, otherwise
/// when a logger is already registered with the same id it returns nullptr.
/// NOTE: Deprecated, use fetch_basic_logger instead.
basic_logger* create_basic_logger(const std::string& id,
sink& s,
bool should_print_context = true);
/// Creates a new logger instance with the specified id string, type and channel
/// references, registering it into the logger repository so that it can be
@ -61,24 +131,13 @@ detail::any* find_logger(const std::string& id);
/// newly created logger, otherwise when a logger is already registered with the
/// same id it returns nullptr.
/// NOTE: T should be a type that is a logger.
/// NOTE: Deprecated, use fetch_logger instead.
template <typename T, typename... Args>
inline T* create_logger(const std::string& id, Args&&... args)
{
static_assert(detail::is_logger<T>::value, "T should be a logger type");
auto logger = detail::make_any<T>(id, std::forward<Args>(args)...);
detail::any* p = detail::create_logger(id, std::move(logger));
return detail::any_cast<T>(p);
}
/// Finds a logger with the specified id string and type in the repository. On
/// success returns a pointer to the requested logger, otherwise nullptr.
/// NOTE: T should be a type that is a logger.
template <typename T>
inline T* find_logger(const std::string& id)
{
static_assert(detail::is_logger<T>::value, "T should be a logger type");
detail::any* p = detail::find_logger(id);
detail::any* p = detail::fetch_logger(id, std::move(logger));
return detail::any_cast<T>(p);
}
@ -87,15 +146,47 @@ inline T* find_logger(const std::string& id)
/// Sink management functions.
///
/// Installs the specified sink to be used as the default one by new log
/// channels and loggers.
/// The initial default sink writes to stdout.
void set_default_sink(sink& s);
/// Returns the instance of the default sink being used.
sink& get_default_sink();
/// Finds a sink with the specified id string in the repository. On
/// success returns a pointer to the requested sink, otherwise nullptr.
sink* find_sink(const std::string& id);
/// Returns an instance of a sink that writes to the stdout stream.
sink& fetch_stdout_sink();
/// Returns an instance of a sink that writes to the stderr stream.
sink& fetch_stderr_sink();
/// Returns an instance of a sink that writes into a file in the specified path.
/// Specifying a max_size value different to zero will make the sink create a
/// new file each time the current file exceeds this value. The units of
/// max_size are bytes.
/// NOTE: Any '#' characters in the id will get removed.
sink& fetch_file_sink(const std::string& path, size_t max_size = 0);
/// Creates a new sink that writes into the a file in the specified path and
/// registers it into a sink repository so that it can be later retrieved in
/// other parts of the application. Returns a pointer to the newly created sink
/// or nullptr if a sink with the same path was already registered.
/// Specifying a max_size value different to zero will make the sink create a
/// new file each time the current file exceeds this value. The units of
/// max_size are bytes.
/// NOTE: Deprecated, use fetch_file_sink instead.
sink* create_file_sink(const std::string& path, size_t max_size = 0);
/// Creates a new sink that writes into the stdout stream and registers it into
/// a sink repository so that it can be later retrieved in other parts of the
/// application. Different stdout sinks can be created by providing different
/// names. Returns a pointer to the newly created sink or nullptr if a sink with
/// the same name was already registered.
/// NOTE: Deprecated, use get_stdout_sink instead.
sink* create_stdout_sink(const std::string& name = "stdout");
/// Creates a new sink that writes into the stderr stream and registers it into
@ -103,17 +194,9 @@ sink* create_stdout_sink(const std::string& name = "stdout");
/// application. Different stderr sinks can be created by providing different
/// names. Returns a pointer to the newly created sink or nullptr if a sink with
/// the same name was already registered.
/// NOTE: Deprecated, use get_stderr_sink instead.
sink* create_stderr_sink(const std::string& name = "stderr");
/// Creates a new sink that writes into the a file in the specified path and
/// registers it into a sink repository so that it can be later retrieved in
/// other parts of the application. Returns a pointer to the newly created sink
/// or nullptr if a sink with the same path was already registered.
/// Specifying a max_size value different to zero will make the sink create a
/// new file each time the current file exceeds this value. The units of
/// max_size are bytes.
sink* create_file_sink(const std::string& path, size_t max_size = 0);
///
/// Framework configuration and control functions.
///
@ -123,9 +206,14 @@ sink* create_file_sink(const std::string& path, size_t max_size = 0);
/// NOTE: Calling this function more than once has no side effects.
void init();
/// Flushes the contents of all the registered sinks. The caller thread will
/// block until the operation is completed.
/// NOTE: This function does nothing if init() has not been called.
void flush();
/// Installs the specified error handler to receive any error messages generated
/// by the framework.
/// NOTE: This function should be called before init().
/// NOTE: This function should be called before init() and is NOT thread safe.
void set_error_handler(error_handler handler);
} // namespace srslog

@ -0,0 +1,168 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#ifndef SRSLOG_SRSLOG_C_H
#define SRSLOG_SRSLOG_C_H
#ifdef __cplusplus
#include <cstddef>
#else
#include <stddef.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* Common types.
*/
typedef int srslog_bool;
typedef struct srslog_opaque_sink srslog_sink;
typedef struct srslog_opaque_log_channel srslog_log_channel;
typedef struct srslog_opaque_basic_logger srslog_logger;
/**
* This function initializes the logging framework. It must be called before
* any log entry is generated.
* NOTE: Calling this function more than once has no side effects.
*/
void srslog_init(void);
/**
* Installs the specified sink to be used as the default one by new log
* channels and loggers.
* The initial default sink writes to stdout.
*/
void srslog_set_default_sink(srslog_sink* s);
/**
* Returns the instance of the default sink being used.
*/
srslog_sink* srslog_get_default_sink(void);
/**
* Returns an instance of a log_channel with the specified id that writes to
* the default sink using the default log channel configuration.
* NOTE: Any '#' characters in the id will get removed.
*/
srslog_log_channel* srslog_fetch_log_channel(const char* id);
/**
* Finds a log channel with the specified id string in the repository. On
* success returns a pointer to the requested log channel, otherwise NULL.
*/
srslog_log_channel* srslog_find_log_channel(const char* id);
/**
* Controls whether the specified channel accepts incoming log entries.
*/
void srslog_set_log_channel_enabled(srslog_log_channel* channel,
srslog_bool enabled);
/**
* Returns 1 if the specified channel is accepting incoming log entries,
* otherwise 0.
*/
srslog_bool srslog_is_log_channel_enabled(srslog_log_channel* channel);
/**
* Returns the id string of the specified channel.
*/
const char* srslog_get_log_channel_id(srslog_log_channel* channel);
/**
* Logs the provided log entry using the specified log channel. When the channel
* is disabled the log entry wil be discarded.
* NOTE: Only printf style formatting is supported when using the C API.
*/
void srslog_log(srslog_log_channel* channel, const char* fmt, ...);
/**
* Returns an instance of a basic logger (see basic_logger type) with the
* specified id string. All logger channels will write into the default sink.
*/
srslog_logger* srslog_fetch_default_logger(const char* id);
/**
* Finds a logger with the specified id string in the repository. On success
* returns a pointer to the requested log channel, otherwise NULL.
*/
srslog_logger* srslog_find_default_logger(const char* id);
/**
* These functions log the provided log entry using the specified logger.
* Entries are automatically discarded depending on the configured level of the
* logger.
* NOTE: Only printf style formatting is supported when using the C API.
*/
void srslog_debug(srslog_logger* log, const char* fmt, ...);
void srslog_info(srslog_logger* log, const char* fmt, ...);
void srslog_warning(srslog_logger* log, const char* fmt, ...);
void srslog_error(srslog_logger* log, const char* fmt, ...);
/**
* Returns the id string of the specified logger.
*/
const char* srslog_get_logger_id(srslog_logger* log);
typedef enum {
srslog_lvl_error, /**< error logging level */
srslog_lvl_warning, /**< warning logging level */
srslog_lvl_info, /**< info logging level */
srslog_lvl_debug /**< debug logging level */
} srslog_log_levels;
/**
* Sets the logging level into the specified logger.
*/
void srslog_set_logger_level(srslog_logger* log, srslog_log_levels lvl);
/**
* Finds a sink with the specified id string in the repository. On
* success returns a pointer to the requested sink, otherwise NULL.
*/
srslog_sink* srslog_find_sink(const char* id);
/**
* Returns an instance of a sink that writes to the stdout stream.
*/
srslog_sink* srslog_fetch_stdout_sink(void);
/**
* Returns an instance of a sink that writes to the stderr stream.
*/
srslog_sink* srslog_fetch_stderr_sink(void);
/**
* Returns an instance of a sink that writes into a file in the specified path.
* Specifying a max_size value different to zero will make the sink create a
* new file each time the current file exceeds this value. The units of
* max_size are bytes.
* NOTE: Any '#' characters in the id will get removed.
*/
srslog_sink* srslog_fetch_file_sink(const char* path, size_t max_size);
#ifdef __cplusplus
}
#endif
#endif

@ -103,29 +103,26 @@ void log_filter::all_log(srslte::LOG_LEVEL_ENUM level,
bool long_msg)
{
char buffer_tti[16] = {};
char buffer_time[64] = {};
if (logger_h) {
logger::unique_log_str_t log_str = nullptr;
if (long_msg || hex) {
// For long messages, dynamically allocate a new log_str with enough size outside the pool.
uint32_t log_str_msg_len = sizeof(buffer_tti) + sizeof(buffer_time) + 20 + strlen(msg) + CHARS_FOR_HEX_DUMP(size);
uint32_t log_str_msg_len = sizeof(buffer_tti) + 20 + strlen(msg) + CHARS_FOR_HEX_DUMP(size);
log_str = logger::unique_log_str_t(new logger::log_str(nullptr, log_str_msg_len), logger::log_str_deleter());
} else {
log_str = logger_h->allocate_unique_log_str();
}
if (log_str) {
now_time(buffer_time, sizeof(buffer_time));
if (do_tti) {
get_tti_str(tti, buffer_tti, sizeof(buffer_tti));
}
snprintf(log_str->str(),
log_str->get_buffer_size(),
"%s [%-4s] %s %s%s%s%s%s",
buffer_time,
"[%-4s] %s %s%s%s%s%s",
get_service_name().c_str(),
log_level_text_short[level],
do_tti ? buffer_tti : "",

@ -19,8 +19,10 @@
#
set(SOURCES
backend_worker.cpp
srslog.cpp
backend_worker.cpp)
srslog_c.cpp
event_trace.cpp)
add_subdirectory(bundled/fmt)

@ -20,6 +20,7 @@
*/
#include "backend_worker.h"
#include "formatter.h"
#include "srslte/srslog/sink.h"
#include <cassert>
@ -69,7 +70,7 @@ void backend_worker::do_work()
continue;
}
report_queue_on_full();
report_queue_on_full_once();
process_log_entry(std::move(item.second));
}
@ -79,12 +80,32 @@ void backend_worker::do_work()
process_outstanding_entries();
}
/// Executes the flush command over all registered sinks.
static void process_flush_command(const detail::flush_backend_cmd& cmd)
{
for (const auto sink : cmd.sinks) {
sink->flush();
}
// Notify caller thread we are done.
cmd.completion_flag = true;
}
void backend_worker::process_log_entry(detail::log_entry&& entry)
{
std::string result = fmt::vsprintf(entry.fmtstring, std::move(entry.store));
// Check first for flush commands.
if (entry.flush_cmd) {
process_flush_command(*entry.flush_cmd);
return;
}
// Save sink pointer before moving the entry.
sink* s = entry.s;
std::string result = format_log_entry_to_text(std::move(entry));
detail::memory_buffer buffer(result);
if (auto err_str = entry.s->write(buffer)) {
if (auto err_str = s->write(buffer)) {
err_handler(err_str.get_error());
}
}

@ -41,8 +41,7 @@ class backend_worker
public:
explicit backend_worker(detail::work_queue<detail::log_entry>& queue) :
queue(queue),
running_flag(false)
queue(queue), running_flag(false)
{}
backend_worker(const backend_worker&) = delete;
@ -100,7 +99,8 @@ private:
/// Checks the current size of the queue reporting an error message if it is
/// about to reach its maximum capacity.
void report_queue_on_full() const
/// Error message is only reported once to avoid spamming.
void report_queue_on_full_once()
{
if (queue.is_almost_full()) {
err_handler(
@ -108,6 +108,7 @@ private:
"capacity of {} elements, new log entries will get "
"discarded.\nConsider increasing the queue capacity.",
queue.get_capacity()));
err_handler = [](const std::string&) {};
}
}

@ -8,12 +8,12 @@
#include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE
namespace internal {
namespace detail {
template <typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision,
T value) {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
#ifdef FMT_FUZZ
if (precision > 100000)
throw std::runtime_error(
"fuzz mode - avoid large allocation inside snprintf");
@ -23,154 +23,47 @@ int format_float(char* buf, std::size_t size, const char* format, int precision,
return precision < 0 ? snprintf_ptr(buf, size, format, value)
: snprintf_ptr(buf, size, format, precision, value);
}
struct sprintf_specs {
int precision;
char type;
bool alt : 1;
} // namespace detail
template <typename Char>
constexpr sprintf_specs(basic_format_specs<Char> specs)
: precision(specs.precision), type(specs.type), alt(specs.alt) {}
constexpr bool has_precision() const { return precision >= 0; }
};
// This is deprecated and is kept only to preserve ABI compatibility.
template <typename Double>
char* sprintf_format(Double value, internal::buffer<char>& buf,
sprintf_specs specs) {
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
FMT_ASSERT(buf.capacity() != 0, "empty buffer");
// Build format string.
enum { max_format_size = 10 }; // longest format: %#-*.*Lg
char format[max_format_size];
char* format_ptr = format;
*format_ptr++ = '%';
if (specs.alt || !specs.type) *format_ptr++ = '#';
if (specs.precision >= 0) {
*format_ptr++ = '.';
*format_ptr++ = '*';
}
if (std::is_same<Double, long double>::value) *format_ptr++ = 'L';
char type = specs.type;
if (type == '%')
type = 'f';
else if (type == 0 || type == 'n')
type = 'g';
#if FMT_MSC_VER
if (type == 'F') {
// MSVC's printf doesn't support 'F'.
type = 'f';
}
#endif
*format_ptr++ = type;
*format_ptr = '\0';
// Format using snprintf.
char* start = nullptr;
char* decimal_point_pos = nullptr;
for (;;) {
std::size_t buffer_size = buf.capacity();
start = &buf[0];
int result =
format_float(start, buffer_size, format, specs.precision, value);
if (result >= 0) {
unsigned n = internal::to_unsigned(result);
if (n < buf.capacity()) {
// Find the decimal point.
auto p = buf.data(), end = p + n;
if (*p == '+' || *p == '-') ++p;
if (specs.type != 'a' && specs.type != 'A') {
while (p < end && *p >= '0' && *p <= '9') ++p;
if (p < end && *p != 'e' && *p != 'E') {
decimal_point_pos = p;
if (!specs.type) {
// Keep only one trailing zero after the decimal point.
++p;
if (*p == '0') ++p;
while (p != end && *p >= '1' && *p <= '9') ++p;
char* where = p;
while (p != end && *p == '0') ++p;
if (p == end || *p < '0' || *p > '9') {
if (p != end) std::memmove(where, p, to_unsigned(end - p));
n -= static_cast<unsigned>(p - where);
}
}
}
}
buf.resize(n);
break; // The buffer is large enough - continue with formatting.
}
buf.reserve(n + 1);
} else {
// If result is negative we ask to increase the capacity by at least 1,
// but as std::vector, the buffer grows exponentially.
buf.reserve(buf.capacity() + 1);
}
}
return decimal_point_pos;
}
} // namespace internal
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,
sprintf_specs);
template FMT_API char* internal::sprintf_format(long double,
internal::buffer<char>&,
sprintf_specs);
template struct FMT_INSTANTIATION_DEF_API internal::basic_data<void>;
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
int (*instantiate_format_float)(double, int, internal::float_specs,
internal::buffer<char>&) =
internal::format_float;
int (*instantiate_format_float)(double, int, detail::float_specs,
detail::buffer<char>&) = detail::format_float;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc);
template FMT_API std::locale internal::locale_ref::get<std::locale>() const;
template FMT_API detail::locale_ref::locale_ref(const std::locale& loc);
template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
#endif
// Explicit instantiations for char.
template FMT_API std::string internal::grouping_impl<char>(locale_ref);
template FMT_API char internal::thousands_sep_impl(locale_ref);
template FMT_API char internal::decimal_point_impl(locale_ref);
template FMT_API std::string detail::grouping_impl<char>(locale_ref);
template FMT_API char detail::thousands_sep_impl(locale_ref);
template FMT_API char detail::decimal_point_impl(locale_ref);
template FMT_API void internal::buffer<char>::append(const char*, const char*);
template FMT_API void detail::buffer<char>::append(const char*, const char*);
template FMT_API void internal::arg_map<format_context>::init(
const basic_format_args<format_context>& args);
template FMT_API FMT_BUFFER_CONTEXT(char)::iterator detail::vformat_to(
detail::buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>);
template FMT_API std::string internal::vformat<char>(
string_view, basic_format_args<format_context>);
template FMT_API format_context::iterator internal::vformat_to(
internal::buffer<char>&, string_view, basic_format_args<format_context>);
template FMT_API int internal::snprintf_float(double, int,
internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::snprintf_float(long double, int,
internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::format_float(double, int, internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::format_float(long double, int,
internal::float_specs,
internal::buffer<char>&);
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
detail::buffer<char>&);
template FMT_API int detail::snprintf_float(long double, int,
detail::float_specs,
detail::buffer<char>&);
template FMT_API int detail::format_float(double, int, detail::float_specs,
detail::buffer<char>&);
template FMT_API int detail::format_float(long double, int, detail::float_specs,
detail::buffer<char>&);
// Explicit instantiations for wchar_t.
template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref);
template FMT_API wchar_t internal::thousands_sep_impl(locale_ref);
template FMT_API wchar_t internal::decimal_point_impl(locale_ref);
template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref);
template FMT_API wchar_t detail::thousands_sep_impl(locale_ref);
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
template FMT_API void internal::buffer<wchar_t>::append(const wchar_t*,
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
const wchar_t*);
template FMT_API std::wstring internal::vformat<wchar_t>(
wstring_view, basic_format_args<wformat_context>);
FMT_END_NAMESPACE

@ -73,14 +73,14 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
FMT_BEGIN_NAMESPACE
#ifdef _WIN32
internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
detail::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
if (int error_code = convert(s)) {
FMT_THROW(windows_error(error_code,
"cannot convert string from UTF-16 to UTF-8"));
}
}
int internal::utf16_to_utf8::convert(wstring_view s) {
int detail::utf16_to_utf8::convert(wstring_view s) {
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
int s_size = static_cast<int>(s.size());
if (s_size == 0) {
@ -105,12 +105,12 @@ void windows_error::init(int err_code, string_view format_str,
format_args args) {
error_code_ = err_code;
memory_buffer buffer;
internal::format_windows_error(buffer, err_code, vformat(format_str, args));
detail::format_windows_error(buffer, err_code, vformat(format_str, args));
std::runtime_error& base = *this;
base = std::runtime_error(to_string(buffer));
}
void internal::format_windows_error(internal::buffer<char>& out, int error_code,
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT {
FMT_TRY {
wmemory_buffer buf;
@ -124,10 +124,7 @@ void internal::format_windows_error(internal::buffer<char>& out, int error_code,
if (result != 0) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
internal::writer w(out);
w.write(message);
w.write(": ");
w.write(utf8_message);
format_to(std::back_inserter(out), "{}: {}", message, utf8_message);
return;
}
break;
@ -143,7 +140,7 @@ void internal::format_windows_error(internal::buffer<char>& out, int error_code,
void report_windows_error(int error_code,
fmt::string_view message) FMT_NOEXCEPT {
report_error(internal::format_windows_error, error_code, message);
report_error(detail::format_windows_error, error_code, message);
}
#endif // _WIN32
@ -234,14 +231,14 @@ std::size_t file::read(void* buffer, std::size_t count) {
RWResult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
return internal::to_unsigned(result);
return detail::to_unsigned(result);
}
std::size_t file::write(const void* buffer, std::size_t count) {
RWResult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
return internal::to_unsigned(result);
return detail::to_unsigned(result);
}
file file::dup(int fd) {
@ -292,7 +289,11 @@ void file::pipe(file& read_end, file& write_end) {
buffered_file file::fdopen(const char* mode) {
// Don't retry as fdopen doesn't return EINTR.
#if defined(__MINGW32__) && defined(_POSIX_)
FILE* f = ::fdopen(fd_, mode);
#else
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
#endif
if (!f)
FMT_THROW(
system_error(errno, "cannot associate stream with file descriptor"));

@ -0,0 +1,123 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#include "srslte/srslog/event_trace.h"
#include "srslte/srslog/srslog.h"
#include <cassert>
#include <ctime>
#undef trace_duration_begin
#undef trace_duration_end
#undef trace_complete_event
using namespace srslog;
/// Log channel where event traces will get sent.
static log_channel* tracer = nullptr;
void srslog::event_trace_init()
{
// Nothing to do if the user previously set a custom channel or this is not
// the first time this function is called.
if (tracer) {
return;
}
// Default file name where event traces will get stored.
static constexpr char default_file_name[] = "event_trace.log";
// Create the default event trace channel.
//:TODO: handle name reservation.
sink* s = create_file_sink(default_file_name);
assert(s && "Default event file sink is reserved");
tracer = create_log_channel("event_trace_channel", *s);
assert(tracer && "Default event trace channel is reserved");
}
void srslog::event_trace_init(log_channel& c)
{
// Nothing to set when a channel has already been installed.
if (!tracer) {
tracer = &c;
}
}
/// Fills in the input buffer with the current time.
static void format_time(char* buffer, size_t len)
{
std::time_t t = std::time(nullptr);
std::strftime(buffer, len, "%FT%T", std::localtime(&t));
}
namespace srslog {
void trace_duration_begin(const std::string& category, const std::string& name)
{
if (!tracer) {
return;
}
char fmt_time[24];
format_time(fmt_time, sizeof(fmt_time));
(*tracer)("[%s] [TID:%0u] Entering \"%s\": %s",
fmt_time,
(unsigned)::pthread_self(),
category,
name);
}
void trace_duration_end(const std::string& category, const std::string& name)
{
if (!tracer) {
return;
}
char fmt_time[24];
format_time(fmt_time, sizeof(fmt_time));
(*tracer)("[%s] [TID:%0u] Leaving \"%s\": %s",
fmt_time,
(unsigned)::pthread_self(),
category,
name);
}
} // namespace srslog
/// Private implementation of the complete event destructor.
srslog::detail::scoped_complete_event::~scoped_complete_event()
{
if (!tracer)
return;
auto end = std::chrono::steady_clock::now();
unsigned long long diff =
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
.count();
char fmt_time[24];
format_time(fmt_time, sizeof(fmt_time));
(*tracer)("[%s] [TID:%0u] Complete event \"%s\" (duration %lld us): %s",
fmt_time,
(unsigned)::pthread_self(),
category,
diff,
name);
}

@ -0,0 +1,96 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#ifndef SRSLOG_FORMATTER_H
#define SRSLOG_FORMATTER_H
#include "srslte/srslog/bundled/fmt/chrono.h"
#include "srslte/srslog/bundled/fmt/ranges.h"
#include "srslte/srslog/detail/log_entry.h"
namespace srslog {
namespace detail {
/// Formats into a hex dump a range of elements, storing the result in the input
/// buffer.
inline void format_hex_dump(const std::vector<uint8_t>& 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<size_t>(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);
}
}
} // namespace detail
/// Formats to text all the fields of a log entry,
inline std::string format_log_entry_to_text(detail::log_entry&& entry)
{
fmt::memory_buffer buffer;
// Time stamp data preparation.
std::tm current_time =
fmt::gmtime(std::chrono::high_resolution_clock::to_time_t(entry.tp));
auto us_fraction = std::chrono::duration_cast<std::chrono::microseconds>(
entry.tp.time_since_epoch())
.count() %
1000000u;
fmt::format_to(buffer, "{:%H:%M:%S}.{:06} ", current_time, us_fraction);
// Format optional fields if present.
if (!entry.log_name.empty()) {
fmt::format_to(buffer, "[{: <4.4}] ", entry.log_name);
}
if (entry.log_tag != '\0') {
fmt::format_to(buffer, "[{}] ", entry.log_tag);
}
if (entry.context.enabled) {
fmt::format_to(buffer, "[{:5}] ", entry.context.value);
}
// Message formatting.
fmt::format_to(
buffer, "{}\n", fmt::vsprintf(entry.fmtstring, std::move(entry.store)));
// Optional hex dump formatting.
detail::format_hex_dump(entry.hex_dump, buffer);
return fmt::to_string(buffer);
}
} // namespace srslog
#endif // SRSLOG_FORMATTER_H

@ -45,15 +45,14 @@ public:
queue.push(std::move(entry));
}
bool is_running() const override { return worker.is_running(); }
/// Installs the specified error handler into the backend worker.
void set_error_handler(error_handler err_handler)
{
worker.set_error_handler(std::move(err_handler));
}
/// Returns true if the backend has been started, otherwise returns false.
bool is_started() const { return worker.is_running(); }
/// Stops the backend worker thread.
void stop() { worker.stop(); }

@ -24,6 +24,7 @@
#include "srslte/srslog/detail/support/thread_utils.h"
#include <unordered_map>
#include <vector>
namespace srslog {
@ -49,8 +50,19 @@ public:
return &insertion.first->second;
}
/// Finds a value with the specified key in the repository. Returns a
/// pointer to the value, otherwise nullptr if not found.
/// Inserts a new entry in-place into the repository if there is no element
/// with the specified key. Returns a reference to the inserted element or to
/// the already existing element if no insertion happened.
template <typename... Args>
V& emplace(Args&&... args)
{
detail::scoped_lock lock(m);
auto insertion = repo.emplace(std::forward<Args>(args)...);
return insertion.first->second;
}
/// Finds a value with the specified key in the repository. Returns a pointer
/// to the value, otherwise nullptr if not found.
V* find(const K& key)
{
detail::scoped_lock lock(m);
@ -63,6 +75,32 @@ public:
const auto it = repo.find(key);
return (it != repo.cend()) ? &it->second : nullptr;
}
/// Returns a copy of the contents of the repository.
std::vector<V*> contents()
{
detail::scoped_lock lock(m);
std::vector<V*> data;
data.reserve(repo.size());
for (auto& elem : repo) {
data.push_back(&elem.second);
}
return data;
}
std::vector<const V*> contents() const
{
detail::scoped_lock lock(m);
std::vector<const V*> data;
data.reserve(repo.size());
for (const auto& elem : repo) {
data.push_back(&elem.second);
}
return data;
}
};
} // namespace srslog

@ -0,0 +1,123 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#ifndef SRSLOG_SINK_REPOSITORY_H
#define SRSLOG_SINK_REPOSITORY_H
#include "object_repository.h"
#include "sinks/stream_sink.h"
namespace srslog {
/// The sink repository stores sink instances associated to an id. Both stdout
/// and stderr stream sinks are created on construction so they accessible
/// without the need of creating them previously.
/// NOTE: Thread safe class.
class sink_repository
{
/// Identifiers for stdout and stderr sinks.
static constexpr char stdout_id[] = "stdout#";
static constexpr char stderr_id[] = "stderr#";
object_repository<std::string, std::unique_ptr<sink> > repo;
public:
sink_repository()
{
//:TODO: GCC5 or lower versions emits an error if we use the new() expression directly, use redundant
//piecewise_construct instead.
repo.emplace(std::piecewise_construct,
std::forward_as_tuple(stdout_id),
std::forward_as_tuple(new stream_sink(sink_stream_type::stdout)));
repo.emplace(std::piecewise_construct,
std::forward_as_tuple(stderr_id),
std::forward_as_tuple(new stream_sink(sink_stream_type::stderr)));
}
/// Returns the instance of the sink that writes to stdout.
sink& get_stdout_sink()
{
auto s = repo.find(stdout_id);
assert(s && "stdout sink should always exist");
return *(s->get());
}
const sink& get_stdout_sink() const
{
const auto s = repo.find(stdout_id);
assert(s && "stdout sink should always exist");
return *(s->get());
}
/// Returns the instance of the sink that writes to stderr.
sink& get_stderr_sink()
{
auto s = repo.find(stderr_id);
assert(s && "stderr sink should always exist");
return *(s->get());
}
const sink& get_stderr_sink() const
{
const auto s = repo.find(stderr_id);
assert(s && "stderr sink should always exist");
return *(s->get());
}
/// Finds a sink with the specified id in the repository. Returns a pointer to
/// the sink, otherwise nullptr if not found.
sink* find(const std::string& id)
{
auto p = repo.find(id);
return (p) ? p->get() : nullptr;
}
const sink* find(const std::string& id) const
{
const auto p = repo.find(id);
return (p) ? p->get() : nullptr;
}
/// Returns an instance of a sink specified by the input arguments.
template <typename... Args>
sink& fetch_sink(Args&&... args)
{
return *repo.emplace(std::forward<Args>(args)...);
}
/// Returns a copy of the list of registered sinks.
std::vector<sink*> contents() const
{
auto repo_contents = repo.contents();
std::vector<sink*> data;
data.reserve(repo_contents.size());
for (const auto& s : repo_contents) {
data.push_back(s->get());
}
return data;
}
};
constexpr char sink_repository::stdout_id[];
constexpr char sink_repository::stderr_id[];
} // namespace srslog
#endif // SRSLOG_SINK_REPOSITORY_H

@ -69,6 +69,10 @@ public:
detail::error_string flush() override { return handler.flush(); }
protected:
/// Returns the current file index.
uint32_t get_file_index() const { return file_index; }
private:
/// Returns true when the sink has never written data to a file, otherwise
/// returns false.

@ -46,6 +46,8 @@ public:
{
assert(handle && "Invalid stream handle");
std::fwrite(buffer.data(), sizeof(char), buffer.size(), handle);
// We want to see the output instantly.
std::fflush(handle);
return {};
}

@ -21,77 +21,294 @@
#include "srslte/srslog/srslog.h"
#include "sinks/file_sink.h"
#include "sinks/stream_sink.h"
#include "srslog_instance.h"
using namespace srslog;
log_channel* srslog::create_log_channel(const std::string& id, sink& s)
/// Returns a copy of the input string with any occurrences of the '#' character
/// removed.
static std::string remove_sharp_chars(const std::string& s)
{
srslog_instance& instance = srslog_instance::get();
auto channel = std::unique_ptr<log_channel>(
new log_channel(id, s, instance.get_backend()));
auto p = instance.get_channel_repo().insert(id, std::move(channel));
std::string result(s);
result.erase(std::remove(result.begin(), result.end(), '#'), result.end());
return result;
}
return (p) ? p->get() : nullptr;
/// Generic argument function that fetches a log channel from the repository.
template <typename... Args>
static log_channel& fetch_log_channel_helper(const std::string& id, Args&&... args)
{
return srslog_instance::get().get_channel_repo().emplace(
std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(id, std::forward<Args>(args)...));
}
///
/// Log channel management function implementations.
///
log_channel* srslog::find_log_channel(const std::string& id)
{
auto p = srslog_instance::get().get_channel_repo().find(id);
return (p) ? p->get() : nullptr;
return srslog_instance::get().get_channel_repo().find(id);
}
sink* srslog::find_sink(const std::string& id)
log_channel& srslog::fetch_log_channel(const std::string& id)
{
auto p = srslog_instance::get().get_sink_repo().find(id);
return (p) ? p->get() : nullptr;
assert(!id.empty() && "Empty id string");
std::string clean_id = remove_sharp_chars(id);
srslog_instance& instance = srslog_instance::get();
return fetch_log_channel_helper(clean_id, instance.get_default_sink(), instance.get_backend());
}
/// Helper for creating and registering sinks.
template <typename T, typename... Args>
static sink* create_sink(const std::string& id, Args&&... args)
log_channel& srslog::fetch_log_channel(const std::string& id, sink& s, log_channel_config config)
{
auto new_sink = std::unique_ptr<sink>(new T(std::forward<Args>(args)...));
auto p =
srslog_instance::get().get_sink_repo().insert(id, std::move(new_sink));
assert(!id.empty() && "Empty id string");
return (p) ? p->get() : nullptr;
std::string clean_id = remove_sharp_chars(id);
srslog_instance& instance = srslog_instance::get();
return fetch_log_channel_helper(clean_id, s, instance.get_backend(), std::move(config));
}
sink* srslog::create_stdout_sink(const std::string& name)
///
/// Sink management function implementations.
///
void srslog::set_default_sink(sink& s)
{
return create_sink<stream_sink>(name, sink_stream_type::stdout);
srslog_instance::get().set_default_sink(s);
}
sink* srslog::create_stderr_sink(const std::string& name)
sink& srslog::get_default_sink()
{
return create_sink<stream_sink>(name, sink_stream_type::stderr);
return srslog_instance::get().get_default_sink();
}
sink* srslog::create_file_sink(const std::string& path, size_t max_size)
sink* srslog::find_sink(const std::string& id)
{
return create_sink<file_sink>(path, path, max_size);
return srslog_instance::get().get_sink_repo().find(id);
}
sink& srslog::fetch_stdout_sink()
{
return srslog_instance::get().get_sink_repo().get_stdout_sink();
}
sink& srslog::fetch_stderr_sink()
{
return srslog_instance::get().get_sink_repo().get_stderr_sink();
}
sink& srslog::fetch_file_sink(const std::string& path, size_t max_size)
{
assert(!path.empty() && "Empty path string");
std::string clean_path = remove_sharp_chars(path);
//:TODO: GCC5 or lower versions emits an error if we use the new() expression directly, use redundant
// piecewise_construct instead.
return srslog_instance::get().get_sink_repo().fetch_sink(std::piecewise_construct,
std::forward_as_tuple(clean_path),
std::forward_as_tuple(new file_sink(clean_path, max_size)));
}
///
/// Framework configuration and control function implementations.
///
void srslog::init()
{
srslog_instance::get().get_backend().start();
}
detail::any* srslog::detail::create_logger(const std::string& id,
detail::any&& logger)
void srslog::flush()
{
return srslog_instance::get().get_logger_repo().insert(id, std::move(logger));
srslog_instance& instance = srslog_instance::get();
// Nothing to do when the backend is not running yet.
if (!instance.get_backend().is_running()) {
return;
}
// The backend will set this shared variable when done.
detail::shared_variable<bool> completion_flag(false);
detail::log_entry cmd;
cmd.flush_cmd = std::unique_ptr<detail::flush_backend_cmd>(
new detail::flush_backend_cmd{completion_flag, instance.get_sink_repo().contents()});
instance.get_backend().push(std::move(cmd));
// Block the caller thread until we are signaled that the flush is completed.
while (!completion_flag) {
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
void srslog::set_error_handler(error_handler handler)
{
srslog_instance::get().set_error_handler(std::move(handler));
}
///
/// Logger management function implementations.
///
detail::any* srslog::detail::find_logger(const std::string& id)
{
return srslog_instance::get().get_logger_repo().find(id);
}
void srslog::set_error_handler(error_handler handler)
detail::any* srslog::detail::fetch_logger(const std::string& id, detail::any&& logger)
{
srslog_instance::get().set_error_handler(std::move(handler));
assert(!id.empty() && "Empty id string");
return &srslog_instance::get().get_logger_repo().emplace(id, std::move(logger));
}
/// Builds a logger name out of the id and tag.
static std::string build_logger_name(const std::string& id, char tag)
{
return fmt::format("{}#{}", id, tag);
}
/// Fetches a logger with all its log channels.
static basic_logger& fetch_basic_logger_helper(const std::string& id, sink& s, bool should_print_context)
{
static constexpr char basic_logger_chan_tags[] = {'E', 'W', 'I', 'D'};
srslog_instance& instance = srslog_instance::get();
// User created log channels cannot have ids with a # character, encode the
// ids here with a # to ensure all channels are unique.
log_channel& error =
fetch_log_channel_helper(build_logger_name(id, basic_logger_chan_tags[0]),
s,
instance.get_backend(),
log_channel_config{id, basic_logger_chan_tags[0], should_print_context});
log_channel& warning =
fetch_log_channel_helper(build_logger_name(id, basic_logger_chan_tags[1]),
s,
instance.get_backend(),
log_channel_config{id, basic_logger_chan_tags[1], should_print_context});
log_channel& info = fetch_log_channel_helper(build_logger_name(id, basic_logger_chan_tags[2]),
s,
instance.get_backend(),
log_channel_config{id, basic_logger_chan_tags[2], should_print_context});
log_channel& debug =
fetch_log_channel_helper(build_logger_name(id, basic_logger_chan_tags[3]),
s,
instance.get_backend(),
log_channel_config{id, basic_logger_chan_tags[3], should_print_context});
return fetch_logger<basic_logger>(id, error, warning, info, debug);
}
basic_logger& srslog::fetch_basic_logger(const std::string& id, bool should_print_context)
{
assert(!id.empty() && "Empty id string");
return fetch_basic_logger_helper(id, srslog_instance::get().get_default_sink(), should_print_context);
}
basic_logger& srslog::fetch_basic_logger(const std::string& id, sink& s, bool should_print_context)
{
assert(!id.empty() && "Empty id string");
return fetch_basic_logger_helper(id, s, should_print_context);
}
///
/// Deprecated functions to be removed.
///
/// Creates and registers a log channel. Returns a pointer to the newly created
/// channel on success, otherwise nullptr.
static log_channel* create_and_register_log_channel(const std::string& id, sink& s)
{
assert(!id.empty() && "Empty id string");
srslog_instance& instance = srslog_instance::get();
auto& p = instance.get_channel_repo().emplace(
std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(id, s, instance.get_backend()));
return &p;
}
static log_channel* create_and_register_log_channel(const std::string& id, log_channel_config config, sink& s)
{
assert(!id.empty() && "Empty id string");
srslog_instance& instance = srslog_instance::get();
auto& p =
instance.get_channel_repo().emplace(std::piecewise_construct,
std::forward_as_tuple(id),
std::forward_as_tuple(id, s, instance.get_backend(), std::move(config)));
return &p;
}
/// Returns true if the input string contains a sharp character, otherwise
/// returns false.
static bool contains_sharp_char(const std::string& s)
{
return s.find('#') != std::string::npos;
}
log_channel* srslog::create_log_channel(const std::string& id, sink& s)
{
if (contains_sharp_char(id)) {
return nullptr;
}
return create_and_register_log_channel(id, s);
}
sink* srslog::create_stdout_sink(const std::string& name)
{
return &srslog_instance::get().get_sink_repo().get_stdout_sink();
}
sink* srslog::create_stderr_sink(const std::string& name)
{
return &srslog_instance::get().get_sink_repo().get_stderr_sink();
}
sink* srslog::create_file_sink(const std::string& path, size_t max_size)
{
//:TODO: GCC5 or lower versions emits an error if we use the new() expression directly, use redundant
// piecewise_construct instead.
return &srslog_instance::get().get_sink_repo().fetch_sink(
std::piecewise_construct, std::forward_as_tuple(path), std::forward_as_tuple(new file_sink(path, max_size)));
}
basic_logger* srslog::create_basic_logger(const std::string& id, sink& s, bool should_print_context)
{
assert(!id.empty() && "Empty id string");
static constexpr char basic_logger_chan_tags[] = {'E', 'W', 'I', 'D'};
auto& logger_repo = srslog_instance::get().get_logger_repo();
// Nothing to do when the logger already exists.
if (logger_repo.find(id)) {
return nullptr;
}
// User created log channels cannot have ids with a # character, encode the
// ids here with a # to ensure all channel creations will be successful
// without any id clashes.
log_channel* error = create_and_register_log_channel(
build_logger_name(id, basic_logger_chan_tags[0]), {id, basic_logger_chan_tags[0], should_print_context}, s);
assert(error && "Could not create channel");
log_channel* warning = create_and_register_log_channel(
build_logger_name(id, basic_logger_chan_tags[1]), {id, basic_logger_chan_tags[1], should_print_context}, s);
assert(warning && "Could not create channel");
log_channel* info = create_and_register_log_channel(
build_logger_name(id, basic_logger_chan_tags[2]), {id, basic_logger_chan_tags[2], should_print_context}, s);
assert(info && "Could not create channel");
log_channel* debug = create_and_register_log_channel(
build_logger_name(id, basic_logger_chan_tags[3]), {id, basic_logger_chan_tags[3], should_print_context}, s);
assert(debug && "Could not create channel");
return create_logger<basic_logger>(id, *error, *warning, *info, *debug);
}

@ -0,0 +1,196 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#include "srslte/srslog/srslog_c.h"
#include "srslte/srslog/srslog.h"
#include <cassert>
#include <cstdarg>
using namespace srslog;
template <typename To, typename From>
static inline To* c_cast(From* x)
{
return reinterpret_cast<To*>(x);
}
/// Helper to format the input argument list writing it into a channel.
static void log_to(log_channel& c, const char* fmt, std::va_list args)
{
char buffer[1024];
std::vsnprintf(buffer, sizeof(buffer) - 1, fmt, args);
c(buffer);
}
void srslog_init(void)
{
init();
}
void srslog_set_default_sink(srslog_sink* s)
{
assert(s && "Expected a valid sink");
set_default_sink(*c_cast<sink>(s));
}
srslog_sink* srslog_get_default_sink(void)
{
return c_cast<srslog_sink>(&get_default_sink());
}
srslog_log_channel* srslog_fetch_log_channel(const char* id)
{
return c_cast<srslog_log_channel>(&fetch_log_channel(id));
}
srslog_log_channel* srslog_find_log_channel(const char* id)
{
return c_cast<srslog_log_channel>(find_log_channel(id));
}
void srslog_set_log_channel_enabled(srslog_log_channel* channel,
srslog_bool enabled)
{
assert(channel && "Expected a valid channel");
c_cast<log_channel>(channel)->set_enabled(enabled);
}
srslog_bool srslog_is_log_channel_enabled(srslog_log_channel* channel)
{
assert(channel && "Expected a valid channel");
return c_cast<log_channel>(channel)->enabled();
}
const char* srslog_get_log_channel_id(srslog_log_channel* channel)
{
assert(channel && "Expected a valid channel");
return c_cast<log_channel>(channel)->id().c_str();
}
void srslog_log(srslog_log_channel* channel, const char* fmt, ...)
{
assert(channel && "Expected a valid channel");
std::va_list args;
va_start(args, fmt);
log_to(*c_cast<log_channel>(channel), fmt, args);
va_end(args);
}
srslog_logger* srslog_fetch_default_logger(const char* id)
{
return c_cast<srslog_logger>(&fetch_basic_logger(id));
}
srslog_logger* srslog_find_default_logger(const char* id)
{
return c_cast<srslog_logger>(find_logger<basic_logger>(id));
}
void srslog_debug(srslog_logger* log, const char* fmt, ...)
{
assert(log && "Expected a valid logger");
std::va_list args;
va_start(args, fmt);
log_to(c_cast<basic_logger>(log)->debug, fmt, args);
va_end(args);
}
void srslog_info(srslog_logger* log, const char* fmt, ...)
{
assert(log && "Expected a valid logger");
std::va_list args;
va_start(args, fmt);
log_to(c_cast<basic_logger>(log)->info, fmt, args);
va_end(args);
}
void srslog_warning(srslog_logger* log, const char* fmt, ...)
{
assert(log && "Expected a valid logger");
std::va_list args;
va_start(args, fmt);
log_to(c_cast<basic_logger>(log)->warning, fmt, args);
va_end(args);
}
void srslog_error(srslog_logger* log, const char* fmt, ...)
{
assert(log && "Expected a valid logger");
std::va_list args;
va_start(args, fmt);
log_to(c_cast<basic_logger>(log)->error, fmt, args);
va_end(args);
}
const char* srslog_get_logger_id(srslog_logger* log)
{
assert(log && "Expected a valid logger");
return c_cast<basic_logger>(log)->id().c_str();
}
/// Translate the C API level enum to basic_levels.
static basic_levels convert_c_enum_to_basic_levels(srslog_log_levels lvl)
{
switch (lvl) {
case srslog_lvl_debug:
return basic_levels::debug;
case srslog_lvl_info:
return basic_levels::info;
case srslog_lvl_warning:
return basic_levels ::warning;
case srslog_lvl_error:
return basic_levels::error;
}
assert(false && "Invalid enum value");
return basic_levels::error;
}
void srslog_set_logger_level(srslog_logger* log, srslog_log_levels lvl)
{
assert(log && "Expected a valid logger");
c_cast<basic_logger>(log)->set_level(convert_c_enum_to_basic_levels(lvl));
}
srslog_sink* srslog_find_sink(const char* id)
{
return c_cast<srslog_sink>(find_sink(id));
}
srslog_sink* srslog_fetch_stdout_sink(void)
{
return c_cast<srslog_sink>(&fetch_stdout_sink());
}
srslog_sink* srslog_fetch_stderr_sink(void)
{
return c_cast<srslog_sink>(&fetch_stderr_sink());
}
srslog_sink* srslog_fetch_file_sink(const char* path, size_t max_size)
{
return c_cast<srslog_sink>(&fetch_file_sink(path, max_size));
}

@ -23,17 +23,16 @@
#define SRSLOG_SRSLOG_INSTANCE_H
#include "log_backend_impl.h"
#include "object_repository.h"
#include "sink_repository.h"
#include "srslte/srslog/detail/support/any.h"
#include "srslte/srslog/log_channel.h"
#include "srslte/srslog/sink.h"
namespace srslog {
/// Singleton of the framework containing all the required classes.
class srslog_instance
{
srslog_instance() = default;
srslog_instance() { default_sink = &sink_repo.get_stdout_sink(); }
public:
srslog_instance(const srslog_instance& other) = delete;
@ -52,15 +51,13 @@ public:
const logger_repo_type& get_logger_repo() const { return logger_repo; }
/// Log channel repository accessor.
using channel_repo_type =
object_repository<std::string, std::unique_ptr<log_channel>>;
using channel_repo_type = object_repository<std::string, log_channel>;
channel_repo_type& get_channel_repo() { return channel_repo; }
const channel_repo_type& get_channel_repo() const { return channel_repo; }
/// Sink repository accessor.
using sink_repo_type = object_repository<std::string, std::unique_ptr<sink>>;
sink_repo_type& get_sink_repo() { return sink_repo; }
const sink_repo_type& get_sink_repo() const { return sink_repo; }
sink_repository& get_sink_repo() { return sink_repo; }
const sink_repository& get_sink_repo() const { return sink_repo; }
/// Backend accessor.
detail::log_backend& get_backend() { return backend; }
@ -72,13 +69,20 @@ public:
backend.set_error_handler(std::move(callback));
}
/// Set the specified sink as the default one.
void set_default_sink(sink& s) { default_sink = &s; }
/// Returns the default sink.
sink& get_default_sink() { return *default_sink; }
private:
/// NOTE: The order of declaration of each member is important here for proper
/// destruction.
sink_repo_type sink_repo;
sink_repository sink_repo;
log_backend_impl backend;
channel_repo_type channel_repo;
logger_repo_type logger_repo;
detail::shared_variable<sink*> default_sink{nullptr};
};
} // namespace srslog

@ -48,3 +48,13 @@ add_executable(file_utils_test file_utils_test.cpp)
target_include_directories(file_utils_test PUBLIC ../../)
target_link_libraries(file_utils_test srslog)
add_test(file_utils_test file_utils_test)
add_executable(tracer_test event_trace_test.cpp)
add_definitions(-DENABLE_SRSLOG_EVENT_TRACE)
target_link_libraries(tracer_test srslog)
add_test(tracer_test tracer_test)
add_executable(formatter_test formatter_test.cpp)
target_include_directories(formatter_test PUBLIC ../../)
target_link_libraries(formatter_test srslog)
add_test(formatter_test formatter_test)

@ -0,0 +1,107 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#include "srslte/srslog/event_trace.h"
#include "srslte/srslog/log_channel.h"
#include "srslte/srslog/sink.h"
#include "testing_helpers.h"
using namespace srslog;
namespace {
/// A Dummy implementation of a sink.
class sink_dummy : public sink
{
public:
detail::error_string write(detail::memory_buffer buffer) override
{
return {};
}
detail::error_string flush() override { return {}; }
};
/// A Spy implementation of a log backend. Tests can query if the push method
/// has been invoked.
class backend_spy : public detail::log_backend
{
public:
void start() override {}
void push(detail::log_entry&& entry) override
{
std::string result = fmt::vsprintf(entry.fmtstring, std::move(entry.store));
++count;
}
bool is_running() const override { return true; }
void reset() { count = 0; }
unsigned push_invocation_count() const { return count; }
private:
unsigned count = 0;
};
} // namespace
static bool
when_tracing_with_duration_event_then_two_events_are_generated(backend_spy& spy)
{
trace_duration_begin("a", "b");
ASSERT_EQ(spy.push_invocation_count(), 1);
trace_duration_end("a", "b");
ASSERT_EQ(spy.push_invocation_count(), 2);
return true;
}
static bool
when_tracing_with_complete_event_then_one_event_is_generated(backend_spy& spy)
{
{
trace_complete_event("a", "b");
}
ASSERT_EQ(spy.push_invocation_count(), 1);
return true;
}
int main()
{
sink_dummy s;
backend_spy backend;
log_channel c("test", s, backend);
// Inject our spy into the framework.
event_trace_init(c);
TEST_FUNCTION(when_tracing_with_duration_event_then_two_events_are_generated,
backend);
backend.reset();
TEST_FUNCTION(when_tracing_with_complete_event_then_one_event_is_generated,
backend);
return 0;
}

@ -19,13 +19,13 @@
*
*/
#include "src/srslog/sinks/file_sink.h"
#include "file_test_utils.h"
#include "src/srslog/sinks/file_sink.h"
#include "testing_helpers.h"
using namespace srslog;
static const char* const log_filename = "testfile.log";
static constexpr char log_filename[] = "file_sink_test.log";
static bool when_data_is_written_to_file_then_contents_are_valid()
{
@ -48,6 +48,18 @@ static bool when_data_is_written_to_file_then_contents_are_valid()
return true;
}
/// A Test-Specific Subclass of file_sink. This subclass provides public access
/// to the data members of the parent class.
class file_sink_subclass : public file_sink
{
public:
file_sink_subclass(std::string name, size_t max_size) :
file_sink(std::move(name), max_size)
{}
uint32_t get_num_of_files() const { return get_file_index(); }
};
static bool when_data_written_exceeds_size_threshold_then_new_file_is_created()
{
std::string filename0 =
@ -59,7 +71,7 @@ static bool when_data_written_exceeds_size_threshold_then_new_file_is_created()
file_test_utils::scoped_file_deleter deleter = {
filename0, filename1, filename2};
file_sink file(log_filename, 5001);
file_sink_subclass file(log_filename, 5001);
// Build a 1000 byte entry.
std::string entry(1000, 'a');
@ -71,16 +83,14 @@ static bool when_data_written_exceeds_size_threshold_then_new_file_is_created()
file.flush();
// Only one file should exist.
ASSERT_EQ(file_test_utils::file_exists(filename0), true);
ASSERT_EQ(file_test_utils::file_exists(filename1), false);
ASSERT_EQ(file.get_num_of_files(), 1);
// Trigger a file rotation.
file.write(detail::memory_buffer(entry));
file.flush();
// A second file should be created.
ASSERT_EQ(file_test_utils::file_exists(filename0), true);
ASSERT_EQ(file_test_utils::file_exists(filename1), true);
ASSERT_EQ(file.get_num_of_files(), 2);
// Fill in the second file with 4000 bytes, one byte less than the threshold.
for (unsigned i = 0; i != 4; ++i) {
@ -88,19 +98,15 @@ static bool when_data_written_exceeds_size_threshold_then_new_file_is_created()
}
file.flush();
// Two file should exist, third should not be created yet.
ASSERT_EQ(file_test_utils::file_exists(filename0), true);
ASSERT_EQ(file_test_utils::file_exists(filename1), true);
ASSERT_EQ(file_test_utils::file_exists(filename2), false);
// Two files should exist, third should not be created yet.
ASSERT_EQ(file.get_num_of_files(), 2);
// Trigger a file rotation.
file.write(detail::memory_buffer(entry));
file.flush();
// Three files should exist.
ASSERT_EQ(file_test_utils::file_exists(filename0), true);
ASSERT_EQ(file_test_utils::file_exists(filename1), true);
ASSERT_EQ(file_test_utils::file_exists(filename2), true);
ASSERT_EQ(file.get_num_of_files(), 3);
return true;
}

@ -19,14 +19,14 @@
*
*/
#include "src/srslog/sinks/file_utils.h"
#include "file_test_utils.h"
#include "src/srslog/sinks/file_utils.h"
#include "testing_helpers.h"
using namespace srslog;
static const char* const log_filename = "testfile.log";
static const char* const log_filename2 = "testfile2.log";
static constexpr char log_filename[] = "file_utils_test.log";
static constexpr char log_filename2[] = "file_utils_test2.log";
static bool filename_extension_split_test()
{

@ -0,0 +1,121 @@
/*
* Copyright 2013-2020 Software Radio Systems Limited
*
* This file is part of srsLTE.
*
* srsLTE is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsLTE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#include "src/srslog/formatter.h"
#include "testing_helpers.h"
#include <numeric>
using namespace srslog;
/// Helper to build a log entry.
static detail::log_entry build_log_entry()
{
// Create a time point 50000us from epoch.
using tp_ty = std::chrono::time_point<std::chrono::high_resolution_clock>;
tp_ty tp(std::chrono::microseconds(50000));
fmt::dynamic_format_arg_store<fmt::printf_context> store;
store.push_back(88);
return {nullptr, tp, {10, true}, "Text %d", std::move(store), "ABC", 'Z'};
}
static bool when_fully_filled_log_entry_then_result_everything_is_formatted()
{
std::string result = format_log_entry_to_text(build_log_entry());
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();
entry.log_name = "";
std::string result = format_log_entry_to_text(std::move(entry));
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();
entry.log_tag = '\0';
std::string result = format_log_entry_to_text(std::move(entry));
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();
entry.context.enabled = false;
std::string result = format_log_entry_to_text(std::move(entry));
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();
entry.hex_dump.resize(20);
std::iota(entry.hex_dump.begin(), entry.hex_dump.end(), 0);
std::string result = format_log_entry_to_text(std::move(entry));
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;
}
int main()
{
TEST_FUNCTION(
when_fully_filled_log_entry_then_result_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);
return 0;
}

@ -29,9 +29,9 @@ static bool when_backend_is_started_then_is_started_returns_true()
{
log_backend_impl backend;
ASSERT_EQ(backend.is_started(), false);
ASSERT_EQ(backend.is_running(), false);
backend.start();
ASSERT_EQ(backend.is_started(), true);
ASSERT_EQ(backend.is_running(), true);
return true;
}
@ -43,7 +43,7 @@ static bool when_backend_is_started_and_stopped_then_is_started_returns_false()
backend.start();
backend.stop();
ASSERT_EQ(backend.is_started(), false);
ASSERT_EQ(backend.is_running(), false);
return true;
}
@ -80,7 +80,7 @@ static bool when_backend_is_not_started_then_pushed_log_entries_are_ignored()
sink_spy spy;
log_backend_impl backend;
detail::log_entry entry = {&spy, "Test"};
detail::log_entry entry = {&spy};
backend.push(std::move(entry));
ASSERT_EQ(spy.write_invocation_count(), 0);
@ -88,19 +88,16 @@ static bool when_backend_is_not_started_then_pushed_log_entries_are_ignored()
return true;
}
/// Helper to build a log entry.
/// Builds a basic log entry.
static detail::log_entry build_log_entry(sink* s)
{
// Populate store with some arguments.
fmt::dynamic_format_arg_store<fmt::printf_context> store;
store.push_back(3);
store.push_back("Hello");
store.push_back(3.14);
using tp_ty = std::chrono::time_point<std::chrono::high_resolution_clock>;
tp_ty tp;
// Send the log entry to the backend.
detail::log_entry entry = {s, "Arg1:%u Arg2:%s Arg3:%.2f", std::move(store)};
fmt::dynamic_format_arg_store<fmt::printf_context> store;
store.push_back(88);
return entry;
return {s, tp, {0, false}, "Text %d", std::move(store), "", '\0'};
}
static bool when_backend_is_started_then_pushed_log_entries_are_sent_to_sink()
@ -116,7 +113,7 @@ static bool when_backend_is_started_then_pushed_log_entries_are_sent_to_sink()
backend.stop();
ASSERT_EQ(spy.write_invocation_count(), 1);
ASSERT_EQ(spy.received_buffer(), "Arg1:3 Arg2:Hello Arg3:3.14");
ASSERT_NE(spy.received_buffer().find("Text 88"), std::string::npos);
return true;
}

@ -46,6 +46,8 @@ public:
void start() override {}
void push(detail::log_entry&& entry) override {}
bool is_running() const override { return true; }
};
} // namespace
@ -102,6 +104,8 @@ public:
++count;
}
bool is_running() const override { return true; }
unsigned push_invocation_count() const { return count; }
const detail::log_entry& last_entry() const { return e; }
@ -120,12 +124,10 @@ when_logging_in_log_channel_then_log_entry_is_pushed_into_the_backend()
sink_dummy s;
log_channel log("id", s, backend);
std::string fmtstring = "Arg1: %u Arg2: %s";
std::string fmtstring = "test";
log(fmtstring, 42, "Hello");
ASSERT_EQ(backend.push_invocation_count(), 1);
ASSERT_NE(backend.last_entry().s, nullptr);
ASSERT_EQ(backend.last_entry().fmtstring, fmtstring);
return true;
}
@ -137,7 +139,7 @@ static bool when_logging_in_disabled_log_channel_then_log_entry_is_ignored()
log_channel log("id", s, backend);
log.set_enabled(false);
std::string fmtstring = "Arg1: %u Arg2: %s";
std::string fmtstring = "test";
log(fmtstring, 42, "Hello");
ASSERT_EQ(backend.push_invocation_count(), 0);
@ -145,6 +147,99 @@ static bool when_logging_in_disabled_log_channel_then_log_entry_is_ignored()
return true;
}
static bool when_logging_then_filled_in_log_entry_is_pushed_into_the_backend()
{
backend_spy backend;
sink_dummy s;
std::string name = "name";
char tag = 'A';
log_channel log("id", s, backend, {name, tag, true});
std::string fmtstring = "test";
uint32_t ctx = 10;
log.set_context(ctx);
log(fmtstring, 42, "Hello");
ASSERT_EQ(backend.push_invocation_count(), 1);
const detail::log_entry& entry = backend.last_entry();
ASSERT_NE(entry.tp.time_since_epoch().count(), 0);
ASSERT_EQ(entry.context.value, ctx);
ASSERT_EQ(entry.context.enabled, true);
ASSERT_EQ(entry.fmtstring, fmtstring);
ASSERT_EQ(entry.log_name, name);
ASSERT_EQ(entry.log_tag, tag);
return true;
}
static bool
when_logging_with_hex_dump_then_filled_in_log_entry_is_pushed_into_the_backend()
{
backend_spy backend;
sink_dummy s;
std::string name = "name";
char tag = 'A';
log_channel log("id", s, backend, {name, tag, true});
std::string fmtstring = "test";
uint32_t ctx = 4;
log.set_context(ctx);
log.set_hex_dump_max_size(4);
uint8_t hex[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
log(hex, sizeof(hex), fmtstring, 42, "Hello");
ASSERT_EQ(backend.push_invocation_count(), 1);
const detail::log_entry& entry = backend.last_entry();
ASSERT_NE(entry.tp.time_since_epoch().count(), 0);
ASSERT_EQ(entry.context.value, ctx);
ASSERT_EQ(entry.context.enabled, true);
ASSERT_EQ(entry.fmtstring, fmtstring);
ASSERT_EQ(entry.log_name, name);
ASSERT_EQ(entry.log_tag, tag);
ASSERT_EQ(entry.hex_dump.size(), 4);
ASSERT_EQ(
std::equal(entry.hex_dump.begin(), entry.hex_dump.end(), std::begin(hex)),
true);
return true;
}
static bool
when_hex_array_length_is_less_than_hex_log_max_size_then_array_length_is_used()
{
backend_spy backend;
sink_dummy s;
std::string name = "name";
char tag = 'A';
log_channel log("id", s, backend);
std::string fmtstring = "test";
log.set_hex_dump_max_size(10);
uint8_t hex[] = {0, 1, 2};
log(hex, sizeof(hex), fmtstring, 42, "Hello");
ASSERT_EQ(backend.push_invocation_count(), 1);
const detail::log_entry& entry = backend.last_entry();
ASSERT_EQ(entry.hex_dump.size(), 3);
ASSERT_EQ(
std::equal(entry.hex_dump.begin(), entry.hex_dump.end(), std::begin(hex)),
true);
return true;
}
int main()
{
TEST_FUNCTION(when_log_channel_is_created_then_id_matches_expected_value);
@ -153,6 +248,12 @@ int main()
TEST_FUNCTION(
when_logging_in_log_channel_then_log_entry_is_pushed_into_the_backend);
TEST_FUNCTION(when_logging_in_disabled_log_channel_then_log_entry_is_ignored);
TEST_FUNCTION(
when_logging_then_filled_in_log_entry_is_pushed_into_the_backend);
TEST_FUNCTION(
when_logging_with_hex_dump_then_filled_in_log_entry_is_pushed_into_the_backend);
TEST_FUNCTION(
when_hex_array_length_is_less_than_hex_log_max_size_then_array_length_is_used);
return 0;
}

@ -25,7 +25,7 @@
using namespace srslog;
static const char* const logger_id = "TestLogger";
static constexpr char logger_id[] = "TestLogger";
namespace {
@ -48,6 +48,8 @@ public:
void start() override {}
void push(detail::log_entry&& entry) override {}
bool is_running() const override { return true; }
};
/// Definition of a three level logger

@ -25,102 +25,93 @@
using namespace srslog;
static const char* const test_id = "Test";
static constexpr char test_id1[] = "Test1";
static constexpr char test_id2[] = "Test2";
//:FIXME: older compilers may not have defined this C++11 trait.
//:TODO: older compilers may not have defined this C++11 trait.
#if (defined(__clang__) && (__clang_major__ >= 5)) || \
(defined(__GNUG__) && (__GNUC__ >= 5))
static_assert(std::is_trivially_copyable<detail::memory_buffer>::value,
"Expected to be trivially copyable");
#endif
namespace {
/// A Dummy implementation of a sink.
class sink_dummy : public sink
{
public:
detail::error_string write(detail::memory_buffer buffer) override
{
return {};
}
detail::error_string flush() override { return {}; }
};
} // namespace
static bool when_no_channel_exists_then_channel_is_created()
static bool when_fetching_channel_then_channel_instance_is_returned()
{
sink_dummy s;
log_channel* channel = create_log_channel(test_id, s);
log_channel& channel1 = fetch_log_channel(test_id1);
log_channel& channel2 = fetch_log_channel(test_id2, fetch_stdout_sink(), {});
ASSERT_NE(channel, nullptr);
ASSERT_EQ(channel->id(), test_id);
ASSERT_EQ(channel1.id(), test_id1);
ASSERT_EQ(channel2.id(), test_id2);
return true;
}
static bool when_channel_already_exists_then_nullptr_is_returned()
static bool when_valid_id_is_passed_then_channel_is_found()
{
sink_dummy s;
log_channel* channel = create_log_channel(test_id, s);
log_channel* channel1 = find_log_channel(test_id1);
log_channel* channel2 = find_log_channel(test_id2);
ASSERT_NE(channel1, nullptr);
ASSERT_EQ(channel1->id(), test_id1);
ASSERT_EQ(channel, nullptr);
ASSERT_NE(channel2, nullptr);
ASSERT_EQ(channel2->id(), test_id2);
return true;
}
static bool when_valid_id_is_passed_then_channel_is_found()
static bool when_non_existent_id_is_passed_then_nothing_is_found()
{
log_channel* c = find_log_channel(test_id);
ASSERT_NE(c, nullptr);
ASSERT_EQ(c->id(), test_id);
ASSERT_EQ(find_log_channel("non_existent_channel"), nullptr);
return true;
}
static bool when_invalid_id_is_passed_then_nothing_is_found()
static bool
when_id_with_sharps_is_passed_then_channel_is_fetched_with_clean_id()
{
ASSERT_EQ(find_log_channel("invalid"), nullptr);
log_channel& channel1 = fetch_log_channel("a1#a");
log_channel& channel2 = fetch_log_channel("a2#a", fetch_stdout_sink(), {});
ASSERT_EQ(channel1.id(), "a1a");
ASSERT_EQ(channel2.id(), "a2a");
return true;
}
static const char* const logger_id = "TestLogger";
static constexpr char logger_id[] = "TestLogger";
static constexpr char basic_logger_id1[] = "BasicTestLogger";
static constexpr char basic_logger_id2[] = "BasicTestLogger2";
static bool when_no_logger_exists_then_logger_is_created()
static bool when_fetching_logger_then_logger_instance_is_returned()
{
sink_dummy s;
log_channel& error = *create_log_channel("logger#error", s);
log_channel& warning = *create_log_channel("logger#warning", s);
log_channel& info = *create_log_channel("logger#info", s);
basic_logger* logger =
create_logger<basic_logger>(logger_id, error, warning, info);
log_channel& error = fetch_log_channel("logger.error");
log_channel& warning = fetch_log_channel("logger.warning");
log_channel& info = fetch_log_channel("logger.info");
log_channel& debug = fetch_log_channel("logger.debug");
auto& logger =
fetch_logger<basic_logger>(logger_id, error, warning, info, debug);
ASSERT_NE(logger, nullptr);
ASSERT_EQ(logger->id(), logger_id);
ASSERT_EQ(logger.id(), logger_id);
return true;
}
static bool when_logger_already_exists_then_nullptr_is_returned()
static bool when_fetching_basic_logger_then_basic_logger_instance_is_returned()
{
log_channel& error = *find_log_channel("logger#error");
log_channel& warning = *find_log_channel("logger#warning");
log_channel& info = *find_log_channel("logger#info");
basic_logger* logger =
create_logger<basic_logger>(logger_id, error, warning, info);
basic_logger& logger1 = fetch_basic_logger(basic_logger_id1);
basic_logger& logger2 = fetch_basic_logger(basic_logger_id2, fetch_stdout_sink());
ASSERT_EQ(logger, nullptr);
ASSERT_EQ(logger1.id(), basic_logger_id1);
ASSERT_EQ(logger2.id(), basic_logger_id2);
return true;
}
static bool when_valid_id_and_type_is_passed_then_logger_is_found()
{
basic_logger* l = find_logger<basic_logger>(logger_id);
auto* l = find_logger<basic_logger>(logger_id);
ASSERT_NE(l, nullptr);
ASSERT_EQ(l->id(), logger_id);
@ -156,58 +147,66 @@ static bool when_valid_id_with_invalid_type_is_passed_then_no_logger_is_found()
return true;
}
static bool when_no_sink_exists_then_sink_is_created()
static constexpr char file_name[] = "file_fetch_test.txt";
static bool when_file_sink_is_fetched_then_sink_instance_is_returned()
{
sink* s = create_stdout_sink();
fetch_file_sink(file_name);
sink* s = find_sink(file_name);
ASSERT_NE(s, nullptr);
return true;
}
static bool when_sink_already_exists_then_nullptr_is_returned()
static bool when_invalid_id_is_passed_then_no_sink_is_found()
{
sink* s = create_stdout_sink();
ASSERT_EQ(s, nullptr);
ASSERT_EQ(find_sink("invalid"), nullptr);
return true;
}
static bool when_valid_id_is_passed_then_sink_is_found()
static bool when_no_installed_default_sink_then_stdout_sink_is_used()
{
sink* s = find_sink("stdout");
sink& default_sink = get_default_sink();
ASSERT_NE(s, nullptr);
ASSERT_EQ(&default_sink, &fetch_stdout_sink());
return true;
}
static bool when_invalid_id_is_passed_then_no_sink_is_found()
static bool
when_setting_stderr_as_default_then_get_default_returns_stderr_sink()
{
ASSERT_EQ(find_sink("invalid"), nullptr);
set_default_sink(fetch_stderr_sink());
sink& default_sink = get_default_sink();
ASSERT_EQ(&default_sink, &fetch_stderr_sink());
return true;
}
int main()
{
TEST_FUNCTION(when_no_channel_exists_then_channel_is_created);
TEST_FUNCTION(when_channel_already_exists_then_nullptr_is_returned);
TEST_FUNCTION(when_fetching_channel_then_channel_instance_is_returned);
TEST_FUNCTION(when_valid_id_is_passed_then_channel_is_found);
TEST_FUNCTION(when_invalid_id_is_passed_then_nothing_is_found);
TEST_FUNCTION(when_no_logger_exists_then_logger_is_created);
TEST_FUNCTION(when_logger_already_exists_then_nullptr_is_returned);
TEST_FUNCTION(when_non_existent_id_is_passed_then_nothing_is_found);
TEST_FUNCTION(
when_id_with_sharps_is_passed_then_channel_is_fetched_with_clean_id);
TEST_FUNCTION(when_fetching_logger_then_logger_instance_is_returned);
TEST_FUNCTION(
when_fetching_basic_logger_then_basic_logger_instance_is_returned);
TEST_FUNCTION(when_valid_id_and_type_is_passed_then_logger_is_found);
TEST_FUNCTION(
when_invalid_id_with_valid_type_is_passed_then_no_logger_is_found);
TEST_FUNCTION(when_invalid_id_and_type_is_passed_then_no_logger_is_found);
TEST_FUNCTION(
when_valid_id_with_invalid_type_is_passed_then_no_logger_is_found);
TEST_FUNCTION(when_no_sink_exists_then_sink_is_created);
TEST_FUNCTION(when_sink_already_exists_then_nullptr_is_returned);
TEST_FUNCTION(when_valid_id_is_passed_then_sink_is_found);
TEST_FUNCTION(when_file_sink_is_fetched_then_sink_instance_is_returned);
TEST_FUNCTION(when_invalid_id_is_passed_then_no_sink_is_found);
TEST_FUNCTION(when_no_installed_default_sink_then_stdout_sink_is_used);
TEST_FUNCTION(
when_setting_stderr_as_default_then_get_default_returns_stderr_sink);
return 0;
}

@ -25,9 +25,9 @@
#include <cstdio>
/// Invokes the given test function and printing test results to stdout.
#define TEST_FUNCTION(func) \
#define TEST_FUNCTION(func, ...) \
do { \
if (!func()) { \
if (!func(__VA_ARGS__)) { \
std::printf("Test \"%s\" FAILED! - %s:%u\n", #func, __FILE__, __LINE__); \
return -1; \
} else { \

@ -75,6 +75,8 @@ uint8_t deactivate_eps_bearer_pdu[] = {0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62,
uint16 mcc = 61441;
uint16 mnc = 65281;
static srslte::logger* g_logger = nullptr;
using namespace srslte;
namespace srslte {
@ -271,14 +273,6 @@ int mme_attach_request_test()
srslte::log_filter usim_log("USIM");
srslte::log_filter gw_log("GW");
// Setup logging.
srslog::sink *log_sink = srslog::find_sink("stdout");
srslog::log_channel* chan = srslog::create_log_channel("mme_attach_request_test", *log_sink);
srslte::srslog_wrapper log_wrapper(*chan);
// Start the log backend.
srslog::init();
rrc_log.set_level(srslte::LOG_LEVEL_DEBUG);
usim_log.set_level(srslte::LOG_LEVEL_DEBUG);
gw_log.set_level(srslte::LOG_LEVEL_DEBUG);
@ -316,8 +310,7 @@ int mme_attach_request_test()
gw_args.log.gw_level = "debug";
gw_args.log.gw_hex_limit = 100000;
srslte::logger* logger = &log_wrapper;
gw.init(gw_args, logger, &stack);
gw.init(gw_args, g_logger, &stack);
stack.init(&nas);
usleep(5000); // Wait for stack to initialize before stoping it.
@ -489,6 +482,15 @@ int dedicated_eps_bearer_test()
int main(int argc, char** argv)
{
// Setup logging.
srslog::sink& log_sink = srslog::fetch_stdout_sink();
srslog::log_channel* chan = srslog::create_log_channel("mme_attach_request_test", log_sink);
srslte::srslog_wrapper log_wrapper(*chan);
g_logger = &log_wrapper;
// Start the log backend.
srslog::init();
srslte::logmap::set_default_log_level(LOG_LEVEL_DEBUG);
srslte::logmap::set_default_hex_limit(100000);

Loading…
Cancel
Save