Parse logcat entries
This commit is contained in:
		
							parent
							
								
									6c339fa799
								
							
						
					
					
						commit
						b6ad9c5d90
					
				
							
								
								
									
										6
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										6
									
								
								Makefile
								
								
								
								
							|  | @ -44,14 +44,14 @@ LIBS = | |||
| ##---------------------------------------------------------------------
 | ||||
| 
 | ||||
| ifeq ($(UNAME_S), Linux) #LINUX
 | ||||
| 	LIBS += $(LINUX_GL_LIBS) -ldl `sdl2-config --libs` -lfontconfig | ||||
| 	LIBS += $(LINUX_GL_LIBS) -ldl `sdl2-config --libs` -lfontconfig -lpcre2-8 -lpcre2-posix | ||||
| 
 | ||||
| 	CXXFLAGS += `sdl2-config --cflags` -DUSE_FONTCONFIG | ||||
| 	CFLAGS = $(CXXFLAGS) | ||||
| endif | ||||
| 
 | ||||
| ifeq ($(UNAME_S), Darwin) #APPLE
 | ||||
| 	LIBS += -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo `sdl2-config --libs` | ||||
| 	LIBS += -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo `sdl2-config --libs` -lpcre2-8 -lpcre2-posix | ||||
| 	LIBS += -L/usr/local/lib -L/opt/local/lib | ||||
| 
 | ||||
| 	CXXFLAGS += `sdl2-config --cflags` | ||||
|  | @ -60,7 +60,7 @@ ifeq ($(UNAME_S), Darwin) #APPLE | |||
| endif | ||||
| 
 | ||||
| ifeq ($(OS), Windows_NT) | ||||
| 	LIBS += -lgdi32 -lopengl32 -limm32 `pkg-config --static --libs sdl2` | ||||
| 	LIBS += -lgdi32 -lopengl32 -limm32 `pkg-config --static --libs sdl2` -lpcre2-8 -lpcre2-posix | ||||
| 
 | ||||
| 	CXXFLAGS += `pkg-config --cflags sdl2` | ||||
| 	CFLAGS = $(CXXFLAGS) | ||||
|  |  | |||
|  | @ -23,6 +23,19 @@ static inline void check_for_logcat_items(LogcatThread& logcat_thread) { | |||
|                 log_entries += '\n'; | ||||
|             } | ||||
|             log_entries += std::move(std::get<std::string>(*logcat_thread_item)); | ||||
|         } else if (std::holds_alternative<LogcatEntry>(*logcat_thread_item)) { | ||||
|             LogcatEntry logcat_entry = std::move(std::get<LogcatEntry>(*logcat_thread_item)); | ||||
|             log("Received new logcat entry"); | ||||
|             log(std::string("    - Buffer: ") + buffer_to(logcat_entry.buffer)); | ||||
|             log(std::string("    - Time: ") + std::to_string(logcat_entry.time)); | ||||
|             if (logcat_entry.user) { | ||||
|                 log(std::string("    - User: ") + *logcat_entry.user); | ||||
|             } | ||||
|             log(std::string("    - PID: ") + std::to_string(logcat_entry.pid)); | ||||
|             log(std::string("    - TID: ") + std::to_string(logcat_entry.tid)); | ||||
|             log(std::string("    - Priority: ") + priority_to(logcat_entry.priority)); | ||||
|             log(std::string("    - Tag: ") + logcat_entry.tag); | ||||
|             log(std::string("    - Message: ") + logcat_entry.message); | ||||
|         } else { | ||||
|             throw std::runtime_error("Cannot handle all possible logcat thread item variants"); | ||||
|         } | ||||
|  |  | |||
|  | @ -0,0 +1,101 @@ | |||
| #include <climits> | ||||
| #include <optional> | ||||
| #include <stdexcept> | ||||
| 
 | ||||
| #include "logcat_entry.h" | ||||
| #include "pcre2_wrapper.h" | ||||
| 
 | ||||
| const Pcre2Regex LogcatEntryRegex("^\\s*(\\d+)(?:\\.\\d+)?(?:\\s+([\\w\\d._-]+))?\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s+(.+?)\\s*:\\s(.*)$"); | ||||
| 
 | ||||
