strange behaviour of format flag in boost::posix_time - c++

I have a string containing two time stamps. I am trying to convert both the time stamps into boost::posix_time::ptime.
the string format looks like:
"ASTRO20220923.1435+0000-20220923.1440+0000"
the format I use in boost::posix_time::time_input_facet for the second time stamp is ".*+A%Y%m%d.%H%M".
I get correct output but i don’t understand how i get it. On searching the format flags i cannot find any flag as ".*" or "+A". Can someone please help explain why this behaviour is happening?
[LIVE on coliru]
#include <iostream>
#include <boost/date_time/posix_time/posix_time.hpp>
int main()
{
std::string timeStr = "ASTRO20220923.1435+0000-20220923.1440+0000";
std::stringstream ss;
boost::posix_time::ptime pTimeStart;
ss.imbue(std::locale(ss.getloc(), new boost::posix_time::time_input_facet("ASTRO%Y%m%d.%H%M.*")));
ss << timeStr;
ss >> pTimeStart;
std::cout << "START: " << boost::posix_time::to_simple_string(pTimeStart) << std::endl;
boost::posix_time::ptime pTimeEnd;
ss.imbue(std::locale(ss.getloc(), new boost::posix_time::time_input_facet(".*+A%Y%m%d.%H%M")));
ss << timeStr;
ss >> pTimeEnd;
std::cout << "END : " << boost::posix_time::to_simple_string(pTimeEnd) << std::endl;
return 0;
}

