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
import sublime_plugin import sublime_plugin
import logging
import random import random
from Codemp.src.task_manager import tm from Codemp.src.task_manager import tm
from Codemp.src.client import client, VirtualClient from Codemp.src.client import client, VirtualClient
from Codemp.src.logger import logger from Codemp.src.logger import inner_logger
from Codemp.src.utils import status_log
from Codemp.src.utils import safe_listener_detach from Codemp.src.utils import safe_listener_detach
from Codemp.src.utils import safe_listener_attach from Codemp.src.utils import safe_listener_attach
from Codemp.src import globals as g 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 TEXT_LISTENER = None
# Initialisation and Deinitialisation # Initialisation and Deinitialisation
############################################################################## ##############################################################################
def plugin_loaded(): def plugin_loaded():
global TEXT_LISTENER global TEXT_LISTENER
# instantiate and start a global asyncio event loop. # instantiate and start a global asyncio event loop.
# pass in the exit_handler coroutine that will be called upon relasing the event loop. # pass in the exit_handler coroutine that will be called upon relasing the event loop.
tm.acquire(disconnect_client) tm.acquire(disconnect_client)
tm.dispatch(logger.log(), "codemp-logger") tm.dispatch(inner_logger.listen(), "codemp-logger")
TEXT_LISTENER = CodempClientTextChangeListener() TEXT_LISTENER = CodempClientTextChangeListener()
status_log("plugin loaded") logger.debug("plugin loaded")
async def disconnect_client(): async def disconnect_client():
@ -47,7 +61,8 @@ async def disconnect_client():
def plugin_unloaded(): def plugin_unloaded():
# releasing the runtime, runs the disconnect callback defined when acquiring the event loop. # 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) tm.release(False)
@ -71,8 +86,8 @@ class EventListener(sublime_plugin.EventListener):
for wsid in s[g.CODEMP_WINDOW_WORKSPACES]: for wsid in s[g.CODEMP_WINDOW_WORKSPACES]:
ws = client[wsid] ws = client[wsid]
if ws is None: if ws is None:
status_log( logger.warning(
"[WARN] a tag on the window was found but not a matching workspace." "a tag on the window was found but not a matching workspace."
) )
continue continue
@ -137,7 +152,7 @@ class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
def on_text_changed(self, changes): def on_text_changed(self, changes):
s = self.buffer.primary_view().settings() s = self.buffer.primary_view().settings()
if s.get(g.CODEMP_IGNORE_NEXT_TEXT_CHANGE, None): 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 s[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = False
return return

View file

@ -1,14 +1,13 @@
import sublime import sublime
import os import os
import logging
from asyncio import CancelledError from asyncio import CancelledError
from codemp import BufferController from codemp import BufferController
from Codemp.src.workspace import VirtualWorkspace
from Codemp.src import globals as g from Codemp.src import globals as g
from Codemp.src.task_manager import tm 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 # 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: class VirtualBuffer:
def __init__( def __init__(
self, self,
workspace: VirtualWorkspace, workspace_id: str,
workspace_rootdir: str,
remote_id: str, remote_id: str,
buffctl: BufferController, buffctl: BufferController,
): ):
self.view = sublime.active_window().new_file() self.view = sublime.active_window().new_file()
self.codemp_id = remote_id self.codemp_id = remote_id
self.sublime_id = self.view.buffer_id() self.sublime_id = self.view.buffer_id()
self.workspace_id = workspace_id
self.workspace = workspace self.workspace_rootdir = workspace_rootdir
self.buffctl = buffctl 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) self.view.set_name(self.codemp_id)
open(self.tmpfile, "a").close() open(self.tmpfile, "a").close()
@ -46,7 +46,7 @@ class VirtualBuffer:
self.view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]") self.view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]")
s[g.CODEMP_BUFFER_TAG] = True s[g.CODEMP_BUFFER_TAG] = True
s[g.CODEMP_REMOTE_ID] = self.codemp_id 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): def cleanup(self):
os.remove(self.tmpfile) os.remove(self.tmpfile)
@ -58,15 +58,15 @@ class VirtualBuffer:
self.view.erase_status(g.SUBLIME_STATUS_ID) self.view.erase_status(g.SUBLIME_STATUS_ID)
tm.stop(f"{g.BUFFCTL_TASK_PREFIX}-{self.codemp_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): 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: try:
while text_change := await self.buffctl.recv(): while text_change := await self.buffctl.recv():
change_id = self.view.change_id() change_id = self.view.change_id()
if text_change.is_empty(): if text_change.is_empty():
status_log("change is empty. skipping.") logger.debug("change is empty. skipping.")
continue continue
# In case a change arrives to a background buffer, just apply it. # In case a change arrives to a background buffer, just apply it.
# We are not listening on it. Otherwise, interrupt the listening # We are not listening on it. Otherwise, interrupt the listening
@ -88,10 +88,10 @@ class VirtualBuffer:
) )
except CancelledError: except CancelledError:
status_log(f"'{self.codemp_id}' buffer worker stopped...") logger.debug(f"'{self.codemp_id}' buffer worker stopped...")
raise raise
except Exception as e: 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 raise
def send_buffer_change(self, changes): 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. # sequential indexing, assuming the changes are applied in the order they are received.
for change in changes: for change in changes:
region = sublime.Region(change.a.pt, change.b.pt) region = sublime.Region(change.a.pt, change.b.pt)
status_log( logger.debug(
"sending txt change: Reg({} {}) -> '{}'".format( "sending txt change: Reg({} {}) -> '{}'".format(
region.begin(), region.end(), change.str region.begin(), region.end(), change.str
) )
) )
self.buffctl.send(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. # TODO: only the last placed cursor/selection.
# status_log(f"sending cursor position in workspace: {vbuff.workspace.id}") # status_log(f"sending cursor position in workspace: {vbuff.workspace.id}")
region = self.view.sel()[0] region = self.view.sel()[0]

View file

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

View file

@ -1,35 +1,44 @@
import logging
from asyncio import CancelledError from asyncio import CancelledError
from codemp import PyLogger from codemp import PyLogger
from Codemp.src.utils import status_log
class CodempLogger: class CodempLogger:
def __init__(self, debug: bool = False): def __init__(self, log_level):
self.handle = None self.logger = logging.getLogger(__name__)
self.level = log_level
self.logger.setLevel(self.level)
self.internal_logger = None
self.started = False self.started = False
try: 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: except Exception:
pass pass
async def log(self): async def listen(self):
if self.started: if self.started:
return return
self.started = True 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: try:
while msg := await self.handle.listen(): while msg := await self.internal_logger.listen():
print(msg) self.logger.log(self.level, msg)
except CancelledError: except CancelledError:
status_log("stopping logger") self.logger.debug("inner logger stopped.")
self.started = False self.started = False
raise raise
except Exception as e: except Exception as e:
status_log(f"logger crashed unexpectedly:\n{e}") self.logger.error(f"inner logger crashed unexpectedly: \n {e}")
raise raise e
def log(self, msg):
self.logger.log(self.level, msg)
DEBUG = False inner_logger = CodempLogger(logging.INFO)
logger = CodempLogger(DEBUG)

View file

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