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
Related
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);
I've had trouble with setting up variables in namespaces multiple times already, but usually now solve it by having the setup be as follows:
.h
namespace MyNS {
extern Variable var;
}
.cpp
#include "MyNS.h"
Variable MyNS::var;
Now, this works fine with primitives, but becomes a problem when the Variable object is somewhat of a more complex object.
Namely, my problem this time is that I would like to leave some variables uninitialized until I call a certain function. Something as follows:
.h
namespace MyNS {
extern Variable var;
void init();
}
.cpp
#include "MyNS.h"
Variable MyNS::var;
void MyNS::init() { var = Variable(a, b, c); }
This gives a compile time error, because Variable does not have a default constructor (And I don't want it to have one). But when I remove the 2nd line in the .cpp, I get linker error unresolved external symbol.
How can I solve this problem? How do I initialize a variable of a namespace "later"?
The "hacky" solution I have so far is to have my namespace hold a Variable*, initialize it to nullptr, and then assign it the actual object in my init function. But that seems incredibly hacky for something so simple, since I have no actual reason for using pointers in this case.
Variable does not have a default constructor (And I don't want it to have one)
Then you simply can't create an instances of Variable without passing values into its constructor, eg:
namespace MyNS {
extern Variable var;
void init();
}
#include "MyNS.h"
Variable MyNS::var(0, 0, 0);
void MyNS::init() { var = Variable(a, b, c); }
Another solution is to make var be a Variable* pointer instead, and then init() can new it, eg:
namespace MyNS {
extern Variable* var;
void init();
void cleanup();
}
#include "MyNS.h"
Variable* MyNS::var = nullptr;
void MyNS::init() { var = new Variable(a, b, c); }
void MyNS::cleanup() { delete var; }
Alternatively:
#include <memory>
namespace MyNS {
extern std::unique_ptr<Variable> var;
void init();
}
#include "MyNS.h"
std::unique_ptr<Variable> MyNS::var;
void MyNS::init() { var = std::make_unique<Variable>(a, b, c); }
Another solution would be to make var be a std::optional<Variable> instead, eg:
#include <optional>
namespace MyNS {
extern std::optional<Variable> var;
void init();
}
#include "MyNS.h"
std::optional<Variable> MyNS::var;
void MyNS::init() { var = Variable(a, b, c); }
Another solution is to wrap var inside of a singleton, eg:
namespace MyNS {
struct VariableAccess {
static Variable& Var();
};
}
#include "MyNS.h"
Variable& MyNS::VariableAccess::Var() {
static Variable var(a, b, c);
return var;
};
I have three files:
main.cpp
MyClass.cpp
MyClass.hpp
I have a library header file, "testLib.hpp", that I want to include in MyClass.hpp so that I can have one of testLib's objects be a class attribute.
I include MyClass.hpp in MyClass.cpp and in main.cpp. When attempting to compile the project, I get the following errors
MyClass.cpp multiple definition of 'testLib::testLib::function1()
obj/Release/main.o:main.cpp first defined here
MyClass.cpp multiple definition of 'testLib::testLib::function2()
obj/Release/main.o:main.cpp first defined here
and so on.
Both main.cpp and MyClass.cpp include MyClass.hpp (which includes testLib.hpp). Judging by the error, it looks like MyClass.cpp is attempting to include the library functions after they've already been included by main.cpp. However, I have include guards present in MyClass.hpp so I don't understand how it's trying to include MyClass.hpp twice.
Here's the code:
MyClass.hpp
#ifndef THIS_HEADER_H
#define THIS_HEADER_H
#include <stdint.h>
#include <iostream>
#include "testLib/testLib.hpp"
class MyClass
{
public:
void test();
int foo;
private:
uint32_t bar;
//I want to include an object from the library as part of this class
//TestLib::Device device;
};
#endif
MyClass.cpp
#include <stdio.h>
#include "MyClass.hpp"
void MyClass::test()
{
}
main.cpp
#include <iostream>
#include "MyClass.hpp"
using namespace std;
int main()
{
cout << "Hello world!" << endl;
return 0;
}
Any help would be greatly appreciated!
EDIT
I tried to hide the actual filenames to make the question more general and clear, but it seems like the problem might be resulting from 'testLib.hpp', which I did not write. That file is actually the following "sweep.hpp" file. I got the 'multiple definition of/first defined here' errors for each of the public functions in this file:
sweep.hpp
#ifndef SWEEP_DC649F4E94D3_HPP
#define SWEEP_DC649F4E94D3_HPP
/*
* C++ Wrapper around the low-level primitives.
* Automatically handles resource management.
*
* sweep::sweep - device to interact with
* sweep::scan - a full scan returned by the device
* sweep::sample - a single sample in a full scan
*
* On error sweep::device_error gets thrown.
*/
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <vector>
#include <sweep/sweep.h>
namespace sweep {
// Error reporting
struct device_error final : std::runtime_error {
using base = std::runtime_error;
using base::base;
};
// Interface
struct sample {
const std::int32_t angle;
const std::int32_t distance;
const std::int32_t signal_strength;
};
struct scan {
std::vector<sample> samples;
};
class sweep {
public:
sweep(const char* port);
sweep(const char* port, std::int32_t bitrate);
void start_scanning();
void stop_scanning();
bool get_motor_ready();
std::int32_t get_motor_speed();
void set_motor_speed(std::int32_t speed);
std::int32_t get_sample_rate();
void set_sample_rate(std::int32_t speed);
scan get_scan();
void reset();
private:
std::unique_ptr<::sweep_device, decltype(&::sweep_device_destruct)> device;
};
// Implementation
namespace detail {
struct error_to_exception {
operator ::sweep_error_s*() { return &error; }
~error_to_exception() noexcept(false) {
if (error) {
device_error e{::sweep_error_message(error)};
::sweep_error_destruct(error);
throw e;
}
}
::sweep_error_s error = nullptr;
};
}
sweep::sweep(const char* port)
: device{::sweep_device_construct_simple(port, detail::error_to_exception{}), &::sweep_device_destruct} {}
sweep::sweep(const char* port, std::int32_t bitrate)
: device{::sweep_device_construct(port, bitrate, detail::error_to_exception{}), &::sweep_device_destruct} {}
void sweep::start_scanning() { ::sweep_device_start_scanning(device.get(), detail::error_to_exception{}); }
void sweep::stop_scanning() { ::sweep_device_stop_scanning(device.get(), detail::error_to_exception{}); }
bool sweep::get_motor_ready() { return ::sweep_device_get_motor_ready(device.get(), detail::error_to_exception{}); }
std::int32_t sweep::get_motor_speed() { return ::sweep_device_get_motor_speed(device.get(), detail::error_to_exception{}); }
void sweep::set_motor_speed(std::int32_t speed) {
::sweep_device_set_motor_speed(device.get(), speed, detail::error_to_exception{});
}
std::int32_t sweep::get_sample_rate() { return ::sweep_device_get_sample_rate(device.get(), detail::error_to_exception{}); }
void sweep::set_sample_rate(std::int32_t rate) {
::sweep_device_set_sample_rate(device.get(), rate, detail::error_to_exception{});
}
scan sweep::get_scan() {
using scan_owner = std::unique_ptr<::sweep_scan, decltype(&::sweep_scan_destruct)>;
scan_owner releasing_scan{::sweep_device_get_scan(device.get(), detail::error_to_exception{}), &::sweep_scan_destruct};
auto num_samples = ::sweep_scan_get_number_of_samples(releasing_scan.get());
scan result;
result.samples.reserve(num_samples);
for (std::int32_t n = 0; n < num_samples; ++n) {
auto angle = ::sweep_scan_get_angle(releasing_scan.get(), n);
auto distance = ::sweep_scan_get_distance(releasing_scan.get(), n);
auto signal = ::sweep_scan_get_signal_strength(releasing_scan.get(), n);
result.samples.push_back(sample{angle, distance, signal});
}
return result;
}
void sweep::reset() { ::sweep_device_reset(device.get(), detail::error_to_exception{}); }
} // ns
#endif
A simplified version of your problem:
buggy.hpp
int function() { return 0; }
main.cpp
#include "buggy.hpp"
int main() { return 0; }
other.cpp
#include "buggy.hpp"
The problem is that buggy.hpp is defining function, not just declaring. Once the header inclusion is expanded, that means function is declared in both main.cpp and other.cpp - and that is not allowed.
The fix is to declare function as inline which allows the function to be declared in multiple translation units.
inline int function() { return 0; }
In fact, allowing multiple definitions is the only meaning of inline to the C++ standard. Compilers may treat it as a hint that the function body may be expanded inline. Good ones won't; they are better at making that sort of decision that programmers).
How can an object access a variable belonging the class containing it?
Right now I have a class called system that contains some other objects, and those objects need to access and modify one of the variables in the System class.
Example:
Class System {
BlockA _blockA = new BlockA();
BlockB _blockB = new BlockB();
BlockC _blockC = new BlockC();
BlockD _blockD = new BlockD();
int myVariable;
...stuff...
}
Class BlockA {
...stuff...
void someFunction () {
System.myVariable++;
}
...stuff...
}
etc...
Alright so I thought about this some more and realized that when initializing the objects, I will pass a pointer to the variable of interest. That way all objects can read that variable. For anyone else with this problem, if you need to write, you'll have to make sure that the variable is atomic.
Hard to know exactly what you're after, but appears something along these lines:
#BlockA.h
#ifndef BLOCKA_H
#define BLOCKA_H
class System;
class BlockA {
System* sys;
public:
BlockA(System* sys) : sys(sys) {}
void SomeFunction();
};
#endif // BLOCKA_H
#BlockA.cpp
#include "System.h"
void BlockA::SomeFunction() {
sys->setMyVariable(sys->getMyVariable() + 1);
}
#System.h
#ifndef SYSTEM_H
#define SYSTEM_H
class BlockA;
class System {
BlockA* _blockA;
int myVariable;
public:
System();
int getMyVariable() const;
void setMyVariable(int value);
BlockA& getBlockA() const;
};
#endif // SYSTEM_H
#System.cpp
#include "System.h"
#include "BlockA.h"
System::System()
: _blockA(new BlockA(this)) { }
int System::getMyVariable() const {
return myVariable;
}
void System::setMyVariable(int value) {
myVariable = value;
}
BlockA& System::getBlockA() const {
return *_blockA;
}
The following sample does not compile, complaining that
In file included from /usr/include/msgpack.hpp:18:
/usr/include/msgpack/object.hpp:211:3: error: member reference base type 'logd::log_level' is not a structure or union
and a corresponding error for the other enum class. My question is how, using msgpack's c++ api, does one serialize a class with members that are of c++11 enum class type?
#ifndef LOG_MSG_HPP_
#define LOG_MSG_HPP_
#include <stdlib.h>
#include <msgpack.hpp>
/** #namespace logd */
namespace logd {
enum class log_level { SILENT,... DEBUG };
enum class log_domain { AI, ... MISC };
class log_msg {
public:
log_msg(log_level lev, log_domain dom, std::string msg);
log_level level();
log_domain domain();
std::string message();
~log_msg();
MSGPACK_DEFINE(lev_, dom_, msg_);
private:
log_msg();
log_level lev_ {log_level::DEBUG};
log_domain dom_ {log_domain::MISC};
std::string msg_ {"No message given."};
};
} /* namespace logd */
#endif /* LOG_MSG_HPP_ */
NOTE: Since the enums are mine, I can happily modify them to make msgpack happy. Unfortunately, I can find no references on the subject in their docs or the first couple pages of Google. I am also not able to determine what to do from reading their headers/source since I'm rather new to c++.
You can use MSGPACK_ADD_ENUM() macro. It has been supported since version 1.0.0. I recommend version 1.1.0 or later.
See:
https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_adaptor#enums
I applied MSGPACK_ADD_ENUM() to your code:
#ifndef LOG_MSG_HPP_
#define LOG_MSG_HPP_
#include <stdlib.h>
#include <msgpack.hpp>
/** #namespace logd */
namespace logd {
enum class log_level { SILENT,DEBUG };
enum class log_domain { AI, MISC };
class log_msg {
public:
log_msg(log_level lev, log_domain dom, std::string msg):lev_(lev), dom_(dom), msg_(msg) {}
log_level level() { return lev_;}
log_domain domain() { return dom_;}
std::string message() { return msg_; }
~log_msg() {}
MSGPACK_DEFINE(lev_, dom_, msg_);
log_msg() = default;
private:
log_level lev_ {log_level::DEBUG};
log_domain dom_ {log_domain::MISC};
std::string msg_ {"No message given."};
};
} /* namespace logd */
MSGPACK_ADD_ENUM(logd::log_level);
MSGPACK_ADD_ENUM(logd::log_domain);
#endif /* LOG_MSG_HPP_ */
#include <sstream>
#include <cassert>
int main() {
logd::log_msg lm { logd::log_level::SILENT, logd::log_domain::AI, "hello" };
std::stringstream ss;
msgpack::pack(ss, lm);
msgpack::object obj = msgpack::unpack(ss.str().data(), ss.str().size()).get();
logd::log_msg lm2 = obj.as<logd::log_msg>();
assert(lm.level() == lm2.level());
assert(lm.domain() == lm2.domain());
assert(lm.message() == lm2.message());
}
One way that seems to work is to wrap the enum in a union...
union log_level_t {
log_level lev;
int levint;
}
...
log_level_t lev_;
...
MSGPACK_DEFINE(lev_.levint, dom_.domint, msg);
This seems like a crappy approach, but it works.
I found another solution, maybe a little more elegant (ok, it's not so perfect):
MSGPACK_DEFINE((int&)lev_, (int&)dom_, msg)
So you can keep your original enum without creating a new union.
The deserialisation method should work without any problem.