Explanation
Boost input facets treat any non-field input character as a wildcard: it matches any input character. This goes for control codes and even embedded NUL characters. None of this is documented, and frankly seems dubious. See this ludicrous test program:
Live On Coliru
Which shows all chosen wildcards succeeding, no matter how far-fetched:
DEBUG : '.....%Y%m%d.%H%M..', '....%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'AAAAA%Y%m%d.%H%MAA', 'AAAA%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'BBBBB%Y%m%d.%H%MBB', 'BBBB%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'CCCCC%Y%m%d.%H%MCC', 'CCCC%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'DDDDD%Y%m%d.%H%MDD', 'DDDD%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'EEEEE%Y%m%d.%H%MEE', 'EEEE%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'fffff%Y%m%d.%H%Mff', 'ffff%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'ggggg%Y%m%d.%H%Mgg', 'gggg%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'hhhhh%Y%m%d.%H%Mhh', 'hhhh%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'iiiii%Y%m%d.%H%Mii', 'iiii%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'jjjjj%Y%m%d.%H%Mjj', 'jjjj%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'kkkkk%Y%m%d.%H%Mkk', 'kkkk%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'lllll%Y%m%d.%H%Mll', 'llll%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'mmmmm%Y%m%d.%H%Mmm', 'mmmm%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : 'nnnnn%Y%m%d.%H%Mnn', 'nnnn%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '11111%Y%m%d.%H%M11', '1111%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '22222%Y%m%d.%H%M22', '2222%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '33333%Y%m%d.%H%M33', '3333%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '44444%Y%m%d.%H%M44', '4444%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '55555%Y%m%d.%H%M55', '5555%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '66666%Y%m%d.%H%M66', '6666%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '77777%Y%m%d.%H%M77', '7777%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '88888%Y%m%d.%H%M88', '8888%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '99999%Y%m%d.%H%M99', '9999%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '00000%Y%m%d.%H%M00', '0000%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '_____%Y%m%d.%H%M__', '____%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '-----%Y%m%d.%H%M--', '----%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '/////%Y%m%d.%H%M//', '////%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '?????%Y%m%d.%H%M??', '????%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '*****%Y%m%d.%H%M**', '****%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '\\\\\\\\\\%Y%m%d.%H%M\\\\', '\\\\\\\\%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '"""""%Y%m%d.%H%M""', '""""%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : "'''''%Y%m%d.%H%M''", "''''%Y%m%d.%H%M" START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '[[[[[%Y%m%d.%H%M[[', '[[[[%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : ']]]]]%Y%m%d.%H%M]]', ']]]]%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '(((((%Y%m%d.%H%M((', '((((%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : ')))))%Y%m%d.%H%M))', '))))%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '&&&&&%Y%m%d.%H%M&&', '&&&&%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '^^^^^%Y%m%d.%H%M^^', '^^^^%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '$$$$$%Y%m%d.%H%M$$', '$$$$%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '#####%Y%m%d.%H%M##', '####%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '#####%Y%m%d.%H%M##', '####%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '!!!!!%Y%m%d.%H%M!!', '!!!!%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : ' %Y%m%d.%H%M ', ' %Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '
%Y%m%d.%H%M
', '
%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '
%Y%m%d.%H%M
', '
%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : ' %Y%m%d.%H%M ', ' %Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
DEBUG : '%Y%m%d.%H%M', '%Y%m%d.%H%M' START: 1400-Jan-01 00:00:00 END: 1400-Jan-01 00:00:00 IGNORED: ASTRO20220923.1435+0000-20220923.1440+0000 (true)
DEBUG : '%Y%m%d.%H%M', '%Y%m%d.%H%M' START: 2022-Sep-23 14:35:00 END: 2022-Sep-23 14:40:00 IGNORED: +0000 (true)
Parsing With Spirit
To parse the start/end period with the given format, I would not use facets in the first place, because none of the format appears locale-aware. The fact that you use ptime (not local_date_time) confirms that the time-zone indication (+0000) is not significant.
So, here's what you could write:
Live On Coliru
// #define BOOST_SPIRIT_X3_DEBUG
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iostream>
using boost::posix_time::ptime;
namespace Astro {
namespace Parser {
namespace x3 = boost::spirit::x3;
using Part = unsigned short;
using Parts = std::tuple<Part, Part, Part, Part, Part>;
static auto const dig4 = x3::uint_parser<Part, 10, 4, 4>{};
static auto const dig2 = x3::uint_parser<Part, 10, 2, 2>{};
static auto const timeformat
= x3::rule<struct partsformat_, Parts>{"timeformat"}
= dig4 >> dig2 >> dig2 >> '.' >> dig2 >> dig2 >> "+0000";
static auto const period //
= "ASTRO" >> Parser::timeformat >> "-" >> Parser::timeformat >> x3::eoi;
} // namespace Parser
ptime to_ptime(Parser::Parts const& p) {
auto [yyyy, mm, dd, HH, MM] = p;
return {{yyyy, mm, dd}, boost::posix_time::hours(HH) + boost::posix_time::minutes(MM)};
}
struct Period {
ptime start, end;
};
Period parsePeriod(std::string_view input) {
Parser::Parts s, e;
auto both = std::tie(s, e);
if (parse(begin(input), end(input), Parser::period, both))
return {to_ptime(s), to_ptime(e)};
throw std::runtime_error("parsePeriod");
}
}
int main() {
auto [s, e] = Astro::parsePeriod("ASTRO20220923.1435+0000-20220923.1440+0000");
std::cout << "start: " << s << "\n";
std::cout << "end: " << e << "\n";
}
Prints
start: 2022-Sep-23 14:35:00
end: 2022-Sep-23 14:40:00
If you really need stream support, you can build that on top:
Live On Coliru
template <typename It>
bool parsePeriod(It& f, It l, Period& into, bool require_eoi = true) {
Parser::Parts s, e;
auto parts = std::tie(s, e);
bool ok = parse(f, l, Parser::period , parts);
if (ok) {
into = {to_ptime(s), to_ptime(e)};
if (require_eoi)
ok = ok && (f == l);
}
return ok;
}
Period parsePeriod(std::string_view input) {
Period result;
auto f = begin(input), l = end(input);
if (!parsePeriod(f, l, result, true))
throw std::runtime_error("parsePeriod");
return result;
}
std::istream& operator>>(std::istream& is, Period& period) {
auto saved = is.flags();
boost::spirit::istream_iterator f(is >> std::noskipws), l;
if (!parsePeriod(f, l, period , false))
is.setstate(std::ios::failbit);
is.flags(saved);
return is;
}
Which allows you to use it in the same way:
std::istringstream iss("ASTRO20220923.1435+0000-20220923.1440+0000");
Astro::Period p;
if (iss >> p) {
std::cout << "start: " << p.start << "\n";
std::cout << "end: " << p.end << "\n";
} else {
std::cout << "Parse error\n";
}
Printing
start: 2022-Sep-23 14:35:00
end: 2022-Sep-23 14:40:00
BENCHMARKS!
To drive home the point how utterly inefficient the input facet is, I made a benchmark.
It includes the
question code (using boost input facet)
naively using the facet but with some of the issues from the question removed
throwing all the Clever™ tricks to optimize at the boost input facet
my Spirit X3 version
Here's the timings (using Nonius): (click for interactive chart with detail samples)
Benchmark code: Coliru
#include <iostream>
#include <boost/date_time/posix_time/posix_time.hpp>
namespace FacetQuestionCode {
// code from the question, already removing output and one unnecessary allocation (`timeStr` is now a
// string view)
//
// - still has unnecessary facet instantiation/destruction, locale creation/destruction, spurious double
// stream insertion and no error detection
auto parsePeriod(std::string_view timeStr) {
std::stringstream ss;
boost::posix_time::ptime pTimeStart;
ss.imbue(std::locale(ss.getloc(), new boost::posix_time::time_input_facet("ASTRO%Y%m%d.%H%M.*")));
ss << timeStr;
ss >> pTimeStart;
// std::cout << "START: " << boost::posix_time::to_simple_string(pTimeStart) << std::endl;
boost::posix_time::ptime pTimeEnd;
ss.imbue(std::locale(ss.getloc(), new boost::posix_time::time_input_facet(".*+A%Y%m%d.%H%M")));
ss << timeStr;
ss >> pTimeEnd;
// std::cout << "END : " << boost::posix_time::to_simple_string(pTimeEnd) << std::endl;
return std::pair(pTimeStart, pTimeEnd);
}
}
namespace FacetNaive {
// undocumented behaviour, error-prone because not-specifc
auto parsePeriod(std::string_view input) {
std::stringstream ss{std::string(input)};
auto* facet = new boost::posix_time::time_input_facet();
ss.exceptions(std::ios::failbit | std::ios::badbit);
ss.imbue(std::locale(ss.getloc(), facet));
boost::posix_time::ptime s, e;
facet->format("?????%Y%m%d.%H%M??"); ss >> s;
facet->format("????%Y%m%d.%H%M?????"); ss >> e;
return std::pair(s, e);
}
}
namespace FacetClever {
// undocumented behaviour, error-prone because not-specifc
// very dirty optimization tricks
auto parsePeriod(std::string_view input) {
thread_local std::stringstream ss;
thread_local boost::posix_time::time_input_facet* facet = [&] {
auto tmp = new boost::posix_time::time_input_facet();
ss.exceptions(std::ios::failbit | std::ios::badbit);
ss.imbue(std::locale(ss.getloc(), tmp));
return tmp;
}();
ss.clear();
ss.str(std::string(input));
boost::posix_time::ptime s, e;
facet->format("?????%Y%m%d.%H%M??"); ss >> s;
facet->format("????%Y%m%d.%H%M?????"); ss >> e;
return std::pair(s, e);
}
}
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/home/x3.hpp>
namespace SpiritX3 {
namespace Parser {
namespace x3 = boost::spirit::x3;
using Part = unsigned short;
using Parts = std::tuple<Part, Part, Part, Part, Part>;
static auto const dig4 = x3::uint_parser<Part, 10, 4, 4>{};
static auto const dig2 = x3::uint_parser<Part, 10, 2, 2>{};
static auto const timeformat
= x3::rule<struct partsformat_, Parts>{"timeformat"}
= dig4 >> dig2 >> dig2 >> '.' >> dig2 >> dig2 >> "+0000";
static auto const period //
= "ASTRO" >> Parser::timeformat >> "-" >> Parser::timeformat >> x3::eoi;
} // namespace Parser
boost::posix_time::ptime to_ptime(Parser::Parts const& p) {
auto [yyyy, mm, dd, HH, MM] = p;
return {{yyyy, mm, dd}, boost::posix_time::hours(HH) + boost::posix_time::minutes(MM)};
}
struct Period {
boost::posix_time::ptime start, end;
};
Period parsePeriod(std::string_view input) {
Parser::Parts s, e;
auto both = std::tie(s, e);
if (parse(begin(input), end(input), Parser::period, both))
return {to_ptime(s), to_ptime(e)};
throw std::runtime_error("parsePeriod");
}
}
#define NONIUS_RUNNER
#include <nonius/benchmark.h++>
#include <nonius/main.h++>
constexpr auto input = "ASTRO20220923.1435+0000-20220923.1440+0000";
NONIUS_BENCHMARK("FacetQuestionCode", []{ FacetQuestionCode::parsePeriod(input); })
NONIUS_BENCHMARK("FacetNaive", []{ FacetNaive::parsePeriod(input); })
NONIUS_BENCHMARK("FacetClever", []{ FacetClever::parsePeriod(input); })
NONIUS_BENCHMARK("SpiritX3", []{ SpiritX3::parsePeriod(input); })
Note how:
the FacetNaive is already 2x faster than the FacetQuestionCode
the Too Clever By Far(TM) optimized version using facets is still 20x slower than Spirit
all the facet versions suffer from lack of specificity and rely on undocumented behavior
The Spirit version is effectively >11,000x faster than the original code, also with the smalles relative deviations
The choice really doesn't seem too hard.

