Skip to content

Commit 98c96f9

Browse files
committed
feat(langserver): implemented window/logMessage to log output directly to the language server protocol client
1 parent 6820cc8 commit 98c96f9

File tree

3 files changed

+73
-8
lines changed

3 files changed

+73
-8
lines changed

packages/jsonrpc2/src/robotcode/jsonrpc2/protocol.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,11 +550,14 @@ def send_message(self, message: JsonRPCMessage) -> None:
550550
if self.write_transport is not None:
551551
msg = header + body
552552

553-
self._data_logger.trace(lambda: f"JSON send: {msg.decode()!r}")
553+
self._do_trace_message(message, msg)
554554

555555
if self._loop:
556556
self._loop.call_soon_threadsafe(self.write_transport.write, msg)
557557

558+
def _do_trace_message(self, message: JsonRPCMessage, msg: bytes) -> None:
559+
self._data_logger.trace(lambda: f"JSON send: {msg.decode()!r}")
560+
558561
@__logger.call
559562
def send_request(
560563
self,

packages/language_server/src/robotcode/language_server/common/protocol.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import logging
23
import threading
34
from threading import Event
45
from typing import Any, ClassVar, Final, List, Optional, Set, Union
@@ -15,6 +16,9 @@
1516
InitializeParamsClientInfoType,
1617
InitializeResult,
1718
InitializeResultServerInfoType,
19+
LogMessageParams,
20+
LogTraceParams,
21+
MessageType,
1822
PositionEncodingKind,
1923
ProgressToken,
2024
Registration,
@@ -29,12 +33,13 @@
2933
UnregistrationParams,
3034
WorkspaceFolder,
3135
)
32-
from robotcode.core.utils.logging import LoggingDescriptor
36+
from robotcode.core.utils.logging import TRACE, LoggingDescriptor
3337
from robotcode.core.utils.process import pid_exists
3438
from robotcode.jsonrpc2.protocol import (
3539
JsonRPCErrorException,
3640
JsonRPCErrors,
3741
JsonRPCException,
42+
JsonRPCMessage,
3843
JsonRPCProtocol,
3944
ProtocolPartDescriptor,
4045
rpc_method,
@@ -74,6 +79,34 @@ class LanguageServerException(JsonRPCException):
7479
pass
7580

7681

82+
class LanguageServerLogHandler(logging.Handler):
83+
MAPPING = {
84+
logging.INFO: MessageType.INFO,
85+
logging.WARNING: MessageType.WARNING,
86+
logging.ERROR: MessageType.ERROR,
87+
TRACE: MessageType.LOG,
88+
}
89+
90+
def __init__(self, protocol: "LanguageServerProtocol"):
91+
super().__init__()
92+
self.protocol = protocol
93+
self.trace: TraceValues = TraceValues.OFF
94+
95+
def emit(self, record: logging.LogRecord) -> None:
96+
if record.levelno == TRACE:
97+
if self.trace != TraceValues.OFF:
98+
self.protocol.log_trace(record.getMessage())
99+
100+
type = self.MAPPING.get(record.levelno, None)
101+
if type is None:
102+
type = MessageType.LOG
103+
104+
self.protocol.window_log_message(
105+
type=type,
106+
message=record.getMessage(),
107+
)
108+
109+
77110
class LanguageServerProtocol(JsonRPCProtocol):
78111
__logger = LoggingDescriptor()
79112

@@ -131,8 +164,13 @@ def __init__(self, server: JsonRPCServer[Any]):
131164
)
132165

133166
self._trace = TraceValues.OFF
167+
self._trace_loghandler: Optional[LanguageServerLogHandler] = None
168+
134169
self.is_initialized = Event()
135170

171+
def __del__(self) -> None:
172+
self.trace = TraceValues.OFF
173+
136174
@event
137175
def on_shutdown(sender) -> None: # pragma: no cover, NOSONAR
138176
...
@@ -149,6 +187,9 @@ def trace(self) -> TraceValues:
149187
def trace(self, value: TraceValues) -> None:
150188
self._trace = value
151189

