C++ spdlog write logs to a file - c++

I made a logger using spdlog which I use all over my program. But I also want to flush everything to a log file when the program is completed. How can I achieve this? I'm new to spdlog and I couldn't find proper documentation suitable for my situation.
Here are my file's:
Log.h:
#pragma once
#include "spdlog/spdlog.h"
#include "spdlog/fmt/ostr.h"
namespace Engine{
class Log{
public:
static void init();
inline static std::shared_ptr<spdlog::logger>& GetCoreLoger() { return s_CoreLogger; }
inline static std::shared_ptr<spdlog::logger>& GetClientLogger () { return s_ClientLogger;}
// I want something like this:
void flush_to_file();
private:
static std::shared_ptr<spdlog::logger> s_CoreLogger;
static std::shared_ptr<spdlog::logger> s_ClientLogger;
};
}
//Client log macros
#define VI_TRACE(...) ::Engine::Log::GetClientLogger()->trace(__VA_ARGS__)
#define VI_INFO(...) ::Engine::Log::GetClientLogger()->info(__VA_ARGS__)
#define VI_WARN(...) ::Engine::Log::GetClientLogger()->warn(__VA_ARGS__)
#define VI_ERROR(...) ::Engine::Log::GetClientLogger()->error(__VA_ARGS__)
Log.cpp:
#include "spdlog/sinks/stdout_color_sinks.h"
namespace Engine {
std::shared_ptr<spdlog::logger> Log::s_CoreLogger;
std::shared_ptr<spdlog::logger> Log::s_ClientLogger;
void Log::init() {
spdlog::set_pattern("%^[%T] %n: %v%$");
s_CoreLogger = spdlog::stdout_color_mt("VIO");
s_CoreLogger->set_level(spdlog::level::trace);
s_ClientLogger = spdlog::stdout_color_mt("APP");
s_ClientLogger->set_level(spdlog::level::trace);
}
// This is what I want:
void Log::flush_to_file(){
spdlog::write_to_file(); // Something like this
}
};
I want everything that spdlog have logged so far to be written into the file when I call that function. Is this possible? If so how can I do it?

What you probably need it is a multisink logger, a sink for stdout as you already have and a new one to also log to a file.
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto basic_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("mylog.log");
std::vector<spdlog::sink_ptr> sinks{console_sink, basic_sink};
auto logger = std::make_shared<spdlog::logger>("main", sinks.begin(), sinks.end());
spdlog::register_logger(logger); //if it would be used in some other place
Your function flush_to_file probably should call
logger->flush();
To see the file content before the logger shutdown or automatic flush, try to add:
logger->flush_on(spdlog::level::info);

Related

C++ spdlog use variables

I'm new to spdlog and following a tutorial which looks like this:
Log.h
#pragma once
#include "spdlog/spdlog.h"
#include "spdlog/fmt/ostr.h"
namespace Engine{
class Log{
public:
static void init();
inline static std::shared_ptr<spdlog::logger>& GetCoreLoger() { return s_CoreLogger; }
inline static std::shared_ptr<spdlog::logger>& GetClientLogger () { return s_ClientLogger;}
private:
static std::shared_ptr<spdlog::logger> s_CoreLogger;
static std::shared_ptr<spdlog::logger> s_ClientLogger;
};
}
//Client log macros
#define TRACE(...) ::Engine::Log::GetClientLogger()->trace(__VA_ARGS__)
#define INFO(...) ::Engine::Log::GetClientLogger()->info(__VA_ARGS__)
#define WARN(...) ::Engine::Log::GetClientLogger()->warn(__VA_ARGS__)
#define ERROR(...) ::Engine::Log::GetClientLogger()->error(__VA_ARGS__)
Log.cpp
#include "spdlog/sinks/stdout_color_sinks.h"
namespace Engine {
std::shared_ptr<spdlog::logger> Log::s_CoreLogger;
std::shared_ptr<spdlog::logger> Log::s_ClientLogger;
void Log::init() {
//The printing pattern, can be changed for preferance,
spdlog::set_pattern("%^[%T] %n: %v%$");
s_CoreLogger = spdlog::stdout_color_mt("VIO");
s_CoreLogger->set_level(spdlog::level::trace);
s_ClientLogger = spdlog::stdout_color_mt("APP");
s_ClientLogger->set_level(spdlog::level::trace);
}
};
This is ample for my work but I cannot seem to use variable's with it. I want to use something like this:
int test_var = 12;
INFO("The variable is: ", test_var, ".");
To get an output of:
[23:01:24] APP: The variable is: 12.
Right now the first [23:01:24] APP: The variable is: part is working but for some reason I can't seem to have it display the variable.
How can I achieve this?
According to the spdlog's wiki pages, your formatting syntax is incorrect.
For formatting a variable, a placeholder {} is required.
Try this:
int test_var = 12;
INFO("The variable is: {}{}", test_var, ".");
// ^^^^ adding these placeholders

Class members have different values in different parts of code

