I have code where I log different categories of information. Each piece of code has a specific tag that has been made for them. (e.g : database.message, database.odbc, event.cevent ...)
Moreover, I've made a function that reads a json file and their value which corresponds to their according severity filter
{
"database" :
{
"message": "INFO",
"odbc": "DEBUG",
},
"event" :
{
"cevent" : "INFO",
},
}
My problem is, I want to set a "basic filter", (for instance only log messages that are "INFO" or above) for all items whose tags were not set in this file.
Right now, I'm adding filters this way:
logging::core::get()->add_global_attribute("Tag", attrs::constant<std::string>(""));
logging::add_common_attributes();
std::vector<std::string> tags; // Suppose it's already filled with the tags and values from the json
...
for (const auto& tag :tags)
{
boost::shared_ptr<text_sink> sink(new text_sink(backend)); // same as the doc
auto level = logging::trivial::info; // just an example for more clarity
sink->set_filter(expr::attr<std::string>("Tag") == tag && expr::attr<logging::trivial::severity_level>("Severity") >= level);
logging::core::get()->add_sink(sink);
}
This piece of code works and correctly reads and sets filter according to the json file.
So to add this "basic filter", I also added this once every filter has been set:
boost::shared_ptr<text_sink> basic_sink(new text_sink(backend));
auto filter = logging::trivial::severity >= logging::trivial::info;
for (const auto& tag : tags)
{
filter = filter && expr::attr<std::string>("Tag") != tag;
}
basic_sink->set_filter(filter);
logging::core::get()->add_sink(basic_sink);
But it duplicates messages that are defined in the json, when I thought this would filter out tags stored. Do you have any ideas on how to avoid such duplication or do I have to implement such a sink as mentionned in this post
You weren't duplicating messages. You were adding duplicate sinks.
As someone else posted, you want to combine into one filter instead of duplicating your sinks.
However, since the filter expression is a compile-time static template expression that describes a deferred invocation, you need a deferred function to work with it.
I'd use boost::phoenix::function to make it simple:
boost::phoenix::function matching = [tags](logging::value_ref<std::string> actual) {
return tags.contains(actual.get());
};
Now you can use the single filter expression:
auto filter = matching(_tag) && (logging::trivial::severity >= level);
C++ 11
The above assumed C++17, but you can spell it out for older beasts:
struct match_impl {
std::set<std::string> target_tags;
using result_type = bool;
result_type operator()(logging::value_ref<std::string> actual_tag) const {
return 0 < target_tags.count(actual_tag.get());
}
};
boost::phoenix::function<match_impl> matching{match_impl{std::move(tags)}};
If Boost Log supports C++03 I know how to get that done too, but hopefully that's not required.
Full Demo
Live On Coliru
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup.hpp>
#include <boost/phoenix.hpp>
#include <set>
namespace logging = boost::log;
namespace sinks = logging::sinks;
namespace attrs = logging::attributes;
void init_logging(logging::trivial::severity_level level, std::set<std::string> tags) {
auto core = logging::core::get();
using text_sink = sinks::synchronous_sink<sinks::text_ostream_backend>;
core->add_global_attribute("Tag", attrs::constant<std::string>(""));
logging::add_common_attributes();
auto backend = boost::make_shared<sinks::text_ostream_backend>();
auto sink = boost::make_shared<text_sink>(backend);
sink->locked_backend()->add_stream(
boost::shared_ptr<std::ostream>(&std::clog, boost::null_deleter{}));
#if __cplusplus < 201703L
struct match_impl {
std::set<std::string> target_tags;
using result_type = bool;
result_type operator()(logging::value_ref<std::string> actual_tag) const {
return 0 < target_tags.count(actual_tag.get());
}
};
boost::phoenix::function<match_impl> matching{match_impl{std::move(tags)}};
#else
boost::phoenix::function matching = [tags](logging::value_ref<std::string> actual) {
return tags.contains(actual.get());
};
#endif
auto _tag = boost::log::expressions::attr<std::string>("Tag");
auto filter = matching(_tag) && (logging::trivial::severity >= level);
sink->set_filter(filter);
core->add_sink(sink);
}
int main() {
init_logging(logging::trivial::severity_level::error, {"foo", "bar", "qux"});
for (std::string tag : {"foo", "bogus", "bar"}) {
BOOST_LOG_SCOPED_THREAD_ATTR("Tag", attrs::constant<std::string>(tag));
BOOST_LOG_TRIVIAL(debug) << "debug tagged with " << tag;
BOOST_LOG_TRIVIAL(error) << "error tagged with " << tag;
BOOST_LOG_TRIVIAL(fatal) << "fatal tagged with " << tag;
BOOST_LOG_TRIVIAL(info) << "info tagged with " << tag;
BOOST_LOG_TRIVIAL(trace) << "trace tagged with " << tag;
BOOST_LOG_TRIVIAL(warning) << "warning tagged with " << tag;
}
}
Prints the expected
error tagged with foo
fatal tagged with foo
error tagged with bar
fatal tagged with bar
Im developing a program in C++ that returns info from a DLL to be used in a webpage.
The DLL returns a big struct with information but only need some fields that i plan to return as a json using https://github.com/nlohmann/json and then to char*.
Here is an example of the struct and the meaning of the values of each field (acording to the documentation pdf)
struct myStruct {
BYTE StatusCode;
BYTE ErrorCode;
DWORD WarningCode[2];
otherStruct SystemInfo[16];
...
}
StatusCode:
0x00 = No Error
0x01 = Error
0x02 = Ready
...
0x05 = Power Off
WarningCode
0x00 0x00 = No warning
0x02 0x01 = Warning Alert
... etc
Here is how i access the fields of the struct:
GetInfoStatus(&myStatusStruct);
jInfo["error_code"] = myStatusStruct.ErrorCode;
jInfo["status_code"] = myStatusStruct.StatusCode;
jInfo["warning_code"] = myStatusStruct.WarningCode2;
jInfo["is_available_warning_code"] = myStatusStruct.AvailableWarningCode2;
std::string info = jInfo.dump();
return info.c_str();
// My current return char* "json"
// {"available_warning_code":1,"error_code":255,"status_code":4}
But i would like to have something like this
{"available_warning_code": [0x01, "warning_alert"], "error_code": [0x01, "error_system_fail"], "status_code": [0x04, "low_battery"]}
Or similar so i can return also an error code to a "string" or "error_message" that indicates the meaning (a traslation) so my backend/frontend (NodeJS) later can detect "low_battery" and do something about it, instead of having to match 0x04 to a table to understand a 0x04 (that is different from other 0x04 in other key)
Ive checked this solution https://stackoverflow.com/a/208003/4620644 but still dont understand if is the best for my case and how to implement it. I have like 20 error codes, 10 warning codes, 15 status codes.
You could create a std:pair and use that in the json. Somewhere, though, you are going to have to type out all the error messages.
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include "json.h"
using namespace nlohmann;
std::pair<int, std::string> make_error(int error)
{
// Use a vector if error codes are sequential
// Otherwise, maybe a switch
std::vector<std::string> error_msgs = {
"No Error", "Error", "Ready"
};
if (error >= 0 && error < error_msgs.size()) {
return std::make_pair(error, error_msgs[error]);
}
else {
return std::make_pair(error, "Unknown");
}
}
int main()
{
json jInfo;
jInfo["error_code"] = make_error(2);
std::cout << jInfo.dump();
return 0;
}
This outputs:
{"error_code":[2,"Ready"]}
You'll have to do this for the other fields as well.
To get error string
class CodeMap {
map<pair<int, int>, string> m_warningCodes {
{make_pair(0,0), "No warning"},
{make_pair(2,1), "Warning Alert"}
};
map<int, string> m_statusCode{
{0, "No Error"},
{1, "Error"},
{2, "Ready"},
{5, "Power Off"},
};
public:
std::string GetWarningCode(int code[]){
return m_warningCodes[make_pair(code[0], code[1])];
}
std::string GetStatusCode(int code){
return m_statusCode[code];
}
};
Hexadecimal in json do not comply to RFC 7159
https://github.com/nlohmann/json/issues/249
Approach 1: Hex as string
To get hex from int type
//with c++ 20 std::format can be used instead below function
std::string GetHex(int i) {
std::stringstream stream;
stream << "0x" <<std::hex << i;
return stream.str();
}
Assign key value pair to json field
CodeMap m; //To get message string
jInfo["error_code"] = make_pair(GetHex(myStatusStruct.ErrorCode), "error_system_fail");
jInfo["status_code"] = make_pair(GetHex(myStatusStruct.StatusCode), m.GetStatusCode(myStatusStruct.StatusCode));
output:
"error_code": ["0x01", "error_system_fail"], "status_code": ["0x04", "low_battery"]
Approach 2: Hex as integer
Assign key value pair to json field
CodeMap m; //To get message string
jInfo["error_code"] = make_pair(myStatusStruct.ErrorCode, "error_system_fail");
jInfo["status_code"] = make_pair(myStatusStruct.StatusCode, m.GetStatusCode(myStatusStruct.StatusCode));
output:
"error_code": [1, "error_system_fail"], "status_code": [4, "low_battery"]
I have an annoying problem with Visual Studio. Indeed, I am in the design of a compiler, and I have a file reserved for error messages. It works with a dictionary (std::map): code → message.
However, the changes made to the dictionary do not seem to affect the program, which reacts as if no changes had been made.
It would be difficult for me to explain more clearly with words, so I recorded a video. (It does not belong to the site, sorry, I did not find how to include it i, this post).
As you can see, we use a function that displays the error message as it gets it from the dictionary :
std::map<ErrorCodes, std::string> Messages;
...
std::string ErrMessage = Messages[code];
I do not understand why the changes made do not affect the program as intended. It seems that Visual Studio no longer compiles part of the code...
Indeed, in the recording, we can see that the error message displayed in the console is the [E29] (the old one before the [E80]), and that the message displayed is not the one that corresponds with the enumeration either.
The problem may seem simple, but I honestly do not see where it might lie...
EDIT:
Here is a more detailed explanation:
In the video, the program running in the console is mine. "Compilation not success" is my own error message, and it is normal that it is displayed.
Here is the call code of the error call display:
exception.ThrowError(exception.E0080);
E0080 is the error listing code, here are the error message files :
*.hpp:
class Exception {
public:
enum ErrorCodes {
E0000,
E0001, // Can't load file
E0002, // Can't find header file
E0003, // Expected file name
E0004, // Unexpected token
E0005, // Expected Identifier
E0006, // Value without storage object
E0007, // Already existing object
E0008, // Redefined preprocessor definition
E0009, // Redefined variable
E0010, // Redefined function
E0011, // Redefined object
E0012, // Redefined rule
E0013, // Redefined value
E0014, // Undefined identifier
E0015, // Buoyage symbol missing
E0016, // Expected end of line symbol (';')
E0017, // Unexpected character
E0018, // Unknown token
E0019, // Non-close comment
E0020, // Non-compliant typing
E0021, // Use of uninitialized data
E0022, // Unacceptable suffix
E0023, // Invalid keyword combination
E0024, // Declaration not in accordance with non-local use
E0025, // Incompatible value with type / object defined
E0026, // Constant value too wide
E0027, // Too much initialization value in a static size array
E0028, // Non-conforming identifier
E0029, // Expected statement
E0030, // Wrong identifier format (the expected format is ASCII)
E0031, // Wrong pointer initialization
E0032, // Non-valid literal suffix
E0033, // No object can have the identifier 'vanaur'
E0034, // Non-existent object field
E0035, // Non-existent object method
E0036, // Missing function call arguments
E0037, // A rvalue cannot be accessed via the pointers
E0038, // The use of a pointer is unacceptable in this context
E0039, // A reference cannot be made to a constant value
E0040, // Invalid assembler code in external expression
E0041, // The types of the two objects do not match in the image assignment
E0042, // Only a defined object can be destroyed by the delete operator
E0043, // Empty template
E0044, // Object not existing in this namespace
E0045, // Typage temptation with a non-instanciated structure
E0046, // Temptation to access a private field of the object
E0047, // Temptation to access a private method of the object
E0048, // Invalid operator
E0049, // Reserved identifier
E0050, // Wrong usage of postfix and prefix operator
E0051, // Temptation to access an inaccessible element in a table
E0052, // The main function can only have 3 arguments variation
E0053, // This function is not defined as being able to return an array
E0054, // A non-instanciated structure cannot be used as an object instance with the keyword 'new'.
E0055, // Wrong choice of the anonymous string concatenation operator (between '^' and'+')
E0056, // An object can only inherit from one other object
E0057, // Incorrect 'typename' value
E0058, // Incorrect 'typesize' value
E0059, // The identifier of a process ('proc') cannot contain spaces
E0060, // The identifier of a process ('proc') cannot contain balises
E0061, // An exception cannot be thrown out of a function
E0062, // Syntax error
E0063, // The increment value of an iteration (with step) must be numerical
E0064, // An anonymous array serving as an iterator to a for loop cannot be empty
E0065, // Wrong ternary condition format
E0066, // A 'ret' instruction must only fit on one instruction (one line = one ';')
E0067, // Division by zero
E0068, // Wrong enumeration value
E0069, // 'Upon' block members cannot contain expressions other than 'it'
E0070, // A match/case expression must contain at least one 'case' expression before using 'default'
E0071, // Unknown error
E0072, // Undefined object
E0073, // Not a valid math expression
E0074, // Expected character
E0075, // Unbalanced brackets
E0076, // Non-ascii character detected
E0077, // The current version of Arlia only accepts constant values as optional function parameters
E0078, // Undefined type
E0079, // Statement not recognised in this context
E0080, // Invalid statement
};
void ThrowError(ErrorCodes, char);
void ThrowError(ErrorCodes, std::string);
void ThrowError(ErrorCodes, token_t);
void ThrowError(ErrorCodes, token_t, Expr);
void ThrowError(ErrorCodes, Expr);
private:
std::map<ErrorCodes, std::string> Messages =
{
{ E0001, " Can't load file" },
{ E0002, " Can't find header file" },
{ E0003, " Expected file name" },
{ E0004, " Unexpected token" },
{ E0005, " Expected Identifier" },
{ E0006, " Value without storage object" },
{ E0007, " Already existing object" },
{ E0008, " Redefined preprocessor definition" },
{ E0009, " Redefined variable" },
{ E0010, " Redefined function" },
{ E0011, " Redefined object" },
{ E0012, " Redefined rule" },
{ E0013, " Redefined value" },
{ E0014, " Undefined identifier" },
{ E0015, " Buoyage symbol missing" },
{ E0016, " Expected end of line symbol (';')" },
{ E0017, " Unexpected character" },
{ E0018, " Unknown token" },
{ E0019, " Non-close comment" },
{ E0020, " Non-compliant typing" },
{ E0021, " Use of uninitialized data" },
{ E0022, " Unacceptable suffix" },
{ E0023, " Invalid keyword combination" },
{ E0024, " Declaration not in accordance with non-local use" },
{ E0025, " Incompatible value with type / object defined" },
{ E0026, " Constant value too wide" },
{ E0027, " Too much initialization value in a static size array" },
{ E0028, " Non-conforming identifier" },
{ E0029, " Expected statement" },
{ E0030, " Wrong identifier format (the expected format is ASCII)" },
{ E0031, " Wrong pointer initialization" },
{ E0032, " Non-valid literal suffix" },
{ E0033, " No object can have the identifier 'vanaur'" },
{ E0034, " Non-existent object field" },
{ E0035, " Non-existent object method" },
{ E0036, " Missing function call arguments" },
{ E0037, " A rvalue cannot be accessed via the pointers" },
{ E0038, " The use of a pointer is unacceptable in this context" },
{ E0039, " A reference cannot be made to a constant value" },
{ E0040, " Invalid assembler code in external expression" },
{ E0041, " The types of the two objects do not match in the image assignment" },
{ E0042, " Only a defined object can be destroyed by the delete operator" },
{ E0043, " Empty template" },
{ E0044, " Object not existing in this namespace" },
{ E0045, " Typage temptation with a non-instanciated structure" },
{ E0046, " Temptation to access a private field of the object" },
{ E0047, " Temptation to access a private method of the object" },
{ E0048, " Invalid operator" },
{ E0049, " Reserved identifier" },
{ E0050, " Wrong usage of postfix and prefix operator" },
{ E0051, " Temptation to access an inaccessible element in a table" },
{ E0052, " The main function can only have 3 arguments variation" },
{ E0053, " This function is not defined as being able to return an array" },
{ E0054, " A non-instanciated structure cannot be used as an object instance with the keyword 'new'" },
{ E0055, " Wrong choice of the anonymous string concatenation operator (between '^' and'+')" },
{ E0056, " An object can only inherit from one other object" },
{ E0057, " Incorrect 'typename' value" },
{ E0058, " Incorrect 'typesize' value" },
{ E0059, " The identifier of a process ('proc') cannot contain spaces" },
{ E0060, " The identifier of a process ('proc') cannot contain balises" },
{ E0061, " An exception cannot be thrown out of a function" },
{ E0062, " Syntax error" },
{ E0063, " The increment value of an iteration (with step) must be numerical" },
{ E0064, " An anonymous array serving as an iterator to a for loop cannot be empty" },
{ E0065, " Wrong ternary condition format" },
{ E0066, " A 'ret' instruction must only fit on one instruction (one line = one ';')" },
{ E0067, " Division by zero" },
{ E0068, " Wrong enumeration value" },
{ E0069, " 'Upon' block members cannot contain expressions other than 'it'" },
{ E0070, " A match/case expression must contain at least one 'case' expression before using 'default'" },
{ E0071, " Unknown error" },
{ E0072, " Undefined object" },
{ E0073, " Not a valid math expression" },
{ E0074, " Expected character" },
{ E0075, " Unbalanced brackets" },
{ E0076, " Non-ascii character detected" },
{ E0077, " The current version of Arlia only accepts constant values as optional function parameters" },
{ E0078, " Undefined type" },
{ E0079, " Statement not recognised in this context" },
{ E0080, " Invalid statement" },
{ E0000, " ... " }
};
};
The *.cpp just implements the functions as follows :
ThrowError(ErrorCodes code) {
colored_message_error("[ E" + std::to_string(code) + " ]" + Messages[code], Color::yellow);
}
As you can see, the list contains 80 + 1 elements. And E0080 has 80 as index in the enumeration.
When I try to access it:
exception.ThrowError(exception.E0080);
my program doesn't seem to want to update the information. That is, the enumeration constant sent to the function: in fact, the program displays the value I set before it (E29).
It's exactly like I'm doing this:
exception.ThrowError(exception.E0029);
So, no, it's most likely not a stupid debugging or compiling problem (I still know how to use a compiler ^^). It seems strange that Visual Studio does not update this new data (how ? why ? is it ?).
I'd like to unit test a function with a set of different inputs and expected outputs.
My function is irrelevant thus I'll instead use an example function which counts english words with the following candidate implementation :
int countEnglishWords( const std::string& text )
{
return 5;
};
The following would be the set of test data. The end of the data is marked by an element with the word "END".
struct TestData {
std::string text;
int englishWords;
};
struct TestData data[] = // Mark end with "END"
{
{ "The car is very fast", 5 },
{ "El coche es muy rapido", 0 },
{ "The rain in Spain stays mainly in the plain", 9},
{ "XXXXX OOOOO TTTT", 0},
{ "Yes Si No No", 3},
{ "I have a cheerful live", 5},
{ "END", 0}
};
I could easily write 6 test cases and I would get the result I want. But this is not maintainable, since any further test added to the test cases would not be tested, it would require another test case to be written, which would be just boiler plate. Thus I've written a single test case which loops through all the test data like this :
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
class cppUnit_test: public CppUnit::TestFixture
{
private:
CPPUNIT_TEST_SUITE (cppUnit_test);
CPPUNIT_TEST(myTest);
CPPUNIT_TEST_SUITE_END();
public:
void myTest();
};
void cppUnit_test::myTest()
{
TestData* p = data;
while ( p->text != "END")
{
std::stringstream ss;
ss << "Text=\"" << p->text << "\" Counted=" <<
countEnglishWords(p->text) << " Expected=" << p->englishWords;
CPPUNIT_ASSERT_MESSAGE( ss.str().c_str(),
countEnglishWords(p->text) == p->englishWords );
++p;
}
}
int main()
{
CPPUNIT_TEST_SUITE_REGISTRATION (cppUnit_test);
CppUnit::Test *suite =
CppUnit::TestFactoryRegistry::getRegistry().makeTest();
CppUnit::TextUi::TestRunner runner;
runner.addTest(suite);
runner.run();
return 0;
}
The problem is that the previous code runs through the 1st test fine and also detects the error in the 2nd test but after that it stops testing. And the report is :
!!!FAILURES!!!
Test Results:
Run: 1 Failures: 1 Errors: 0
While the result I'd like to get is :
!!!FAILURES!!!
Test Results:
Run: 6 Failures: 4 Errors: 0
As I already mentioned in the comment cppunit 1.14.0 can support your use case.
I you want to reference an external array the quickest way is to use CPPUNIT_TEST_PARAMETERIZED. This macro expects two parameters: first similar to CPPUNIT_TEST a test method and then as a second parameter an iteratable.
Based on your code it would look like:
CPPUNIT_TEST_PARAMETERIZED(myTest, aData);
Now we need to adapt your myTest function a little bit.
void cppUnit_test::myTest(const TestData& data)
{
std::stringstream ss;
ss << "Text=\"" << data.text << "\" Counted=" <<
countEnglishWords(data.text) << " Expected=" << data.englishWords;
bool b = countEnglishWords(data.text) == data.englishWords;
std::string a = ss.str();
CPPUNIT_ASSERT_MESSAGE( a,
b);
}
Finally as the framework needs a way to report which test failed it expects that it can print the parameter that is passed to the test function. In this case the easiest way is to add a simple operator<< overload.
std::ostream& operator<<(std::ostream& strm, const TestData& data)
{
strm << data.text;
return strm;
}
If you combine these pieces you should quickly get a generic solution that will allow you to add as much data to your data array as you want without adapting the test code.
CPPUNIT_TEST_SUITE(TestSuite);
CPPUNIT_TEST_PARAMETERIZED(testMethod, {1, 2, 3, 4});
CPPUNIT_TEST_SUITE_END();
void testMethod(int /*val*/)
{
}