Cast a boost::log::expressions::attr< std::string > to std::string - c++

While using Boost.Log, I am trying to keep my TimeStamp formatter such as:
logging::add_file_log
(
keywords::file_name = "my.log",
keywords::format =
(
expr::stream
<< expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
<< "," << expr::attr< int >("Line")
<< " " << expr::attr< std::string >("File")
<< " " << logging::trivial::severity
<< " - " << expr::smessage
)
);
It is said that I cannot use the other form of formatter since I'll have much difficulties turning "TimeStamp" into my custom format:
static void my_formatter(logging::record_view const& rec, logging::formatting_ostream& strm)
{
strm << logging::extract< boost::posix_time::ptime >("TimeStamp", rec);
will output something like: 2015-Jul-01 16:06:31.514053, while I am only interested in: "%Y-%m-%d %H:%M:%S". However the first form is extremely hard to use, for instance I am not able to cast an expr::attr< std::string > to a simple std::string for instance:
logging::add_file_log
(
keywords::file_name = "my.log",
keywords::format =
(
expr::stream
<< expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
<< "," << expr::attr< int >("Line")
<< " " << boost::filesystem::path(expr::attr< std::string >("File"))
.filename().string()
<< " " << logging::trivial::severity
<< " - " << expr::smessage
)
);
The above code does not even compile.
Is there an easy way to both print TimeStamp using my custom format and at the same time use a custom cast to string to be able to use boost::filesystem::path::filename() ?

In custom formatter you can easily build the timestamp in "%Y-%m-%d %H:%M:%S" format:
void my_formatter(logging::record_view const& rec, logging::formatting_ostream& strm)
{
const boost::posix_time::ptime &pt = *logging::extract< boost::posix_time::ptime >("TimeStamp", rec);
strm << pt.date() << " " << pt.time_of_day().hours() << ":" << pt.time_of_day().minutes() << ":" << pt.time_of_day().seconds()
...
<< rec[expr::smessage];
}

There are multiple ways to achieve what you want. The key point to understand is that Boost.Log formatting expressions (as well as filters, by the way) are Boost.Phoenix lambda functions. As such you can inject your own functions in them using Boost.Phoenix constructs such as boost::phoenix::bind. See an example here, for instance. Your code would look something like this:
std::string file_basename(logging::value_ref< std::string > const& filename)
{
// Check to see if the attribute value has been found
if (filename)
return boost::filesystem::path(filename.get()).filename().string();
else
return std::string();
}
// ...
logging::add_file_log
(
keywords::file_name = "my.log",
keywords::format =
(
expr::stream
<< expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
<< "," << expr::attr< int >("Line")
<< " " << boost::phoenix::bind(&file_basename, expr::attr< std::string >("File"))
<< " " << logging::trivial::severity
<< " - " << expr::smessage
)
);
Another way to do that is to use attribute keywords and define an operator<< specific for the File attribute. You can find an example here.
BOOST_LOG_ATTRIBUTE_KEYWORD(a_timestamp, "TimeStamp", boost::posix_time::ptime)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_file, "File", std::string)
namespace std {
logging::formatting_ostream& operator<<
(
logging::formatting_ostream& strm,
logging::to_log_manip< std::string, tag::a_file > const& manip
)
{
strm << boost::filesystem::path(manip.get()).filename().string();
return strm;
}
} // namespace std
// ...
logging::add_file_log
(
keywords::file_name = "my.log",
keywords::format =
(
expr::stream
<< expr::format_date_time(a_timestamp, "%Y-%m-%d %H:%M:%S")
<< "," << a_line
<< " " << a_file
<< " " << logging::trivial::severity
<< " - " << expr::smessage
)
);
Notice that attribute keywords simplify expressions significantly.
Lastly, you can use wrap_formatter to inject your own function into the streaming expression. When it comes to formatting, wrap_formatter invokes your function providing it with the log record being formatted and the formatting stream. When your function returns the wrapper automatically returns the reference to the formatting stream so that the rest of the formatting expression can proceed.
BOOST_LOG_ATTRIBUTE_KEYWORD(a_timestamp, "TimeStamp", boost::posix_time::ptime)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_file, "File", std::string)
void file_basename(logging::record_view const& record, logging::formatting_ostream& strm)
{
// Check to see if the attribute value has been found
logging::value_ref< std::string, tag::a_file > filename = record[a_file];
if (filename)
strm << boost::filesystem::path(filename.get()).filename().string();
}
// ...
logging::add_file_log
(
keywords::file_name = "my.log",
keywords::format =
(
expr::stream
<< expr::format_date_time(a_timestamp, "%Y-%m-%d %H:%M:%S")
<< "," << a_line
<< " " << expr::wrap_formatter(&file_basename)
<< " " << logging::trivial::severity
<< " - " << expr::smessage
)
);
The above is similar to the first variant with boost::phoenix::bind but allows for more flexibility in the file_basename implementation.

