Skip to content

Introduce Traceformat to allow JSON format log #366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 28, 2018
Merged

Conversation

octref
Copy link
Contributor

@octref octref commented Jun 22, 2018

Things I plan to do but haven't done yet:

  • When verbosity is set to message, ignore params on Request / Notification messages
  • Expose traceFormat to client's initialization params

Questions:

  • Can we rename this to [langId].lsp.trace? server is misleading as the logs are actually logged from the client perspective. I'll write documentation for this too so I hope we can get this right.

@dbaeumer Would like to get some high-level insight - let me know if this is the right direction, what more things you would want to me to add, etc.

More details are at microsoft/language-server-protocol-inspector#8

@octref octref requested a review from dbaeumer June 22, 2018 09:49
@dbaeumer
Copy link
Member

@octref goes absolutely into the right direction. Only one little comment so far.

@octref
Copy link
Contributor Author

octref commented Aug 22, 2018

Hey Dirk, there are a bunch of updates that happened, so I'm wondering if you can take another look at this PR. Also /cc @aeschli in case you are interested.

High Level Changes

This PR remain as it is, so:

  • trace.server continues to work
  • trace.server can be an object now, for example:
    "mls.trace.server": {
      "verbosity": "verbose", // values same as old `trace.server`
      "format": "json" // or `text` for old behavior
    }
    

For streaming support, it can be implemented trivially by Language Server authors using the existing API LanguageClientOptions.outputChannel. For example: octref/vscode-language-server-template@4181af8.

This goes against what we initially agreed on, e.g:

{
  "format": "json" | "text",
  "verbosity": "off" | "messages" | "verbose",
  "output": "channel" | "log" | "websocket"
}

the benefit of leaving out logging / websocket-streaming is that we don't add dependencies (ws for websocket) and we give ext authors more freedom on handling logs.

How Things Work Now

There are 3 repos:

Play With It

@octref octref changed the title [WIP] Introduce Traceformat to allow JSON format log Introduce Traceformat to allow JSON format log Aug 22, 2018
type: LSPMessageType;
message: RequestMessage | ResponseMessage | NotificationMessage;
timestamp: number;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LSP uses tabs not spaces

@@ -205,6 +206,36 @@ export namespace Trace {
}
}

