295 lines
11 KiB
C++
295 lines
11 KiB
C++
#include <climits>
|
|
#include <cstring>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
|
|
#include "config.h"
|
|
#include "logcat_entry.h"
|
|
#include "pcre2_wrapper.h"
|
|
|
|
static const Pcre2Regex LogcatEntryRegex(
|
|
"^ *(?:(\\d+)|(\\d{4,})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}))(?:\\.\\d+)?" // time and date
|
|
"(?: +([\\w\\d._-]+))?" // optional user
|
|
" +(\\d+)" // pid
|
|
" +(\\d+)" // tid
|
|
" +([A-Z])" // priority
|
|
" +(.+?) *: (.*)$" // tag and message
|
|
);
|
|
static const Pcre2Regex BufferRegex("^--------- (?:beginning of|switch to) (\\w+)$");
|
|
|
|
static std::string leftpad(const std::string& str, size_t characters);
|
|
static inline char to_char(Priority priority);
|
|
static inline std::string rightpad(const std::string& str, size_t characters);
|
|
|
|
static inline Priority priority_from(char c);
|
|
static inline int to_int(const char* str, const char* expected_end);
|
|
static inline long to_long(const char* str, const char* expected_end);
|
|
static unsigned long long to_ull(const char* str, const char* expected_end);
|
|
|
|
const char* to_string(Priority priority) {
|
|
switch (priority) {
|
|
case Priority::Verbose: return "Verbose";
|
|
case Priority::Debug: return "Debug";
|
|
case Priority::Info: return "Info";
|
|
case Priority::Warn: return "Warning";
|
|
case Priority::Error: return "Error";
|
|
case Priority::Fatal: return "Fatal";
|
|
case Priority::Unknown: return "Unknown";
|
|
}
|
|
}
|
|
|
|
const char* to_string(Buffer buffer) {
|
|
switch (buffer) {
|
|
case Buffer::Main: return "Main";
|
|
case Buffer::System: return "System";
|
|
case Buffer::Radio: return "Radio";
|
|
case Buffer::Events: return "Events";
|
|
case Buffer::Crash: return "Crash";
|
|
case Buffer::Unknown: return "Unknown";
|
|
}
|
|
}
|
|
|
|
const char* to_string_lower(Buffer buffer) {
|
|
switch (buffer) {
|
|
case Buffer::Main: return "main";
|
|
case Buffer::System: return "system";
|
|
case Buffer::Radio: return "radio";
|
|
case Buffer::Events: return "events";
|
|
case Buffer::Crash: return "crash";
|
|
case Buffer::Unknown: return "unknown";
|
|
}
|
|
}
|
|
|
|
std::string to_string(const LogcatEntry& logcat_entry) {
|
|
struct tm tm = logcat_entry.time;
|
|
time_t time = mktime(&tm);
|
|
gmtime_r(&time, &tm);
|
|
|
|
char logcat_style_time_as_str[32] = {0};
|
|
strftime(logcat_style_time_as_str, 31 * sizeof(char), "%Y-%m-%d %H:%M:%S", &tm);
|
|
|
|
return std::string(logcat_style_time_as_str)
|
|
+ ' ' + leftpad(logcat_entry.user.value_or(" "), 5)
|
|
+ ' ' + leftpad(std::to_string(logcat_entry.pid), 5)
|
|
+ ' ' + leftpad(std::to_string(logcat_entry.tid), 5)
|
|
+ ' ' + to_char(logcat_entry.priority)
|
|
+ ' ' + rightpad(logcat_entry.tag, 5)
|
|
+ ": " + logcat_entry.message;
|
|
}
|
|
|
|
|
|
std::optional<LogcatEntry> try_parse_logcat_entry(char* buf, size_t length, Buffer buffer) {
|
|
regmatch_t matches[14];
|
|
matches[0].rm_so = 0;
|
|
matches[0].rm_eo = static_cast<regoff_t>(length);
|
|
if (static_cast<size_t>(matches[0].rm_eo) != length) {
|
|
throw std::range_error("Logcat entry line too long for int");
|
|
}
|
|
|
|
if (!LogcatEntryRegex.match(buf, sizeof(matches) / sizeof(regmatch_t), matches, REG_STARTEND)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto group_exists = [](const regmatch_t& match) { return match.rm_so > -1 && match.rm_eo > -1; };
|
|
auto group_int = [&](const regmatch_t& match) { return to_int(&buf[match.rm_so], &buf[match.rm_eo]); };
|
|
auto group_long = [&](const regmatch_t& match) { return to_long(&buf[match.rm_so], &buf[match.rm_eo]); };
|
|
auto group_ull = [&](const regmatch_t& match) { return to_ull(&buf[match.rm_so], &buf[match.rm_eo]); };
|
|
auto group_string = [&](const regmatch_t& match) { return std::string(&buf[match.rm_so], static_cast<size_t>(match.rm_eo - match.rm_so)); };
|
|
auto group_priority = [&](const regmatch_t& match) { return priority_from(buf[match.rm_so]); };
|
|
|
|
// if pcre2 gives us negative offsets then i'll die
|
|
LogcatEntry logcat_entry = {
|
|
.buffer = buffer,
|
|
// time to be set later
|
|
.user = group_exists(matches[8]) ? std::optional(group_string(matches[8])) : std::nullopt,
|
|
.pid = group_ull(matches[9]),
|
|
.tid = group_ull(matches[10]),
|
|
.priority = group_priority(matches[11]),
|
|
.tag = group_string(matches[12]),
|
|
.message = group_string(matches[13]),
|
|
};
|
|
|
|
if (group_exists(matches[1])) {
|
|
time_t time = group_long(matches[1]);
|
|
localtime_r(&time, &logcat_entry.time);
|
|
} else {
|
|
struct tm tm = {
|
|
.tm_sec = group_int(matches[7]),
|
|
.tm_min = group_int(matches[6]),
|
|
.tm_hour = group_int(matches[5]),
|
|
.tm_mday = group_int(matches[4]),
|
|
.tm_mon = group_int(matches[3]),
|
|
.tm_year = group_int(matches[2]) - 1900,
|
|
.tm_isdst = -1,
|
|
.tm_gmtoff = 0,
|
|
};
|
|
time_t time = mktime(&tm) + tm.tm_gmtoff; // for some reason you have to add the local time offset?
|
|
localtime_r(&time, &logcat_entry.time);
|
|
}
|
|
|
|
return std::move(logcat_entry);
|
|
}
|
|
|
|
std::optional<Buffer> try_parse_buffer(char* buf, size_t length) {
|
|
regmatch_t matches[2];
|
|
matches[0].rm_so = 0;
|
|
matches[0].rm_eo = static_cast<regoff_t>(length);
|
|
if (static_cast<size_t>(matches[0].rm_eo) != length) {
|
|
throw std::range_error("Buffer line too long for int");
|
|
}
|
|
|
|
if (!BufferRegex.match(buf, sizeof(matches) / sizeof(regmatch_t), matches, REG_STARTEND)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const char* buffer_str = &buf[matches[1].rm_so];
|
|
if (!strncmp(buffer_str, "main", 4)) return Buffer::Main;
|
|
if (!strncmp(buffer_str, "system", 6)) return Buffer::System;
|
|
if (!strncmp(buffer_str, "radio", 5)) return Buffer::Radio;
|
|
if (!strncmp(buffer_str, "events", 6)) return Buffer::Events;
|
|
if (!strncmp(buffer_str, "crash", 5)) return Buffer::Crash;
|
|
return Buffer::Unknown;
|
|
}
|
|
|
|
|
|
void LogcatEntries::push_back(LogcatEntry entry) {
|
|
this->_logcat_entries.push_back(std::move(entry));
|
|
if (matches(this->_logcat_entries.back(), this->_config.filters, this->_config.exclusions)) {
|
|
this->_filtered_offsets.push_back(this->_logcat_entries.size() - 1);
|
|
}
|
|
}
|
|
|
|
void LogcatEntries::refresh() {
|
|
this->_filtered_offsets.clear();
|
|
for (size_t i = 0; i < this->_logcat_entries.size(); i++) {
|
|
if (matches(this->_logcat_entries[i], this->_config.filters, this->_config.exclusions)) {
|
|
this->_filtered_offsets.push_back(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void from_json(const nlohmann::json& j, Buffer& buffer) {
|
|
const std::string& str = j.get_ref<const nlohmann::json::string_t&>();
|
|
|
|
if (str == "UNKNOWN") buffer = Buffer::Unknown;
|
|
else if (str == "MAIN") buffer = Buffer::Main;
|
|
else if (str == "SYSTEM") buffer = Buffer::System;
|
|
else if (str == "RADIO") buffer = Buffer::Radio;
|
|
else if (str == "EVENTS") buffer = Buffer::Events;
|
|
else if (str == "CRASH") buffer = Buffer::Crash;
|
|
else throw std::invalid_argument(std::string("Unknown buffer value: ") + str);
|
|
}
|
|
|
|
void to_json(nlohmann::json& j, const Buffer& buffer) {
|
|
switch (buffer) {
|
|
case Buffer::Unknown: j = "UNKNOWN"; break;
|
|
case Buffer::Main: j = "MAIN"; break;
|
|
case Buffer::System: j = "SYSTEM"; break;
|
|
case Buffer::Radio: j = "RADIO"; break;
|
|
case Buffer::Events: j = "EVENTS"; break;
|
|
case Buffer::Crash: j = "CRASH"; break;
|
|
}
|
|
}
|
|
|
|
void from_json(const nlohmann::json& j, Priority& priority) {
|
|
const std::string& str = j.get_ref<const nlohmann::json::string_t&>();
|
|
|
|
if (str == "UNKNOWN") priority = Priority::Unknown;
|
|
else if (str == "VERBOSE") priority = Priority::Verbose;
|
|
else if (str == "DEBUG") priority = Priority::Debug;
|
|
else if (str == "INFO") priority = Priority::Info;
|
|
else if (str == "WARN") priority = Priority::Warn;
|
|
else if (str == "ERROR") priority = Priority::Error;
|
|
else if (str == "FATAL") priority = Priority::Fatal;
|
|
else throw std::invalid_argument(std::string("Unknown priority value: ") + str);
|
|
}
|
|
|
|
void to_json(nlohmann::json& j, const Priority& priority) {
|
|
switch (priority) {
|
|
case Priority::Unknown: j = "UNKNOWN"; break;
|
|
case Priority::Verbose: j = "VERBOSE"; break;
|
|
case Priority::Debug: j = "DEBUG"; break;
|
|
case Priority::Info: j = "INFO"; break;
|
|
case Priority::Warn: j = "WARN"; break;
|
|
case Priority::Error: j = "ERROR"; break;
|
|
case Priority::Fatal: j = "FATAL"; break;
|
|
}
|
|
}
|
|
|
|
|
|
static std::string leftpad(const std::string& str, size_t characters) {
|
|
return str.size() >= characters
|
|
? str
|
|
: std::string(characters - str.size(), ' ') + str;
|
|
}
|
|
|
|
static inline char to_char(Priority priority) {
|
|
switch (priority) {
|
|
case Priority::Verbose: return 'V';
|
|
case Priority::Debug: return 'D';
|
|
case Priority::Info: return 'I';
|
|
case Priority::Warn: return 'W';
|
|
case Priority::Error: return 'E';
|
|
case Priority::Fatal: return 'F';
|
|
case Priority::Unknown: return 'U';
|
|
}
|
|
}
|
|
|
|
static inline std::string rightpad(const std::string& str, size_t characters) {
|
|
return str.size() >= characters
|
|
? str
|
|
: str + std::string(characters - str.size(), ' ');
|
|
}
|
|
|
|
|
|
static inline Priority priority_from(char c) {
|
|
switch (c) {
|
|
case 'V': return Priority::Verbose;
|
|
case 'D': return Priority::Debug;
|
|
case 'I': return Priority::Info;
|
|
case 'W': return Priority::Warn;
|
|
case 'E': return Priority::Error;
|
|
case 'F': return Priority::Fatal;
|
|
default: return Priority::Unknown;
|
|
}
|
|
}
|
|
|
|
static inline int to_int(const char* str, const char* expected_end) {
|
|
long res = to_long(str, expected_end);
|
|
if (res > INT_MAX) {
|
|
throw std::overflow_error(std::string(str) + " is too big");
|
|
} else if (res < INT_MIN) {
|
|
throw std::underflow_error(std::string(str) + " is too small");
|
|
}
|
|
return static_cast<int>(res);
|
|
}
|
|
|
|
static inline long to_long(const char* str, const char* expected_end) {
|
|
char* endptr;
|
|
|
|
errno = 0;
|
|
long res = strtol(str, &endptr, 10);
|
|
if (endptr != expected_end) {
|
|
throw std::invalid_argument(std::string(str) + " has trailing text");
|
|
} else if (res == LONG_MAX && errno == ERANGE) {
|
|
throw std::overflow_error(std::string(str) + " is too big");
|
|
} else if (res == LONG_MIN && errno == ERANGE) {
|
|
throw std::underflow_error(std::string(str) + " is too small");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static unsigned long long to_ull(const char* str, const char* expected_end) {
|
|
char* endptr;
|
|
|
|
errno = 0;
|
|
unsigned long long res = strtoull(str, &endptr, 10);
|
|
if (endptr != expected_end) {
|
|
throw std::invalid_argument(std::string(str) + " has trailing text");
|
|
} else if (res == ULLONG_MAX && errno == ERANGE) {
|
|
throw std::overflow_error(std::string(str) + " is too big");
|
|
}
|
|
return res;
|
|
}
|