-
Notifications
You must be signed in to change notification settings - Fork 36
[RTOP-76] runtime logs parser #13
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
Merged
Merged
Changes from all commits
Commits
Show all changes
56 commits
Select commit
Hold shift + click to select a range
11e9d9a
Add plugin driver system with config parsing
marconetsf fb3b5d0
Refactor plugin driver for Python Modbus support
marconetsf cd10661
sync plugin driver
marconetsf 158b390
Adjusting python.h include
marconetsf 77dbf44
fix init driver's args encapsulation
marconetsf b10e7b8
adjusting brackets position and function identation
marconetsf 75dfe2f
fix cmakelist
marconetsf e89ff10
adjust python_plugin_bridge.h identation
marconetsf ccef749
python start funct running within a thread
marconetsf ce90100
fixing pointer dereferencing
marconetsf 3063116
Fix buffer access in Python plugin driver
marconetsf ef9b1ae
deleting stop call
marconetsf 39a893d
Remove unused _runtime_args_capsule variable
marconetsf df1ac5d
Refactor Python plugin threading and lifecycle management
marconetsf 89e8a3e
Refactor plugin driver cleanup and GIL management
marconetsf 32fc0ec
Refactor plugin loop to run in a separate thread
marconetsf 993cfef
Refactor Modbus plugin for safer buffer access
marconetsf 763b105
Update Python plugin documentation and type safety
marconetsf d447cc1
moving examples and plugins to respective folder
marconetsf 85ac4cb
deleting unused plugins from config
marconetsf 79bb1b6
Expose plugin mutex helpers and use in PLC cycle
marconetsf c3c0ac9
Merge branch 'development' into task/RTOP-57-implement-driver-contracts
marconetsf e5cfc77
Changing pluggins paths to meet exec.sh start path
marconetsf 345fc60
Merge pull request #5 from Autonomy-Logic/task/RTOP-57-implement-driv…
thiagoralves 469fc7b
Rtop 58 plugin modbus slave (#6)
marconetsf 70c560e
Merge branch 'development' into RTOP-52-Standard-Network-Driver
marconetsf 9cf4e5f
fixing plugin's dedicated data retrieval
marconetsf 9bb279c
RTOP 74 adding plugin individual venv usage
marconetsf 2771514
Merge branch 'development' into RTOP-74-Define-venv-environment-for-p…
marconetsf 14301df
Merge branch 'development' into RTOP-52-Standard-Network-Driver
marconetsf 69d1056
Update scripts/manage_plugin_venvs.sh
marconetsf cefb954
Disabling initial plugin prints and avoiding code insertion
marconetsf b021601
Merge branch 'RTOP-74-Define-venv-environment-for-python-plugins' of …
marconetsf f8ab311
install now has support for apt yum and dfs and plc program is being …
marconetsf e086fea
adding proper build error and success logs
marconetsf 55dfcd2
[RTOP 74][WIP] implementing initialization scripts
marconetsf 7f98748
changing runtime venv from .venv to venvs/runtime/
marconetsf 43e43ac
Merge branch 'development' into RTOP-52-Standard-Network-Driver
marconetsf 950a475
Merge branch 'RTOP-52-Standard-Network-Driver' into RTOP-74-Define-ve…
thiagoralves 659f49b
Merge pull request #11 from Autonomy-Logic/RTOP-74-Define-venv-enviro…
thiagoralves a919d86
Add requirements.txt to the Modbus driver
thiagoralves c2acb8c
Add checks on install and start_openplc scripts
f8974ee
Quick fix on start_openplc.sh
aad7e53
Fix zip file check to allow generated bash script
lucasbutzke 865c429
[RTOP-72] Parse and log unix socket log messages
lucasbutzke 8791754
[RTOP-72] Additional Exceptions
lucasbutzke debb9e9
[RTOP-72] Logging format refactor
lucasbutzke 4d65c3d
[RTOP-76] Logging module
lucasbutzke c1f7aa7
Merge branch 'fix-reastapi-zip-file-check' into RTOP-76-Runtime-logs-…
lucasbutzke b0ea1fd
[RTOP-76] Logger parser
lucasbutzke 58b5f48
[RTOP-76] Logger in app
lucasbutzke 7287680
Merge branch 'development' into RTOP-76-Runtime-logs-parser
lucasbutzke 7dd4fc3
[RTOP-76] Runtime logging buffer test
lucasbutzke 6651c8c
[RTOP-76] Fix logging instantiation, parser and buffer
lucasbutzke b8bff1e
[RTOP-76] Runtime Logs parser, buffering and json response
lucasbutzke 4dd1e49
Merge remote-tracking branch 'origin/development' into RTOP-76-Runtim…
lucasbutzke File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import logging | ||
| from collections import deque | ||
| from typing import List, Optional | ||
| import json | ||
| import re | ||
| from datetime import datetime | ||
|
|
||
|
|
||
| class BufferHandler(logging.Handler): | ||
| """ | ||
| Custom logging handler that stores log records in memory (FIFO). | ||
| Logs are formatted using the attached formatter (JSON). | ||
| """ | ||
|
|
||
| def __init__(self, capacity: int = 1000): | ||
| super().__init__() | ||
| self.buffer = deque(maxlen=capacity) | ||
|
|
||
| def emit(self, record: logging.LogRecord) -> None: | ||
| try: | ||
| self.buffer.append(self.format(record)) | ||
| except Exception: | ||
| self.handleError(record) | ||
|
|
||
| def get_logs(self, count: Optional[int] = None) -> List[str]: | ||
| """Retrieve logs from buffer.""" | ||
| if count is None or count > len(self.buffer): | ||
| return list(self.buffer) | ||
| return list(self.buffer)[-count:] | ||
|
|
||
| def normalize_buffer_logs(self, buffer_records): | ||
| """ | ||
| Takes a list of log strings from buffer and returns a list of clean JSON dicts. | ||
| """ | ||
| result = [] | ||
| json_extract = re.compile(r'(\{.*\})') # match JSON inside log line | ||
|
|
||
| for record in buffer_records: | ||
| match = json_extract.search(record) | ||
| if not match: | ||
| continue | ||
|
|
||
| try: | ||
| raw_json = json.loads(match.group(1)) | ||
| # Convert unix timestamp → readable datetime | ||
| ts = int(raw_json.get("timestamp", 0)) | ||
| dt = datetime.utcfromtimestamp(ts).isoformat() + "Z" | ||
|
|
||
| entry = { | ||
| "timestamp": dt, | ||
| "level": raw_json.get("level", "INFO"), | ||
| "message": raw_json.get("message", "") | ||
| } | ||
| result.append(entry) | ||
| except (json.JSONDecodeError, ValueError): | ||
| continue | ||
|
|
||
| return result | ||
|
|
||
| def clear(self) -> None: | ||
| self.buffer.clear() | ||
|
|
||
| def __len__(self): | ||
| return len(self.buffer) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import logging | ||
| import time | ||
| import json | ||
|
|
||
| class JsonFormatter(logging.Formatter): | ||
| """Format log records as JSON strings.""" | ||
|
|
||
| def format(self, record: logging.LogRecord) -> str: | ||
| log_dict = { | ||
| "timestamp": str(int(record.created)), # epoch seconds | ||
| "level": record.levelname, | ||
| "message": record.getMessage() | ||
| } | ||
|
|
||
| # Include optional fields if present | ||
| if hasattr(record, "source"): | ||
| log_dict["source"] = record.source | ||
|
|
||
| return json.dumps(log_dict, ensure_ascii=False) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import logging | ||
| from .formatter import JsonFormatter | ||
| from .bufferhandler import BufferHandler | ||
|
|
||
|
|
||
| def get_logger(name: str = "logger", | ||
| level: int = logging.INFO, | ||
| use_buffer: bool = False): | ||
| """Return a logger instance with custom formatting.""" | ||
|
|
||
| collector_logger = logging.getLogger(name) | ||
| collector_logger.setLevel(logging.DEBUG) | ||
|
|
||
| handler = logging.StreamHandler() | ||
| handler.setFormatter(JsonFormatter()) | ||
| collector_logger.addHandler(handler) | ||
|
|
||
| buffer_handler = None | ||
|
|
||
| if use_buffer: | ||
| # Use buffer handler for log messages | ||
| buffer_handler = BufferHandler() | ||
| buffer_handler.setFormatter( | ||
| logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) | ||
| collector_logger.addHandler(buffer_handler) | ||
|
|
||
| if use_buffer: | ||
|
lucasbutzke marked this conversation as resolved.
|
||
| # Find buffer handler again if it already exists | ||
| if buffer_handler is None: | ||
| for h in collector_logger.handlers: | ||
| if isinstance(h, BufferHandler): | ||
| buffer_handler = h | ||
| break | ||
|
lucasbutzke marked this conversation as resolved.
|
||
| return collector_logger, buffer_handler | ||
| else: | ||
| return collector_logger, None | ||
|
|
||
| # return collector_logger | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # logger/parser.py | ||
| import logging | ||
| import re | ||
| import time | ||
| import json | ||
|
|
||
| LOG_PATTERN = re.compile(r'^\[(?P<level>\w+)\]\s*(?P<message>.*)$') | ||
|
|
||
| LEVEL_MAP = { | ||
| "DEBUG": logging.DEBUG, | ||
| "INFO": logging.INFO, | ||
| "WARNING": logging.WARNING, | ||
| "ERROR": logging.ERROR, | ||
| "CRITICAL": logging.CRITICAL, | ||
| } | ||
|
|
||
|
|
||
| class LogParser: | ||
| def __init__(self, collector_logger: logging.Logger): | ||
| self.collector_logger = collector_logger | ||
|
|
||
| def parse_and_log(self, line: str): | ||
| """Parse incoming log line and re-log it in normalized JSON format.""" | ||
| sline = line.strip() | ||
| if not sline: | ||
| return | ||
|
|
||
| timestamp = int(time.time()) | ||
| level_name = "INFO" | ||
| level = logging.INFO | ||
| message = sline | ||
|
|
||
| # Case 1: JSON log already | ||
| try: | ||
| parsed = json.loads(sline) | ||
| if isinstance(parsed, dict) and "message" in parsed: | ||
| # Preserve incoming JSON fields, but ensure timestamp is present | ||
| parsed.setdefault("timestamp", str(timestamp)) | ||
| level_name = parsed.get("level", "INFO") | ||
| level = LEVEL_MAP.get(level_name, logging.INFO) | ||
| log_entry = parsed | ||
| else: | ||
| raise ValueError("Not a valid log JSON dict") | ||
| except (json.JSONDecodeError, ValueError): | ||
| # Case 2: Regex log like "[INFO] Something" | ||
| match = LOG_PATTERN.match(sline) | ||
| if match: | ||
| level_name = match["level"] | ||
| level = LEVEL_MAP.get(level_name, logging.INFO) | ||
| message = match["message"] | ||
| else: | ||
| message = sline | ||
|
|
||
| log_entry = { | ||
| "timestamp": str(timestamp), | ||
| "level": level_name, | ||
| "message": message | ||
| } | ||
|
|
||
| # Create final JSON string | ||
| json_log = json.dumps(log_entry, ensure_ascii=False) | ||
|
|
||
| # Push into Python logging | ||
| record = self.collector_logger.makeRecord( | ||
| name="external", | ||
| level=level, | ||
| fn="", | ||
| lno=0, | ||
| msg=json_log, | ||
| args=(), | ||
| exc_info=None | ||
| ) | ||
| record.source = "external" | ||
| self.collector_logger.handle(record) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.