190+
if self._trace_loghandler:
191+
self._trace_loghandler.trace = value
192+
152193
@property
153194
def workspace(self) -> Workspace:
154195
if self._workspace is None:
@@ -210,7 +251,14 @@ def _initialize(
210251
if self.parent_process_id and pid_exists(self.parent_process_id):
211252
self.start_parent_process_watcher()
212253

254+
logger = logging.getLogger()
255+
256+
if self._trace_loghandler is None:
257+
self._trace_loghandler = LanguageServerLogHandler(self)
258+
logger.addHandler(self._trace_loghandler)
259+
213260
self.trace = trace or TraceValues.OFF
261+
214262
self.client_info = client_info
215263

216264
self.client_capabilities = capabilities
@@ -292,6 +340,13 @@ def _shutdown(self, *args: Any, **kwargs: Any) -> None:
292340

293341
self.shutdown_received = True
294342

343+
logger = logging.getLogger()
344+
345+
if self._trace_loghandler in logger.handlers:
346+
logging.getLogger().removeHandler(self._trace_loghandler)
347+
del self._trace_loghandler
348+
self._trace_loghandler = None
349+
295350
try:
296351
self.cancel_all_received_request()
297352
except BaseException as e:
@@ -311,6 +366,16 @@ def _exit(self, *args: Any, **kwargs: Any) -> None:
311366
def _set_trace(self, value: TraceValues, *args: Any, **kwargs: Any) -> None:
312367
self.trace = value
313368

369+
def window_log_message(self, type: MessageType, message: str) -> None:
370+
self.send_notification("window/logMessage", LogMessageParams(type=type, message=message))
371+
372+
def log_trace(self, message: str, verbose: Optional[str] = None) -> None:
373+
self.send_notification("$/logTrace", LogTraceParams(message=message, verbose=verbose))
374+
375+
def _do_trace_message(self, message: JsonRPCMessage, msg: bytes) -> None:
376+
if getattr(message, "method", None) not in ["$/logTrace", "window/logMessage"]:
377+
self._data_logger.trace(lambda: f"JSON send: {msg.decode()!r}")
378+
314379
@rpc_method(name="$/cancelRequest", param_type=CancelParams)
315380
@__logger.call
316381
def _cancel_request(self, id: Union[int, str], **kwargs: Any) -> None:

vscode-client/extension/languageclientsmanger.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -536,10 +536,7 @@ export class LanguageClientsManager {
536536
return undefined;
537537
}
538538

539-
const name = `RobotCode Language Server mode=${mode} for folder '${workspaceFolder.name}'`;
540-
541-
const outputChannel = this.outputChannels.get(name) ?? vscode.window.createOutputChannel(name);
542-
this.outputChannels.set(name, outputChannel);
539+
const name = `RobotCode Language Server for folder ${workspaceFolder.name}`;
543540

544541
let closeHandlerAction = CloseAction.DoNotRestart;
545542

@@ -565,7 +562,6 @@ export class LanguageClientsManager {
565562
env: config.get<object>("robot.env", []),
566563
settings: { robotcode: config },
567564
},
568-
revealOutputChannelOn: RevealOutputChannelOn.Never, // TODO: should we make this configurable?
569565
initializationFailedHandler: (error: ResponseError<InitializeError> | Error | undefined) => {
570566
if (error)
571567
void vscode.window // NOSONAR
@@ -593,7 +589,8 @@ export class LanguageClientsManager {
593589
},
594590
// TODO: how we can start a language client on workspace level, not on folder level
595591
workspaceFolder,
596-
outputChannel,
592+
revealOutputChannelOn: RevealOutputChannelOn.Error, // TODO: should we make this configurable?
593+
outputChannelName: name,
597594
markdown: {
598595
isTrusted: true,
599596
supportHtml: true,

0 commit comments

Comments
 (0)