github.com/april1989/origin-go-tools@v0.0.32/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/april1989/origin-go-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  }