Skip to content

Commit d93e913

Browse files
committed
internal/lsp/debug: hook runtime/trace into event spans
Existing spans defined within gopls are leveraged to add runtime/trace tasks, regions, and logging. This made it easier to understand gopls' execution, though we still have relatively sparse logging (arguably because we're conscious of the fact that logs are reflected back to the LSP client). Add a new log package with the concept of log level to facilitate excluding trace logs from the LSP. Add a little bit of additional instrumentation to demonstrate the usage of the new package. Change-Id: Id337be806484201103e30bfe2c8c62c7d7c363c7 Reviewed-on: https://go-review.googlesource.com/c/tools/+/275252 Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]> Trust: Robert Findley <[email protected]>
1 parent abf6a1d commit d93e913

File tree

6 files changed

+140
-15
lines changed

6 files changed

+140
-15
lines changed

internal/lsp/debug/log/log.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package log provides helper methods for exporting log events to the
6+
// internal/event package.
7+
package log
8+
9+
import (
10+
"context"
11+
"fmt"
12+
13+
"golang.org/x/tools/internal/event"
14+
"golang.org/x/tools/internal/event/label"
15+
"golang.org/x/tools/internal/lsp/debug/tag"
16+
)
17+
18+
// Level parameterizes log severity.
19+
type Level int
20+
21+
const (
22+
_ Level = iota
23+
Error
24+
Warning
25+
Info
26+
Debug
27+
Trace
28+
)
29+
30+
// Log exports a log event labeled with level l.
31+
func (l Level) Log(ctx context.Context, msg string) {
32+
event.Log(ctx, msg, tag.Level.Of(int(l)))
33+
}
34+
35+
// Logf formats and exports a log event labeled with level l.
36+
func (l Level) Logf(ctx context.Context, format string, args ...interface{}) {
37+
l.Log(ctx, fmt.Sprintf(format, args...))
38+
}
39+
40+
// LabeledLevel extracts the labeled log l
41+
func LabeledLevel(lm label.Map) Level {
42+
return Level(tag.Level.Get(lm))
43+
}

internal/lsp/debug/serve.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"fmt"
1212
"html/template"
1313
"io"
14-
"log"
14+
stdlog "log"
1515
"net"
1616
"net/http"
1717
"net/http/pprof"
@@ -34,15 +34,19 @@ import (
3434
"golang.org/x/tools/internal/event/keys"
3535
"golang.org/x/tools/internal/event/label"
3636
"golang.org/x/tools/internal/lsp/cache"
37+
"golang.org/x/tools/internal/lsp/debug/log"
3738
"golang.org/x/tools/internal/lsp/debug/tag"
3839
"golang.org/x/tools/internal/lsp/protocol"
3940
"golang.org/x/tools/internal/lsp/source"
4041
errors "golang.org/x/xerrors"
4142
)
4243

43-
type instanceKeyType int
44+
type contextKeyType int
4445

45-
const instanceKey = instanceKeyType(0)
46+
const (
47+
instanceKey contextKeyType = iota
48+
traceKey
49+
)
4650