Related

Missed timestamp in Boost.Log

I'm new in Boost.Log. I'd like to print out data in the following format:
[TimeStamp][Severity] Message.
I have such code
auto my_formatter = [](boost::log::record_view const& rec, boost::log::formatting_ostream& strm)
{
strm << "[" << boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S") << "]";
strm << "[" << rec[boost::log::trivial::severity] << "] ";
strm << rec[boost::log::expressions::smessage];
};
void addFileSink()
{
using sinkType = boost::log::sinks::synchronous_sink<boost::log::sinks::text_file_backend>;
auto backend = boost::make_shared<boost::log::sinks::text_file_backend>(
boost::log::keywords::file_name = "log_%N.log",
boost::log::keywords::rotation_size = 10 * 1024 * 1024,
boost::log::keywords::open_mode = std::ios_base::app
);
boost::shared_ptr<sinkType> sink(new sinkType(backend));
sink->set_formatter(my_formatter);
sink->set_filter(boost::log::trivial::severity >= boost::log::trivial::severity_level::info);
boost::log::core::get()->add_sink(sink);
}
void addConsoleSink()
{
using sinkType = boost::log::sinks::synchronous_sink<boost::log::sinks::text_ostream_backend>;
auto backend = boost::make_shared<boost::log::sinks::text_ostream_backend>();
backend->add_stream(boost::shared_ptr<std::ostream>(&std::cout, boost::null_deleter()));
backend->auto_flush(true);
boost::shared_ptr<sinkType> sink(new sinkType(backend));
sink->set_formatter(my_formatter);
sink->set_filter(boost::log::trivial::severity >= boost::log::trivial::severity_level::error);
boost::log::core::get()->add_sink(sink);
}
int main()
{
boost::log::add_common_attributes();
addFileSink();
addConsoleSink();
boost::log::sources::severity_logger<boost::log::trivial::severity_level> lg;
BOOST_LOG_SEV(lg, boost::log::trivial::severity_level::error) << "Message error";
BOOST_LOG_SEV(lg, boost::log::trivial::severity_level::info) << "Message info";
return 0;
}
It works fine beside of TimeStamp attribute, I'm getting such output:
[[error] Message error
I've tried different ways from examples and so on, but I can't find solution for this problem..
boost::log::add_common_attributes(); doesn't help.
boost::log::expressions::format_date_time is not a function to format date and time. It is a formatter generator to be used in lambda formatting expressions. In other words, format_date_time creates a formatter (a function object with a specific signature), which, upon calling, will extract the attribute value from the log record and format it.
You should either use lambda expressions to build the formatter instead of your my_formatter function, in which case you can use format_date_time, or use one of the traditional methods of formatting date and time, like strftime or Boost.DateTime IO.
Example of using the lambda expressions:
namespace expr = boost::log::expressions;
sink->set_formatter
(
expr::stream
<< "[" << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S") << "]"
<< "[" << boost::log::trivial::severity << "] "
<< expr::smessage
);
Example of using strftime:
auto my_formatter = [](boost::log::record_view const& rec, boost::log::formatting_ostream& strm)
{
if (auto timestamp = boost::log::extract< boost::posix_time::ptime >("TimeStamp", rec))
{
std::tm ts = boost::posix_time::to_tm(*timestamp);
char buf[128];
if (std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &ts) > 0)
strm << "[" << buf << "]";
}
strm << "[" << rec[boost::log::trivial::severity] << "] ";
strm << rec[boost::log::expressions::smessage];
};