| 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; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const char* priority_to(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"; | ||||
|         default: return "Unknown"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const char* buffer_to(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"; | ||||
|         default: return "Unknown"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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_unsigned_long_long(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; | ||||
| } | ||||
| 
 | ||||
| std::optional<LogcatEntry> try_parse_logcat_entry(char* buf, size_t length, Buffer buffer) { | ||||
|     regmatch_t matches[8]; | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     std::optional<std::string> user; | ||||
|     if (matches[2].rm_so > -1 && matches[2].rm_eo > -1) { | ||||
|         user = std::string(&buf[matches[2].rm_so], static_cast<size_t>(matches[2].rm_eo - matches[2].rm_so)); | ||||
|     } | ||||
|     // if pcre2 gives us negative offsets then i'll die
 | ||||
|     LogcatEntry logcat_entry = { | ||||
|         .buffer = buffer, | ||||
|         .time = to_long(&buf[matches[1].rm_so], &buf[matches[1].rm_eo]), | ||||
|         .user = std::move(user), | ||||
|         .pid = to_unsigned_long_long(&buf[matches[3].rm_so], &buf[matches[3].rm_eo]), | ||||
|         .tid = to_unsigned_long_long(&buf[matches[4].rm_so], &buf[matches[4].rm_eo]), | ||||
|         .priority = priority_from(buf[matches[5].rm_so]), | ||||
|         .tag = std::string(&buf[matches[6].rm_so], static_cast<size_t>(matches[6].rm_eo - matches[6].rm_so)), | ||||
|         .message = std::string(&buf[matches[7].rm_so], static_cast<size_t>(matches[7].rm_eo - matches[7].rm_so)), | ||||
|     }; | ||||
|     return std::move(logcat_entry); | ||||
| } | ||||
|  | @ -0,0 +1,39 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <string> | ||||
| #include <optional> | ||||
| 
 | ||||
| enum class Buffer { | ||||
|     Unknown, | ||||
|     Main, | ||||
|     System, | ||||
|     Radio, | ||||
|     Events, | ||||
|     Crash, | ||||
| }; | ||||
| 
 | ||||
| enum class Priority { | ||||
|     Unknown, | ||||
|     Verbose, | ||||
|     Debug, | ||||
|     Info, | ||||
|     Warn, | ||||
|     Error, | ||||
|     Fatal, | ||||
| }; | ||||
| 
 | ||||
| struct LogcatEntry { | ||||
|     Buffer buffer; | ||||
|     time_t time; | ||||
|     std::optional<std::string> user; | ||||
|     size_t pid; | ||||
|     size_t tid; | ||||
|     Priority priority; | ||||
|     std::string tag; | ||||
|     std::string message; | ||||
| }; | ||||
| 
 | ||||
| Priority priority_from(char c); | ||||
| const char* priority_to(Priority priority); | ||||
| const char* buffer_to(Buffer buffer); | ||||
| std::optional<LogcatEntry> try_parse_logcat_entry(char* buf, size_t length, Buffer buffer); | ||||
|  | @ -7,6 +7,7 @@ | |||
| #include "log.h" | ||||
| #include "misc.h" | ||||
| #include "logcat_thread.h" | ||||
| #include "logcat_entry.h" | ||||
| 
 | ||||
| #define EPOLL_MAX_EVENTS 10 | ||||
| 
 | ||||
|  | @ -151,16 +152,29 @@ void LogcatThread::join() { | |||
|     this->_thread.join(); | ||||
| } | ||||
| 
 | ||||
| void LogcatThread::_handle_line(char* buf, size_t used, bool is_stdout) { | ||||
| void LogcatThread::_handle_line(char* buf, size_t length, bool is_stdout) { | ||||
|     if (!is_stdout) { | ||||
|         std::string log_entry = format_log(std::string("logcat stderr: ") + std::string(buf, used)); | ||||
|         std::string log_entry = format_log(std::string("Received from logcat stderr: ") + std::string(buf, length)); | ||||
|         printf("%s\n", log_entry.c_str()); | ||||
|         this->atomic_ring_buffer.put_and_increment_write(std::move(log_entry)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // TODO actually parse
 | ||||
|     std::string log_entry = format_log(std::string("logcat stdout: ") + std::string(buf, used)); | ||||
|     std::optional<LogcatEntry> logcat_entry; | ||||
|     try { | ||||
|         logcat_entry = try_parse_logcat_entry(buf, length, Buffer::Unknown); | ||||
|     } catch (const std::exception& e) { | ||||
|         std::string log_entry = format_log(std::string("Failed to parse logcat entry: ") + e.what()); | ||||
|         printf("%s\n", log_entry.c_str()); | ||||
|         this->atomic_ring_buffer.put_and_increment_write(std::move(log_entry)); | ||||
|     } | ||||
|     if (logcat_entry) { | ||||
|         this->atomic_ring_buffer.put_and_increment_write(std::move(*logcat_entry)); | ||||
|         return; | ||||
|     } | ||||
|     // TODO handle buffers
 | ||||
| 
 | ||||
|     std::string log_entry = format_log(std::string("Cannot parse logcat stdout: ") + std::string(buf, length)); | ||||
|     printf("%s\n", log_entry.c_str()); | ||||
|     this->atomic_ring_buffer.put_and_increment_write(std::move(log_entry)); | ||||
| } | ||||
|  |  | |||
|  | @ -3,8 +3,9 @@ | |||
| #include <thread> | ||||
| #include <variant> | ||||
| #include "arb.h" | ||||
| #include "logcat_entry.h" | ||||
| 
 | ||||
| typedef std::variant<std::string> LogcatThreadItem; | ||||
| typedef std::variant<std::string, LogcatEntry> LogcatThreadItem; | ||||
| #define NEWLINE_BUF_SIZE 512 * 1024 | ||||
| 
 | ||||
| class LogcatThread { | ||||
|  | @ -25,7 +26,7 @@ public: | |||
| #endif | ||||
| 
 | ||||
| private: | ||||
|     void _handle_line(char* buf, size_t used, bool is_stdout); | ||||
|     void _handle_line(char* buf, size_t length, bool is_stdout); | ||||
|     void _run(std::stop_token stoken); | ||||
| 
 | ||||
|     int _epoll_fd = -1; | ||||
|  |  | |||
|  | @ -0,0 +1,31 @@ | |||
| #include "pcre2_wrapper.h" | ||||
| 
 | ||||
| Pcre2Exception::Pcre2Exception(int error_code, const regex_t* regex) { | ||||
|     pcre2_regerror(error_code, regex, this->_error_message, 1024); | ||||
| } | ||||
| 
 | ||||
| const char* Pcre2Exception::what() const noexcept { | ||||
|     return this->_error_message; | ||||
| } | ||||
| 
 | ||||
| Pcre2Regex::Pcre2Regex(const char* pattern, int cflags) { | ||||
|     int res = pcre2_regcomp(&this->_regex, pattern, cflags); | ||||
|     if (res) { | ||||
|         throw Pcre2Exception(res); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Pcre2Regex::~Pcre2Regex() { | ||||
|     pcre2_regfree(&this->_regex); | ||||
| } | ||||
| 
 | ||||
| bool Pcre2Regex::match(const char* str, size_t nmatch, regmatch_t pmatch[], int eflags) const { | ||||
|     int res = pcre2_regexec(&this->_regex, str, nmatch, pmatch, eflags); | ||||
|     if (res == REG_NOMATCH) { | ||||
|         return false; | ||||
|     } | ||||
|     if (res) { | ||||
|         throw Pcre2Exception(res, &this->_regex); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | @ -0,0 +1,27 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <exception> | ||||
| #include <pcre2posix.h> | ||||
| 
 | ||||
| class Pcre2Exception : public std::exception { | ||||
| public: | ||||
|     Pcre2Exception(int error_code, const regex_t* regex = nullptr); | ||||
|     const char* what() const noexcept; | ||||
| 
 | ||||
| private: | ||||
|     char _error_message[1024]; | ||||
| }; | ||||
| 
 | ||||
| class Pcre2Regex { | ||||
| public: | ||||
|     // https://stackoverflow.com/a/2173764
 | ||||
|     Pcre2Regex(const Pcre2Regex&) = delete; | ||||
|     Pcre2Regex& operator=(const Pcre2Regex&) = delete; | ||||
| 
 | ||||
|     Pcre2Regex(const char* pattern, int cflags = 0); | ||||
|     ~Pcre2Regex(); | ||||
|     bool match(const char* str, size_t nmatch, regmatch_t pmatch[], int eflags = 0) const; | ||||
| 
 | ||||
| private: | ||||
|     regex_t _regex; | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue