Added tracing logging, removed some unhelpful python level logging messages

Former-commit-id: 68610be7c21c24dc9c354fc0162bed4dd3438606
This commit is contained in:
Camillo Schenone 2024-03-02 15:28:39 +01:00
parent 9003215014
commit a302180a19
7 changed files with 103 additions and 43 deletions

View file

@ -14,6 +14,8 @@ pyo3 = { version = "0.20", features = ["extension-module"] }
pyo3-asyncio = { version = "0.20", features = ["tokio-runtime"] } pyo3-asyncio = { version = "0.20", features = ["tokio-runtime"] }
serde = { version = "1.0.196", features = ["derive"] } serde = { version = "1.0.196", features = ["derive"] }
tokio = "1.29.1" tokio = "1.29.1"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
[build-dependencies] [build-dependencies]
pyo3-build-config = "0.19.2" pyo3-build-config = "0.19.2"

View file

@ -1 +1 @@
cbf3777125b44718cba5f2e67dcbe9a01700840b 28b1d0465b66af6afb0266207d139e48c95173fd

View file

@ -2,6 +2,7 @@ import sublime
import sublime_plugin import sublime_plugin
from Codemp.src.codemp_client import VirtualClient from Codemp.src.codemp_client import VirtualClient
from Codemp.src.codemp_client import CodempLogger
from Codemp.src.TaskManager import rt from Codemp.src.TaskManager import rt
from Codemp.src.utils import status_log from Codemp.src.utils import status_log
from Codemp.src.utils import safe_listener_detach from Codemp.src.utils import safe_listener_detach
@ -9,6 +10,7 @@ from Codemp.src.utils import get_contents
from Codemp.src.utils import populate_view from Codemp.src.utils import populate_view
from Codemp.src.utils import get_view_from_local_path from Codemp.src.utils import get_view_from_local_path
import Codemp.src.globals as g import Codemp.src.globals as g
from Codemp.bindings.codemp_client import init_logger
CLIENT = None CLIENT = None
TEXT_LISTENER = None TEXT_LISTENER = None
@ -25,6 +27,9 @@ def plugin_loaded():
CLIENT = VirtualClient(disconnect_client) CLIENT = VirtualClient(disconnect_client)
TEXT_LISTENER = CodempClientTextChangeListener() TEXT_LISTENER = CodempClientTextChangeListener()
logger = CodempLogger(init_logger(True))
CLIENT.tm.dispatch(logger.spawn_logger(), "codemp-logger")
status_log("plugin loaded") status_log("plugin loaded")
@ -43,7 +48,6 @@ def plugin_unloaded():
global CLIENT global CLIENT
# 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.
CLIENT.tm.release(False) CLIENT.tm.release(False)
status_log("plugin unloaded")
# Listeners # Listeners
@ -60,9 +64,6 @@ 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 not None: if ws is not None:
status_log(
f"current active: {CLIENT.active_workspace.id}, ws = {ws.id}"
)
if ws.id == CLIENT.active_workspace.id: if ws.id == CLIENT.active_workspace.id:
CLIENT.active_workspace = None CLIENT.active_workspace = None
CLIENT.tm.stop(f"{g.CURCTL_TASK_PREFIX}-{ws.id}") CLIENT.tm.stop(f"{g.CURCTL_TASK_PREFIX}-{ws.id}")
@ -87,23 +88,18 @@ class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
if vbuff is not None: if vbuff is not None:
CLIENT.send_cursor(vbuff) CLIENT.send_cursor(vbuff)
# We only edit on one view at a time, therefore we only need one TextChangeListener
# Each time we focus a view to write on it, we first attach the listener to that buffer.
# When we defocus, we detach it.
def on_activated(self): def on_activated(self):
global TEXT_LISTENER
# sublime has no proper way to check if a view gained or lost input focus outside of this # sublime has no proper way to check if a view gained or lost input focus outside of this
# callback (i know right?), so we have to manually keep track of which view has the focus # callback (i know right?), so we have to manually keep track of which view has the focus
g.ACTIVE_CODEMP_VIEW = self.view.id() g.ACTIVE_CODEMP_VIEW = self.view.id()
print("view {} activated".format(self.view.id())) # print("view {} activated".format(self.view.id()))
global TEXT_LISTENER
TEXT_LISTENER.attach(self.view.buffer()) TEXT_LISTENER.attach(self.view.buffer())
def on_deactivated(self): def on_deactivated(self):
global TEXT_LISTENER
g.ACTIVE_CODEMP_VIEW = None g.ACTIVE_CODEMP_VIEW = None
print("view {} deactivated".format(self.view.id())) # print("view {} deactivated".format(self.view.id()))
global TEXT_LISTENER
safe_listener_detach(TEXT_LISTENER) safe_listener_detach(TEXT_LISTENER)
def on_pre_close(self): def on_pre_close(self):
@ -193,7 +189,7 @@ class CodempJoinCommand(sublime_plugin.WindowCommand):
class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand): class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand):
def run(self, workspace_id): def run(self, workspace_id):
global CLIENT global CLIENT
rt.dispatch(CLIENT.join_workspace(workspace_id)) rt.dispatch(CLIENT.join_workspace(workspace_id, "sublime2"))
def input_description(self): def input_description(self):
return "Join specific workspace" return "Join specific workspace"
@ -208,7 +204,7 @@ class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand):
class CodempJoinBufferCommand(sublime_plugin.WindowCommand): class CodempJoinBufferCommand(sublime_plugin.WindowCommand):
def run(self, buffer_id): def run(self, buffer_id):
global CLIENT global CLIENT
if CLIENT.active_workspace is not None: if CLIENT.active_workspace is None:
sublime.error_message( sublime.error_message(
"You haven't joined any worksapce yet. \ "You haven't joined any worksapce yet. \
use `Codemp: Join Workspace` or `Codemp: Join`" use `Codemp: Join Workspace` or `Codemp: Join`"

View file

@ -56,9 +56,7 @@ class TaskManager:
def stop(self, name): def stop(self, name):
t = self.get_task(name) t = self.get_task(name)
if t is not None: if t is not None:
rt.sync( rt.sync(self._stop(t))
self._stop(t)
) # awaiting the task blocks until it actually is finished.
def stop_all(self): def stop_all(self):
for task in self.tasks: for task in self.tasks:

View file

@ -15,6 +15,26 @@ from Codemp.src.utils import status_log, rowcol_to_region
from Codemp.src.TaskManager import TaskManager from Codemp.src.TaskManager import TaskManager
class CodempLogger:
def __init__(self, handle):
self.handle = handle
async def message(self):
return await self.handle.message()
async def spawn_logger(self):
status_log("spinning up the logger...")
try:
while msg := await self.handle.message():
print(msg)
except asyncio.CancelledError:
status_log("stopping logger")
raise
except Exception as e:
status_log(f"logger crashed unexpectedly:\n{e}")
raise
# 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
# remote buffers (codemp side), to handle the syncronicity. # remote buffers (codemp side), to handle the syncronicity.
# This class is mainly manipulated by a VirtualWorkspace, that manages its buffers # This class is mainly manipulated by a VirtualWorkspace, that manages its buffers
@ -133,7 +153,6 @@ class VirtualWorkspace:
async def attach(self, id: str): async def attach(self, id: str):
if id is None: if id is None:
status_log("can't attach if buffer does not exist, aborting.")
return return
await self.handle.fetch_buffers() await self.handle.fetch_buffers()
@ -172,10 +191,8 @@ class VirtualClient:
return self.workspaces.get(key) return self.workspaces.get(key)
def make_active(self, ws: VirtualWorkspace): def make_active(self, ws: VirtualWorkspace):
# TODO: Logic to deal with swapping to and from workspaces,
# what happens to the cursor tasks etc..
if self.active_workspace is not None: if self.active_workspace is not None:
self.tm.stop_and_pop(f"{g.CURCTL_TASK_PREFIX}-{self.active_workspace.id}") self.tm.stop(f"{g.CURCTL_TASK_PREFIX}-{self.active_workspace.id}")
self.active_workspace = ws self.active_workspace = ws
self.spawn_cursor_manager(ws) self.spawn_cursor_manager(ws)
@ -193,26 +210,20 @@ class VirtualClient:
status_log(f"Connected to '{server_host}' with user id: {id}") status_log(f"Connected to '{server_host}' with user id: {id}")
async def join_workspace( async def join_workspace(
self, workspace_id: str, user="sublime", password="***REMOVED***" self, workspace_id: str, user="sublime2", password="***REMOVED***"
) -> VirtualWorkspace: ) -> VirtualWorkspace:
try: try:
status_log(f"Logging into workspace: '{workspace_id}'") status_log(f"Logging into workspace: '{workspace_id}'")
await self.handle.login(user, password, workspace_id) await self.handle.login(user, password, workspace_id)
except Exception as e: except Exception as e:
status_log(f"Failed to login to workspace '{workspace_id}'.\nerror: {e}") status_log(f"Failed to login to workspace '{workspace_id}'.\nerror: {e}", True)
sublime.error_message(
f"Failed to login to workspace '{workspace_id}'.\nerror: {e}"
)
return return
try: try:
status_log(f"Joining workspace: '{workspace_id}'") status_log(f"Joining workspace: '{workspace_id}'")
workspace_handle = await self.handle.join_workspace(workspace_id) workspace_handle = await self.handle.join_workspace(workspace_id)
except Exception as e: except Exception as e:
status_log(f"Could not join workspace '{workspace_id}'.\nerror: {e}") status_log(f"Could not join workspace '{workspace_id}'.\nerror: {e}", True)
sublime.error_message(
f"Could not join workspace '{workspace_id}'.\nerror: {e}"
)
return return
vws = VirtualWorkspace(self, workspace_id, workspace_handle) vws = VirtualWorkspace(self, workspace_id, workspace_handle)
@ -229,10 +240,6 @@ class VirtualClient:
vbuff = vws.get_by_remote(cursor_event.buffer) vbuff = vws.get_by_remote(cursor_event.buffer)
if vbuff is None: if vbuff is None:
status_log(
f"Received a cursor event for an unknown \
or inactive buffer: {cursor_event.buffer}"
)
continue continue
reg = rowcol_to_region( reg = rowcol_to_region(
@ -264,7 +271,7 @@ class VirtualClient:
def send_cursor(self, vbuff: VirtualBuffer): def send_cursor(self, vbuff: VirtualBuffer):
# 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 = vbuff.view.sel()[0] region = vbuff.view.sel()[0]
start = vbuff.view.rowcol(region.begin()) # only counts UTF8 chars start = vbuff.view.rowcol(region.begin()) # only counts UTF8 chars
end = vbuff.view.rowcol(region.end()) end = vbuff.view.rowcol(region.end())
@ -283,9 +290,6 @@ class VirtualClient:
# We are not listening on it. Otherwise, interrupt the listening # We are not listening on it. Otherwise, interrupt the listening
# to avoid echoing back the change just received. # to avoid echoing back the change just received.
if vb.view.id() == g.ACTIVE_CODEMP_VIEW: if vb.view.id() == g.ACTIVE_CODEMP_VIEW:
status_log(
"received a text change with view active, stopping the echo."
)
vb.view.settings()[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = True vb.view.settings()[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = True
# we need to go through a sublime text command, since the method, # we need to go through a sublime text command, since the method,

View file

@ -1,7 +1,9 @@
use codemp::proto::common::Identity; use codemp::proto::common::Identity;
use pyo3::types::PyList; use pyo3::types::PyList;
use std::{format, sync::Arc}; use std::{format, sync::Arc};
use tokio::sync::RwLock; use tokio::sync::{mpsc, Mutex, RwLock};
use tracing;
use tracing_subscriber;
use codemp::prelude::*; use codemp::prelude::*;
use codemp::{errors::Error as CodempError, proto::files::BufferNode}; use codemp::{errors::Error as CodempError, proto::files::BufferNode};
@ -12,7 +14,7 @@ use pyo3::{
types::{PyString, PyType}, types::{PyString, PyType},
}; };
// ERRORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ERRORS And LOGGING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct PyCodempError(CodempError); struct PyCodempError(CodempError);
impl From<CodempError> for PyCodempError { impl From<CodempError> for PyCodempError {
fn from(err: CodempError) -> Self { fn from(err: CodempError) -> Self {
@ -39,6 +41,33 @@ impl From<PyCodempError> for PyErr {
} }
} }
} }
#[derive(Debug, Clone)]
struct LoggerProducer(mpsc::Sender<String>);
impl std::io::Write for LoggerProducer {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
// TODO this is a LOSSY logger!!
let _ = self.0.try_send(String::from_utf8_lossy(buf).to_string()); // ignore: logger disconnected or with full buffer
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[pyclass]
struct PyLogger(Arc<Mutex<mpsc::Receiver<String>>>);
#[pymethods]
impl PyLogger {
fn message<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> {
let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move { Ok(rc.lock().await.recv().await) })
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Workflow: // Workflow:
@ -57,6 +86,33 @@ fn codemp_init<'a>(py: Python<'a>) -> PyResult<Py<PyClient>> {
Ok(Py::new(py, PyClient::default())?) Ok(Py::new(py, PyClient::default())?)
} }
#[pyfunction]
fn init_logger(py: Python<'_>, debug: Option<bool>) -> PyResult<Py<PyLogger>> {
let (tx, rx) = mpsc::channel(256);
let level = if debug.unwrap_or(false) {
tracing::Level::DEBUG
} else {
tracing::Level::INFO
};
let format = tracing_subscriber::fmt::format()
.without_time()
.with_level(true)
.with_target(true)
.with_thread_ids(false)
.with_thread_names(false)
.with_file(false)
.with_line_number(false)
.with_source_location(false)
.compact();
tracing_subscriber::fmt()
.with_ansi(false)
.event_format(format)
.with_max_level(level)
.with_writer(std::sync::Mutex::new(LoggerProducer(tx)))
.init();
Ok(Py::new(py, PyLogger(Arc::new(Mutex::new(rx))))?)
}
#[pyclass] #[pyclass]
struct PyClient(Arc<RwLock<Option<CodempClient>>>); struct PyClient(Arc<RwLock<Option<CodempClient>>>);
@ -477,10 +533,12 @@ impl PyTextChange {
#[pymodule] #[pymodule]
fn codemp_client(_py: Python, m: &PyModule) -> PyResult<()> { fn codemp_client(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(codemp_init, m)?)?; m.add_function(wrap_pyfunction!(codemp_init, m)?)?;
m.add_function(wrap_pyfunction!(init_logger, m)?)?;
m.add_class::<PyClient>()?; m.add_class::<PyClient>()?;
m.add_class::<PyWorkspace>()?; m.add_class::<PyWorkspace>()?;
m.add_class::<PyCursorController>()?; m.add_class::<PyCursorController>()?;
m.add_class::<PyBufferController>()?; m.add_class::<PyBufferController>()?;
m.add_class::<PyLogger>()?;
m.add_class::<PyId>()?; m.add_class::<PyId>()?;
m.add_class::<PyCursorEvent>()?; m.add_class::<PyCursorEvent>()?;

View file

@ -2,9 +2,11 @@ import sublime
import sublime_plugin import sublime_plugin
def status_log(msg): def status_log(msg, popup=False):
sublime.status_message("[codemp] {}".format(msg)) sublime.status_message("[codemp] {}".format(msg))
print("[codemp] {}".format(msg)) print("[codemp] {}".format(msg))
if popup:
sublime.error_message(msg)
def rowcol_to_region(view, start, end): def rowcol_to_region(view, start, end):