Easiest way to print timestamp to ostream

I'd like to add a timestamp to certain outputs to the std::cout / std::cerr ostreams, without using modified standard streams, like so:
std::cerr << timestamp << "Warning!\n";
or so:
std::cerr << timestamp() << "Warning!\n";
The output should look like this:
[2020-01-23 17:40:15 CET] Warning!
But I'm really not happy with what I've come up with:
class TimeStamp {};
std::ostream &operator<<(std::ostream &stream, const TimeStamp &ts)
{
std::time_t t = std::time(nullptr);
stream << "[" << std::put_time(std::localtime(&t), "%F %T %Z") << "] ";
return stream;
}
TimeStamp ts;
int main()
{
std::cerr << ts << "Warning!\n";
std::cerr << ts << "Another warning!\n";
}
So I'm basically defining an empty class, using a global declaration and overloading the '<<' operator. This feels wrong. A static function like timestamp() is probably better suited, but I'm not quite sure how to go on about this. All the examples I've found online used the overloaded '<<' operator, but it usually made more sense to do so, because some class state was output. Can I locally create an ostream and return that in the function?
There's nothing wrong with the way you've done it. But if you're looking for alternatives, you could create an ostream wrapper:
class Logger {
private:
std::ostream &stream;
void print_time() {
std::time_t t = std::time(nullptr);
stream << "[" << std::put_time(std::localtime(&t), "%F %T %Z") << "] ";
}
public:
//Maybe also take options for how to log?
Logger(std::ostream &stream) : stream(stream) { }
template <typename T>
std::ostream &operator<<(const T &thing) {
print_time();
return stream << thing;
}
};
int main()
{
Logger log(std::cerr);
log << "Warning!" << std::endl;
log << "Another warning!" << std::endl;
}
See it run here: https://ideone.com/YRawuQ
If you're just looking for a standalone function which is what I understood from a "static function like timestamp()" you can just return the date as a string:
std::string timeStamp(){
std::ostringstream strStream;
std::time_t t = std::time(nullptr);
strStream<< "[" << std::put_time(std::localtime(&t), "%F %T %Z") << "] ";
return strStream.str();
}
int main(){
std::cout<<timeStamp()<<" Testing!";
return 0;
}
Remember to include sstream
You can use standard std::chrono::time_point class from the date and time library to represent the timestamp. Then, you need to convert that timestamp to std::time_t type and, eventually, convert the date and time information from a given calendar time to a character string according to the format string.
auto const now = std::chrono::system_clock::now();
auto now_time = std::chrono::system_clock::to_time_t(now);
std::cout << std::put_time(std::localtime(&now_time), "%F %T") << std::endl;
For those who want to know more...
You can use the source_location class that represents certain information about the source code, such as file names, line numbers, and function names. It is being merged into ISO C++ and is available for use.
Full code
#include <ctime>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <string_view>
#include <experimental/source_location>
void error(std::string_view const& message,
std::ostream& os = std::cout,
std::experimental::source_location const& location = std::experimental::source_location::current()) {
auto const now = std::chrono::system_clock::now();
auto now_time = std::chrono::system_clock::to_time_t(now);
os << "[" << std::put_time(std::localtime(&now_time), "%F %T") << "] "
<< "[INFO] "
<< location.file_name() << ":"
<< location.line() << " "
<< message << '\n';
}
void info(std::string_view const& message,
std::ostream& os = std::cout,
std::experimental::source_location const& location = std::experimental::source_location::current()) {
auto const now = std::chrono::system_clock::now();
auto now_time = std::chrono::system_clock::to_time_t(now);
os << "[" << std::put_time(std::localtime(&now_time), "%F %T") << "] "
<< "[INFO] "
<< location.file_name() << ":"
<< location.line() << " "
<< message << '\n';
}
int main() {
error("Some error");
info("Some info");
// or
error("Some error 2", std::cerr);
info("Some info 2", std::cerr);
return 0;
}
Check it out live

