Skip to content

Commit

Permalink
Add rsyslog plugin and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Ubuntu committed Jun 14, 2022
1 parent 091ced5 commit 999a175
Show file tree
Hide file tree
Showing 11 changed files with 396 additions and 0 deletions.
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;

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) {
switch(option_val) {
case 'r':
if(optarg != NULL) {
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;
}

SyslogParser* parser = new SyslogParser({}, json::array());
RsyslogPlugin* plugin = new RsyslogPlugin(parser, module_name, regex_path);

plugin->run();
return 0;
}
77 changes: 77 additions & 0 deletions src/sonic-eventd/rsyslog_plugin/rsyslog_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#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;
using namespace swss;
using json = nlohmann::json;

void RsyslogPlugin::onMessage(string msg) {
string tag = "";
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());
} 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());
}
}
}

[[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) {
SWSS_LOG_ERROR("Invalid regex, throws exception: %s\n", exception.what());
return false;
}
parser->expressions.push_back(expression);
}
regex_file.close();
return true;
}


RsyslogPlugin::RsyslogPlugin(SyslogParser* syslog_parser, string mod_name, string path) {
parser = syslog_parser;
module_name = mod_name;
regex_path = path;
onInit();
}
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);
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() {
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]);
vector<string> groups;
vector<string> params;
try {
event_tag = regex_list[i]["tag"];
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++) {
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;

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);
}

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

0 comments on commit 999a175

Please sign in to comment.