I'm writing a multi-threaded system using c++. I defined a log object as global variable, and all threads write logs into this object.
Log class is defined as follow.
log.h
#ifndef LOG_H_
#define LOG_H_
#include <string>
#include <fstream>
#include <mutex>
enum class LogType{
ERROR,
WARNING,
INFO,
DEBUG
};
class ContentsCopyLog
{
public:
bool open(std::string logFile);
void write(LogType type, std::string msg);
void close();
private:
std::fstream f_;
std::mutex mtx_;
};
#endif
log.cpp
#include <string>
#include <fstream>
#include <chrono>
#include <time.h>
#include <iostream>
#include "log.h"
bool ContentsCopyLog::open(std::string logFile)
{
f_.open(logFile, std::fstream::out | std::fstream::app);
if(!f_.is_open()){
return false;
}
return true;
}
void ContentsCopyLog::write(LogType type, std::string msg)
{
auto now = std::chrono::system_clock::now();
std::time_t current_time = std::chrono::system_clock::to_time_t(now);
std::lock_guard<std::mutex> lck(mtx_);
char buf[100];
strftime(buf, 100, "%Y-%m-%d %H:%M:%S ", localtime(¤t_time));
f_ << buf;
if(type == LogType::ERROR){
f_ << "[ERROR ] ";
}else if(type == LogType::WARNING){
f_ << "[WARNING] ";
}else if(type == LogType::INFO){
f_ << "[INFO ] ";
}else if(type == LogType::DEBUG){
f_ << "[DEBUG ] ";
}
f_ << msg << std::endl;
}
void ContentsCopyLog::close()
{
if(f_.is_open()){
f_.close();
}
}
All worker threads execute the same function. In this function some logs will be written into the log file.
The problem now is it seems logs from some threads have not been written into the log file.
Any idea?
Related
After a long time I started doing some C++ development again. Right now I'm struggling with my logging class. It's already working quite nicely, but I want to introduce some log levels. To be honest, I'm not quite sure how to continue in the right way.
My logger works with five simple macros:
#define PLUGINLOG_INIT(path, fileName) logInstance.initialise(path, fileName);
#define LOG_ERROR (logInstance << logInstance.prefix(error))
#define LOG_WARN (logInstance << logInstance.prefix(warn))
#define LOG_INFO (logInstance << logInstance.prefix(info))
#define LOG_DEBUG (logInstance << logInstance.prefix(debug))
The first one opens the file stream and the other four write log entries into the file. It's a rather simple approach. The prefix methods writes a datetime stamp and the log level as text:
2021-05.26 12:07:23 WARN File not found!
For the log level, I created an enum and store the current log level in my class. My questions is however, how can I avoid logging if the log level is set lower?
Example:
LogLevel in class = warn
When I log an info entry, I'd put the following line into my source code:
LOG_INFO << "My info log entry" << std::endl;
Since the LogLevel is set to warning, this info entry should not be logged. I could try putting an if statement into the LOG_INFO macro, but I would rather avoid complicated macros. Is there a better way to achieve what I need?
Many thanks,
Marco
Complete header file of my logger:
#ifndef PLUGINLOGGER_H_
#define PLUGINLOGGER_H_
// Standard
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
// Forward declarations
class PluginLogger;
extern PluginLogger logInstance;
// Enumerations
enum LogLevel {
error = 0,
warn = 1,
info = 2,
debug = 3
};
// Macros
#define PLUGINLOG_INIT(path, fileName) logInstance.initialise(path, fileName);
#define LOG_ERROR (logInstance << logInstance.prefix(error))
#define LOG_WARN (logInstance << logInstance.prefix(warn))
#define LOG_INFO (logInstance << logInstance.prefix(info))
#define LOG_DEBUG (logInstance << logInstance.prefix(debug))
class PluginLogger {
public:
PluginLogger();
void initialise(std::string_view path, std::string_view fileName);
void close();
template<typename T> PluginLogger& operator<<(T t);
// to enable std::endl
PluginLogger& operator<<(std::ostream& (*fun) (std::ostream&));
std::string prefix(const LogLevel logLevel);
private:
std::string m_fileName;
std::ofstream m_stream;
LogLevel m_logLevel;
};
template<typename T> inline PluginLogger& PluginLogger::operator<<(T t) {
if (m_stream.is_open())
m_stream << t;
return* this;
}
inline PluginLogger& PluginLogger::operator<<(std::ostream& (*fun)( std::ostream&)) {
if (m_stream.is_open())
m_stream << std::endl;
return* this;
}
#endif // PLUGINLOGGER_H_
Complete source file of my logger:
#include <chrono>
#include "PluginLogger.h"
PluginLogger logInstance;
PluginLogger::PluginLogger() {
m_fileName = "";
m_logLevel = error;
}
void PluginLogger::initialise(std::string_view path, std::string_view fileName) {
if (!path.empty() && !fileName.empty()) {
m_fileName = std::string(path) + std::string(fileName) + "_log.txt";
m_stream.open(m_fileName);
unsigned char bom[] = { 0xEF,0xBB,0xBF };
m_stream.write((char*)bom, sizeof(bom));
}
}
void PluginLogger::close() {
if (m_stream.is_open())
m_stream.close();
}
std::string PluginLogger::prefix(const LogLevel logLevel) {
// add a date time string
std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::string dateTimeStr(25, '\0');
std::strftime(&dateTimeStr[0], dateTimeStr.size(), "%Y-%m-%d %H:%M:%S", std::localtime(&now));
// add log level
std::string logLevelText;
switch (logLevel) {
case error:
logLevelText = " ERROR ";
break;
case warn:
logLevelText = " WARN ";
break;
case info:
logLevelText = " INFO ";
break;
case debug:
logLevelText = " DEBUG ";
break;
}
return dateTimeStr + logLevelText;
}
To begin - the code as written works in Linux, but when building and running on Windows the log file is not created.
I've created a simple Logging library and that should create a file local to the executable.
Logger.h
#ifndef LOGGER_H
#define LOGGER_H
namespace Logger
{
enum class Level
{
DEBUG,
INFO,
WARNING,
ERROR
};
std::string GetTime();
class Log
{
private:
std::string filename;
std::stringstream inStr;
std::ofstream logFile;
std::recursive_mutex inStrMutex;
std::mutex fileMutex;
void ClearStrStream();
std::string LevelToString(Level lvl);
public:
Log(std::string file);
~Log();
template<typename T>
void Write(Level lvl, const T& arg)
{
std::lock_guard<std::recursive_mutex> inStrLock(inStrMutex);
std::lock_guard<std::mutex> fileLock(fileMutex);
inStr << std::noskipws << arg << '\n';
logFile << std::noskipws << "[" << GetTime() << "]"
<< "[" << LevelToString(lvl) << "] "
<< inStr.str();
ClearStrStream();
return;
}
template<typename T, typename... Args>
void Write(Level lvl, const T& firstArg, Args... args)
{
std::lock_guard<std::recursive_mutex> lock(inStrMutex);
inStr << std::noskipws << firstArg;
Write(lvl, args...);
return;
}
};
}
#endif
The constructor defined in Logger.cpp
Logger::Log::Log(std::string file) : filename{ file }
{
std::cout << "Ctor called\n";
auto now = Logger::GetTime();
auto fullName = now + "_" + filename;
logFile = std::ofstream(fullName);
inStr = std::stringstream("", std::ios_base::ate | std::ios_base::out | std::ios_base::in);
}
This gets built into a static library and then linked in LoggerUnitTest.cpp
#include <chrono>
#include <fstream>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <functional>
#include "Logger.h"
#include "LoggerConfig.h"
#include "date.h"
Logger::Log GLogObject("LoggerTest.log");
void WriteToLogger()
{
GLogObject.Write(Logger::Level::WARNING,
"The quick brown ",
"fox jumps ",
"over the ",
"lazy ",
"dog.");
GLogObject.Write(Logger::Level::ERROR,
"Because nighttime ",
"is the best time ",
"to fight crime.");
return;
}
void TestMultipleTypes()
{
GLogObject.Write(Logger::Level::DEBUG,
"String ",
123,
" and integer.");
return;
}
int main()
{
std::cout << "Testing Logger Version: " << LOGGER_VERSION_MAJOR << "."
<< LOGGER_VERSION_MINOR << std::endl;
std::thread t1(TestMultipleTypes);
std::thread t2(WriteToLogger);
WriteToLogger();
std::thread t3(WriteToLogger);
WriteToLogger();
std::thread t4(WriteToLogger);
std::thread t5(WriteToLogger);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
return 0;
}
When LoggerUnitTest is built and run in Linux, things work as expected. A log file is created and everything is written into the log file as expected.
When LoggerUnitTest is build and run in Windows, LoggerUnitTest.exe runs, but no log file is created. What's different about Windows that results in no file being written?
I've also attempted to explicitly add std::flush in Logger's Write function which doesn't seem to make a difference.
The issue was with the way GetTime() returned a date/time formatted string. The formate was YYYY/mm/DD_HH:MM:SS. According to Microsoft Docs, colons : and forward slashes / are prohibited characters in file names. After adjusting the format of the date/time, everything works as expected!
I have implemented a sink which logs records to a std::vector and flushes all logs to console when the flush function is called. I have added four attributes according to:
BOOST_LOG_ATTRIBUTE_KEYWORD(a_timestamp, "TimeStamp", boost::log::attributes::local_clock::value_type)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_severity, "Severity", boost::log::trivial::severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_file, "File", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", std::string)
However, when I use the set_formatter function I get an compiler error (MSVC17) saying:
Error C2679 binary '<<': no operator found which takes a right-hand
operand of type 'const T' (or there is no acceptable
conversion) ControllerSoftware c:\local\boost_1_68_0\boost\log\utility\formatting_ostream.hpp 870
I'll provide full code for sake of clarity:
// cswlogger.h
#pragma once
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/attributes/mutable_constant.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sources/global_logger_storage.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>
#include <boost/log/core.hpp>
#include <boost/log/sinks/basic_sink_backend.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <vector>
#include <string>
namespace sinks = boost::log::sinks;
BOOST_LOG_GLOBAL_LOGGER(sysLogger,
boost::log::sources::severity_logger_mt<boost::log::trivial::severity_level>);
class CswLogger
{
public:
/// Init with default info level logging
static void init(bool useRamLog = false, boost::log::trivial::severity_level level = boost::log::trivial::info);
/// Disable logging
static void disable();
// Flush log
static void flushLogs();
// Convert file path to only the filename
static std::string path_to_filename(std::string path)
{
return path.substr(path.find_last_of("/\\") + 1);
}
private:
static void registerRamLog();
};
class RamLogSink :
public sinks::basic_formatted_sink_backend<
char,
sinks::combine_requirements<
sinks::synchronized_feeding,
sinks::flushing
>::type
>
{
public:
// The function consumes the log records that come from the frontend
void consume(boost::log::record_view const& rec, string_type const& strType);
// The function flushes the logs in vector
void flush();
private:
std::vector<std::string> logEntries_;
};
// Complete sink type
typedef sinks::synchronous_sink< RamLogSink > sink_t;
#define LOG_LOG_LOCATION(LOGGER, LEVEL, ARG) \
BOOST_LOG_SEV(LOGGER, boost::log::trivial::LEVEL) \
<< boost::log::add_value("Line", std::to_string(__LINE__)) \
<< boost::log::add_value("File", CswLogger::path_to_filename(__FILE__)) << ARG
// System Log macros.
// TRACE < DEBUG < INFO < WARN < ERROR < FATAL
#define LOG_TRACE(ARG) LOG_LOG_LOCATION(sysLogger::get(), trace, ARG);
#define LOG_DEBUG(ARG) LOG_LOG_LOCATION(sysLogger::get(), debug, ARG);
#define LOG_INFO(ARG) LOG_LOG_LOCATION(sysLogger::get(), info, ARG);
#define LOG_WARN(ARG) LOG_LOG_LOCATION(sysLogger::get(), warning, ARG);
#define LOG_ERROR(ARG) LOG_LOG_LOCATION(sysLogger::get(), error, ARG);
#define LOG_FATAL(ARG) LOG_LOG_LOCATION(sysLogger::get(), fatal, ARG);
Source file:
// cswlogger.cpp
#include "cswlogger.h"
#include <boost/log/core.hpp>
#include <boost/log/common.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/settings.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <string>
#include <iostream>
BOOST_LOG_GLOBAL_LOGGER_DEFAULT(sysLogger,
boost::log::sources::severity_channel_logger_mt<boost::log::trivial::severity_level>);
BOOST_LOG_ATTRIBUTE_KEYWORD(a_timestamp, "TimeStamp", boost::log::attributes::local_clock::value_type)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_severity, "Severity", boost::log::trivial::severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_file, "File", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", std::string)
void CswLogger::init(bool useRamLog, boost::log::trivial::severity_level level)
{
boost::log::register_simple_formatter_factory<boost::log::trivial::severity_level, char>("Severity");
if (useRamLog)
{
registerRamLog();
}
else
{
boost::log::add_console_log
(
std::clog,
boost::log::keywords::format = "[%TimeStamp%] [%Severity%] %File%(%Line%): %Message%"
);
}
boost::log::add_common_attributes();
boost::log::core::get()->set_filter
(
boost::log::trivial::severity >= level
);
// Indicate start of logging
LOG_INFO("Log Start");
}
void CswLogger::disable()
{
boost::log::core::get()->set_logging_enabled(false);
}
void CswLogger::flushLogs()
{
boost::log::core::get()->flush();
}
void CswLogger::registerRamLog()
{
boost::shared_ptr< RamLogSink > backend(new RamLogSink());
boost::shared_ptr< sink_t > sink(new sink_t(backend));
sink->set_formatter(boost::log::expressions::stream
<< "[" << a_timestamp << "] " // <--- Compile error here
<< "[" << a_severity << "] "
<< "[" << a_file << "(" << a_line << ")] "
<< boost::log::expressions::message);
boost::log::core::get()->add_sink(sink);
}
// RamlogSink Class declarations
void RamLogSink::consume(boost::log::record_view const& rec, string_type const& strType)
{
logEntries_.emplace_back(strType);
}
void RamLogSink::flush()
{
for (auto const& entry : logEntries_)
{
std::cout << entry << std::endl;
}
logEntries_.clear();
}
main.cpp:
#include "cswlogger.h"
CswLogger::init(true);
LOG_TRACE("This should not be printed");
LOG_ERROR("Very bad error that needs to be printed");
int theInt = 234;
LOG_FATAL("TheInt integer is: " << theInt);
CswLogger::flushLogs();
I also hade a problem when adding the "Line" attribute with:
BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", int)
I hade to change to std::string and convert the integer to a string in
#define LOG_LOG_LOCATION(LOGGER, LEVEL, ARG) \
BOOST_LOG_SEV(LOGGER, boost::log::trivial::LEVEL) \
<< boost::log::add_value("Line", std::to_string(__LINE__)) \
<< boost::log::add_value("File", CswLogger::path_to_filename(__FILE__)) << ARG
Otherwise the line number came out as an empty string.
I can't close my thread. Am I forgetting to do something? The thread seems like it's saving the value I'm using for close, and then never checks if it has changed. Here is some example code that has an identical effect:
#include "stdafx.h"
#include "Windows.h"
#include <iostream>
#include <thread>
class test {
private:
bool user_wants_thread = true;
bool time_to_close = false;
public:
bool set_timetoclose(bool in) {
time_to_close = in;
if (time_to_close == in) {
return true;
}
return false;
}
void function() {
while (user_wants_thread) {
// CODE
std::cout << time_to_close;
Sleep(100);
if (time_to_close) {
goto close;
}
}
close:
Sleep(1);
}
};
int main() {
test t;
std::thread thread_func(&test::function, t);
Sleep(1000);
bool success;
do {
success = t.set_timetoclose(true);
} while (!success);
thread_func.join();
std::cout << "Closed";
std::cin.get();
}
I removed some unused parts and changed the actual condition to be an atomic<bool> and it seems to work as shown on this link:
http://rextester.com/TWHK12491
I'm not claiming this is absolutely correct, however, but it shows how using the atomic causes synchronization across reads/writes to the value which could result in a data race.
#include "Windows.h"
#include <iostream>
#include <thread>
#include <atomic>
class test {
public:
std::atomic<bool> time_to_close = false;
test()=default;
void function() {
while (!time_to_close) {
std::cout << "Running..." << std::endl;
Sleep(100);
}
std::cout << "closing" << std::endl;
}
};
int main() {
test t;
std::thread thread_func([&t](){t.function();});
Sleep(500);
t.time_to_close = true;
std::cout << "Joining on thread" << std::endl;
thread_func.join();
std::cout << "Closed";
return 0;
}
How can I read a *.json file and put the output on a std::string?
I have this sample, but I always get null on std::string.
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include <rapidjson/ostreamwrapper.h>
#include <fstream>
#include <iostream>
using namespace rapidjson;
using namespace std;
void main()
{
ifstream ifs("input.json");
IStreamWrapper isw(ifs);
Document d;
d.ParseStream(isw);
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
d.Accept(writer);
std::string jsonStr(buffer.GetString());
if(jsonStr == "null")
std::cout << "is null..." << std::endl; //<--always here!
else
{
std::cout << jsonStr.c_str() << std::endl;
d["ip"] = "123456789";
ofstream ofs("output.json");
OStreamWrapper osw(ofs);
Writer<OStreamWrapper> writer2(osw);
d.Accept(writer2);
}
}
This is my json file:
{
"ip" : "192.168.0.100",
"angle x": 20,
"angle y": 0,
"angle z": 0
}
You need to check for all the errors before converting to std::string. Make sure that the file is open for reading / writing and the parsing is successful i.e. the JSON is valid. GetParseError() and GetErrorOffset() are the functions to validate parsing.
I've used your example and enhanced it. Hope you won't mind. :-)
Here's a working example:
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/ostreamwrapper.h>
int main()
{
using namespace rapidjson;
std::ifstream ifs { R"(C:\Test\Test.json)" };
if ( !ifs.is_open() )
{
std::cerr << "Could not open file for reading!\n";
return EXIT_FAILURE;
}
IStreamWrapper isw { ifs };
Document doc {};
doc.ParseStream( isw );
StringBuffer buffer {};
Writer<StringBuffer> writer { buffer };
doc.Accept( writer );
if ( doc.HasParseError() )
{
std::cout << "Error : " << doc.GetParseError() << '\n'
<< "Offset : " << doc.GetErrorOffset() << '\n';
return EXIT_FAILURE;
}
const std::string jsonStr { buffer.GetString() };
std::cout << jsonStr << '\n';
doc[ "ip" ] = "127.0.0.1";
std::ofstream ofs { R"(C:\Test\NewTest.json)" };
if ( !ofs.is_open() )
{
std::cerr << "Could not open file for writing!\n";
return EXIT_FAILURE;
}
OStreamWrapper osw { ofs };
Writer<OStreamWrapper> writer2 { osw };
doc.Accept( writer2 );
return EXIT_SUCCESS;
}