I'm trying to implement a non-blocking serial communication in my C++ app. A thread is responsible to do serial communication, and I've written a ThreadSafeClass to exchange data between serial thread and main thread. Here is the core of my code:
main.cpp
#include "serial.hpp"
#include "tsqueue.hpp"
int main(int argc, char *argv[])
{
serial::init();
while (true)
{
fgets(s);
serial::outQueue.enqueue(std::string(s));
}
serial::shutdown();
return 0;
}
tsqueue.hpp
#include <mutex>
#include <queue>
namespace tsqueue
{
template <typename T>
class ThreadSafeQueue
{
private:
mutable std::mutex _mtx;
std::queue<T> _que;
public:
ThreadSafeQueue();
~ThreadSafeQueue();
void enqueue(const T &item);
T tryDequeue(const T &defaultValue, bool &done);
void clear();
bool isEmpty() const;
};
template <typename T>
ThreadSafeQueue<T>::ThreadSafeQueue() {}
template <typename T>
ThreadSafeQueue<T>::~ThreadSafeQueue() { clear(); }
template <typename T>
void tsqueue::ThreadSafeQueue<T>::enqueue(const T &item)
{
std::lock_guard<std::mutex> lock(_mtx);
_que.push(item);
}
template <typename T>
T tsqueue::ThreadSafeQueue<T>::tryDequeue(const T &defaultValue, bool &done)
{
std::lock_guard<std::mutex> lock(_mtx);
if (_que.empty())
{
done = false;
return defaultValue;
}
else
{
T item = _que.front();
_que.pop();
done = true;
return item;
}
}
} // namespace tsqueue
And serial declaration/definition,
serial.hpp
#include <string>
#include "tsqueue.hpp"
namespace serial
{
static tsqueue::ThreadSafeQueue<std::string> inQueue;
static tsqueue::ThreadSafeQueue<std::string> outQueue;
void init();
void shutdown();
}
serial.cpp
#include <string>
#include "serial.hpp"
#include "tsqueue.hpp"
static std::thread _thread;
void run()
{
while (true)
{
std::string str = serial::outQueue.tryDequeue(emptyStr, dequeued);
if (dequeued) { /* Do send 'str' */ }
if (terminationRequested) { break; }
// Some sleep
}
}
void serial::init()
{
serial::inQueue.clear();
serial::outQueue.clear();
_thread = std::thread(run);
}
void serial::shutdown()
{
if (_thread.joinable()) { _thread.join(); }
}
The problem is, when tryDequeue(...) is called by serial thread's run() in serial.cpp, it always sees empty outQueue. However while loop still sees outQueue in main.cpp with provided data, even at later times. I've find out that using debug tools of vscode. I'm new to C++, but experienced in other languages. What am I doing wrong in above code? Do run() and main() see different objects?
Compiler: g++ 7.3.0, Environment: Linux (Ubuntu 18.04)
Edit: If I remove static from definitions of inQueue and outQueue, I get multiple definition error by linker for both. Although I have appropriate include guards.
(Heavily edited after all the non-issues have been repaired and after I finally spotted what was the actual problem:)
The problem is that you have two completely separate instances of outQueue: One in main.o and one in serial.o (or .obj if you are on Windows). The problem is that you declare these as static in a header. That results in individual copies of this in every *.cpp/object which included this header.
Ideally outQueue would not be a global variable. Assuming it should be a global variable you can fix this like this:
serial.hpp
namespace serial
{
// This is a declaration of a global variable. It is harmless
// to include this everywhere.
extern tsqueue::ThreadSafeQueue<std::string> inQueue;
extern tsqueue::ThreadSafeQueue<std::string> outQueue;
}
serial.cpp
namespace serial
{
// This is the actual definition of the variables.
// Without this you get unresolved symbols during link time
// (but no error during compile time). If you have this in
// two *.cpp files you will get multiple definition linker
// errors (but no error at compile time). This must not be
// static because we want all other objects to see this as well.
tsqueue::ThreadSafeQueue<std::string> inQueue;
tsqueue::ThreadSafeQueue<std::string> outQueue;
}
The ThreadSafeQueue itself looks ok to me.

Best practice for a global config