Related

Unexpected end of file while reading mpstat output with BOOST ASIO async_read_until

I try to read the output of mpstat command (collecting cpu information every second, ie: "mptstat -P ALL 1") in order to get information about cpu and core usage. On a multicore cpu, I get an unexpected "end of file" status just after having read the first measurements.
It appears that mpstat formats its output in such a way that measurements for all cores are separated by an empty line.
I have used async_read_until with a delimiter equal to '\n'.
Please find below a small reproducer. With this reproducer, I get the following:
Enter handle_read
handle_read got data: --Linux 4.13.0-46-generic (pierre) 26/08/2018 _x86_64_ (4 CPU)
--Enter handle_read
handle_read got data: --
11:39:11 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
11:39:11 all--Enter handle_read
handle_read got data: -- 5,69 0,06 2,90 0,20 0,00 0,02 0,00 0,00 0,00 91,13
11:39:11 0 5,95 0,05--Enter handle_read
handle_read got data: -- 2,80 0,13 0,00 0,01 0,00 0,00 0,00 91,06
11:39:11 1 5,24 0,01 2,50 0,14 0,00 0,02 0,00 0,00 0,00 92,09
11:39:11 2 6,30 0,17--Enter handle_read
handle_read got data: -- 2,29 0,36 0,00 0,04 0,00 0,00 0,00 90,85
11:39:11 3 5,28 0,01 4,01 0,17 0,00 0,00 0,00 0,00 0,00 90,52
--Enter handle_read
Problem while trying to read mpstat data: End of file
Basically, I am able to receive the first measurement but I immediatly get an "end of file" just after. Looks like the empty line issued by mpstat is why I get the "end of line" indication... but I can't understand why...
Could somebody provide some help? Many thanks in advance.
#include <boost/asio.hpp>
#include <unistd.h>
#include <iostream>
#define PIPE_READ 0
#define PIPE_WRITE 1
#define ENDOFLINE "\n"
static boost::asio::streambuf data;
static std::shared_ptr<boost::asio::posix::stream_descriptor> cpuLoadDataStream;
static void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) {
printf("Enter handle_read\n");
if (error == boost::system::errc::success) {
if (data.size() > 0) {
std::string dataReceived((std::istreambuf_iterator<char>(&data)), std::istreambuf_iterator<char>());
std::cout << "handle_read got data: " << "--" << dataReceived << "--";
}
boost::asio::async_read_until(*cpuLoadDataStream, data, ENDOFLINE, handle_read);
} else {
std::cout << "Problem while trying to read mpstat data: " << error.message() << std::endl;
}
}
int main() {
int pipeFd[2];
boost::asio::io_service ioService;
if (pipe(pipeFd) < 0) {
std::cout << "pipe error" << std::endl;
exit(EXIT_FAILURE);
}
int pid;
if ((pid = fork()) == 0) {
// son
close(pipeFd[PIPE_READ]);
if (dup2(pipeFd[PIPE_WRITE], STDOUT_FILENO) == -1) {
std::cout << "dup2 error" << std::endl;
exit(EXIT_FAILURE);
}
close(pipeFd[PIPE_WRITE]);
if (execlp("mpstat", "1", "-P", "ALL", 0) == -1) {
std::cout << "execlp error" << std::endl;
exit(EXIT_FAILURE);
}
} else {
// parent
if (pid == -1) {
std::cout << "fork error" << std::endl;
exit(EXIT_FAILURE);
} else {
close(pipeFd[PIPE_WRITE]);
cpuLoadDataStream = std::make_shared<boost::asio::posix::stream_descriptor>(ioService, ::dup(pipeFd[PIPE_READ]));
boost::asio::async_read_until(*cpuLoadDataStream, data, ENDOFLINE, handle_read);
}
}
ioService.run();
return 1;
}
First off: if you want "1" to mean that mpstat runs repeatedly at 1 second interval, you must make it the first argument, not the process name:
if (execlp("mpstat", "mpstat", "1", "-P", "ALL", 0) == -1) {
See the documentation:
The first argument, by convention, should point to the filename associated with the file being executed.
async_read_until reads until the input buffer at least contains the delimiter. So when you read the buffer, you must take into account the bytes_tranferred which will be excluding the delimiter.
Make sure to also consume the delimiter to avoid getting stuck in an infinite loop (because the stopping condition is already met):
void handle_read(const boost::system::error_code &error, std::size_t bytes_transferred) {
std::cout << "Enter handle_read (" << error.message() << ")\n";
if (!error) {
if (bytes_transferred > 0) {
std::copy_n(std::istreambuf_iterator<char>(&data), bytes_transferred, std::ostreambuf_iterator<char>(std::cout << "handle_read got data: '"));
std::cout << "' --\n";
data.consume(delimiter.size());
}
do_read_loop();
}
}
Or simpler:
void handle_read(const boost::system::error_code &error, std::size_t bytes_transferred) {
std::cout << "Enter handle_read (" << error.message() << ")\n";
if (!error) {
std::string line;
if (getline(std::istream(&data), line))
std::cout << "handle_read got data: '" << line << "'\n";
do_read_loop();
}
}
I'd prefer the first one because it is more explicit and generally applicable.
Side Notes:
there was a problem with the way you used global data (that would only get destroyed after the corresponding io_servce was already out of scope). That was UB - using asan/ubsan allows you to spot these bugs.
Using io_service across forks is not supported without support from the service implementations, see https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/io_context/notify_fork.html
Fixed/Simplified Asio
Here's with some simplifications, and cutting the globals:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <unistd.h>
namespace ba = boost::asio;
struct Reader : std::enable_shared_from_this<Reader> {
ba::streambuf data;
ba::posix::stream_descriptor cpuLoadDataStream;
std::string const delimiter = "\n";
Reader(ba::io_service& svc, int fd)
: cpuLoadDataStream(svc, fd) {}
void do_read_loop() {
async_read_until(cpuLoadDataStream, data, delimiter, boost::bind(&Reader::handle_read, shared_from_this(), _1, _2));
}
void handle_read(const boost::system::error_code &error, std::size_t bytes_transferred) {
std::cout << "Enter handle_read (" << error.message() << ")\n";
if (!error) {
if (bytes_transferred > 0) {
std::copy_n(std::istreambuf_iterator<char>(&data), bytes_transferred, std::ostreambuf_iterator<char>(std::cout << "handle_read got data: '"));
std::cout << "' --\n";
data.consume(delimiter.size());
}
do_read_loop();
}
}
};
int main() {
int fds[2];
int& readFd = fds[0];
int& writeFd = fds[1];
if (pipe(fds) == -1) {
std::cout << "pipe error" << std::endl;
return 1;
}
if (int pid = fork()) {
ba::io_service ioService;
// parent
if (pid == -1) {
std::cerr << "fork error" << std::endl;
return 2;
} else {
close(writeFd);
std::make_shared<Reader>(ioService, ::dup(readFd))->do_read_loop();
}
ioService.run();
} else {
ba::io_service ioService;
// child
if (dup2(writeFd, STDOUT_FILENO) == -1) {
std::cerr << "dup2 error" << std::endl;
return 3;
}
close(readFd);
close(writeFd);
if (execlp("mpstat", "mpstat", "-P", "ALL", 0) == -1) {
std::cerr << "execlp error" << std::endl;
return 4;
}
ioService.run();
}
}
Note it's not continuous for obvious reasons on Coliru
Much Simpler: Boost Process
Why not use Boost Process instead?
#include <boost/process.hpp>
#include <iostream>
#include <iomanip>
namespace bp = boost::process;
int main() {
bp::pstream output;
bp::system("mpstat -P ALL", bp::std_out > output);
for (std::string line; std::getline(output, line);) {
std::cout << "Got: " << std::quoted(line) << "\n";
}
}
Or to make it async again:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <iostream>
#include <iomanip>
namespace bp = boost::process;
using Args = std::vector<std::string>;
int main() {
boost::asio::io_service io;
bp::async_pipe output(io);
bp::child mpstat(bp::search_path("mpstat"),
//Args { "1", "-P", "ALL" },
Args { "1", "3" }, // limited to 3 iterations on Coliru
bp::std_out > output, io);
boost::asio::streambuf data;
std::function<void(boost::system::error_code, size_t)> handle = [&](boost::system::error_code ec, size_t /*bytes_transferred*/) {
if (ec) {
std::cout << "Good bye (" << ec.message() << ")\n";
} else {
std::string line;
std::getline(std::istream(&data), line);
std::cout << "Got: " << std::quoted(line) << "\n";
async_read_until(output, data, "\n", handle);
}
};
async_read_until(output, data, "\n", handle);
io.run();
}
Prints
Got: "Linux 4.4.0-57-generic (stacked-crooked) 08/26/18 _x86_64_ (4 CPU)"
Got: ""
Got: "13:23:20 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle"
Got: "13:23:21 all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00"
Got: "13:23:22 all 0.00 0.00 0.00 0.25 0.00 0.00 0.00 0.00 0.00 99.75"
Got: "13:23:23 all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00"
Got: "Average: all 0.00 0.00 0.00 0.08 0.00 0.00 0.00 0.00 0.00 99.92"
Good bye (End of file)

`boost::asio::write` with a timeout [duplicate]

I´m trying to build a synchronous FTP client code with timeout using a thread as the timeout control. The thread will be started on every transaction and will close the socket in case of timeout - that will force the syncronous call to return with error.
So here is my code:
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <chrono>
#include <boost/asio.hpp>
#define TIMEOUT_SECONDS 5
#define MAX_MESSAGE_SIZE 4096
using boost::asio::ip::tcp;
enum { max_length = 1024 };
bool timerOn;
void socket_timer(tcp::socket& s, int seconds)
{
std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
while (timerOn)
{
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
auto interval = std::chrono::duration_cast<std::chrono::seconds>(now - start).count();
if (interval > seconds)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Not to run in 100% CPU
}
if (timerOn)
s.close();
}
void start_timer(int seconds, tcp::socket& s)
{
timerOn = true;
std::thread t(socket_timer, s, seconds);
t.detach();
}
void stop_timer()
{
timerOn = false;
}
int main(int argc, char* argv[])
{
std::string address;
while(address != "END")
{
try
{
boost::asio::io_service io_service;
std::cout << "Enter FTP server address to connect or END to finish: " << std::endl;
std::cin >> address;
if (address == "END")
break;
tcp::socket s(io_service);
tcp::resolver resolver(io_service);
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(address), 21);
start_timer(TIMEOUT_SECONDS, s);
boost::system::error_code ec;
s.connect(endpoint, ec);
stop_timer();
if (ec)
{
throw std::runtime_error("Error connecting to server.");
}
std::cout << "Connected to " << s.remote_endpoint().address().to_string() << std::endl;
char reply[max_length];
start_timer(TIMEOUT_SECONDS, s);
size_t bytes = s.receive(boost::asio::buffer(reply, MAX_MESSAGE_SIZE), 0, ec);
stop_timer();
if (ec)
{
throw std::runtime_error("Error receiving message.");
}
std::cout << "Received message is: ";
std::cout.write(reply, bytes);
std::cout << "\n";
std::cout << "Enter message: ";
char request[max_length];
std::cin.getline(request, max_length);
size_t request_length = std::strlen(request);
start_timer(TIMEOUT_SECONDS, s);
boost::asio::write(s, boost::asio::buffer(request, request_length));
stop_timer();
if (ec)
{
throw std::runtime_error("Error sending message.");
}
}
catch (std::exception& e)
{
std::cerr << "COMMUNICATIONS ERROR." << "\n";
std::cerr << "Exception: " << e.what() << "\n";
}
}
return 0;
}
I simply cannot compile this code, as boost is showing me the following error:
1>------ Build started: Project: TestAsio, Configuration: Debug Win32 ------
1> main.cpp
1>c:\boost_1_60\boost\asio\basic_socket.hpp(1513): error C2248: 'boost::asio::basic_io_object<IoObjectService>::basic_io_object' : cannot access private member declared in class 'boost::asio::basic_io_object<IoObjectService>'
1> with
1> [
1> IoObjectService=boost::asio::stream_socket_service<boost::asio::ip::tcp>
1> ]
1> c:\boost_1_60\boost\asio\basic_io_object.hpp(230) : see declaration of 'boost::asio::basic_io_object<IoObjectService>::basic_io_object'
1> with
1> [
1> IoObjectService=boost::asio::stream_socket_service<boost::asio::ip::tcp>
1> ]
1> This diagnostic occurred in the compiler generated function 'boost::asio::basic_socket<Protocol,SocketService>::basic_socket(const boost::asio::basic_socket<Protocol,SocketService> &)'
1> with
1> [
1> Protocol=boost::asio::ip::tcp,
1> SocketService=boost::asio::stream_socket_service<boost::asio::ip::tcp>
1> ]
========== Build: 0 succeeded, 1 failed, 9 up-to-date, 0 skipped ==========
So, I wanna know about 2 things:
a) What am I doing wrong in the code ?
b) Will this approach of closing the socket on a parallel thread work for timing out the socket ? Please fell free to comment it.
Thanks for helping.
I've made a helper facility to do any Asio async operation "synchronously" with a timeout here, look for await_operation:
boost::asio + std::future - Access violation after closing socket
You should be able to adapt the pattern for your sample.
Demo
It took a while since I wanted to test this with an ftp server.
Notes:
you didn't resolve the address (effectively requiring the user to type in IP address)
you didn't make sure commands were closed with newline
you didn't handle any kind of input error
Fixing these things and using my await_operation you'd get this:
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <chrono>
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#define TIMEOUT std::chrono::seconds(5)
#define MAX_MESSAGE_SIZE 4096
using boost::asio::ip::tcp;
enum { max_length = 2048 };
struct Service {
using error_code = boost::system::error_code;
template<typename AllowTime, typename Cancel> void await_operation_ex(AllowTime const& deadline_or_duration, Cancel&& cancel) {
using namespace boost::asio;
ioservice.reset();
{
high_resolution_timer tm(ioservice, deadline_or_duration);
tm.async_wait([&cancel](error_code ec) { if (ec != error::operation_aborted) std::forward<Cancel>(cancel)(); });
ioservice.run_one();
}
ioservice.run();
}
template<typename AllowTime, typename ServiceObject> void await_operation(AllowTime const& deadline_or_duration, ServiceObject& so) {
return await_operation_ex(deadline_or_duration, [&so]{ so.cancel(); });
}
boost::asio::io_service ioservice;
};
int main()
{
while(true)
{
try
{
Service service;
std::cout << "Enter FTP server address to connect or END to finish: " << std::endl;
std::string address;
if (std::cin >> address) {
if (address == "END") break;
} else {
if (std::cin.eof())
break;
std::cerr << "Invalid input ignored\n";
std::cin.clear();
std::cin.ignore(1024, '\n');
continue;
}
tcp::socket s(service.ioservice);
tcp::resolver resolver(service.ioservice);
boost::asio::async_connect(s, resolver.resolve({address, "21"}), [](boost::system::error_code ec, tcp::resolver::iterator it) {
if (ec) throw std::runtime_error("Error connecting to server: " + ec.message());
std::cout << "Connected to " << it->endpoint() << std::endl;
});
service.await_operation_ex(TIMEOUT, [&]{
throw std::runtime_error("Error connecting to server: timeout\n");
});
auto receive = [&] {
boost::asio::streambuf sb;
size_t bytes;
boost::asio::async_read_until(s, sb, '\n', [&](boost::system::error_code ec, size_t bytes_transferred) {
if (ec) throw std::runtime_error("Error receiving message: " + ec.message());
bytes = bytes_transferred;
std::cout << "Received message is: " << &sb;
});
service.await_operation(TIMEOUT, s);
return bytes;
};
receive(); // banner
auto send = [&](std::string cmd) {
boost::asio::async_write(s, boost::asio::buffer(cmd), [](boost::system::error_code ec, size_t /*bytes_transferred*/) {
if (ec) throw std::runtime_error("Error sending message: " + ec.message());
});
service.await_operation(TIMEOUT, s);
};
auto ftp_command = [&](std::string cmd) {
send(cmd + "\r\n");
receive(); // response
};
//ftp_command("USER bob");
//ftp_command("PASS hello");
while (true) {
std::cout << "Enter command: ";
std::string request;
if (!std::getline(std::cin, request))
break;
ftp_command(request);
}
}
catch (std::exception const& e)
{
std::cerr << "COMMUNICATIONS ERROR " << e.what() << "\n";
}
}
return 0;
}
Which, in my test run, prints e.g.:

