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  }