feat: improved logging system

fix: remove hard dependency from virtual workspace from virtual buffer

Former-commit-id: cb765c754d225d6a5beb6963facffd4211737bd2
This commit is contained in:
cschen 2024-08-09 19:20:58 +02:00
parent b8a591b7d1
commit d3388bd6d3
5 changed files with 97 additions and 72 deletions

View file

@ -2,33 +2,47 @@
import sublime
import sublime_plugin
import logging
import random
from Codemp.src.task_manager import tm
from Codemp.src.client import client, VirtualClient
from Codemp.src.logger import logger
from Codemp.src.utils import status_log
from Codemp.src.logger import inner_logger
from Codemp.src.utils import safe_listener_detach
from Codemp.src.utils import safe_listener_attach
from Codemp.src import globals as g
LOG_LEVEL = logging.DEBUG
handler = logging.StreamHandler()
handler.setFormatter(
logging.Formatter(
fmt="<{thread}/{threadName}>[codemp] [{name} :: {funcName}] {levelname}: {message}",
style="{",
)
)
package_logger = logging.getLogger(__package__)
package_logger.addHandler(handler)
package_logger.setLevel(LOG_LEVEL)
package_logger.propagate = False
logger = logging.getLogger(__name__)
TEXT_LISTENER = None
# Initialisation and Deinitialisation
##############################################################################
def plugin_loaded():
global TEXT_LISTENER
# instantiate and start a global asyncio event loop.
# pass in the exit_handler coroutine that will be called upon relasing the event loop.
tm.acquire(disconnect_client)
tm.dispatch(logger.log(), "codemp-logger")
tm.dispatch(inner_logger.listen(), "codemp-logger")
TEXT_LISTENER = CodempClientTextChangeListener()
status_log("plugin loaded")
logger.debug("plugin loaded")
async def disconnect_client():
@ -47,7 +61,8 @@ async def disconnect_client():
def plugin_unloaded():
# releasing the runtime, runs the disconnect callback defined when acquiring the event loop.
status_log("unloading")
logger.debug("unloading")
package_logger.removeHandler(handler)
tm.release(False)
@ -71,8 +86,8 @@ class EventListener(sublime_plugin.EventListener):
for wsid in s[g.CODEMP_WINDOW_WORKSPACES]:
ws = client[wsid]
if ws is None:
status_log(
"[WARN] a tag on the window was found but not a matching workspace."
logger.warning(
"a tag on the window was found but not a matching workspace."
)
continue
@ -137,7 +152,7 @@ class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
def on_text_changed(self, changes):
s = self.buffer.primary_view().settings()
if s.get(g.CODEMP_IGNORE_NEXT_TEXT_CHANGE, None):
status_log("Ignoring echoing back the change.")
logger.debug("Ignoring echoing back the change.")
s[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = False
return

View file

@ -1,14 +1,13 @@
import sublime
import os
import logging
from asyncio import CancelledError
from codemp import BufferController
from Codemp.src.workspace import VirtualWorkspace
from Codemp.src import globals as g
from Codemp.src.task_manager import tm
from Codemp.src.utils import status_log
logger = logging.getLogger(__name__)
# This class is used as an abstraction between the local buffers (sublime side) and the
@ -18,18 +17,19 @@ from Codemp.src.utils import status_log
class VirtualBuffer:
def __init__(
self,
workspace: VirtualWorkspace,
workspace_id: str,
workspace_rootdir: str,
remote_id: str,
buffctl: BufferController,
):
self.view = sublime.active_window().new_file()
self.codemp_id = remote_id
self.sublime_id = self.view.buffer_id()
self.workspace = workspace
self.workspace_id = workspace_id
self.workspace_rootdir = workspace_rootdir
self.buffctl = buffctl
self.tmpfile = os.path.join(workspace.rootdir, self.codemp_id)
self.tmpfile = os.path.join(workspace_rootdir, self.codemp_id)
self.view.set_name(self.codemp_id)
open(self.tmpfile, "a").close()
@ -46,7 +46,7 @@ class VirtualBuffer:
self.view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]")
s[g.CODEMP_BUFFER_TAG] = True
s[g.CODEMP_REMOTE_ID] = self.codemp_id
s[g.CODEMP_WORKSPACE_ID] = self.workspace.id
s[g.CODEMP_WORKSPACE_ID] = self.workspace_id
def cleanup(self):
os.remove(self.tmpfile)
@ -58,15 +58,15 @@ class VirtualBuffer:
self.view.erase_status(g.SUBLIME_STATUS_ID)
tm.stop(f"{g.BUFFCTL_TASK_PREFIX}-{self.codemp_id}")
status_log(f"cleaning up virtual buffer '{self.codemp_id}'")
logger.info(f"cleaning up virtual buffer '{self.codemp_id}'")
async def apply_bufferchange_task(self):
status_log(f"spinning up '{self.codemp_id}' buffer worker...")
logger.debug(f"spinning up '{self.codemp_id}' buffer worker...")
try:
while text_change := await self.buffctl.recv():
change_id = self.view.change_id()
if text_change.is_empty():
status_log("change is empty. skipping.")
logger.debug("change is empty. skipping.")
continue
# In case a change arrives to a background buffer, just apply it.
# We are not listening on it. Otherwise, interrupt the listening
@ -88,10 +88,10 @@ class VirtualBuffer:
)
except CancelledError:
status_log(f"'{self.codemp_id}' buffer worker stopped...")
logger.debug(f"'{self.codemp_id}' buffer worker stopped...")
raise
except Exception as e:
status_log(f"buffer worker '{self.codemp_id}' crashed:\n{e}")
logger.error(f"buffer worker '{self.codemp_id}' crashed:\n{e}")
raise
def send_buffer_change(self, changes):
@ -99,14 +99,14 @@ class VirtualBuffer:
# sequential indexing, assuming the changes are applied in the order they are received.
for change in changes:
region = sublime.Region(change.a.pt, change.b.pt)
status_log(
logger.debug(
"sending txt change: Reg({} {}) -> '{}'".format(
region.begin(), region.end(), change.str
)
)
self.buffctl.send(region.begin(), region.end(), change.str)
def send_cursor(self, vws: VirtualWorkspace):
def send_cursor(self, vws): # pyright: ignore # noqa: F821
# TODO: only the last placed cursor/selection.
# status_log(f"sending cursor position in workspace: {vbuff.workspace.id}")
region = self.view.sel()[0]

View file

@ -1,11 +1,13 @@
from __future__ import annotations
import sublime
import logging
from codemp import Client
from Codemp.src import globals as g
from Codemp.src.workspace import VirtualWorkspace
from Codemp.src.utils import status_log
logger = logging.getLogger(__name__)
class VirtualClient:
@ -18,47 +20,44 @@ class VirtualClient:
return self.workspaces.get(key)
def connect(self, host: str, user: str, password: str):
status_log(f"Connecting to {host} with user {user}")
logger.info(f"Connecting to {host} with user {user}")
try:
self.handle = Client(host, user, password)
except Exception as e:
logger.error(f"Could not connect: {e}")
sublime.error_message(
f"Could not connect:\n Make sure the server is up.\n\
or your credentials are correct\n\nerror: {e}"
"Could not connect:\n Make sure the server is up.\n\
or your credentials are correct."
)
return
id = self.handle.user_id()
status_log(f"Connected to '{host}' with user {user} and id: {id}")
logger.debug(f"Connected to '{host}' with user {user} and id: {id}")
async def join_workspace(
self,
workspace_id: str,
) -> VirtualWorkspace | None:
if self.handle is None:
status_log("Connect to a server first!", True)
return
status_log(f"Joining workspace: '{workspace_id}'")
logger.info(f"Joining workspace: '{workspace_id}'")
try:
workspace = await self.handle.join_workspace(workspace_id)
except Exception as e:
status_log(
f"Could not join workspace '{workspace_id}'.\n\nerror: {e}", True
)
logger.error(f"Could not join workspace '{workspace_id}'.\n\nerror: {e}")
sublime.error_message(f"Could not join workspace '{workspace_id}'")
return
vws = VirtualWorkspace(workspace)
self.workspaces[workspace_id] = vws
# self.make_active(vws)
return vws
def leave_workspace(self, id: str):
if self.handle is None:
status_log("Connect to a server first!", True)
return False
status_log(f"Leaving workspace: '{id}'")
logger.info(f"Leaving workspace: '{id}'")
if self.handle.leave_workspace(id):
self.workspaces[id].cleanup()
del self.workspaces[id]
@ -70,9 +69,7 @@ class VirtualClient:
ws = self.workspaces.get(tag_id)
if ws is None:
status_log(
"[WARN] a tag on the view was found but not a matching workspace."
)
logging.warning("a tag on the view was found but not a matching workspace.")
return
return ws

View file

@ -1,35 +1,44 @@
import logging
from asyncio import CancelledError
from codemp import PyLogger
from Codemp.src.utils import status_log
class CodempLogger:
def __init__(self, debug: bool = False):
self.handle = None
def __init__(self, log_level):
self.logger = logging.getLogger(__name__)
self.level = log_level
self.logger.setLevel(self.level)
self.internal_logger = None
self.started = False
try:
self.handle = PyLogger(debug)
# PyLogger spins a tracing_subscriber rust side with a
# .try_init() and errors out if already initialized.
# initialize only once
self.internal_logger = PyLogger(self.level == logging.DEBUG)
except Exception:
pass
async def log(self):
async def listen(self):
if self.started:
return
self.started = True
status_log("spinning up the logger...")
self.logger.debug("spinning up internal logger listener...")
assert self.internal_logger is not None
try:
while msg := await self.handle.listen():
print(msg)
while msg := await self.internal_logger.listen():
self.logger.log(self.level, msg)
except CancelledError:
status_log("stopping logger")
self.logger.debug("inner logger stopped.")
self.started = False
raise
except Exception as e:
status_log(f"logger crashed unexpectedly:\n{e}")
raise
self.logger.error(f"inner logger crashed unexpectedly: \n {e}")
raise e
def log(self, msg):
self.logger.log(self.level, msg)
DEBUG = False
logger = CodempLogger(DEBUG)
inner_logger = CodempLogger(logging.INFO)

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import sublime
import shutil
import tempfile
import logging
from asyncio import CancelledError
from codemp import Workspace
@ -10,9 +11,10 @@ from codemp import Workspace
from Codemp.src import globals as g
from Codemp.src.buffers import VirtualBuffer
from Codemp.src.task_manager import tm
from Codemp.src.utils import status_log
from Codemp.src.utils import rowcol_to_region
logger = logging.getLogger(__name__)
# A virtual workspace is a bridge class that aims to translate
# events that happen to the codemp workspaces into sublime actions
@ -51,7 +53,7 @@ class VirtualWorkspace:
)
d["folders"] = newf
self.sublime_window.set_project_data(d)
status_log(f"cleaning up virtual workspace '{self.id}'")
logger.info(f"cleaning up virtual workspace '{self.id}'")
shutil.rmtree(self.rootdir, ignore_errors=True)
self.curctl.stop()
@ -70,7 +72,7 @@ class VirtualWorkspace:
# initialise the virtual filesystem
tmpdir = tempfile.mkdtemp(prefix="codemp_")
status_log("setting up virtual fs for workspace in: {} ".format(tmpdir))
logging.debug("setting up virtual fs for workspace in: {} ".format(tmpdir))
self.rootdir = tmpdir
# and add a new "project folder"
@ -119,8 +121,8 @@ class VirtualWorkspace:
vbuff = self.active_Buffer.get(local_id)
if vbuff is None:
status_log(
"[WARN] a local-remote buffer id pair was found but \
logging.warning(
"a local-remote buffer id pair was found but \
not the matching virtual buffer."
)
return
@ -153,7 +155,7 @@ class VirtualWorkspace:
try:
await self.handle.create(id)
except Exception as e:
status_log(f"could not create buffer:\n\n {e}", True)
logging.error(f"could not create buffer:\n\n {e}", True)
return
else:
return
@ -162,10 +164,10 @@ class VirtualWorkspace:
try:
buff_ctl = await self.handle.attach(id)
except Exception as e:
status_log(f"error when attaching to buffer '{id}':\n\n {e}", True)
logging.error(f"error when attaching to buffer '{id}':\n\n {e}", True)
return
vbuff = Buffer.VirtualBuffer(self, id, buff_ctl)
vbuff = VirtualBuffer(self.id, self.rootdir, id, buff_ctl)
self.add_buffer(id, vbuff)
# TODO! if the view is already active calling focus_view() will not trigger the on_activate
@ -177,7 +179,7 @@ class VirtualWorkspace:
attached_Buffer = self.handle.buffer_by_name(id)
if attached_Buffer is None:
status_log(f"You are not attached to the buffer '{id}'", True)
logging.warning(f"You are not attached to the buffer '{id}'", True)
return
self.handle.detach(id)
@ -190,7 +192,7 @@ class VirtualWorkspace:
await self.handle.fetch_buffers()
existing_Buffer = self.handle.filetree()
if id not in existing_Buffer:
status_log(f"The buffer '{id}' does not exists.", True)
logging.info(f"The buffer '{id}' does not exists.", True)
return
# delete a buffer that exists but you are not attached to
attached_Buffer = self.handle.buffer_by_name(id)
@ -204,7 +206,9 @@ class VirtualWorkspace:
try:
await self.handle.delete(id)
except Exception as e:
status_log(f"error when deleting the buffer '{id}':\n\n {e}", True)
logging.error(
f"error when deleting the buffer '{id}':\n\n {e}", True
)
return
else:
return
@ -221,11 +225,11 @@ class VirtualWorkspace:
try:
await self.handle.delete(id)
except Exception as e:
status_log(f"error when deleting the buffer '{id}':\n\n {e}", True)
logging.error(f"error when deleting the buffer '{id}':\n\n {e}", True)
return
async def move_cursor_task(self):
status_log(f"spinning up cursor worker for workspace '{self.id}'...")
logger.debug(f"spinning up cursor worker for workspace '{self.id}'...")
try:
while cursor_event := await self.curctl.recv():
vbuff = self.get_by_remote(cursor_event.buffer)
@ -247,8 +251,8 @@ class VirtualWorkspace:
)
except CancelledError:
status_log(f"cursor worker for '{self.id}' stopped...")
logger.debug(f"cursor worker for '{self.id}' stopped...")
raise
except Exception as e:
status_log(f"cursor worker '{self.id}' crashed:\n{e}")
logger.error(f"cursor worker '{self.id}' crashed:\n{e}")
raise