github.com/v2fly/tools@v0.100.0/internal/lsp/protocol/log.go (about) 1 // Copyright 2019 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 protocol 6 7 import ( 8 "context" 9 "fmt" 10 "io" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/v2fly/tools/internal/jsonrpc2" 16 ) 17 18 type loggingStream struct { 19 stream jsonrpc2.Stream 20 logMu sync.Mutex 21 log io.Writer 22 } 23 24 // LoggingStream returns a stream that does LSP protocol logging too 25 func LoggingStream(str jsonrpc2.Stream, w io.Writer) jsonrpc2.Stream { 26 return &loggingStream{stream: str, log: w} 27 } 28 29 func (s *loggingStream) Read(ctx context.Context) (jsonrpc2.Message, int64, error) { 30 msg, count, err := s.stream.Read(ctx) 31 if err == nil { 32 s.logCommon(msg, true) 33 } 34 return msg, count, err 35 } 36 37 func (s *loggingStream) Write(ctx context.Context, msg jsonrpc2.Message) (int64, error) { 38 s.logCommon(msg, false) 39 count, err := s.stream.Write(ctx, msg) 40 return count, err 41 } 42 43 func (s *loggingStream) Close() error { 44 return s.stream.Close() 45 } 46 47 type req struct { 48 method string 49 start time.Time 50 } 51 52 type mapped struct { 53 mu sync.Mutex 54 clientCalls map[string]req 55 serverCalls map[string]req 56 } 57 58 var maps = &mapped{ 59 sync.Mutex{}, 60 make(map[string]req), 61 make(map[string]req), 62 } 63 64 // these 4 methods are each used exactly once, but it seemed 65 // better to have the encapsulation rather than ad hoc mutex 66 // code in 4 places 67 func (m *mapped) client(id string) req { 68 m.mu.Lock() 69 defer m.mu.Unlock() 70 v := m.clientCalls[id] 71 delete(m.clientCalls, id) 72 return v 73 } 74 75 func (m *mapped) server(id string) req { 76 m.mu.Lock() 77 defer m.mu.Unlock() 78 v := m.serverCalls[id] 79 delete(m.serverCalls, id) 80 return v 81 } 82 83 func (m *mapped) setClient(id string, r req) { 84 m.mu.Lock() 85 defer m.mu.Unlock() 86 m.clientCalls[id] = r 87 } 88 89 func (m *mapped) setServer(id string, r req) { 90 m.mu.Lock() 91 defer m.mu.Unlock() 92 m.serverCalls[id] = r 93 } 94 95 const eor = "\r\n\r\n\r\n" 96 97 func (s *loggingStream) logCommon(msg jsonrpc2.Message, isRead bool) { 98 s.logMu.Lock() 99 defer s.logMu.Unlock() 100 direction, pastTense := "Received", "Received" 101 get, set := maps.client, maps.setServer 102 if isRead { 103 direction, pastTense = "Sending", "Sent" 104 get, set = maps.server, maps.setClient 105 } 106 if msg == nil || s.log == nil { 107 return 108 } 109 tm := time.Now() 110 tmfmt := tm.Format("15:04:05.000 PM") 111 112 buf := strings.Builder{} 113 fmt.Fprintf(&buf, "[Trace - %s] ", tmfmt) // common beginning 114 switch msg := msg.(type) { 115 case *jsonrpc2.Call: 116 id := fmt.Sprint(msg.ID()) 117 fmt.Fprintf(&buf, "%s request '%s - (%s)'.\n", direction, msg.Method(), id) 118 fmt.Fprintf(&buf, "Params: %s%s", msg.Params(), eor) 119 set(id, req{method: msg.Method(), start: tm}) 120 case *jsonrpc2.Notification: 121 fmt.Fprintf(&buf, "%s notification '%s'.\n", direction, msg.Method()) 122 fmt.Fprintf(&buf, "Params: %s%s", msg.Params(), eor) 123 case *jsonrpc2.Response: 124 id := fmt.Sprint(msg.ID()) 125 if err := msg.Err(); err != nil { 126 fmt.Fprintf(s.log, "[Error - %s] %s #%s %s%s", pastTense, tmfmt, id, err, eor) 127 return 128 } 129 cc := get(id) 130 elapsed := tm.Sub(cc.start) 131 fmt.Fprintf(&buf, "%s response '%s - (%s)' in %dms.\n", 132 direction, cc.method, id, elapsed/time.Millisecond) 133 fmt.Fprintf(&buf, "Result: %s%s", msg.Result(), eor) 134 } 135 s.log.Write([]byte(buf.String())) 136 }