Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rsyslog plugin and tests #2

Merged
merged 8 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/sonic-eventd/rsyslog_plugin/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <iostream>
#include <unistd.h>
#include "rsyslog_plugin.h"
#include "syslog_parser.h"

using namespace std;
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved

void showUsage() {
cerr << "Usage for rsyslog_plugin: " << " <option(s)> SOURCES\n"
<< "Options:\n"
<< "\t-r,required,type=string\t\tPath to regex file"
<< "\t-m,required,type=string\t\tYANG module name of source generating syslog message"
<< endl;
}

int main(int argc, char** argv) {
string regex_path;
string module_name;
int option_val;

while((option_val = getopt(argc, argv, "r:m:")) != -1) {
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
switch(option_val) {
case 'r':
if(optarg != NULL) {
renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved
regex_path = optarg;
}
break;
case 'm':
if(optarg != NULL) {
module_name = optarg;
}
break;
default:
showUsage();
return 1;
}
}

if(regex_path.empty() || module_name.empty()) { // Missing required rc path
showUsage();
return 1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1

Since there are multiple exit reason, let's define multiple return code, and user macro name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

SyslogParser* parser = new SyslogParser({}, json::array());
RsyslogPlugin* plugin = new RsyslogPlugin(parser, module_name, regex_path);
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved

plugin->run();
renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
return 0;
}
79 changes: 79 additions & 0 deletions src/sonic-eventd/rsyslog_plugin/rsyslog_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include <ios>
#include <iostream>
#include <vector>
#include <fstream>
#include <regex>
#include "rsyslog_plugin.h"
#include "common/logger.h"
#include "common/json.hpp"
#include "common/events.h"

using namespace std;
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
using namespace swss;
using json = nlohmann::json;

void RsyslogPlugin::onMessage(string msg) {
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
string tag = "";
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
event_params_t param_dict;
if(!parser->parseMessage(msg, tag, param_dict)) {
SWSS_LOG_INFO("%s was not able to be parsed into a structured event\n", msg.c_str());
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
} else {
int return_code = event_publish(fetchHandle(), tag, &param_dict);
if (return_code != 0) {
SWSS_LOG_INFO("rsyslog_plugin was not able to publish event for %s\n", tag.c_str());
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

[[noreturn]] void RsyslogPlugin::run() {
while(true) {
string line;
getline(cin, line);
if(line.empty()) {
continue;
}
onMessage(line);
}
}

bool RsyslogPlugin::createRegexList() {
fstream regex_file;
regex_file.open(regex_path, ios::in);
if (!regex_file) {
SWSS_LOG_ERROR("No such path exists: %s for source %s\n", regex_path.c_str(), module_name.c_str());
return false;
}
try {
regex_file >> parser->regex_list;
} catch (exception& exception) {
SWSS_LOG_ERROR("Invalid JSON file: %s, throws exception: %s\n", regex_path.c_str(), exception.what());
return false;
}

string regex_string = "";
regex expression;

for(long unsigned int i = 0; i < parser->regex_list.size(); i++) {
try {
regex_string = parser->regex_list[i]["regex"];
regex expr(regex_string);
expression = expr;
} catch (exception& exception) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exception

Do not catch general exception in normal use cases. It will hide bugs. Use more specific types.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

SWSS_LOG_ERROR("Invalid regex, throws exception: %s\n", exception.what());
return false;
}
parser->expressions.push_back(expression);
}
regex_file.close();
return true;
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
}


RsyslogPlugin::RsyslogPlugin(SyslogParser* syslog_parser, string mod_name, string path) {
parser = syslog_parser;
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
module_name = mod_name;
regex_path = path;
if(!onInit()) {
SWSS_LOG_ERROR("Initializing rsyslog plugin failed.\n");
}
}
43 changes: 43 additions & 0 deletions src/sonic-eventd/rsyslog_plugin/rsyslog_plugin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#ifndef RSYSLOG_PLUGIN_H
#define RSYSLOG_PLUGIN_H

#include <string>
#include <fstream>
#include "syslog_parser.h"
#include "common/logger.h"
#include "common/events.h"

using namespace std;
using json = nlohmann::json;

/**
* Rsyslog Plugin will utilize an instance of a syslog parser to read syslog messages from rsyslog.d and will continuously read from stdin
* A plugin instance is created for each container/host.
*
*/

class RsyslogPlugin {
public:
RsyslogPlugin(SyslogParser* syslog_parser, string mod_name, string path);
void onMessage(string msg);
renukamanavalan marked this conversation as resolved.
Show resolved Hide resolved
void run();
bool createRegexList();
event_handle_t fetchHandle() {
return event_handle;
}
SyslogParser* parser;
private:
string regex_path;
string module_name;
event_handle_t event_handle;

bool onInit() {
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
event_handle = events_init_publisher(module_name);
int return_code = createRegexList();
return (event_handle != NULL && return_code == 0);
}

};

#endif

49 changes: 49 additions & 0 deletions src/sonic-eventd/rsyslog_plugin/syslog_parser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <iostream>
#include <ios>
#include <fstream>
#include <regex>
#include "syslog_parser.h"
#include "common/logger.h"


using namespace std;
/**
* Parses syslog message and returns structured event
*
* @param nessage us syslog message being fed in by rsyslog.d
* @return return structured event json for publishing
*
*/

bool SyslogParser::parseMessage(string message, string& event_tag, event_params_t& param_map) {
for(long unsigned int i = 0; i < regex_list.size(); i++) {
smatch match_results;
regex_search(message, match_results, expressions[i]);
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
vector<string> groups;
vector<string> params;
try {
event_tag = regex_list[i]["tag"];
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
vector<string> p = regex_list[i]["params"];
params = p;
} catch (exception& exception) {
SWSS_LOG_ERROR("Invalid regex list, throws exception: %s\n", exception.what());
return false;
}
// first match in groups is entire message
for(long unsigned int j = 1; j < match_results.size(); j++) {
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
groups.push_back(match_results.str(j));
}
if (groups.size() == params.size()) { // found matching regex
transform(params.begin(), params.end(), groups.begin(), inserter(param_map, param_map.end()), [](string a, string b) {
return make_pair(a,b);
});
return true;
}
}
return false;
}

SyslogParser::SyslogParser(vector<regex> regex_expressions, json list) {
expressions = regex_expressions;
regex_list = list;
}
28 changes: 28 additions & 0 deletions src/sonic-eventd/rsyslog_plugin/syslog_parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#ifndef SYSLOG_PARSER_H
#define SYSLOG_PARSER_H

#include <vector>
#include <string>
#include <regex>
#include "common/json.hpp"
#include "common/events.h"

using namespace std;
using json = nlohmann::json;

/**
* Syslog Parser is responsible for parsing log messages fed by rsyslog.d and returns
* matched result to rsyslog_plugin to use with events publish API
*
*/

class SyslogParser {
public:
SyslogParser(vector<regex> regex_expressions, json list);
bool parseMessage(string message, string& tag, event_params_t& param_dict);

vector<regex> expressions;
json regex_list = json::array();
};

#endif
125 changes: 125 additions & 0 deletions src/sonic-eventd/rsyslog_plugin_tests/rsyslog_plugin_ut.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#include <iostream>
#include <fstream>
#include <regex>
#include "gtest/gtest.h"
#include "common/json.hpp"
#include "common/events.h"
#include "rsyslog_plugin/rsyslog_plugin.h"
#include "rsyslog_plugin/syslog_parser.h"

using namespace std;
using json = nlohmann::json;

RsyslogPlugin* plugin = NULL;
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved

json j_list_1 = json::array();
json j_list_2 = json::array();
vector<regex> test_expressions_1;
vector<regex> test_expressions_2;

void createTests() {
string regex_string_1 = "timestamp (.*) message (.*) other_data (.*)";
string regex_string_2 = "no match";

json j_test_1;
j_test_1["tag"] = "test_tag_1";
j_test_1["regex"] = regex_string_1;
j_test_1["params"] = { "timestamp", "message", "other_data" };
j_list_1.push_back(j_test_1);

json j_test_2;
j_test_2["tag"] = "test_tag_2";
j_test_2["regex"] = regex_string_2;
j_test_2["params"] = {};
j_list_2.push_back(j_test_2);

regex expression_1(regex_string_1);
test_expressions_1.push_back(expression_1);
regex expression_2(regex_string_2);
test_expressions_2.push_back(expression_2);
}


TEST(syslog_parser, matching_regex) {
createTests();
string tag = "";
event_params_t param_dict;

event_params_t expected_dict;
expected_dict["timestamp"] = "test_timestamp";
expected_dict["message"] = "test_message";
expected_dict["other_data"] = "test_data";

SyslogParser* parser = new SyslogParser(test_expressions_1, j_list_1);

bool success = parser->parseMessage("timestamp test_timestamp message test_message other_data test_data", tag, param_dict);
EXPECT_EQ(true, success);
EXPECT_EQ("test_tag_1", tag);
EXPECT_EQ(expected_dict, param_dict);

delete parser;
}

TEST(syslog_parser, no_matching_regex) {
string tag = "";
event_params_t param_dict;
SyslogParser* parser = new SyslogParser(test_expressions_2, j_list_2);
bool success = parser->parseMessage("Test Message", tag, param_dict);
EXPECT_EQ(false, success);
delete parser;
}


void createPlugin(string path) {
SyslogParser* testParser = new SyslogParser({}, json::array());
plugin = new RsyslogPlugin(testParser, "test_mod_name", path);
zbud-msft marked this conversation as resolved.
Show resolved Hide resolved
}

TEST(rsyslog_plugin, createRegexList_invalidJS0N) {
createPlugin("./test_regex_1.rc.json");
if(plugin != NULL) {
EXPECT_EQ(false, plugin->createRegexList());
}
delete plugin;
}

TEST(rsyslog_plugin, createRegexList_missingRegex) {
createPlugin("./test_regex_3.rc.json");
if(plugin != NULL) {
EXPECT_EQ(false, plugin->createRegexList());
}
delete plugin;
}

TEST(rsyslog_plugin, createRegexList_invalidRegex) {
createPlugin("./test_regex_4.rc.json");
if(plugin != NULL) {
EXPECT_EQ(false, plugin->createRegexList());
}
delete plugin;
}

TEST(rsyslog_plugin, createRegexList_validRegex) {
createPlugin("./test_regex_2.rc.json");
if(plugin != NULL) {
auto parser = plugin->parser;
EXPECT_EQ(1, parser->regex_list.size());
EXPECT_EQ(1, parser->expressions.size());

ifstream infile("test_syslogs.txt");
string log_message;
bool parse_result;

while(infile >> log_message >> parse_result) {
string tag = "";
event_params_t param_dict;
EXPECT_EQ(parse_result, parser->parseMessage(log_message, tag, param_dict));
}
}
delete plugin;
}

int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
1 change: 1 addition & 0 deletions src/sonic-eventd/rsyslog_plugin_tests/test_regex_1.rc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*
7 changes: 7 additions & 0 deletions src/sonic-eventd/rsyslog_plugin_tests/test_regex_2.rc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"tag": "bgp-state",
"regex": "([a-zA-Z]{3} [0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{0,6}) .* %ADJCHANGE: neighbor (.*) (Up|Down) .*",
"params": [ "timestamp", "neighbor_ip", "state" ]
}
]
6 changes: 6 additions & 0 deletions src/sonic-eventd/rsyslog_plugin_tests/test_regex_3.rc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"tag": "TEST-TAG-NO-REGEX",
"param": []
}
]
Loading