Skip to content

Commit e7f1c66

Browse files
authored
Merge pull request #243 from MerginMaps/send-logs
allow sending diagnostic logs from client
2 parents 9aa18d5 + 7d5cc2c commit e7f1c66

File tree

3 files changed

+105
-1
lines changed

3 files changed

+105
-1
lines changed

mergin/client.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from typing import List
2121

22-
from .common import ClientError, LoginError, WorkspaceRole, ProjectRole
22+
from .common import ClientError, LoginError, WorkspaceRole, ProjectRole, LOG_FILE_SIZE_TO_SEND, MERGIN_DEFAULT_LOGS_URL
2323
from .merginproject import MerginProject
2424
from .client_pull import (
2525
download_file_finalize,
@@ -1360,3 +1360,64 @@ def remove_project_collaborator(self, project_id: str, user_id: int):
13601360
Remove a user from project collaborators
13611361
"""
13621362
self.delete(f"v2/projects/{project_id}/collaborators/{user_id}")
1363+
1364+
def server_config(self) -> dict:
1365+
"""Get server configuration as dictionary."""
1366+
response = self.get("/config")
1367+
return json.load(response)
1368+
1369+
def send_logs(
1370+
self,
1371+
logfile: str,
1372+
global_log_file: typing.Optional[str] = None,
1373+
application: typing.Optional[str] = None,
1374+
meta: typing.Optional[str] = None,
1375+
):
1376+
"""Send logs to configured server or the default Mergin server."""
1377+
1378+
if application is None:
1379+
application = "mergin-client-{}".format(__version__)
1380+
1381+
params = {"app": application, "username": self.username()}
1382+
1383+
config = self.server_config()
1384+
diagnostic_logs_url = config.get("diagnostic_logs_url", None)
1385+
1386+
use_server_api = False
1387+
if is_version_acceptable(self.server_version(), "2025.4.1") and (
1388+
diagnostic_logs_url is None or diagnostic_logs_url == ""
1389+
):
1390+
url = "v2/diagnostic-logs" + "?" + urllib.parse.urlencode(params)
1391+
use_server_api = True
1392+
else:
1393+
if diagnostic_logs_url:
1394+
url = diagnostic_logs_url + "?" + urllib.parse.urlencode(params)
1395+
else:
1396+
# fallback to default logs URL
1397+
url = MERGIN_DEFAULT_LOGS_URL + "?" + urllib.parse.urlencode(params)
1398+
1399+
if meta is None:
1400+
meta = "Python API Client\nSystem: {} \nMergin Maps URL: {} \nMergin Maps user: {} \n--------------------------------\n\n".format(
1401+
platform.system(), self.url, self.username()
1402+
)
1403+
1404+
global_logs = b""
1405+
if global_log_file and os.path.exists(global_log_file):
1406+
with open(global_log_file, "rb") as f:
1407+
if os.path.getsize(global_log_file) > LOG_FILE_SIZE_TO_SEND:
1408+
f.seek(-LOG_FILE_SIZE_TO_SEND, os.SEEK_END)
1409+
global_logs = f.read() + b"\n--------------------------------\n\n"
1410+
1411+
with open(logfile, "rb") as f:
1412+
if os.path.getsize(logfile) > 512 * 1024:
1413+
f.seek(-512 * 1024, os.SEEK_END)
1414+
logs = f.read()
1415+
1416+
payload = meta.encode() + global_logs + logs
1417+
header = {"content-type": "text/plain"}
1418+
1419+
if use_server_api:
1420+
return self.post(url, data=payload, headers=header)
1421+
else:
1422+
request = urllib.request.Request(url, data=payload, headers=header)
1423+
return self._do_request(request)

mergin/common.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
# there is an upper limit for chunk size on server, ideally should be requested from there once implemented
77
UPLOAD_CHUNK_SIZE = 10 * 1024 * 1024
88

9+
# size of the log file part to send (if file is larger only this size from end will be sent)
10+
LOG_FILE_SIZE_TO_SEND = 100 * 1024
11+
12+
# default URL for submitting logs
13+
MERGIN_DEFAULT_LOGS_URL = "https://g4pfq226j0.execute-api.eu-west-1.amazonaws.com/mergin_client_log_submit"
914

1015
this_dir = os.path.dirname(os.path.realpath(__file__))
1116

mergin/test/test_client.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2833,3 +2833,41 @@ def test_access_management(mc: MerginClient, mc2: MerginClient):
28332833
with pytest.raises(ClientError) as exc_info:
28342834
mc.remove_workspace_member(workspace_id, new_user["id"])
28352835
assert exc_info.value.http_error == 404
2836+
2837+
2838+
def test_server_config(mc: MerginClient):
2839+
"""Test retrieving server configuration and some keys."""
2840+
config = mc.server_config()
2841+
2842+
assert config
2843+
assert isinstance(config, dict)
2844+
2845+
assert "server_type" in config
2846+
assert "version" in config
2847+
assert "server_configured" in config
2848+
2849+
2850+
def test_send_logs(mc: MerginClient, monkeypatch):
2851+
"""Test that logs can be send to the server."""
2852+
test_project = "test_logs_send"
2853+
project = API_USER + "/" + test_project
2854+
project_dir = os.path.join(TMP_DIR, test_project)
2855+
2856+
cleanup(mc, project, [project_dir])
2857+
# prepare local project
2858+
shutil.copytree(TEST_DATA_DIR, project_dir)
2859+
2860+
# create remote project
2861+
mc.create_project_and_push(project, directory=project_dir)
2862+
2863+
# patch mc.server_config() to return empty config which means that logs will be send to the server
2864+
# but it is not configured to accept them so client error with message will be raised
2865+
def server_config(self):
2866+
return {}
2867+
2868+
monkeypatch.setattr(mc, "server_config", server_config.__get__(mc))
2869+
2870+
logs_path = os.path.join(project_dir, ".mergin", "client-log.txt")
2871+
2872+
with pytest.raises(ClientError, match="The requested URL was not found on the server"):
2873+
mc.send_logs(logs_path)

0 commit comments

Comments
 (0)