I have several c++ programs that are all reading a YAML configuration file in /etc/foo/config.yml. I have written a function that reads the config from the file
YAML::Node load_config();
(using the yaml-cpp library).
I would like this configuration to be loaded once, at the beginning of the main() function of my program, and then accessible everywhere as some kind of global variable.
Currently, many of my functions have extra parameters that are just values read from the configuration file. It could be avoided by having this global configuration, making my function definitions and calls much simpler and readable.
Side note: I am also using OpenMP for distributing computation, which means that the configuration must be accessible to all parallel processes.
Could someone give a tiny example of what this would look like when done the right way?
Thanks!
here's one way. It's a variation on the idea of the schwartz counter to manage a global singleton (for example, std::cout itself)
// globals.hpp
#include <istream>
struct globals_object
{
globals_object()
{
// record number of source files which instanciate a globals_object
++init_count_;
}
~globals_object()
{
// The last source file cleans up at program exit
if(--init_count_ == 0)
{
if (pimpl_)
{
delete pimpl_;
}
}
}
// internal implementation
struct impl
{
void load(std::istream& is)
{
// do loading code here
}
int get_param_a() const {
return a_;
}
int a_;
};
// (re)load global state
void load(std::istream&& is)
{
if (pimpl_) delete pimpl_;
pimpl_ = new impl;
pimpl_->load(is);
}
// public parameter accessor
int get_param_a() const {
return get_impl().get_param_a();
}
private:
static int init_count_;
static impl* pimpl_;
static impl& get_impl()
{
return *pimpl_;
}
};
// one of these per translation unit
static globals_object globals;
// globals.cpp
// note - not initialised - will be zero-initialised
// before global constructors are called
// you need one of these in a cpp file
int globals_object::init_count_;
globals_object::impl* globals_object::pimpl_;
// main file
// #include "globals.hpp"
#include <fstream>
int main()
{
globals.load(std::ifstream("settings.yml"));
}
// any other file
// #include "globals.hpp"
#include <iostream>
void foo()
{
std::cout << globals.get_param_a() << std::endl;
}

How to automatically add handler to the global map?

I have Application singleton wich has method
void addHandler(const std::string& command, std::function<std::string (const std::string&)> handler)
I want to create a lot of cpp files with handlers like this
//create_user_handler.cpp
Application::getInstance()->addHandler("create_user", [](std::string name) {
UserPtr user = User::create(name);
return user->toJson();
});
How automatically call this from my cpp files?
I try to change from void addHandler to bool addHandler and than use
namespace {
bool b = Application::getInatance()->addHandler......
}
but it didn't work for me
Udate
It works now, but could it be done in a better way, without unused bool variable?
Make use of static class instantiation.
Pseudo code -
Add a registrator class.
class Registrator {
template <typename Func>
Registrator(const std::string& name, Func handler) {
Application::getInstance()->addHandler(name, handler);
}
};
And in each cpp file, create a static class object:
test.cpp
static Registrator test_cpp_reg("create_user", [](std::string name) {
UserPtr user = User::create(name);
return user->toJson();
});
I assume that addHandler() should return bool? Otherwise, you can't assign to the bool variable.
To remove the bool return of addHandler, make the call from the constructor of some other class that you in turn instantiate statically.
This kind of code can work, but it is tricky. The problem is that in C/C++, the order of static-storage initializers is undefined. So while a static initializer is allowed to call any code, if that code references as-yet-uninitialized data, it will fail. And unfortunately the failure is non-deterministic. It might work for a while, and then you change some compiler flag or module order, and splat!
One trick is to implement the instance state of getInstance() using a dumb pointer, because that is always initialized to zero (null) before any of the static initializers fire. For example, the following code will print "Added foo" before main starts:
#include <string>
#include <functional>
#include <map>
#include <iostream>
class Application {
public:
static Application* getInstance() {
// Not thread-safe!
if (instance == 0) {
instance = new Application;
}
return instance;
}
typedef std::function<std::string(const std::string&)> HANDLER;
typedef std::map<std::string, HANDLER> HANDLER_MAP;
bool addHandler(const std::string& command, HANDLER handler) {
handlerMap.insert(HANDLER_MAP::value_type(command, handler));
std::cout << "Added " << command << "\n";
return true;
}
HANDLER_MAP handlerMap;
static Application* instance;
};
Application* Application::instance;
std::string myHandler(const std::string&) { return ""; }
bool b = Application::getInstance()->addHandler("foo", myHandler);
int main()
{
return 0;
}

error: ‘std::ios_base::ios_base(const std::ios_base&)’ is private

#include <iostream>
#include <fcntl.h>
#include <fstream>
using namespace std;
class Logger
{
private:
ofstream debug;
Logger()
{
debug.open("debug.txt");
}
static Logger log;
public:
static Logger getLogger()
{
return log;
}
void writeToFile(const char *data)
{
debug << data;
}
void close()
{
debug.close();
}
};
Logger Logger::log;
Through this class i m trying to create a Logger class which logs into a file. But it gives error like
error: ‘std::ios_base::ios_base(const std::ios_base&)’ is private
i googled it and found that its because of copying ofstreams. As far as i understand in this code no copying of ofstreams is taking place.
Can u guys help me out.
Thanks in advance.
~
static Logger getLogger()
{
return log;
}
attempts to return a Logger by value, which requires a copy-constructor. The compiler-generated copy-constructor attempts to make a copy of the member debug. Which is why you get the error.
You can either implement a copy constructor (probably doesn't make sense, since the debug member would be different) or return by reference:
static Logger& getLogger()
{
return log;
}
which is safe in this case, since log has static storage duration.
A correct call would look like:
Logger& l = Logger::getLogger();
in which case l refers to Logger::log.