Boost async_read_until with timeout and bloking read [duplicate]

I´m trying to build a synchronous FTP client code with timeout using a thread as the timeout control. The thread will be started on every transaction and will close the socket in case of timeout - that will force the syncronous call to return with error.
So here is my code:
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <chrono>
#include <boost/asio.hpp>
#define TIMEOUT_SECONDS 5
#define MAX_MESSAGE_SIZE 4096
using boost::asio::ip::tcp;
enum { max_length = 1024 };
bool timerOn;
void socket_timer(tcp::socket& s, int seconds)
{
std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
while (timerOn)
{
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
auto interval = std::chrono::duration_cast<std::chrono::seconds>(now - start).count();
if (interval > seconds)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Not to run in 100% CPU
}
if (timerOn)
s.close();
}
void start_timer(int seconds, tcp::socket& s)
{
timerOn = true;
std::thread t(socket_timer, s, seconds);
t.detach();
}
void stop_timer()
{
timerOn = false;
}
int main(int argc, char* argv[])
{
std::string address;
while(address != "END")
{
try
{
boost::asio::io_service io_service;
std::cout << "Enter FTP server address to connect or END to finish: " << std::endl;
std::cin >> address;
if (address == "END")
break;
tcp::socket s(io_service);
tcp::resolver resolver(io_service);
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(address), 21);
start_timer(TIMEOUT_SECONDS, s);
boost::system::error_code ec;
s.connect(endpoint, ec);
stop_timer();
if (ec)
{
throw std::runtime_error("Error connecting to server.");
}
std::cout << "Connected to " << s.remote_endpoint().address().to_string() << std::endl;
char reply[max_length];
start_timer(TIMEOUT_SECONDS, s);
size_t bytes = s.receive(boost::asio::buffer(reply, MAX_MESSAGE_SIZE), 0, ec);
stop_timer();
if (ec)
{
throw std::runtime_error("Error receiving message.");
}
std::cout << "Received message is: ";
std::cout.write(reply, bytes);
std::cout << "\n";
std::cout << "Enter message: ";
char request[max_length];
std::cin.getline(request, max_length);
size_t request_length = std::strlen(request);
start_timer(TIMEOUT_SECONDS, s);
boost::asio::write(s, boost::asio::buffer(request, request_length));
stop_timer();
if (ec)
{
throw std::runtime_error("Error sending message.");
}
}
catch (std::exception& e)
{
std::cerr << "COMMUNICATIONS ERROR." << "\n";
std::cerr << "Exception: " << e.what() << "\n";
}
}
return 0;
}
I simply cannot compile this code, as boost is showing me the following error:
1>------ Build started: Project: TestAsio, Configuration: Debug Win32 ------
1> main.cpp
1>c:\boost_1_60\boost\asio\basic_socket.hpp(1513): error C2248: 'boost::asio::basic_io_object<IoObjectService>::basic_io_object' : cannot access private member declared in class 'boost::asio::basic_io_object<IoObjectService>'
1> with
1> [
1> IoObjectService=boost::asio::stream_socket_service<boost::asio::ip::tcp>
1> ]
1> c:\boost_1_60\boost\asio\basic_io_object.hpp(230) : see declaration of 'boost::asio::basic_io_object<IoObjectService>::basic_io_object'
1> with
1> [
1> IoObjectService=boost::asio::stream_socket_service<boost::asio::ip::tcp>
1> ]
1> This diagnostic occurred in the compiler generated function 'boost::asio::basic_socket<Protocol,SocketService>::basic_socket(const boost::asio::basic_socket<Protocol,SocketService> &)'
1> with
1> [
1> Protocol=boost::asio::ip::tcp,
1> SocketService=boost::asio::stream_socket_service<boost::asio::ip::tcp>
1> ]
========== Build: 0 succeeded, 1 failed, 9 up-to-date, 0 skipped ==========
So, I wanna know about 2 things:
a) What am I doing wrong in the code ?
b) Will this approach of closing the socket on a parallel thread work for timing out the socket ? Please fell free to comment it.
Thanks for helping.
I've made a helper facility to do any Asio async operation "synchronously" with a timeout here, look for await_operation:
boost::asio + std::future - Access violation after closing socket
You should be able to adapt the pattern for your sample.
Demo
It took a while since I wanted to test this with an ftp server.
Notes:
you didn't resolve the address (effectively requiring the user to type in IP address)
you didn't make sure commands were closed with newline
you didn't handle any kind of input error
Fixing these things and using my await_operation you'd get this:
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <chrono>
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#define TIMEOUT std::chrono::seconds(5)
#define MAX_MESSAGE_SIZE 4096
using boost::asio::ip::tcp;
enum { max_length = 2048 };
struct Service {
using error_code = boost::system::error_code;
template<typename AllowTime, typename Cancel> void await_operation_ex(AllowTime const& deadline_or_duration, Cancel&& cancel) {
using namespace boost::asio;
ioservice.reset();
{
high_resolution_timer tm(ioservice, deadline_or_duration);
tm.async_wait([&cancel](error_code ec) { if (ec != error::operation_aborted) std::forward<Cancel>(cancel)(); });
ioservice.run_one();
}
ioservice.run();
}
template<typename AllowTime, typename ServiceObject> void await_operation(AllowTime const& deadline_or_duration, ServiceObject& so) {
return await_operation_ex(deadline_or_duration, [&so]{ so.cancel(); });
}
boost::asio::io_service ioservice;
};
int main()
{
while(true)
{
try
{
Service service;
std::cout << "Enter FTP server address to connect or END to finish: " << std::endl;
std::string address;
if (std::cin >> address) {
if (address == "END") break;
} else {
if (std::cin.eof())
break;
std::cerr << "Invalid input ignored\n";
std::cin.clear();
std::cin.ignore(1024, '\n');
continue;
}
tcp::socket s(service.ioservice);
tcp::resolver resolver(service.ioservice);
boost::asio::async_connect(s, resolver.resolve({address, "21"}), [](boost::system::error_code ec, tcp::resolver::iterator it) {
if (ec) throw std::runtime_error("Error connecting to server: " + ec.message());
std::cout << "Connected to " << it->endpoint() << std::endl;
});
service.await_operation_ex(TIMEOUT, [&]{
throw std::runtime_error("Error connecting to server: timeout\n");
});
auto receive = [&] {
boost::asio::streambuf sb;
size_t bytes;
boost::asio::async_read_until(s, sb, '\n', [&](boost::system::error_code ec, size_t bytes_transferred) {
if (ec) throw std::runtime_error("Error receiving message: " + ec.message());
bytes = bytes_transferred;
std::cout << "Received message is: " << &sb;
});
service.await_operation(TIMEOUT, s);
return bytes;
};
receive(); // banner
auto send = [&](std::string cmd) {
boost::asio::async_write(s, boost::asio::buffer(cmd), [](boost::system::error_code ec, size_t /*bytes_transferred*/) {
if (ec) throw std::runtime_error("Error sending message: " + ec.message());
});
service.await_operation(TIMEOUT, s);
};
auto ftp_command = [&](std::string cmd) {
send(cmd + "\r\n");
receive(); // response
};
//ftp_command("USER bob");
//ftp_command("PASS hello");
while (true) {
std::cout << "Enter command: ";
std::string request;
if (!std::getline(std::cin, request))
break;
ftp_command(request);
}
}
catch (std::exception const& e)
{
std::cerr << "COMMUNICATIONS ERROR " << e.what() << "\n";
}
}
return 0;
}
Which, in my test run, prints e.g.:

