-
Notifications
You must be signed in to change notification settings - Fork 133
[WIP] Wrap all RPC methods with exception logging #276
Conversation
I know that my main complaint about it is how invasive it feels. This call has to go everywhere. If this were Python, I'd probably use a decorator to wrap the functions, but that's not exactly possible in C# unless you have something like PostSharp. |
Why |
We have StreamJsonRpc version 1.4.121. TraceSource is new as of a month ago, and is only released in |
Related issue on their tracker: microsoft/vs-streamjsonrpc#115 I'm sure we could use this for both logging/telemetry, but we'd only get it by upgrading our version of the library to that alpha release (and I don't know anything about its stability or when they'd plan to release a non-alpha version). |
@@ -452,6 +460,26 @@ public Task LoadExtension(JToken token, CancellationToken cancellationToken) | |||
_searchPaths = _initParams.initializationOptions.searchPaths; | |||
} | |||
|
|||
private async Task LogOnException(Func<Task> func) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should try sending telemetry via ITelemetryServce
. We can craft test event and check how it shows up in the app insights
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I know. Above I stated that I specifically didn't put it in this WIP because I hadn't looked into the right schema for the telemetry events (Since ITelemetryService seems to just accept object
).
The 2.0 version of the library has no known stability issues, but the API may change before we release a stable 2.0 package, which is why the latest release is still a -prerelease.
If the goal is to improve the exception message shown in the client, I don't know why you're transmitting an error and still throwing from your method. That would lead to Yes the tracing that is new in 2.0 can help you with telemetry, but if your goal is to better control the error responses to invocations so that a particular client will display what you want, 2.0 has a new feature that helps with that: you can intercept any message going either direction, so you can massage the error message however you'd like. You can also throw a special exception that gives you the ability to add whatever extra data you'd like, and control the error code, etc. |
One other thing (and this feature exists in the 1.x versions) is you can add a All of these approaches I've mentioned do not require you to change each individual serer method. |
Thanks @AArnott , overriding |
Yes, I'd like to avoid sending the error twice. The trouble is that VS Code gets the RPC error just fine, and is able to print |
I should clarify that I'm assuming that |
How would sending an extra error message achieve the right experience in VS Code? Won't it still display an error message when the RPC error result comes back, with just the message? And yes, the Data object is relayed back to the RPC client. |
We should NOT be sending all exceptions. Cancellation exception does not make sense since it happens normally. We also throwing InvalidOperation when symbol cannot be renamed. Editor simply shows the message in the exception. |
@MikhailArkhipov:
I don't hear anyone disagreeing with that. My question was how sending the error response back to the client twice fixes anything. |
It doesn't achieve the right experience. Like I said, I don't really want to send the error twice. This is just a WIP idea to try to get the stack trace back to VS Code so that it displays it. I'm also assuming that not rethrowing the error is a bad idea in this specific implementation, since otherwise the code needs to make a decision about what to actually send back. |
I agree: not re-throwing would be problematic for multiple reasons. While the existing 2.0 functionality will allow you to massage the message, adding a general message interceptor feels a bit heavy-handed. I'm going to look to see if we can make an easier way for hand-crafting the error message so you can include whatever you want. |
Yeah, part of me is going "I wish VS Code were printing Regardless, if we want to have telemetry and not just better logging, then we'd probably want a bit extra on top of |
This is the bit that's actually doing the printing of that error in languageserver-client: public error(message: string, data?: any): void {
this.outputChannel.appendLine(`[Error - ${(new Date().toLocaleTimeString())}] ${message}`);
if (data) {
this.outputChannel.appendLine(this.data2String(data));
}
if (this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Error) {
this.outputChannel.show(true);
}
} Where private data2String(data: any): string {
if (data instanceof ResponseError) {
const responseError = data as ResponseError<any>;
return ` Message: ${responseError.message}\n Code: ${responseError.code} ${responseError.data ? '\n' + responseError.data.toString() : ''}`
}
if (data instanceof Error) {
if (Is.string(data.stack)) {
return data.stack;
}
return (data as Error).message;
}
if (Is.string(data)) {
return data;
}
return data.toString();
} So it's correctly detecting that it's a |
@AArnott - the intent is two-fold
The above does not apply to all exceptions. Currently we can throw InvalidOperationException to tell editor that say, rename cannot be performed. Although we can probably derive different one for that purpose, such as |
Do you really mean crashes? Won't Watson take care of that already, if you have your process name registered for collection? Or do you mean to send telemetry regarding exceptions from RPC methods? I notice that our new If you wanted to stick with 1.x, and didn't mind wrapping each server method with your delegate wrapper, then when you catch exceptions with your wrapper, you can throw a new exception (retaining the inner exception) with a Message that you want to show. |
@AArnott - Watson on Linux? Also, some exception do not necessarily cause crash - including null refs. In VS Code output window it looks like |
I didn't know what platform you were on. But I don't know how anything you do in-proc can send telemetry on crashes, since by definition when you crash, you're not running any more. |
Unless I'm mistaken, I'm not sure that actual crashes are in scope for tracking with an RPC library. Exceptions are, for sure. If we want to try to track true crashes on Linux/macOS, I think that'll have to be done some other way separate from this discussion. (The extension already is able to detect when a crash occurs.) EDIT: not sure. Oof. Bad typo. |
Catchable exceptions thrown from RPC methods do not turn into crashes because |
You quoted my unedited comment. I typo'd in the worst possible way and ended up saying the exact opposite of what I intended. 🙁 |
Just to be clear, I don't think we should be tackling actual process crashes in the scope of improving the messages and telemetry that is being sent over RPC. That's likely better handled in the extension code, perhaps by overriding Looking at microsoft/vs-streamjsonrpc#187 seems like it'd be very useful, as we could override |
@jakebailey - yes, we should. There is little dependency on the extension as the LS can be used in other applications. We should not be assuming extension functionality beyond that it launches the process. |
But as Andrew's said, if the process really, truly crashes, then there's no RPC stream anymore nor a process to report the crash. Outside of Windows, we have no way of knowing that has happened other than by having the thing that starts/communicates with LS do something, or a user bug report. vscode-python does the logging bit already, since it uses |
s/Aaron/Andrew. Not my day... |
The fixes I've made to streamjsonrpc based on this discussion will be available on nuget.org in v2.0.94-beta in a few minutes. |
Alright, so the latest StreamJsonRpc build works, and you can see what a PR to switch to it looks like here: master...jakebailey:streamjsonrpc However, due to microsoft/vs-streamjsonrpc#195, it might be a little bit annoying to upgrade because with NuGet's behavior on code analyzers our project would inherit all of StreamJsonRpc's analyzers (which causes a large number of warnings to show in our code, specifically to do with style we don't adhere to). Switching to the newer StreamJsonRpc to get access to the error API would be a better solution, but this PR works too. Any thoughts on just merging this so that we have something in the way of printing stack traces, even if it means doubling up messages, then remove this in the future with a better method? Another method would be for me to open a bug with the |
We can disable those analyzers. |
I haven't yet found a way to do so, other than to explicitly suppress each individual code that the introduced analyzer emits (which I then don't know how to make apply to everything at once). But, I'm not extremely experienced with dealing with C# analyzers. If you know of a way, that'd be great to see. |
Aha, yes, that works. I might see if any of the warnings being shown are of use before disabling them all, though. (The one about naming is definitely getting disabled.) |
Eh, I'll just disable them all. Picking and choosing is out of scope of "show stack traces". |
Closed in favor of #303. |
Adding this PR to get some feedback (do not merge). Leading into #246.
Right now, any exceptions thrown while handing an RPC method bubble back up to the RPC. They're encoded as a standard JSON RPC error message, which the editor does print. However, the RPC library we use only gives
Exception.Message
directly, instead supplying the stack trace in the RPC error'sdata
member, which VS Code does not print. This leads to not-so-helpful errors like #23 which only show the message text and nothing else.My first attempt subclassed
JsonRpc
to override the methods that invoke the different handlers, but sending logs/telemetry is also RPC that calls those methods, which means that reporting anything led to infinite recursion, so I threw that away.Instead, I've written a function
LogOnException
which each function with attributeJsonRpcMethod
calls manually. Exceptions are captured, sent back over RPC as an error, then rethrown as to not change the overall code flow. I have not included actual telemetry calls until I figure out the right schema for the object to send, and what information to omit.Example before, if I just throw an exception from LineFormatter:
And what this code shows:
Any thoughts on this?