How to automatically generate new CSV file according to current time using C++

I have finished the task of reading sensor data and store into CSV file successfully. But everytime I want to stop reading & storing sensor data, I must close the exe file. Due to the factory operation, they want to continuously run the program during a month without closing the exe file. I was required to modify the code to generate a new CSV file after a period (e.g. 1 hour). So the file name maybe: 20190516_10h0m0s, 20190516_11h0m0s, 20190516_12h0m0s...
I tried to use (struct tm) and for(;timeinfo->tm_min < 60;) to make a new CSV produced after 60'. But every loop, the item Data, time, Channel are written to the CSV again. It looks weird. But when I create the file dest and put the dest.open outside for loop, it only store correct data format without creating a new CSV as I want. Please suggest me how to make it work as expectation.
void EX_GetMultiValue(LONG i_lDriverHandle,
WORD i_wSlotID,
struct SlotInfo &i_SlotInfo)
{
char filename[20], filename2[20];
time_t rawtime;
struct tm * timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
timeinfo->tm_mon++; // Because the range of tm_mon is 0~11, so we need to increment it by 1
timeinfo->tm_year = timeinfo->tm_year + 1900; // Because year counted since 1900
clock_t start = clock();
sprintf(filename,
"%04d%02d%02d_%02dh%02dm%02ds.csv",
timeinfo->tm_year,
timeinfo->tm_mon,
timeinfo->tm_mday,
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec);
printf("\nFilename: %s", filename);
ofstream dest2;
dest2.open(filename, ios_base::app | ios_base::out);
dest2 << "Date" << "," << "Time" << "," << "milisecond" << ","
<< "Channel 0" << "," << "Channel 1" << "," << "Channel 2" << ","
<< "Channel 3" << "," << "Channel 4" << "," << "Channel 5" << ","
<< "Channel 6" << "," << "Channel 7" << "," << "Channel 8" << ","
<< "Channel 9" << "," << "Channel 10" << "," << "Channel 11" << ","
<< endl;
for (; timeinfo->tm_min < 60;)
{
ofstream dest;
dest.open(filename, ios_base::app | ios_base::out);
LONG lGetMultiValueResult = AIO_GetValues(i_lDriverHandle,
i_wSlotID,
wRawValue); //get raw value
if (ERR_SUCCESS == lGetMultiValueResult)
{
clock_t timeElapsed = clock() - start;
unsigned secElapsed = timeElapsed / CLOCKS_PER_SEC;
unsigned msElapsed = timeElapsed / CLOCKS_PER_MS;
while (msElapsed >= 1000)
msElapsed -= 1000;
while ((timeinfo->tm_sec + secElapsed) > 59)
{
timeinfo->tm_sec -= 60;
timeinfo->tm_min++;
}
while (timeinfo->tm_min > 59)
{
timeinfo->tm_min -= 60;
timeinfo->tm_hour++;
}
while (timeinfo->tm_hour > 23)
{
timeinfo->tm_hour -= 24;
timeinfo->tm_mday++;
}
dest << timeinfo->tm_year << "-" << timeinfo->tm_mon << "-"
<< timeinfo->tm_mday << "," << timeinfo->tm_hour << "h"
<< timeinfo->tm_min << "m" << timeinfo->tm_sec + secElapsed
<< "s" << ",";
dest << msElapsed << "ms" << ",";
for (iCnt = 0; iCnt < g_ChannelNum; iCnt++)
{
wRangeType = *(i_SlotInfo.wChRange + iCnt); //get range type
EX_ScaleRawValue(wRangeType,
wRawValue[iCnt],
&dScaledValue,
cUnit); //get scale value
if (strcmp(cUnit, "UN") != 0)
{
printf("Channel %d raw data is 0x%04X, scaled value is %.4f %s.\n",
iCnt,
wRawValue[iCnt],
dScaledValue,
cUnit);
dest << dScaledValue << ",";
Sleep(1);
}
else
printf("Channel %d range is unknown.\n", iCnt);
}
dest << endl;
}
else
printf("Fail to get value, error code = %d\n",
lGetMultiValueResult);
dest.close();
dest2.close();
if (dest == NULL)
{
perror("Error creating file: ");
return;
}
}
Using standard C++-facilities and Howard Hinnants date.h:
#include <chrono>
#include <string>
#include <iostream>
#include "date.h"
int main()
{
auto now{ std::chrono::system_clock::now() };
std::string filename{ date::format("%Y%m%e_%Hh%Mm%Ss.csv", now) };
std::cout << filename << '\n';
}
I don't know what you're trying to do with the rest of your code, so ...

C++ Boost convert UNIX Timestamp to MySQL compatible DATETIME String

I'm trying to convert a UNIX Timestamp which is in long to a Date Time string that needs to be stored in MySQL, in this format 2016-02-01 03:15:10
This is what i have so far. Its not working on the time extraction part. I couldn't find any constructor for boost::posix_time::time_duration that can take directly a boost::posix_time::ptime object. So i tried to include a workaround. But it doesn't work in the hours() part.
static inline std::string getDateTime(long timestamp) {
std::stringstream date_str;
boost::posix_time::ptime pt_1 = boost::posix_time::from_time_t(timestamp);
/* workaround to somehow get a time_duration object constructed */
boost::posix_time::ptime pt_temp = boost::posix_time::from_time_t(0);
boost::gregorian::date d = pt_1.date();
boost::posix_time::time_duration td = pt_1 - pt_temp;
/* construct the Date Time string */
date_str << d.year() << "-" << std::setw(2) << std::setfill('0') << d.month().as_number() << "-" << std::setw(2) << std::setfill('0') << d.day() << " "
<< td.hours() << ":" << td.minutes() << ":" << td.seconds();
return date_str.str();
}
With an Timestamp input such as 1455892259 i'm getting this 2016-02-19 404414:30:59 as a DateTime String from the function. How to actually get the correct Date Time string which in this case would be 2016-02-19 14:30:59. Using Boost for this is compulsory.
UPDATE
This is the final working function rewritten using the answer provided by Jarra McIntyre below.
static inline std::string getDateTime(long timestamp) {
std::stringstream date_str;
boost::posix_time::ptime pt_1 = boost::posix_time::from_time_t(timestamp);
boost::gregorian::date d = pt_1.date();
auto td = pt_1.time_of_day();
/* construct the Date Time string */
date_str << d.year() << "-" << std::setw(2) << std::setfill('0') << d.month().as_number() << "-" << std::setw(2) << std::setfill('0') << d.day() << " "
<< td.hours() << ":" << td.minutes() << ":" << td.seconds();
return date_str.str();
}
Use
auto td = pt_1.time_of_day();
There is no need for a workaround to get the time of day. The number of hours being displayed in your question is probably the number of hours between 1970-01-01 00:00 and 2016-02-19 14:00. For your method of getting the time_duration to work you would have to construct a ptime on the same day not at unix time 0.

pugixml not finding file

I have the following code:
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file( "C:/Users/James/Documents/Visual Studio 2013/Projects/Games/Jimmy/Game/Assets/Levels/Scene.dae" );
std::cout << "Load result: " << result.description( ) << ", mesh name: " << doc.child( "mesh" ).attribute( "name" ).value( ) << std::endl;
pugi::xml_node tools = doc.child( "Profile" ).child( "Tools" );
for( pugi::xml_node tool = tools.child( "Tool" ); tool; tool = tool.next_sibling( "Tool" ) )
{
std::cout << "Tool " << tool.attribute( "Filename" ).value( );
std::cout << ": AllowRemote " << tool.attribute( "AllowRemote" ).as_bool( );
std::cout << ", Timeout " << tool.attribute( "Timeout" ).as_int( );
std::cout << ", Description '" << tool.child_value( "Description" ) << "'/n";
}
For some reason I am getting:
+ result {status=status_file_not_found (1) offset=0 encoding=encoding_auto (0) } pugi::xml_parse_result
Any ideas why it can't find my file?
pugi only opens .xml... I also had to change my path to a relative one.