| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- //
- // format.cpp
- // logger
- //
- // Created by Sam Jaffe on 8/21/16.
- //
- #include <cstdint>
- #include <cstring>
- #include <ctime>
- #include <iomanip>
- #include <iostream>
- #include <map>
- #include <stdexcept>
- #include <string>
- #include "format.hpp"
- #include "logger.hpp"
- #if defined( _WIN32 )
- # define NEWLINE "\r\n"
- #else
- # define NEWLINE "\n"
- #endif
- namespace logging {
- class format_parsing_exception : public std::logic_error {
- public:
- using std::logic_error::logic_error;
- };
-
- class unknown_format_specifier : public std::logic_error {
- public:
- using std::logic_error::logic_error;
- };
- }
- namespace logging {
- namespace {
- std::string fmt_time(struct timeval round, char const * const fmt) {
- struct tm time;
- 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);
- return fmt_time(round, buf);
- }
- }
- using string_generator = std::function<std::string(logpacket const &)>;
-
- string_generator parse_date_format_string( char const * str ) {
- char const * const end = strchr(str, '}');
- if (end == nullptr)
- throw format_parsing_exception{"expected date-format specifier to terminate"};
- 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"), 4, "%.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;
- if ( is_string("{ISO8601}")) {
- predef_format = "%%Y-%%m-%%d %%H:%%M:%%S,%.03d";
- } else if (is_string("{ABSOLUTE}")) {
- predef_format = "%%H:%%M:%%S,%.04d";
- } else if (is_string("{DATE}")) {
- predef_format = "%%d %%b %%Y %%H:%%M:%%S,%.04d";
- } else if (is('{')) {
- return parse_date_format_string(next);
- }
- 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 {
- throw unknown_format_specifier{std::string("unknown format character: '") + *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);
- 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, '%' ) + 1 )
- != nullptr ) {
- if ( end == next ) {
- throw format_parsing_exception{"expected format specifier, got end of string"}; // TODO
- }
-
- if ( curr < next - 1 ) {
- out.gen.push_back(convert(string_token({curr, next - 1})));
- }
- if ( is('d') ) {
- ++next;
- if ( is('{') ) curr = std::strchr(next, '}');
- out.gen.push_back(convert(date_token(next)));
- } else if ( is('n') ) {
- out.gen.push_back(convert(string_token(NEWLINE)));
- } else if ( is('%') ) {
- ++next;
- out.gen.push_back(convert(string_token("%")));
- } else if ( is('.') || is('-') ||
- isdigit( *next ) ) {
- parse_with_bounds( next );
- } else {
- handle( next );
- }
- curr = ++next;
- }
- if ( curr < end ) {
- out.gen.push_back(convert(string_token({curr, end})));
- }
- return out;
- }
- #undef is
- }
- namespace logging {
- format_point_t get_next_format_specifier( std::string const & interp, size_t pos ) {
- size_t next = pos;
- char const * str = interp.c_str();
- do {
- pos = interp.find("{}", next);
- next = pos + 2;
- } while ( pos > 0 && pos != std::string::npos &&
- ! strncmp(str + pos - 1, "{{}}", 4) );
- return { pos, next };
- }
- }
|