// // format.cpp // logger // // Created by Sam Jaffe on 8/21/16. // #include "logger/format.h" #include #include #include #include #include #include #include #include #include "common.h" #include "logger/exception.h" #include "logger/logger.h" #if defined( _WIN32 ) # define NEWLINE "\r\n" #else # define NEWLINE "\n" #endif namespace logging { namespace { std::string fmt_time(struct timeval round, char const * const fmt) { struct tm time; // Supports localtime when requested, but you can't really test that if (strstr(fmt, "%z") || strstr(fmt, "%Z")) { localtime_r(&round.tv_sec, &time); } else { gmtime_r(&round.tv_sec, &time); } char buf[64] = {'\0'}; std::strftime(buf, sizeof(buf), fmt, &time); return buf; } std::string fmt_time(struct timeval round, std::string const & fmt) { return fmt_time(round, fmt.c_str()); } std::string fmt_time_with_milis(struct timeval round, std::string const & fmt) { char buf[64] = {'\0'}; snprintf(buf, sizeof(buf), fmt.c_str(), round.tv_usec/1000); return fmt_time(round, buf); } } using string_generator = std::function; string_generator parse_date_format_string( char const * str ) { char const * const end = strchr(str, '}'); if (end == nullptr) { std::string error_msg{"expected date-format specifier to terminate"}; throw format_parsing_exception{error_msg}; } char const * const has_millis = strstr( str, "%_ms"); std::string fmtstring{str, end}; if ( has_millis && has_millis < end ) { size_t pos = 0; while ( (pos = fmtstring.find("%", pos)) != std::string::npos ) { fmtstring.replace(pos, 1, "%%"); pos += 2; } fmtstring.replace(fmtstring.find("%%_ms"), 5, "%.03d"); return [=](logpacket const & lp) { return fmt_time_with_milis(lp.time, fmtstring); }; } else { return [=](logpacket const & lp) { return fmt_time(lp.time, fmtstring); }; } } #define is( chr ) *next == chr #define is_string( str ) ! strncmp(next, str, strlen(str)) string_generator date_token(char const * next) { std::string predef_format = "%%Y-%%m-%%d %%H:%%M:%%S,%.03d"; if ( is_string("{ISO8601}")) { predef_format = "%%Y-%%m-%%dT%%H:%%M:%%S.%.03dZ"; } else if (is_string("{ABSOLUTE}")) { predef_format = "%%H:%%M:%%S,%.03d"; } else if (is_string("{DATE}")) { predef_format = "%%d %%b %%Y %%H:%%M:%%S,%.03d"; } else if (is('{')) { return parse_date_format_string(next+1); } return [=](logpacket const & lp ){ return fmt_time_with_milis(lp.time, predef_format); }; } string_generator string_token(std::string str) { return [=]( logpacket const & ){ return str; }; } string_generator handle( char const * & next ) { if (is('c')) { return [](logpacket const & lp){ return lp.logger; }; } else if (is('p')) { return [](logpacket const & lp){ return level_header(lp.level); }; } else if (is('m')) { return [](logpacket const & lp){ return lp.message; }; } else { std::string error_msg{"unknown format character: '"}; throw unknown_format_specifier{error_msg + *next + "'"}; } } format::generator parse_with_bounds( char const * & next ) { bool const is_left = *next == '-'; auto align = is_left ? std::left : std::right; bool const trunc = *next == '.'; if ( is_left || trunc ) ++next; string_generator gen; size_t chars = 0; int min = std::stoi( next, &chars ); size_t max = 0xFFFFFFFF; next += chars; if ( trunc ) { max = min; min = 0; } else if ( *next == '.' ) { max = std::stoi( next + 1, &chars ); next += chars + 1; } gen = handle( next ); return [=](logpacket const & lp, std::ostream & out) { std::string str = gen(lp); if (str.length() > max) str.erase(str.begin()+max, str.end()); out << align << std::setw(min) << str; }; } format::generator convert( string_generator gen ) { return [=](logpacket const & lp, std::ostream & out) { out << gen( lp ); }; } format format::parse_format_string( std::string const & fmt ) { format out; char const * curr = fmt.c_str(); char const * next = nullptr; char const * const end = curr + fmt.size(); while ((next = std::strchr(curr, '%')) != nullptr) { ++next; if (end == next) { std::string error_msg{"expected format specifier, got end of string"}; throw format_parsing_exception{error_msg}; // TODO } if (curr < next-1) { out.gen.push_back(convert(string_token({curr, next - 1}))); } if (is('d')) { ++next; out.gen.push_back(convert(date_token(next))); if (is('{')) next = std::strchr(next, '}'); } else if (is('n')) { out.gen.push_back(convert(string_token(NEWLINE))); } else if (is('%')) { out.gen.push_back(convert(string_token("%"))); } else if (is('.') || is('-') || isdigit( *next )) { out.gen.push_back(parse_with_bounds(next)); } else { out.gen.push_back(convert(handle(next))); } curr = ++next; } if (curr < end) { out.gen.push_back(convert(string_token({curr, end}))); } return out; } #undef is void format::process(logpacket const & pkt, std::ostream & os) const { for (auto func : gen) { func(pkt, os); } } std::string format::process(logpacket const & pkt) const { std::stringstream ss; process(pkt, ss); return ss.str(); } } namespace logging { format_point_t get_next_format_specifier(std::string const & interp, size_t pos) { size_t next = interp.find("{}", pos); return { next, next + 2 }; } }