Boost logging, filter by named scope

I am using boost log in my application, and while it has been tricky to configure it is generally working well.
Now however, I would like to add some more advanced filtering logic to my application, and I can't figure it out.
What I would like is to have two "levels" of filtering:
I am already using a "severity logger" with different levels like debug, warn, note etc. This is setup and working.
I would like to add an additional way to filter records by looking at the "named scope" that the record emanates from.
So I would like for instance to be able to see only the records with severity >= note, AND within a NAMED_SCOPE of monthly.
I have successfully been able to use the BOOST_LOG_NAMED_SCOPE() macro and can see the scope stack in the log messages.
I have tried implementing a custom filter with boost::phoenix, but I can't get it to work.
The code I have pasted here compiles in my application (I am working on stripping this down so I can include a complete working minimal example here), but nothing appears to happen in the my_filter(..) function. I think that I am not correctly passing the scope stack into the bound function, because if I put a std::cout statement within the loop over the scope stack, I do not see anything printed.
Minimal example here:
// Excerpt from MyLogger.cpp class
bool my_filter(attrs::named_scope_list const& scopeList) {
for (attrs::named_scope_list::const_iterator iter = scopeList.begin(); iter != scopeList.end(); ++iter) {
if ( (*iter).scope_name == "monthly") {
return true;
}
}
return false;
}
void setup_logging(std::string lvl) {
logging::core::get()->add_global_attribute(
"Scope", attrs::named_scope()
);
logging::add_console_log(
std::clog,
keywords::format = (
expr::stream
<< "[" << severity << "] "
<< "[" << named_scope << "] "
<< expr::smessage
)
);
try {
// set the severity level...
EnumParser<severity_level> parser;
logging::core::get()->set_filter(
(
severity >= parser.parseEnum(lvl) &&
( expr::has_attr("Scope") && ( boost::phoenix::bind(&my_filter, attrs::named_scope::get_scopes() ) ) )
)
);
} catch (std::runtime_error& e) {
std::cout << e.what() << std::endl;
std::cout << "'" << lvl << "' is an invalid --log-level! Must be one of "
<< "[debug, info, note, warn, err, fatal]\n";
exit(-1);
}
}
EDIT Updated with minimal working example:
TEMLogger.h
#ifndef _TEMLOGGER_H_
#define _TEMLOGGER_H_
#include <string>
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <exception>
#include <map>
#include <iomanip>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sources/global_logger_storage.hpp>
#include <boost/log/sources/severity_feature.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/attributes/current_process_id.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace attrs = boost::log::attributes;
namespace keywords = boost::log::keywords;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
/** Define the "severity levels" for Boost::Log's severity logger. */
enum severity_level {
debug, info, note, warn, err, fatal
};
/** Convert from string to enum integer value.
*
* Inspired by: http://stackoverflow.com/questions/726664/string-to-enum-in-c
*/
template <typename T>
class EnumParser {
std::map <std::string, T> enumMap;
public:
EnumParser(){};
T parseEnum(const std::string &value) {
typename std::map<std::string, T>::const_iterator iValue = enumMap.find(value);
if (iValue == enumMap.end())
throw std::runtime_error("Value not found in enum!");
return iValue->second;
}
};
BOOST_LOG_GLOBAL_LOGGER(my_logger, src::severity_logger< severity_level >);
/** Send string representing an enum value to stream
*/
std::ostream& operator<< (std::ostream& strm, severity_level lvl);
void setup_logging(std::string lvl);
#endif /* _TEMLOGGER_H_ */
TEMLogger.cpp
#include <boost/log/expressions/formatters/named_scope.hpp>
#include <boost/log/expressions.hpp>
#include <boost/phoenix.hpp>
#include "TEMLogger.h"
// Create the global logger object
src::severity_logger< severity_level > glg;
// Add a bunch of attributes to it
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(named_scope, "Scope", attrs::named_scope::value_type)
/** Initialize the enum parser map from strings to the enum levels.*/
template<>
EnumParser< severity_level >::EnumParser() {
enumMap["debug"] = debug;
enumMap["info"] = info;
enumMap["note"] = note;
enumMap["warn"] = warn;
enumMap["err"] = err;
enumMap["fatal"] = fatal;
}
std::ostream& operator<< (std::ostream& strm, severity_level level) {
static const char* strings[] = {
"debug", "info", "note", "warn", "err", "fatal"
};
if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings))
strm << strings[level];
else
strm << static_cast< int >(level);
return strm;
}
bool my_filter(boost::log::value_ref< attrs::named_scope > const& theNamedScope) {
// I think I want something like this:
// for (attrs::named_scope_list::const_iterator iter = scopeList.begin(); iter != scopeList.end(); ++iter) {
// if ( (*iter).scope_name == "monthly"){
// return true;
// }
// }
return true;
}
void setup_logging(std::string lvl) {
logging::core::get()->add_global_attribute(
"Scope", attrs::named_scope()
);
logging::add_console_log(
std::clog,
keywords::format = (
expr::stream
<< "[" << severity << "] "
<< "[" << named_scope << "] "
<< expr::smessage
)
);
try {
// set the severity level...
EnumParser<severity_level> parser;
logging::core::get()->set_filter(
(
severity >= parser.parseEnum(lvl) &&
( expr::has_attr("Scope") && ( boost::phoenix::bind(&my_filter, expr::attr< attrs::named_scope >("Scope").or_none()) ) )
)
);
} catch (std::runtime_error& e) {
std::cout << e.what() << std::endl;
std::cout << "'" << lvl << "' is an invalid --log-level! Must be one of "
<< "[debug, info, note, warn, err, fatal]\n";
exit(-1);
}
}
Main Program
#include "TEMLogger.h"
extern src::severity_logger< severity_level > glg;
void func1() {
BOOST_LOG_NAMED_SCOPE("monthly");
for (int i=0; i<5; ++i) {
BOOST_LOG_SEV(glg, note) << "doing iteration " << i << "within monthly scope!";
}
}
int main(int argc, char* argv[]) {
std::cout << "Setting up logging...\n";
setup_logging("debug");
BOOST_LOG_SEV(glg, note) << "Some message in the main scope";
func1();
BOOST_LOG_SEV(glg, note) << "Another message in the main scope";
return 0;
}
Compile
(I am on a Mac, and due to the way I installed Boost I have to specify the compiler, and the method of linking the Boost libs. YMMV)
g++-4.8 -o TEMLogger.o -c -g -DBOOST_ALL_DYN_LINK TEMLogger.cpp
g++-4.8 -o log-filter-example.o -c -g -DBOOST_ALL_DYN_LINK log-filter-example.cpp
g++-4.8 -o a.out log-filter-example.o TEMLogger.o -L/usr/local/lib -lboost_system-mt -lboost_filesystem-mt -lboost_thread-mt -lboost_log-mt
Run
$ ./a.out
Setting up logging...
[note] [] Some message in the main scope
[note] [monthly] doing iteration 0within monthly scope!
[note] [monthly] doing iteration 1within monthly scope!
[note] [monthly] doing iteration 2within monthly scope!
[note] [monthly] doing iteration 3within monthly scope!
[note] [monthly] doing iteration 4within monthly scope!
[note] [] Another message in the main scope
Analysis
Your problems lie in how you use phoenix::bind to create the filtering expression.
boost::phoenix::bind(&my_filter
, attrs::named_scope::get_scopes())
The way it's written, it will bind to the value returned by get_scopes() at the time of binding. Instead, we want lazy evaluation, which will happen just before the my_filter function is invoked for each message. This is accomplished by boost::log::expressions::attribute_actor, which we can create using boost::log::expressions::attr(...)
boost::phoenix::bind(&my_filter
, expr::attr<attrs::named_scope>("Scope").or_none())
The next issue lies in boost::log::attributes::named_scope. As the documentation says
The basic_named_scope attribute is essentially a hook to the thread-specific instance of scope list.
Instead of this dummy attribute, we're actually interested in extracting the current scope stack for the given message. According to the value_type, this is an instance of boost::log::attributes::named_scope_list. Hence, we should change the code to
boost::phoenix::bind(&my_filter
, expr::attr<attrs::named_scope_list>("Scope").or_none())
and also adjust the signature of my_filter(...) to match:
bool my_filter(boost::log::value_ref<attrs::named_scope_list> const& scopes)
Now, since we're using .or_none() to create the attribute_actor, we can drop from our filter expression the check for existence of attribute "Scope"
expr::has_attr("Scope") // This goes away
and do this test in our filter function instead
if (!scopes.empty()) { // ...
} else { return false; }
Finally, we should probably have a way to configure the scope we want to filter. So, let's add a parameter to the filter function:
bool my_filter(boost::log::value_ref<attrs::named_scope_list> const& scopes
, std::string const& target_scope)
{ // ...
}
And bind it to the desired value
std::string target_scope("scope_2"); // Or read from config
// .....
(boost::phoenix::bind(&my_filter
, expr::attr<attrs::named_scope_list>("Scope").or_none()
, target_scope))
Sample Code
Includes, type definitions:
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
// NB: The following two are just convenience
// Reduce them to just the headers needed to reduce compile time
#include <boost/log/attributes.hpp>
#include <boost/log/expressions.hpp>
#include <boost/phoenix.hpp>
// ============================================================================
namespace bl = boost::log;
// ----------------------------------------------------------------------------
typedef bl::sources::severity_logger <bl::trivial::severity_level> logger_t;
typedef bl::trivial::severity_level log_level;
// ----------------------------------------------------------------------------
BOOST_LOG_ATTRIBUTE_KEYWORD(my_named_scope, "Scope", bl::attributes::named_scope::value_type)
// ============================================================================
The filter function:
// ============================================================================
bool my_filter(bl::value_ref<bl::attributes::named_scope_list> const& scopes
, std::string const& target_scope)
{
bool matched(false);
if (!scopes.empty()) {
for (auto& scope : scopes.get()) {
if (scope.scope_name == target_scope) {
matched = matched || true; // Any scope name matches...
}
}
}
return matched;
}
// ============================================================================
Logger initialization:
// ============================================================================
void init_logging()
{
bl::core::get()->add_global_attribute(
"Scope", bl::attributes::named_scope()
);
bl::add_console_log(std::clog
, bl::keywords::format = (
bl::expressions::stream
<< "[" << bl::trivial::severity << "] "
<< "[" << my_named_scope << "] "
// Alternative way to format this:
// << bl::expressions::format_named_scope("Scope", bl::keywords::format = "[%n] ")
<< bl::expressions::smessage
));
// Hard-coded, determine this as appropriate from config
log_level target_severity(log_level::info);
std::string target_scope("scope_2");
bl::core::get()->set_filter(
(bl::trivial::severity >= target_severity)
&& (boost::phoenix::bind(&my_filter
, bl::expressions::attr<bl::attributes::named_scope_list>("Scope").or_none()
, target_scope))
);
}
// ============================================================================
Finally testing it:
// ============================================================================
void log_it(logger_t& log, int n)
{
BOOST_LOG_SEV(log, log_level::debug) << "A" << n;
BOOST_LOG_SEV(log, log_level::trace) << "B" << n;
BOOST_LOG_SEV(log, log_level::info) << "C" << n;
BOOST_LOG_SEV(log, log_level::warning) << "D" << n;
BOOST_LOG_SEV(log, log_level::error) << "E" << n;
BOOST_LOG_SEV(log, log_level::fatal) << "F" << n;
}
// ============================================================================
int main()
{
init_logging();
logger_t log;
log_it(log, 1);
{
BOOST_LOG_NAMED_SCOPE("scope_1");
log_it(log, 2);
}
{
BOOST_LOG_NAMED_SCOPE("scope_2");
log_it(log, 3);
{
BOOST_LOG_NAMED_SCOPE("scope_3");
log_it(log, 4);
}
log_it(log, 5);
}
return 0;
}
// ============================================================================
Test Run
Output without filtering:
[debug] [] A1
[trace] [] B1
[info] [] C1
[warning] [] D1
[error] [] E1
[fatal] [] F1
[debug] [scope_1] A2
[trace] [scope_1] B2
[info] [scope_1] C2
[warning] [scope_1] D2
[error] [scope_1] E2
[fatal] [scope_1] F2
[debug] [scope_2] A3
[trace] [scope_2] B3
[info] [scope_2] C3
[warning] [scope_2] D3
[error] [scope_2] E3
[fatal] [scope_2] F3
[debug] [scope_2->scope_3] A4
[trace] [scope_2->scope_3] B4
[info] [scope_2->scope_3] C4
[warning] [scope_2->scope_3] D4
[error] [scope_2->scope_3] E4
[fatal] [scope_2->scope_3] F4
[debug] [scope_2] A5
[trace] [scope_2] B5
[info] [scope_2] C5
[warning] [scope_2] D5
[error] [scope_2] E5
[fatal] [scope_2] F5
Output with filtering (as in sample code, only info level and higher, and only those with some scope named "scope_2"):
[info] [scope_2] C3
[warning] [scope_2] D3
[error] [scope_2] E3
[fatal] [scope_2] F3
[info] [scope_2->scope_3] C4
[warning] [scope_2->scope_3] D4
[error] [scope_2->scope_3] E4
[fatal] [scope_2->scope_3] F4
[info] [scope_2] C5
[warning] [scope_2] D5
[error] [scope_2] E5
[fatal] [scope_2] F5