4751
// An Instance holds all debug information associated with a gopls instance.
4852
type Instance struct {
@@ -373,7 +377,7 @@ func (i *Instance) SetLogFile(logfile string, isDaemon bool) (func(), error) {
373377
closeLog = func() {
374378
defer f.Close()
375379
}
376-
log.SetOutput(io.MultiWriter(os.Stderr, f))
380+
stdlog.SetOutput(io.MultiWriter(os.Stderr, f))
377381
i.LogWriter = f
378382
}
379383
i.Logfile = logfile
@@ -384,7 +388,7 @@ func (i *Instance) SetLogFile(logfile string, isDaemon bool) (func(), error) {
384388
// It also logs the port the server starts on, to allow for :0 auto assigned
385389
// ports.
386390
func (i *Instance) Serve(ctx context.Context) error {
387-
log.SetFlags(log.Lshortfile)
391+
stdlog.SetFlags(stdlog.Lshortfile)
388392
if i.DebugAddress == "" {
389393
return nil
390394
}
@@ -396,7 +400,7 @@ func (i *Instance) Serve(ctx context.Context) error {
396400

397401
port := listener.Addr().(*net.TCPAddr).Port
398402
if strings.HasSuffix(i.DebugAddress, ":0") {
399-
log.Printf("debug server listening at http://localhost:%d", port)
403+
stdlog.Printf("debug server listening at http://localhost:%d", port)
400404
}
401405
event.Log(ctx, "Debug serving", tag.Port.Of(port))
402406
go func() {
@@ -520,15 +524,31 @@ func makeGlobalExporter(stderr io.Writer) event.Exporter {
520524
p.WriteEvent(stderr, ev, lm)
521525
pMu.Unlock()
522526
}
527+
level := log.LabeledLevel(lm)
528+
// Exclude trace logs from LSP logs.
529+
if level < log.Trace {
530+
ctx = protocol.LogEvent(ctx, ev, lm, messageType(level))
531+
}
523532
}
524-
ctx = protocol.LogEvent(ctx, ev, lm)
525533
if i == nil {
526534
return ctx
527535
}
528536
return i.exporter(ctx, ev, lm)
529537
}
530538
}
531539

540+
func messageType(l log.Level) protocol.MessageType {
541+
switch l {
542+
case log.Error:
543+
return protocol.Error
544+
case log.Warning:
545+
return protocol.Warning
546+
case log.Debug:
547+
return protocol.Log
548+
}
549+
return protocol.Info
550+
}
551+
532552
func makeInstanceExporter(i *Instance) event.Exporter {
533553
exporter := func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
534554
if i.ocagent != nil {
@@ -573,6 +593,9 @@ func makeInstanceExporter(i *Instance) event.Exporter {
573593
}
574594
return ctx
575595
}
596+
// StdTrace must be above export.Spans below (by convention, export
597+
// middleware applies its wrapped exporter last).
598+
exporter = StdTrace(exporter)
576599
metrics := metric.Config{}
577600
registerMetrics(&metrics)
578601
exporter = metrics.Exporter(exporter)

internal/lsp/debug/tag/tag.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ var (
4141
DebugAddress = keys.NewString("debug_address", "")
4242
GoplsPath = keys.NewString("gopls_path", "")
4343
ClientID = keys.NewString("client_id", "")
44+
45+
Level = keys.NewInt("level", "The logging level")
4446
)
4547

4648
var (

internal/lsp/debug/trace.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"html/template"
1212
"net/http"
13+
"runtime/trace"
1314
"sort"
1415
"strings"
1516
"sync"
@@ -75,6 +76,48 @@ type traceEvent struct {
7576
Tags string
7677
}
7778

79+
func StdTrace(exporter event.Exporter) event.Exporter {
80+
return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
81+
span := export.GetSpan(ctx)
82+
if span == nil {
83+
return exporter(ctx, ev, lm)
84+
}
85+
switch {
86+
case event.IsStart(ev):
87+
if span.ParentID.IsValid() {
88+
region := trace.StartRegion(ctx, span.Name)
89+
ctx = context.WithValue(ctx, traceKey, region)
90+
} else {
91+
var task *trace.Task
92+
ctx, task = trace.NewTask(ctx, span.Name)
93+
ctx = context.WithValue(ctx, traceKey, task)
94+
}
95+
// Log the start event as it may contain useful labels.
96+
msg := formatEvent(ctx, ev, lm)
97+
trace.Log(ctx, "start", msg)
98+
case event.IsLog(ev):
99+
category := ""
100+
if event.IsError(ev) {
101+
category = "error"
102+
}
103+
msg := formatEvent(ctx, ev, lm)
104+
trace.Log(ctx, category, msg)
105+
case event.IsEnd(ev):
106+
if v := ctx.Value(traceKey); v != nil {
107+
v.(interface{ End() }).End()
108+
}
109+
}
110+
return exporter(ctx, ev, lm)
111+
}
112+
}
113+
114+
func formatEvent(ctx context.Context, ev core.Event, lm label.Map) string {
115+
buf := &bytes.Buffer{}
116+
p := export.Printer{}
117+
p.WriteEvent(buf, ev, lm)
118+
return buf.String()
119+
}
120+
78121
func (t *traces) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context {
79122
t.mu.Lock()
80123
defer t.mu.Unlock()

internal/lsp/diagnostics.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"sync"
1515

1616
"golang.org/x/tools/internal/event"
17+
"golang.org/x/tools/internal/lsp/debug/log"
1718
"golang.org/x/tools/internal/lsp/debug/tag"
1819
"golang.org/x/tools/internal/lsp/mod"
1920
"golang.org/x/tools/internal/lsp/protocol"
@@ -74,6 +75,8 @@ func (s *Server) diagnoseDetached(snapshot source.Snapshot) {
7475

7576
func (s *Server) diagnoseSnapshot(snapshot source.Snapshot, changedURIs []span.URI, onDisk bool) {
7677
ctx := snapshot.BackgroundContext()
78+
ctx, done := event.Start(ctx, "Server.diagnoseSnapshot", tag.Snapshot.Of(snapshot.ID()))
79+
defer done()
7780

7881
delay := snapshot.View().Options().ExperimentalDiagnosticsDelay
7982
if delay > 0 {
@@ -99,7 +102,7 @@ func (s *Server) diagnoseSnapshot(snapshot source.Snapshot, changedURIs []span.U
99102
}
100103

101104
func (s *Server) diagnoseChangedFiles(ctx context.Context, snapshot source.Snapshot, uris []span.URI, onDisk bool) {
102-
ctx, done := event.Start(ctx, "Server.diagnoseChangedFiles")
105+
ctx, done := event.Start(ctx, "Server.diagnoseChangedFiles", tag.Snapshot.Of(snapshot.ID()))
103106
defer done()
104107
packages := make(map[source.Package]struct{})
105108
for _, uri := range uris {
@@ -140,7 +143,7 @@ func (s *Server) diagnoseChangedFiles(ctx context.Context, snapshot source.Snaps
140143
// diagnose is a helper function for running diagnostics with a given context.
141144
// Do not call it directly. forceAnalysis is only true for testing purposes.
142145
func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, forceAnalysis bool) {
143-
ctx, done := event.Start(ctx, "Server.diagnose")
146+
ctx, done := event.Start(ctx, "Server.diagnose", tag.Snapshot.Of(snapshot.ID()))
144147
defer done()
145148

146149
// Wait for a free diagnostics slot.
@@ -156,6 +159,7 @@ func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, forceAn
156159
// First, diagnose the go.mod file.
157160
modReports, modErr := mod.Diagnostics(ctx, snapshot)
158161
if ctx.Err() != nil {
162+
log.Trace.Log(ctx, "diagnose cancelled")
159163
return
160164
}
161165
if modErr != nil {
@@ -238,6 +242,8 @@ func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, forceAn
238242
}
239243

240244
func (s *Server) diagnosePkg(ctx context.Context, snapshot source.Snapshot, pkg source.Package, alwaysAnalyze bool) {
245+
ctx, done := event.Start(ctx, "Server.diagnosePkg", tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(pkg.ID()))
246+
defer done()
241247
includeAnalysis := alwaysAnalyze // only run analyses for packages with open files
242248
var gcDetailsDir span.URI // find the package's optimization details, if available
243249
for _, pgf := range pkg.CompiledGoFiles() {
@@ -439,8 +445,16 @@ func (s *Server) storeErrorDiagnostics(ctx context.Context, snapshot source.Snap
439445

440446
// publishDiagnostics collects and publishes any unpublished diagnostic reports.
441447
func (s *Server) publishDiagnostics(ctx context.Context, final bool, snapshot source.Snapshot) {
448+
ctx, done := event.Start(ctx, "Server.publishDiagnostics", tag.Snapshot.Of(snapshot.ID()))
449+
defer done()
442450
s.diagnosticsMu.Lock()
443451
defer s.diagnosticsMu.Unlock()
452+
453+
published := 0
454+
defer func() {
455+
log.Trace.Logf(ctx, "published %d diagnostics", published)
456+
}()
457+
444458
for uri, r := range s.diagnostics {
445459
// Snapshot IDs are always increasing, so we use them instead of file
446460
// versions to create the correct order for diagnostics.
@@ -491,6 +505,7 @@ func (s *Server) publishDiagnostics(ctx context.Context, final bool, snapshot so
491505
URI: protocol.URIFromSpanURI(uri),
492506
Version: version,
493507
}); err == nil {
508+
published++
494509
r.publishedHash = hash
495510
r.snapshotID = snapshot.ID()
496511
for dsource, hash := range reportHashes {
@@ -501,6 +516,7 @@ func (s *Server) publishDiagnostics(ctx context.Context, final bool, snapshot so
501516
} else {
502517
if ctx.Err() != nil {
503518
// Publish may have failed due to a cancelled context.
519+
log.Trace.Log(ctx, "publish cancelled")
504520
return
505521
}
506522
event.Error(ctx, "publishReports: failed to deliver diagnostic", err, tag.URI.Of(uri))

internal/lsp/protocol/context.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,16 @@ func WithClient(ctx context.Context, client Client) context.Context {
2121
return context.WithValue(ctx, clientKey, client)
2222
}
2323

24-
func LogEvent(ctx context.Context, ev core.Event, tags label.Map) context.Context {
25-
if !event.IsLog(ev) {
26-
return ctx
27-
}
24+
func LogEvent(ctx context.Context, ev core.Event, lm label.Map, mt MessageType) context.Context {
2825
client, ok := ctx.Value(clientKey).(Client)
2926
if !ok {
3027
return ctx
3128
}
3229
buf := &bytes.Buffer{}
3330
p := export.Printer{}
34-
p.WriteEvent(buf, ev, tags)
35-
msg := &LogMessageParams{Type: Info, Message: buf.String()}
31+
p.WriteEvent(buf, ev, lm)
32+
msg := &LogMessageParams{Type: mt, Message: buf.String()}
33+
// Handle messages generated via event.Error, which won't have a level Label.
3634
if event.IsError(ev) {
3735
msg.Type = Error
3836
}

0 commit comments

Comments
 (0)