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