Find the last added file (boost)

I'm trying to build a function that finds the last added file with a specific extension.
Here's what I did:
void getLastAdded(const fs::path path)
{
const string& ext = ".xml";
fs::path last;
vector<fs::path> files;
fs::recursive_directory_iterator it(path);
fs::recursive_directory_iterator endit;
while (it != endit)
{
if (fs::is_regular_file(*it) && it->path().extension() == ext)
files.push_back(it->path());
++it;
}
for (size_t i = 0; i < files.size(); i++) {
if (i == 0)
last = files[i];
if (fs::last_write_time(last) <= fs::last_write_time(files[i]))
last = files[i];
}
cout << "Last:" << last.filename() << endl;
}
Is there any better way to accomplish this?
Instead of building a (potentially huge) vector of filenames that you won't use/need, I'd filter for the max modification time on-the-fly.
Moreover, don't forget to handle errors:
#include <boost/filesystem.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
namespace fs = boost::filesystem;
fs::path getLastAdded(const fs::path path, std::string const& ext = ".xml") {
std::vector<fs::path> files;
namespace pt = boost::posix_time;
pt::ptime max = {pt::neg_infin};
fs::path last;
for (fs::recursive_directory_iterator it(path), endit; it != endit; ++it)
if (fs::is_regular_file(*it) && it->path().extension() == ext)
{
try {
auto stamp = pt::from_time_t(fs::last_write_time(*it));
if (stamp >= max) {
last = *it;
max = stamp;
}
} catch(std::exception const& e) {
std::cerr << "Skipping: " << *it << " (" << e.what() << ")\n";
}
}
return last; // empty if no file matched
}
int main() {
std::cout << "Last: " << getLastAdded(".") << "\n";
}
With some debug information on Coliru:
Live On Coliru
Prints
DEBUG: "./i.xml"
DEBUG: "./z.xml"
DEBUG: "./q.xml"
DEBUG: "./c.xml"
DEBUG: "./v.xml"
DEBUG: "./f.xml"
DEBUG: "./t.xml"
DEBUG: "./d.xml"
DEBUG: "./a.xml"
DEBUG: "./b.xml"
DEBUG: "./e.xml"
DEBUG: "./u.xml"
DEBUG: "./p.xml"
DEBUG: "./g.xml"
DEBUG: "./x.xml"
DEBUG: "./y.xml"
DEBUG: "./j.xml"
DEBUG: "./h.xml"
DEBUG: "./o.xml"
DEBUG: "./m.xml"
DEBUG: "./s.xml"
DEBUG: "./w.xml"
DEBUG: "./l.xml"
DEBUG: "./n.xml"
DEBUG: "./r.xml"
DEBUG: "./k.xml"
Last: "./k.xml"