export enum TraceFormat {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a number enum and a TraceFormatValues would it make sense to use a string Enum which is now supported by TS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was aligning to TraceValues, string enum makes sense.

@@ -224,6 +255,7 @@ export namespace LogTraceNotification {

export interface Tracer {
log(message: string, data?: string): void;
logLSP(message: string): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking API change. We should either make it optional or reuse the log signature. I looked at the client code and I was under the impression that the only difference is that the log prints [Trace - ...] and logLSP prints [LSP - ...]. Since the client knows the log format could this be decided on the client side.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we should have a log method that takes an JSON object literal. So the JSON RPC code shouldn't even convert it to a strings. Then someone could plug in a tracer that writes the JSON objects to a file.

Copy link
Contributor Author

@octref octref Aug 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we should have a log method that takes an JSON object literal.


NVM, found a way to do that.

@@ -157,7 +157,7 @@ export interface ProtocolConnection {
/**
* Enables tracing mode for the connection.
*/
trace(value: Trace, tracer: Tracer, sendNotification?: boolean): void;
trace(value: Trace, traceFormat: TraceFormat, tracer: Tracer, sendNotification?: boolean): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is breaking as well. We should support this in a non breaking way by having two signatures. If the old one is used we do the old text tracing.

});
}

public set traceFormat(value: TraceFormat) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to fold that into trace as an optional param. If not provided it is text. I doubt that users set this independently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, since trace function already covers the format as an optional param. However I'm wondering if you mind adding traceFormat as one of the LanguageClientOptions? I feel this is something people want to configure on LS Client init but not setting it each time.

@@ -157,7 +157,7 @@ export interface ProtocolConnection {
/**
* Enables tracing mode for the connection.
*/
trace(value: Trace, tracer: Tracer, sendNotification?: boolean): void;
trace(value: Trace, traceFormat: TraceFormat, tracer: Tracer, sendNotification?: boolean): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as on the implementation side. This is breaking...

@dbaeumer
Copy link
Member

@octref provided by comments.

@@ -2498,6 +2505,17 @@ export abstract class BaseLanguageClient {
}
}

private logObjectTrace(data: any): void {
if (data.isLSPMessage && data.type) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbaeumer Not sure if this is what you meant by "if the data is an object then log it by JSON stringify here". Also not sure if that isLSPMessage is the best way to go. If you have better suggestions, let me know!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, in this case I would simply do

this.outputChannel.append(`[LSP   - ${(new Date().toLocaleTimeString())}] `);
this.outputChannel.appendLine(`${JSON.stringify(data)}`);

@octref
Copy link
Contributor Author

octref commented Aug 24, 2018

Addressed comments, but left some questions too.

@@ -161,7 +161,7 @@ function createConnection(input: any, output: any, errorHandler: ConnectionError
sendNotification: (type: string | RPCMessageType, params?: any): void => connection.sendNotification(Is.string(type) ? type : type.method, params),
onNotification: (type: string | RPCMessageType, handler: GenericNotificationHandler): void => connection.onNotification(Is.string(type) ? type : type.method, handler),

trace: (value: Trace, tracer: Tracer, sendNotification: boolean = false): void => connection.trace(value, tracer, sendNotification),
trace: (value: Trace, tracer: Tracer, sendNotification: boolean = false, traceFormat: TraceFormat = TraceFormat.Text): void => connection.trace(value, tracer, sendNotification, traceFormat),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it is easier to implement if we simply add an optional arg at the endd but I would prefer to have to signatures like this:

trace: (value: Trace, tracer: Tracer, sendNotification: boolean = false)
trace: (value: Trace, traceFormat: TraceFormat, tracer: Tracer, sendNotification: boolean = false)

to keep the args together and may be deprecate the old signature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbaeumer Decided to have this:

trace(value: Trace, tracer: Tracer, sendNotification?: boolean): void;
trace(value: Trace, tracer: Tracer, traceOptions?: TraceOptions): void;

// In json-rpc

export interface TraceOptions {
	sendNotification?: boolean;
	traceFormat?: TraceFormat;
}

I initially had what you suggested, but later abandoned it in favor of traceFormat? at the end, as

  • I'd prefer to keep traceFormat optional
  • A call with three args can be of either signature.

With this new one it's easier to detect the params of the overloaded functions, and in the future it's easier to add more options without breaking backward compatibility.

Copy link
Member

@dbaeumer dbaeumer Aug 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is not what I meant. I was suggesting to have this:

trace: (value: Trace, tracer: Tracer, sendNotification: boolean = false)
trace: (value: Trace, traceFormat: TraceFormat, tracer: Tracer, sendNotification: boolean = false)

with one implementation overload like this:

function trace(arg0: Trace, arg1: Tracer | TraceFormat, arg2?: Tracer | boolean, arg3?: boolean)

There is enough information in the signatures to decide which on is meant.

@@ -158,6 +158,7 @@ export interface ProtocolConnection {
* Enables tracing mode for the connection.
*/
trace(value: Trace, tracer: Tracer, sendNotification?: boolean): void;
trace(value: Trace, tracer: Tracer, sendNotification?: boolean, traceFormat?: TraceFormat): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

this.logTrace(message, data);
}
log: (...params: any[]) => {
if (typeof params[0] === 'string') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a Is.string() helper I consistently use.

log: (message: string, data?: string) => {
this.logTrace(message, data);
}
log: (...params: any[]) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this not still be message & data and message can be string | any. ...params makes it harder to understand what data can actually come.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants