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

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

@ -13,7 +13,33 @@
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE 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 // Part of a compiled format string. It can be either literal text or a
// replacement field. // replacement field.
@ -62,13 +88,15 @@ template <typename Char> struct part_counter {
if (begin != end) ++num_parts; if (begin != end) ++num_parts;
} }
FMT_CONSTEXPR void on_arg_id() { ++num_parts; } FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; }
FMT_CONSTEXPR void on_arg_id(int) { ++num_parts; } FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; }
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; } 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) { const Char* end) {
// Find the matching brace. // Find the matching brace.
unsigned brace_counter = 0; unsigned brace_counter = 0;
@ -116,25 +144,28 @@ class format_string_compiler : public error_handler {
handler_(part::make_text({begin, to_unsigned(end - begin)})); 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()); 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); parse_context_.check_arg_id(id);
part_ = part::make_arg_index(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); 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; part_.arg_id_end = ptr;
handler_(part_); 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) { const Char* end) {
auto repl = typename part::replacement(); auto repl = typename part::replacement();
dynamic_specs_handler<basic_format_parse_context<Char>> handler( 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)); 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( 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) { Context& ctx, Id arg_id) {
ctx.advance_to( ctx.advance_to(visit_format_arg(
visit_format_arg(arg_formatter<Range>(ctx, &parse_ctx), ctx.arg(arg_id))); arg_formatter<OutputIt, typename Context::char_type>(ctx, &parse_ctx),
ctx.arg(arg_id)));
} }
// vformat_to is defined in a subnamespace to prevent ADL. // vformat_to is defined in a subnamespace to prevent ADL.
namespace cf { namespace cf {
template <typename Context, typename Range, typename CompiledFormat> template <typename Context, typename OutputIt, typename CompiledFormat>
auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args) auto vformat_to(OutputIt out, CompiledFormat& cf,
-> typename Context::iterator { basic_format_args<Context> args) -> typename Context::iterator {
using char_type = typename Context::char_type; using char_type = typename Context::char_type;
basic_format_parse_context<char_type> parse_ctx( basic_format_parse_context<char_type> parse_ctx(
to_string_view(cf.format_str_)); to_string_view(cf.format_str_));
Context ctx(out.begin(), args); Context ctx(out, args);
const auto& parts = cf.parts(); const auto& parts = cf.parts();
for (auto part_it = std::begin(parts); part_it != std::end(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: case format_part_t::kind::arg_index:
advance_to(parse_ctx, part.arg_id_end); 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; break;
case format_part_t::kind::arg_name: case format_part_t::kind::arg_name:
advance_to(parse_ctx, part.arg_id_end); 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; break;
case format_part_t::kind::replacement: { 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); advance_to(parse_ctx, part.arg_id_end);
ctx.advance_to( 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; break;
} }
} }
@ -240,7 +274,7 @@ struct basic_compiled_format {};
template <typename S, typename = void> template <typename S, typename = void>
struct compiled_format_base : basic_compiled_format { struct compiled_format_base : basic_compiled_format {
using char_type = char_t<S>; 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; 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 { const parts_container& parts() const {
static FMT_CONSTEXPR_DECL const auto compiled_parts = static FMT_CONSTEXPR_DECL const auto compiled_parts =
compile_to_parts<char_type, num_format_parts>( compile_to_parts<char_type, num_format_parts>(
internal::to_string_view(S())); detail::to_string_view(S()));
return compiled_parts.data; return compiled_parts.data;
} }
}; };
@ -318,8 +352,8 @@ class compiled_format : private compiled_format_base<S> {
private: private:
basic_string_view<char_type> format_str_; basic_string_view<char_type> format_str_;
template <typename Context, typename Range, typename CompiledFormat> template <typename Context, typename OutputIt, typename CompiledFormat>
friend auto cf::vformat_to(Range out, CompiledFormat& cf, friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf,
basic_format_args<Context> args) -> basic_format_args<Context> args) ->
typename Context::iterator; typename Context::iterator;
@ -359,8 +393,7 @@ template <typename Char> struct text {
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const { OutputIt format(OutputIt out, const Args&...) const {
// TODO: reserve return write<Char>(out, data);
return copy_str<Char>(data.begin(), data.end(), out);
} }
}; };
@ -373,33 +406,6 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
return {{&s[pos], size}}; 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. // A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field { template <typename Char, typename T, int N> struct field {
using char_type = Char; 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 { OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`. // This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...); const T& arg = get<N>(args...);
return format_default<Char>(out, arg); return write<Char>(out, arg);
} }
}; };
template <typename Char, typename T, int N> template <typename Char, typename T, int N>
struct is_compiled_format<field<Char, T, N>> : std::true_type {}; 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 { template <typename L, typename R> struct concat {
L lhs; L lhs;
R rhs; 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> template <typename Args, size_t POS, int ID, typename T, typename S>
constexpr auto parse_tail(T head, S format_str) { 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); constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>, if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
unknown_format>()) 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 // Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input. // or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S> 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); return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}') { } else if constexpr (str[POS + 1] == '}') {
using type = get_type<ID, Args>; 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>(),
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(), format_str);
format_str); } else if constexpr (str[POS + 1] == ':') {
} else { using type = get_type<ID, Args>;
return unknown_format(); 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 { } else {
return unknown_format(); return unknown_format();
} }
@ -494,100 +534,130 @@ constexpr auto compile_format_string(S format_str) {
format_str); format_str);
} }
} }
#endif // __cpp_if_constexpr
} // namespace internal
#if FMT_USE_CONSTEXPR
# ifdef __cpp_if_constexpr
template <typename... Args, typename S, 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 auto compile(S format_str) {
constexpr basic_string_view<typename S::char_type> str = format_str; constexpr basic_string_view<typename S::char_type> str = format_str;
if constexpr (str.size() == 0) { if constexpr (str.size() == 0) {
return internal::make_text(str, 0, 0); return detail::make_text(str, 0, 0);
} else { } else {
constexpr auto result = 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); format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(result)>, if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
internal::unknown_format>()) { detail::unknown_format>()) {
return internal::compiled_format<S, Args...>(to_string_view(format_str)); return detail::compiled_format<S, Args...>(to_string_view(format_str));
} else { } else {
return result; 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, template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type, typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) { FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
basic_memory_buffer<Char> buffer; 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); return to_string(buffer);
} }
template <typename OutputIt, typename CompiledFormat, typename... Args, 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, OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) { const Args&... args) {
return cf.format(out, 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 // __cpp_if_constexpr
#endif // FMT_USE_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, template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type, 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)> CompiledFormat>::value)>
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) { std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer<Char> buffer; basic_memory_buffer<Char> buffer;
using range = buffer_range<Char>;
using context = buffer_context<Char>; using context = buffer_context<Char>;
internal::cf::vformat_to<context>(range(buffer), cf, detail::buffer<Char>& base = buffer;
make_format_args<context>(args...)); detail::cf::vformat_to<context>(std::back_inserter(base), cf,
make_format_args<context>(args...));
return to_string(buffer); 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, 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)> CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf, OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) { const Args&... args) {
using char_type = typename CompiledFormat::char_type; using char_type = typename CompiledFormat::char_type;
using range = internal::output_range<OutputIt, char_type>;
using context = format_context_t<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...)); make_format_args<context>(args...));
} }
template <typename OutputIt, typename CompiledFormat, typename... Args, template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)> 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, format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const CompiledFormat& cf, const CompiledFormat& cf,
const Args&... args) { const Args&... args) {
auto it = 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()}; return {it.base(), it.count()};
} }
template <typename CompiledFormat, typename... Args> template <typename CompiledFormat, typename... Args>
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) { size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
return format_to(internal::counting_iterator(), cf, args...).count(); return format_to(detail::counting_iterator(), cf, args...).count();
} }
FMT_END_NAMESPACE FMT_END_NAMESPACE

File diff suppressed because it is too large Load Diff

@ -15,6 +15,7 @@
#include <cstdarg> #include <cstdarg>
#include <cstring> // for std::memmove #include <cstring> // for std::memmove
#include <cwchar> #include <cwchar>
#include <exception>
#include "format.h" #include "format.h"
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
@ -22,8 +23,16 @@
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
# 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> # include <io.h>
# include <windows.h>
#endif #endif
#ifdef _MSC_VER #ifdef _MSC_VER
@ -33,15 +42,19 @@
// Dummy implementations of strerror_r and strerror_s called if corresponding // Dummy implementations of strerror_r and strerror_s called if corresponding
// system functions are not available. // system functions are not available.
inline fmt::internal::null<> strerror_r(int, char*, ...) { return {}; } inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; }
inline fmt::internal::null<> strerror_s(char*, std::size_t, ...) { return {}; } inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; }
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace internal { namespace detail {
FMT_FUNC void assert_fail(const char* file, int line, const char* message) { FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
print(stderr, "{}:{}: assertion failed: {}", file, line, message); // Use unchecked std::fprintf to avoid triggering another assertion when
std::abort(); // 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 #ifndef _MSC_VER
@ -67,14 +80,14 @@ inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) {
// other - failure // other - failure
// Buffer should be at least of size 1. // Buffer should be at least of size 1.
FMT_FUNC int safe_strerror(int error_code, char*& buffer, 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"); FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer");
class dispatcher { class dispatcher {
private: private:
int error_code_; int error_code_;
char*& buffer_; char*& buffer_;
std::size_t buffer_size_; size_t buffer_size_;
// A noop assignment operator to avoid bogus warnings. // A noop assignment operator to avoid bogus warnings.
void operator=(const dispatcher&) {} 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. // Handle the case when strerror_r is not available.
FMT_MAYBE_UNUSED FMT_MAYBE_UNUSED
int handle(internal::null<>) { int handle(detail::null<>) {
return fallback(strerror_s(buffer_, buffer_size_, error_code_)); 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 #if !FMT_MSC_VER
// Fallback to strerror if strerror_r and strerror_s are not available. // Fallback to strerror if strerror_r and strerror_s are not available.
int fallback(internal::null<>) { int fallback(detail::null<>) {
errno = 0; errno = 0;
buffer_ = strerror(error_code_); buffer_ = strerror(error_code_);
return errno; return errno;
@ -119,7 +132,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer,
#endif #endif
public: 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) {} : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
int run() { return handle(strerror_r(error_code_, buffer_, buffer_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(); 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 { string_view message) FMT_NOEXCEPT {
// Report error code making sure that the output fits into // Report error code making sure that the output fits into
// inline_buffer_size to avoid dynamic memory allocation and potential // 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 SEP[] = ": ";
static const char ERROR_STR[] = "error "; static const char ERROR_STR[] = "error ";
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR. // 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); 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; abs_value = 0 - abs_value;
++error_code_size; ++error_code_size;
} }
error_code_size += internal::to_unsigned(internal::count_digits(abs_value)); error_code_size += detail::to_unsigned(detail::count_digits(abs_value));
internal::writer w(out); auto it = std::back_inserter(out);
if (message.size() <= inline_buffer_size - error_code_size) { if (message.size() <= inline_buffer_size - error_code_size)
w.write(message); format_to(it, "{}{}", message, SEP);
w.write(SEP); format_to(it, "{}{}", ERROR_STR, error_code);
}
w.write(ERROR_STR);
w.write(error_code);
assert(out.size() <= inline_buffer_size); 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); size_t written = std::fwrite(ptr, size, count, stream);
if (written < count) FMT_THROW(system_error(errno, "cannot write to file")); if (written < count) FMT_THROW(system_error(errno, "cannot write to file"));
} }
} // namespace internal } // namespace detail
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
namespace internal { namespace detail {
template <typename Locale> template <typename Locale>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { 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>()) return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
.decimal_point(); .decimal_point();
} }
} // namespace internal } // namespace detail
#else #else
template <typename Char> template <typename Char>
FMT_FUNC std::string internal::grouping_impl(locale_ref) { FMT_FUNC std::string detail::grouping_impl(locale_ref) {
return "\03"; return "\03";
} }
template <typename Char> template <typename Char> FMT_FUNC Char detail::thousands_sep_impl(locale_ref) {
FMT_FUNC Char internal::thousands_sep_impl(locale_ref) {
return FMT_STATIC_THOUSANDS_SEPARATOR; return FMT_STATIC_THOUSANDS_SEPARATOR;
} }
template <typename Char> template <typename Char> FMT_FUNC Char detail::decimal_point_impl(locale_ref) {
FMT_FUNC Char internal::decimal_point_impl(locale_ref) {
return '.'; return '.';
} }
#endif #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)); 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. // fallback_uintptr is always stored in little endian.
int i = static_cast<int>(sizeof(void*)) - 1; int i = static_cast<int>(sizeof(void*)) - 1;
while (i > 0 && n.value[i] == 0) --i; 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> template <typename T>
const char basic_data<T>::digits[] = const typename basic_data<T>::digit_pair basic_data<T>::digits[] = {
"0001020304050607080910111213141516171819" {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'},
"2021222324252627282930313233343536373839" {'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'},
"4041424344454647484950515253545556575859" {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'},
"6061626364656667686970717273747576777879" {'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'},
"8081828384858687888990919293949596979899"; {'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> template <typename T>
const char basic_data<T>::hex_digits[] = "0123456789abcdef"; 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 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 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>::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 { template <typename T> struct bits {
static FMT_CONSTEXPR_DECL const int value = static FMT_CONSTEXPR_DECL const int value =
@ -576,9 +603,10 @@ class bigint {
void operator=(const bigint&) = delete; void operator=(const bigint&) = delete;
void assign(const bigint& other) { void assign(const bigint& other) {
bigits_.resize(other.bigits_.size()); auto size = other.bigits_.size();
bigits_.resize(size);
auto data = other.bigits_.data(); 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_; exp_ = other.exp_;
} }
@ -594,7 +622,7 @@ class bigint {
int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; } int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
bigint& operator<<=(int shift) { FMT_NOINLINE bigint& operator<<=(int shift) {
assert(shift >= 0); assert(shift >= 0);
exp_ += shift / bigit_bits; exp_ += shift / bigit_bits;
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; precision = (precision >= 0 ? precision : 6) - 1;
// Build the format string. // 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[max_format_size];
char* format_ptr = format; char* format_ptr = format;
*format_ptr++ = '%'; *format_ptr++ = '%';
@ -1145,13 +1173,13 @@ int snprintf_float(T value, int precision, float_specs specs,
for (;;) { for (;;) {
auto begin = buf.data() + offset; auto begin = buf.data() + offset;
auto capacity = buf.capacity() - offset; auto capacity = buf.capacity() - offset;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION #ifdef FMT_FUZZ
if (precision > 100000) if (precision > 100000)
throw std::runtime_error( throw std::runtime_error(
"fuzz mode - avoid large allocation inside snprintf"); "fuzz mode - avoid large allocation inside snprintf");
#endif #endif
// Suppress the warning about a nonliteral format string. // 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 (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
int result = precision >= 0 int result = precision >= 0
? snprintf_ptr(begin, capacity, format, precision, value) ? 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; 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) { format_parse_context::iterator parse(format_parse_context& ctx) {
return ctx.begin(); return ctx.begin();
} }
format_context::iterator format(const internal::bigint& n, format_context::iterator format(const detail::bigint& n,
format_context& ctx) { format_context& ctx) {
auto out = ctx.out(); auto out = ctx.out();
bool first = true; bool first = true;
@ -1289,12 +1317,12 @@ template <> struct formatter<internal::bigint> {
out = format_to(out, "{:08x}", value); out = format_to(out, "{:08x}", value);
} }
if (n.exp_ > 0) 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; 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 transcode = [this](const char* p) {
auto cp = uint32_t(); auto cp = uint32_t();
auto error = 0; auto error = 0;
@ -1325,7 +1353,7 @@ FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
buffer_.push_back(0); 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 { string_view message) FMT_NOEXCEPT {
FMT_TRY { FMT_TRY {
memory_buffer buf; memory_buffer buf;
@ -1333,12 +1361,9 @@ FMT_FUNC void format_system_error(internal::buffer<char>& out, int error_code,
for (;;) { for (;;) {
char* system_message = &buf[0]; char* system_message = &buf[0];
int result = int result =
internal::safe_strerror(error_code, system_message, buf.size()); detail::safe_strerror(error_code, system_message, buf.size());
if (result == 0) { if (result == 0) {
internal::writer w(out); format_to(std::back_inserter(out), "{}: {}", message, system_message);
w.write(message);
w.write(": ");
w.write(system_message);
return; return;
} }
if (result != ERANGE) 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); 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)); 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); 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) { FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
memory_buffer buffer; memory_buffer buffer;
internal::vformat_to(buffer, format_str, detail::vformat_to(buffer, format_str,
basic_format_args<buffer_context<char>>(args)); basic_format_args<buffer_context<char>>(args));
#ifdef _WIN32 #ifdef _WIN32
auto fd = _fileno(f); auto fd = _fileno(f);
if (_isatty(fd)) { 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(); auto written = DWORD();
if (!WriteConsoleW(reinterpret_cast<HANDLE>(_get_osfhandle(fd)), if (!WriteConsoleW(reinterpret_cast<HANDLE>(_get_osfhandle(fd)),
u16.c_str(), static_cast<DWORD>(u16.size()), &written, u16.c_str(), static_cast<DWORD>(u16.size()), &written,
@ -1376,16 +1426,16 @@ FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
return; return;
} }
#endif #endif
internal::fwrite_fully(buffer.data(), 1, buffer.size(), f); detail::fwrite_fully(buffer.data(), 1, buffer.size(), f);
} }
#ifdef _WIN32 #ifdef _WIN32
// Print assuming legacy (non-Unicode) encoding. // 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) { format_args args) {
memory_buffer buffer; memory_buffer buffer;
internal::vformat_to(buffer, format_str, detail::vformat_to(buffer, format_str,
basic_format_args<buffer_context<char>>(args)); basic_format_args<buffer_context<char>>(args));
fwrite_fully(buffer.data(), 1, buffer.size(), f); fwrite_fully(buffer.data(), 1, buffer.size(), f);
} }
#endif #endif

File diff suppressed because it is too large Load Diff

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

@ -50,7 +50,7 @@
#ifdef FMT_SYSTEM #ifdef FMT_SYSTEM
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) # define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
#else #else
# define FMT_SYSTEM(call) call # define FMT_SYSTEM(call) ::call
# ifdef _WIN32 # ifdef _WIN32
// Fix warnings about deprecated symbols. // Fix warnings about deprecated symbols.
# define FMT_POSIX_CALL(call) ::_##call # define FMT_POSIX_CALL(call) ::_##call
@ -133,7 +133,7 @@ class error_code {
}; };
#ifdef _WIN32 #ifdef _WIN32
namespace internal { namespace detail {
// A converter from UTF-16 to UTF-8. // A converter from UTF-16 to UTF-8.
// It is only provided for Windows since other systems support UTF-8 natively. // It is only provided for Windows since other systems support UTF-8 natively.
class utf16_to_utf8 { class utf16_to_utf8 {
@ -156,7 +156,7 @@ class utf16_to_utf8 {
FMT_API void format_windows_error(buffer<char>& out, int error_code, FMT_API void format_windows_error(buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT; string_view message) FMT_NOEXCEPT;
} // namespace internal } // namespace detail
/** A Windows error. */ /** A Windows error. */
class windows_error : public system_error { class windows_error : public system_error {
@ -277,7 +277,8 @@ class file {
enum { enum {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing 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. // Constructs a file object which doesn't represent any file.
@ -313,10 +314,10 @@ class file {
FMT_API long long size() const; FMT_API long long size() const;
// Attempts to read count bytes from the file into the specified buffer. // 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. // 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 // Duplicates a file descriptor with the dup function and returns
// the duplicate as a file object. // the duplicate as a file object.
@ -341,6 +342,63 @@ class file {
// Returns the memory page size. // Returns the memory page size.
long getpagesize(); 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 #endif // FMT_USE_FCNTL
#ifdef FMT_LOCALE #ifdef FMT_LOCALE

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

@ -1,2 +1,2 @@
#include "os.h" #include "os.h"
#warning "fmt/posix.h is deprecated; use fmt/os.h instead" #warning "fmt/posix.h is deprecated; use fmt/os.h instead"

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

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

@ -42,6 +42,9 @@ public:
/// Pushes a log entry into the backend. /// Pushes a log entry into the backend.
virtual void push(detail::log_entry&& entry) = 0; 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 } // namespace detail

@ -23,6 +23,8 @@
#define SRSLOG_DETAIL_LOG_ENTRY_H #define SRSLOG_DETAIL_LOG_ENTRY_H
#include "srslte/srslog/bundled/fmt/printf.h" #include "srslte/srslog/bundled/fmt/printf.h"
#include "srslte/srslog/detail/support/thread_utils.h"
#include <chrono>
namespace srslog { namespace srslog {
@ -30,12 +32,33 @@ class sink;
namespace detail { 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 /// This structure packs all the required data required to create a log entry in
/// the backend. /// the backend.
//:TODO: provide proper command objects when we have custom formatting.
struct log_entry { struct log_entry {
sink* s; sink* s;
std::chrono::high_resolution_clock::time_point tp;
log_context context;
std::string fmtstring; std::string fmtstring;
fmt::dynamic_format_arg_store<fmt::printf_context> store; 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 } // namespace detail

@ -48,7 +48,7 @@ struct is_in_place_type_t<in_place_type_t<T>> : std::true_type {};
class any class any
{ {
public: 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. // due to DR253. Declare the defaulted constructor out of the class.
any(); any();
@ -149,7 +149,7 @@ private:
std::unique_ptr<type_interface> storage; 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; inline any::any() = default;
/// Constructs an any object containing an object of type T, passing the /// Constructs an any object containing an object of type T, passing the

@ -26,7 +26,7 @@
#include <queue> #include <queue>
#ifndef SRSLOG_QUEUE_CAPACITY #ifndef SRSLOG_QUEUE_CAPACITY
#define SRSLOG_QUEUE_CAPACITY 2048 #define SRSLOG_QUEUE_CAPACITY 8192
#endif #endif
namespace srslog { 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 #define SRSLOG_LOG_CHANNEL_H
#include "srslte/srslog/detail/log_backend.h" #include "srslte/srslog/detail/log_backend.h"
#include "srslte/srslog/detail/support/thread_utils.h" #include <cassert>
namespace srslog { 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. /// 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 /// It can deliver a log entry to one or more different sinks, for example a
@ -38,9 +58,21 @@ class log_channel
{ {
public: public:
log_channel(std::string id, sink& s, detail::log_backend& backend) : 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_id(std::move(id)),
log_sink(s), log_sink(s),
backend(backend), 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) is_enabled(true)
{} {}
@ -57,21 +89,67 @@ public:
/// Returns the id string of the channel. /// Returns the id string of the channel.
const std::string& id() const { return log_id; } 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 /// Builds the provided log entry and passes it to the backend. When the
/// channel is disabled the log entry will be discarded. /// channel is disabled the log entry will be discarded.
template <typename... Args> template <typename... Args>
void operator()(const std::string& fmtstr, Args&&... 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()) { if (!enabled()) {
return; return;
} }
assert(&log_sink);
// Populate the store with all incoming arguments. // Populate the store with all incoming arguments.
fmt::dynamic_format_arg_store<fmt::printf_context> store; fmt::dynamic_format_arg_store<fmt::printf_context> store;
(void)std::initializer_list<int>{(store.push_back(args), 0)...}; (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. // 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)); backend.push(std::move(entry));
} }
@ -79,6 +157,11 @@ private:
const std::string log_id; const std::string log_id;
sink& log_sink; sink& log_sink;
detail::log_backend& backend; 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; detail::shared_variable<bool> is_enabled;
}; };

@ -31,6 +31,7 @@ namespace detail {
/// The logger_impl template class contains the common functionality to loggers. /// The logger_impl template class contains the common functionality to loggers.
/// Its main responsibility is to control the logging level by individually /// Its main responsibility is to control the logging level by individually
/// enabling or disabling the logging channels that form the logger. /// enabling or disabling the logging channels that form the logger.
/// NOTE: Thread safe class.
template <typename T, typename Enum> template <typename T, typename Enum>
class logger_impl : public T class logger_impl : public T
{ {
@ -60,11 +61,31 @@ public:
/// Change the logging level. /// Change the logging level.
void set_level(Enum lvl) void set_level(Enum lvl)
{ {
detail::scoped_lock lock(m);
for (unsigned i = 0, e = channels.size(); i != e; ++i) { for (unsigned i = 0, e = channels.size(); i != e; ++i) {
channels[i]->set_enabled(static_cast<Enum>(i) <= lvl); 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: private:
/// Comparison operator for enum types, used by the set_level method. /// Comparison operator for enum types, used by the set_level method.
friend bool operator<=(Enum lhs, Enum rhs) friend bool operator<=(Enum lhs, Enum rhs)
@ -75,6 +96,7 @@ private:
private: private:
const std::string logger_id; const std::string logger_id;
const std::array<log_channel*, size> channels; const std::array<log_channel*, size> channels;
mutable detail::mutex m;
}; };
/// Type trait to detect if T is a logger. /// 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. /// Common logger types.
/// ///
/// Basic logger with three levels. /// Basic logger with four levels.
enum class basic_levels { error, warning, info, LAST }; enum class basic_levels { error, warning, info, debug, LAST };
struct basic_logger_channels { struct basic_logger_channels {
log_channel& error; log_channel& error;
log_channel& warning; log_channel& warning;
log_channel& info; log_channel& info;
log_channel& debug;
}; };
using basic_logger = build_logger_type<basic_logger_channels, basic_levels>; using basic_logger = build_logger_type<basic_logger_channels, basic_levels>;

@ -28,32 +28,102 @@
namespace srslog { namespace srslog {
///
/// NOTE: All functions are thread safe unless otherwise specified.
///
/// ///
/// Log channel management functions. /// 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, /// 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 /// 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 /// retrieved in other parts of the application. Returns a pointer to the
/// newly created channel, otherwise when a channel is already registered with /// newly created channel, otherwise when a channel is already registered with
/// the same id it returns nullptr. /// 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); log_channel* create_log_channel(const std::string& id, sink& s);
/// Finds a log channel with the specified id string in the repository. On namespace detail {
/// success returns a pointer to the requested log channel, otherwise nullptr.
log_channel* find_log_channel(const std::string& id); /// 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. /// 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. return detail::any_cast<T>(p);
detail::any* create_logger(const std::string& id, detail::any&& logger); }
detail::any* find_logger(const std::string& id);
} // 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 /// 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 /// 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 /// newly created logger, otherwise when a logger is already registered with the
/// same id it returns nullptr. /// same id it returns nullptr.
/// NOTE: T should be a type that is a logger. /// NOTE: T should be a type that is a logger.
/// NOTE: Deprecated, use fetch_logger instead.
template <typename T, typename... Args> template <typename T, typename... Args>
inline T* create_logger(const std::string& id, Args&&... args) inline T* create_logger(const std::string& id, Args&&... args)
{ {
static_assert(detail::is_logger<T>::value, "T should be a logger type"); static_assert(detail::is_logger<T>::value, "T should be a logger type");
auto logger = detail::make_any<T>(id, std::forward<Args>(args)...); auto logger = detail::make_any<T>(id, std::forward<Args>(args)...);
detail::any* p = detail::create_logger(id, std::move(logger)); detail::any* p = detail::fetch_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);
return detail::any_cast<T>(p); return detail::any_cast<T>(p);
} }
@ -87,15 +146,47 @@ inline T* find_logger(const std::string& id)
/// Sink management functions. /// 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 /// Finds a sink with the specified id string in the repository. On
/// success returns a pointer to the requested sink, otherwise nullptr. /// success returns a pointer to the requested sink, otherwise nullptr.
sink* find_sink(const std::string& id); 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 /// 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 /// 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 /// 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 /// names. Returns a pointer to the newly created sink or nullptr if a sink with
/// the same name was already registered. /// the same name was already registered.
/// NOTE: Deprecated, use get_stdout_sink instead.
sink* create_stdout_sink(const std::string& name = "stdout"); sink* create_stdout_sink(const std::string& name = "stdout");
/// Creates a new sink that writes into the stderr stream and registers it into /// 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 /// 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 /// names. Returns a pointer to the newly created sink or nullptr if a sink with
/// the same name was already registered. /// the same name was already registered.
/// NOTE: Deprecated, use get_stderr_sink instead.
sink* create_stderr_sink(const std::string& name = "stderr"); 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. /// 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. /// NOTE: Calling this function more than once has no side effects.
void init(); 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 /// Installs the specified error handler to receive any error messages generated
/// by the framework. /// 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); void set_error_handler(error_handler handler);
} // namespace srslog } // 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) bool long_msg)
{ {
char buffer_tti[16] = {}; char buffer_tti[16] = {};
char buffer_time[64] = {};
if (logger_h) { if (logger_h) {
logger::unique_log_str_t log_str = nullptr; logger::unique_log_str_t log_str = nullptr;
if (long_msg || hex) { if (long_msg || hex) {
// For long messages, dynamically allocate a new log_str with enough size outside the pool. // 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()); log_str = logger::unique_log_str_t(new logger::log_str(nullptr, log_str_msg_len), logger::log_str_deleter());
} else { } else {
log_str = logger_h->allocate_unique_log_str(); log_str = logger_h->allocate_unique_log_str();
} }
if (log_str) { if (log_str) {
now_time(buffer_time, sizeof(buffer_time));
if (do_tti) { if (do_tti) {
get_tti_str(tti, buffer_tti, sizeof(buffer_tti)); get_tti_str(tti, buffer_tti, sizeof(buffer_tti));
} }
snprintf(log_str->str(), snprintf(log_str->str(),
log_str->get_buffer_size(), log_str->get_buffer_size(),
"%s [%-4s] %s %s%s%s%s%s", "[%-4s] %s %s%s%s%s%s",
buffer_time,
get_service_name().c_str(), get_service_name().c_str(),
log_level_text_short[level], log_level_text_short[level],
do_tti ? buffer_tti : "", do_tti ? buffer_tti : "",

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

@ -20,6 +20,7 @@
*/ */
#include "backend_worker.h" #include "backend_worker.h"
#include "formatter.h"
#include "srslte/srslog/sink.h" #include "srslte/srslog/sink.h"
#include <cassert> #include <cassert>
@ -69,7 +70,7 @@ void backend_worker::do_work()
continue; continue;
} }
report_queue_on_full(); report_queue_on_full_once();
process_log_entry(std::move(item.second)); process_log_entry(std::move(item.second));
} }
@ -79,12 +80,32 @@ void backend_worker::do_work()
process_outstanding_entries(); 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) 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); 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()); err_handler(err_str.get_error());
} }
} }

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

@ -8,12 +8,12 @@
#include "fmt/format-inl.h" #include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace internal { namespace detail {
template <typename T> template <typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision, int format_float(char* buf, std::size_t size, const char* format, int precision,
T value) { T value) {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION #ifdef FMT_FUZZ
if (precision > 100000) if (precision > 100000)
throw std::runtime_error( throw std::runtime_error(
"fuzz mode - avoid large allocation inside snprintf"); "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) return precision < 0 ? snprintf_ptr(buf, size, format, value)
: snprintf_ptr(buf, size, format, precision, value); : snprintf_ptr(buf, size, format, precision, value);
} }
struct sprintf_specs { } // namespace detail
int precision;
char type;
bool alt : 1;
template <typename Char> template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
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>;
// Workaround a bug in MSVC2013 that prevents instantiation of format_float. // Workaround a bug in MSVC2013 that prevents instantiation of format_float.
int (*instantiate_format_float)(double, int, internal::float_specs, int (*instantiate_format_float)(double, int, detail::float_specs,
internal::buffer<char>&) = detail::buffer<char>&) = detail::format_float;
internal::format_float;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc); template FMT_API detail::locale_ref::locale_ref(const std::locale& loc);
template FMT_API std::locale internal::locale_ref::get<std::locale>() const; template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
#endif #endif
// Explicit instantiations for char. // Explicit instantiations for char.
template FMT_API std::string internal::grouping_impl<char>(locale_ref); template FMT_API std::string detail::grouping_impl<char>(locale_ref);
template FMT_API char internal::thousands_sep_impl(locale_ref); template FMT_API char detail::thousands_sep_impl(locale_ref);
template FMT_API char internal::decimal_point_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( template FMT_API FMT_BUFFER_CONTEXT(char)::iterator detail::vformat_to(
const basic_format_args<format_context>& args); detail::buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>);
template FMT_API std::string internal::vformat<char>( template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
string_view, basic_format_args<format_context>); detail::buffer<char>&);
template FMT_API int detail::snprintf_float(long double, int,
template FMT_API format_context::iterator internal::vformat_to( detail::float_specs,
internal::buffer<char>&, string_view, basic_format_args<format_context>); detail::buffer<char>&);
template FMT_API int detail::format_float(double, int, detail::float_specs,
template FMT_API int internal::snprintf_float(double, int, detail::buffer<char>&);
internal::float_specs, template FMT_API int detail::format_float(long double, int, detail::float_specs,
internal::buffer<char>&); detail::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>&);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.
template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref); template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref);
template FMT_API wchar_t internal::thousands_sep_impl(locale_ref); template FMT_API wchar_t detail::thousands_sep_impl(locale_ref);
template FMT_API wchar_t internal::decimal_point_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*,
const wchar_t*);
template FMT_API std::wstring internal::vformat<wchar_t>( template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
wstring_view, basic_format_args<wformat_context>); const wchar_t*);
FMT_END_NAMESPACE FMT_END_NAMESPACE

@ -73,14 +73,14 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
#ifdef _WIN32 #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)) { if (int error_code = convert(s)) {
FMT_THROW(windows_error(error_code, FMT_THROW(windows_error(error_code,
"cannot convert string from UTF-16 to UTF-8")); "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; if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
int s_size = static_cast<int>(s.size()); int s_size = static_cast<int>(s.size());
if (s_size == 0) { if (s_size == 0) {
@ -105,13 +105,13 @@ void windows_error::init(int err_code, string_view format_str,
format_args args) { format_args args) {
error_code_ = err_code; error_code_ = err_code;
memory_buffer buffer; 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; std::runtime_error& base = *this;
base = std::runtime_error(to_string(buffer)); 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 { string_view message) FMT_NOEXCEPT {
FMT_TRY { FMT_TRY {
wmemory_buffer buf; wmemory_buffer buf;
buf.resize(inline_buffer_size); buf.resize(inline_buffer_size);
@ -124,10 +124,7 @@ void internal::format_windows_error(internal::buffer<char>& out, int error_code,
if (result != 0) { if (result != 0) {
utf16_to_utf8 utf8_message; utf16_to_utf8 utf8_message;
if (utf8_message.convert(system_message) == ERROR_SUCCESS) { if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
internal::writer w(out); format_to(std::back_inserter(out), "{}: {}", message, utf8_message);
w.write(message);
w.write(": ");
w.write(utf8_message);
return; return;
} }
break; break;
@ -143,7 +140,7 @@ void internal::format_windows_error(internal::buffer<char>& out, int error_code,
void report_windows_error(int error_code, void report_windows_error(int error_code,
fmt::string_view message) FMT_NOEXCEPT { 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 #endif // _WIN32
@ -234,14 +231,14 @@ std::size_t file::read(void* buffer, std::size_t count) {
RWResult result = 0; RWResult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file")); 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) { std::size_t file::write(const void* buffer, std::size_t count) {
RWResult result = 0; RWResult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file")); 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) { 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) { buffered_file file::fdopen(const char* mode) {
// Don't retry as fdopen doesn't return EINTR. // 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)); FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
#endif
if (!f) if (!f)
FMT_THROW( FMT_THROW(
system_error(errno, "cannot associate stream with file descriptor")); 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)); queue.push(std::move(entry));
} }
bool is_running() const override { return worker.is_running(); }
/// Installs the specified error handler into the backend worker. /// Installs the specified error handler into the backend worker.
void set_error_handler(error_handler err_handler) void set_error_handler(error_handler err_handler)
{ {
worker.set_error_handler(std::move(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. /// Stops the backend worker thread.
void stop() { worker.stop(); } void stop() { worker.stop(); }

@ -24,6 +24,7 @@
#include "srslte/srslog/detail/support/thread_utils.h" #include "srslte/srslog/detail/support/thread_utils.h"
#include <unordered_map> #include <unordered_map>
#include <vector>
namespace srslog { namespace srslog {
@ -49,8 +50,19 @@ public:
return &insertion.first->second; return &insertion.first->second;
} }
/// Finds a value with the specified key in the repository. Returns a /// Inserts a new entry in-place into the repository if there is no element
/// pointer to the value, otherwise nullptr if not found. /// 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) V* find(const K& key)
{ {
detail::scoped_lock lock(m); detail::scoped_lock lock(m);
@ -63,6 +75,32 @@ public:
const auto it = repo.find(key); const auto it = repo.find(key);
return (it != repo.cend()) ? &it->second : nullptr; 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 } // 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(); } detail::error_string flush() override { return handler.flush(); }
protected:
/// Returns the current file index.
uint32_t get_file_index() const { return file_index; }
private: private:
/// Returns true when the sink has never written data to a file, otherwise /// Returns true when the sink has never written data to a file, otherwise
/// returns false. /// returns false.

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

@ -21,77 +21,294 @@
#include "srslte/srslog/srslog.h" #include "srslte/srslog/srslog.h"
#include "sinks/file_sink.h" #include "sinks/file_sink.h"
#include "sinks/stream_sink.h"
#include "srslog_instance.h" #include "srslog_instance.h"
using namespace srslog; 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(); std::string result(s);
result.erase(std::remove(result.begin(), result.end(), '#'), result.end());
auto channel = std::unique_ptr<log_channel>( return result;
new log_channel(id, s, instance.get_backend())); }
auto p = instance.get_channel_repo().insert(id, std::move(channel));
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) log_channel* srslog::find_log_channel(const std::string& id)
{ {
auto p = srslog_instance::get().get_channel_repo().find(id); return srslog_instance::get().get_channel_repo().find(id);
return (p) ? p->get() : nullptr;
} }
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); 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, instance.get_default_sink(), instance.get_backend());
} }
/// Helper for creating and registering sinks. log_channel& srslog::fetch_log_channel(const std::string& id, sink& s, log_channel_config config)
template <typename T, typename... Args>
static sink* create_sink(const std::string& id, Args&&... args)
{ {
auto new_sink = std::unique_ptr<sink>(new T(std::forward<Args>(args)...)); assert(!id.empty() && "Empty id string");
auto p =
srslog_instance::get().get_sink_repo().insert(id, std::move(new_sink));
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() void srslog::init()
{ {
srslog_instance::get().get_backend().start(); srslog_instance::get().get_backend().start();
} }
detail::any* srslog::detail::create_logger(const std::string& id, void srslog::flush()
detail::any&& logger)
{ {
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) detail::any* srslog::detail::find_logger(const std::string& id)
{ {
return srslog_instance::get().get_logger_repo().find(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 #define SRSLOG_SRSLOG_INSTANCE_H
#include "log_backend_impl.h" #include "log_backend_impl.h"
#include "object_repository.h" #include "sink_repository.h"
#include "srslte/srslog/detail/support/any.h" #include "srslte/srslog/detail/support/any.h"
#include "srslte/srslog/log_channel.h" #include "srslte/srslog/log_channel.h"
#include "srslte/srslog/sink.h"
namespace srslog { namespace srslog {
/// Singleton of the framework containing all the required classes. /// Singleton of the framework containing all the required classes.
class srslog_instance class srslog_instance
{ {
srslog_instance() = default; srslog_instance() { default_sink = &sink_repo.get_stdout_sink(); }
public: public:
srslog_instance(const srslog_instance& other) = delete; srslog_instance(const srslog_instance& other) = delete;
@ -52,15 +51,13 @@ public:
const logger_repo_type& get_logger_repo() const { return logger_repo; } const logger_repo_type& get_logger_repo() const { return logger_repo; }
/// Log channel repository accessor. /// Log channel repository accessor.
using channel_repo_type = using channel_repo_type = object_repository<std::string, log_channel>;
object_repository<std::string, std::unique_ptr<log_channel>>;
channel_repo_type& get_channel_repo() { return channel_repo; } channel_repo_type& get_channel_repo() { return channel_repo; }
const channel_repo_type& get_channel_repo() const { return channel_repo; } const channel_repo_type& get_channel_repo() const { return channel_repo; }
/// Sink repository accessor. /// Sink repository accessor.
using sink_repo_type = object_repository<std::string, std::unique_ptr<sink>>; sink_repository& get_sink_repo() { return sink_repo; }
sink_repo_type& get_sink_repo() { return sink_repo; } const sink_repository& get_sink_repo() const { return sink_repo; }
const sink_repo_type& get_sink_repo() const { return sink_repo; }
/// Backend accessor. /// Backend accessor.
detail::log_backend& get_backend() { return backend; } detail::log_backend& get_backend() { return backend; }
@ -72,13 +69,20 @@ public:
backend.set_error_handler(std::move(callback)); 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: private:
/// NOTE: The order of declaration of each member is important here for proper /// NOTE: The order of declaration of each member is important here for proper
/// destruction. /// destruction.
sink_repo_type sink_repo; sink_repository sink_repo;
log_backend_impl backend; log_backend_impl backend;
channel_repo_type channel_repo; channel_repo_type channel_repo;
logger_repo_type logger_repo; logger_repo_type logger_repo;
detail::shared_variable<sink*> default_sink{nullptr};
}; };
} // namespace srslog } // namespace srslog

@ -48,3 +48,13 @@ add_executable(file_utils_test file_utils_test.cpp)
target_include_directories(file_utils_test PUBLIC ../../) target_include_directories(file_utils_test PUBLIC ../../)
target_link_libraries(file_utils_test srslog) target_link_libraries(file_utils_test srslog)
add_test(file_utils_test file_utils_test) 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 "file_test_utils.h"
#include "src/srslog/sinks/file_sink.h"
#include "testing_helpers.h" #include "testing_helpers.h"
using namespace srslog; 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() 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; 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() static bool when_data_written_exceeds_size_threshold_then_new_file_is_created()
{ {
std::string filename0 = 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 = { file_test_utils::scoped_file_deleter deleter = {
filename0, filename1, filename2}; filename0, filename1, filename2};
file_sink file(log_filename, 5001); file_sink_subclass file(log_filename, 5001);
// Build a 1000 byte entry. // Build a 1000 byte entry.
std::string entry(1000, 'a'); std::string entry(1000, 'a');
@ -71,16 +83,14 @@ static bool when_data_written_exceeds_size_threshold_then_new_file_is_created()
file.flush(); file.flush();
// Only one file should exist. // Only one file should exist.
ASSERT_EQ(file_test_utils::file_exists(filename0), true); ASSERT_EQ(file.get_num_of_files(), 1);
ASSERT_EQ(file_test_utils::file_exists(filename1), false);
// Trigger a file rotation. // Trigger a file rotation.
file.write(detail::memory_buffer(entry)); file.write(detail::memory_buffer(entry));
file.flush(); file.flush();
// A second file should be created. // A second file should be created.
ASSERT_EQ(file_test_utils::file_exists(filename0), true); ASSERT_EQ(file.get_num_of_files(), 2);
ASSERT_EQ(file_test_utils::file_exists(filename1), true);
// Fill in the second file with 4000 bytes, one byte less than the threshold. // Fill in the second file with 4000 bytes, one byte less than the threshold.
for (unsigned i = 0; i != 4; ++i) { 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(); file.flush();
// Two file should exist, third should not be created yet. // Two files should exist, third should not be created yet.
ASSERT_EQ(file_test_utils::file_exists(filename0), true); ASSERT_EQ(file.get_num_of_files(), 2);
ASSERT_EQ(file_test_utils::file_exists(filename1), true);
ASSERT_EQ(file_test_utils::file_exists(filename2), false);
// Trigger a file rotation. // Trigger a file rotation.
file.write(detail::memory_buffer(entry)); file.write(detail::memory_buffer(entry));
file.flush(); file.flush();
// Three files should exist. // Three files should exist.
ASSERT_EQ(file_test_utils::file_exists(filename0), true); ASSERT_EQ(file.get_num_of_files(), 3);
ASSERT_EQ(file_test_utils::file_exists(filename1), true);
ASSERT_EQ(file_test_utils::file_exists(filename2), true);
return true; return true;
} }

@ -19,14 +19,14 @@
* *
*/ */
#include "src/srslog/sinks/file_utils.h"
#include "file_test_utils.h" #include "file_test_utils.h"
#include "src/srslog/sinks/file_utils.h"
#include "testing_helpers.h" #include "testing_helpers.h"
using namespace srslog; using namespace srslog;
static const char* const log_filename = "testfile.log"; static constexpr char log_filename[] = "file_utils_test.log";
static const char* const log_filename2 = "testfile2.log"; static constexpr char log_filename2[] = "file_utils_test2.log";
static bool filename_extension_split_test() 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; log_backend_impl backend;
ASSERT_EQ(backend.is_started(), false); ASSERT_EQ(backend.is_running(), false);
backend.start(); backend.start();
ASSERT_EQ(backend.is_started(), true); ASSERT_EQ(backend.is_running(), true);
return true; return true;
} }
@ -43,7 +43,7 @@ static bool when_backend_is_started_and_stopped_then_is_started_returns_false()
backend.start(); backend.start();
backend.stop(); backend.stop();
ASSERT_EQ(backend.is_started(), false); ASSERT_EQ(backend.is_running(), false);
return true; return true;
} }
@ -80,7 +80,7 @@ static bool when_backend_is_not_started_then_pushed_log_entries_are_ignored()
sink_spy spy; sink_spy spy;
log_backend_impl backend; log_backend_impl backend;
detail::log_entry entry = {&spy, "Test"}; detail::log_entry entry = {&spy};
backend.push(std::move(entry)); backend.push(std::move(entry));
ASSERT_EQ(spy.write_invocation_count(), 0); 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; return true;
} }
/// Helper to build a log entry. /// Builds a basic log entry.
static detail::log_entry build_log_entry(sink* s) static detail::log_entry build_log_entry(sink* s)
{ {
// Populate store with some arguments. using tp_ty = std::chrono::time_point<std::chrono::high_resolution_clock>;
fmt::dynamic_format_arg_store<fmt::printf_context> store; tp_ty tp;
store.push_back(3);
store.push_back("Hello");
store.push_back(3.14);
// Send the log entry to the backend. fmt::dynamic_format_arg_store<fmt::printf_context> store;
detail::log_entry entry = {s, "Arg1:%u Arg2:%s Arg3:%.2f", std::move(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() 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(); backend.stop();
ASSERT_EQ(spy.write_invocation_count(), 1); 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; return true;
} }

@ -46,6 +46,8 @@ public:
void start() override {} void start() override {}
void push(detail::log_entry&& entry) override {} void push(detail::log_entry&& entry) override {}
bool is_running() const override { return true; }
}; };
} // namespace } // namespace
@ -102,6 +104,8 @@ public:
++count; ++count;
} }
bool is_running() const override { return true; }
unsigned push_invocation_count() const { return count; } unsigned push_invocation_count() const { return count; }
const detail::log_entry& last_entry() const { return e; } 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; sink_dummy s;
log_channel log("id", s, backend); log_channel log("id", s, backend);
std::string fmtstring = "Arg1: %u Arg2: %s"; std::string fmtstring = "test";
log(fmtstring, 42, "Hello"); log(fmtstring, 42, "Hello");
ASSERT_EQ(backend.push_invocation_count(), 1); ASSERT_EQ(backend.push_invocation_count(), 1);
ASSERT_NE(backend.last_entry().s, nullptr);
ASSERT_EQ(backend.last_entry().fmtstring, fmtstring);
return true; 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_channel log("id", s, backend);
log.set_enabled(false); log.set_enabled(false);
std::string fmtstring = "Arg1: %u Arg2: %s"; std::string fmtstring = "test";
log(fmtstring, 42, "Hello"); log(fmtstring, 42, "Hello");
ASSERT_EQ(backend.push_invocation_count(), 0); 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; 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() int main()
{ {
TEST_FUNCTION(when_log_channel_is_created_then_id_matches_expected_value); TEST_FUNCTION(when_log_channel_is_created_then_id_matches_expected_value);
@ -153,6 +248,12 @@ int main()
TEST_FUNCTION( TEST_FUNCTION(
when_logging_in_log_channel_then_log_entry_is_pushed_into_the_backend); 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_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; return 0;
} }

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

@ -25,102 +25,93 @@
using namespace srslog; 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)) || \ #if (defined(__clang__) && (__clang_major__ >= 5)) || \
(defined(__GNUG__) && (__GNUC__ >= 5)) (defined(__GNUG__) && (__GNUC__ >= 5))
static_assert(std::is_trivially_copyable<detail::memory_buffer>::value, static_assert(std::is_trivially_copyable<detail::memory_buffer>::value,
"Expected to be trivially copyable"); "Expected to be trivially copyable");
#endif #endif
namespace { static bool when_fetching_channel_then_channel_instance_is_returned()
/// 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()
{ {
sink_dummy s; log_channel& channel1 = fetch_log_channel(test_id1);
log_channel* channel = create_log_channel(test_id, s); log_channel& channel2 = fetch_log_channel(test_id2, fetch_stdout_sink(), {});
ASSERT_NE(channel, nullptr); ASSERT_EQ(channel1.id(), test_id1);
ASSERT_EQ(channel->id(), test_id); ASSERT_EQ(channel2.id(), test_id2);
return true; 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* channel1 = find_log_channel(test_id1);
log_channel* channel = create_log_channel(test_id, s); 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; 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_EQ(find_log_channel("non_existent_channel"), nullptr);
ASSERT_NE(c, nullptr);
ASSERT_EQ(c->id(), test_id);
return true; 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; 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 = fetch_log_channel("logger.error");
log_channel& error = *create_log_channel("logger#error", s); log_channel& warning = fetch_log_channel("logger.warning");
log_channel& warning = *create_log_channel("logger#warning", s); log_channel& info = fetch_log_channel("logger.info");
log_channel& info = *create_log_channel("logger#info", s); log_channel& debug = fetch_log_channel("logger.debug");
basic_logger* logger =
create_logger<basic_logger>(logger_id, error, warning, info); 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; 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"); basic_logger& logger1 = fetch_basic_logger(basic_logger_id1);
log_channel& warning = *find_log_channel("logger#warning"); basic_logger& logger2 = fetch_basic_logger(basic_logger_id2, fetch_stdout_sink());
log_channel& info = *find_log_channel("logger#info");
basic_logger* logger =
create_logger<basic_logger>(logger_id, error, warning, info);
ASSERT_EQ(logger, nullptr); ASSERT_EQ(logger1.id(), basic_logger_id1);
ASSERT_EQ(logger2.id(), basic_logger_id2);
return true; return true;
} }
static bool when_valid_id_and_type_is_passed_then_logger_is_found() 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_NE(l, nullptr);
ASSERT_EQ(l->id(), logger_id); 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; 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); ASSERT_NE(s, nullptr);
return true; 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(find_sink("invalid"), nullptr);
ASSERT_EQ(s, nullptr);
return true; 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; 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; return true;
} }
int main() int main()
{ {
TEST_FUNCTION(when_no_channel_exists_then_channel_is_created); TEST_FUNCTION(when_fetching_channel_then_channel_instance_is_returned);
TEST_FUNCTION(when_channel_already_exists_then_nullptr_is_returned);
TEST_FUNCTION(when_valid_id_is_passed_then_channel_is_found); 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_non_existent_id_is_passed_then_nothing_is_found);
TEST_FUNCTION(when_no_logger_exists_then_logger_is_created); TEST_FUNCTION(
TEST_FUNCTION(when_logger_already_exists_then_nullptr_is_returned); 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_valid_id_and_type_is_passed_then_logger_is_found);
TEST_FUNCTION( TEST_FUNCTION(
when_invalid_id_with_valid_type_is_passed_then_no_logger_is_found); 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_invalid_id_and_type_is_passed_then_no_logger_is_found);
TEST_FUNCTION( TEST_FUNCTION(
when_valid_id_with_invalid_type_is_passed_then_no_logger_is_found); 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_file_sink_is_fetched_then_sink_instance_is_returned);
TEST_FUNCTION(when_sink_already_exists_then_nullptr_is_returned);
TEST_FUNCTION(when_valid_id_is_passed_then_sink_is_found);
TEST_FUNCTION(when_invalid_id_is_passed_then_no_sink_is_found); 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; return 0;
} }

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

@ -75,6 +75,8 @@ uint8_t deactivate_eps_bearer_pdu[] = {0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62,
uint16 mcc = 61441; uint16 mcc = 61441;
uint16 mnc = 65281; uint16 mnc = 65281;
static srslte::logger* g_logger = nullptr;
using namespace srslte; using namespace srslte;
namespace srslte { namespace srslte {
@ -271,14 +273,6 @@ int mme_attach_request_test()
srslte::log_filter usim_log("USIM"); srslte::log_filter usim_log("USIM");
srslte::log_filter gw_log("GW"); 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); rrc_log.set_level(srslte::LOG_LEVEL_DEBUG);
usim_log.set_level(srslte::LOG_LEVEL_DEBUG); usim_log.set_level(srslte::LOG_LEVEL_DEBUG);
gw_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_level = "debug";
gw_args.log.gw_hex_limit = 100000; gw_args.log.gw_hex_limit = 100000;
srslte::logger* logger = &log_wrapper; gw.init(gw_args, g_logger, &stack);
gw.init(gw_args, logger, &stack);
stack.init(&nas); stack.init(&nas);
usleep(5000); // Wait for stack to initialize before stoping it. 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) 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_log_level(LOG_LEVEL_DEBUG);
srslte::logmap::set_default_hex_limit(100000); srslte::logmap::set_default_hex_limit(100000);

Loading…
Cancel
Save