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.
5 // Package parse provides functions to parse LSP logs.
6 // Fully processed logs are returned by ToRLog().
20 // MsgType is the type of message.
24 // ClRequest from client to server has method and id
25 ClRequest MsgType = iota
26 // ClResponse from server to client
28 // SvRequest from server to client, has method and id
30 // SvResponse from client to server
32 // ToServer notification has method, but no id
34 // ToClient notification
36 // ReportErr is an error message
37 ReportErr // errors have method and id
40 // Logmsg is the type of a parsed log entry.
44 ID string // for requests/responses. Client and server request ids overlap
45 Elapsed string // for responses
46 Hdr string // header. do we need to keep all these strings?
47 Rest string // the unparsed result, with newlines or not
48 Body interface{} // the parsed result
51 // ReadLogs from a file. Most users should use ToRlog().
52 func ReadLogs(fname string) ([]*Logmsg, error) {
53 byid := make(map[string]int)
55 fd, err := os.Open(fname)
60 logrdr := bufio.NewScanner(fd)
61 logrdr.Buffer(nil, 1<<25) // a large buffer, for safety
62 logrdr.Split(scanLogs)
63 for i := 0; logrdr.Scan(); i++ {
64 flds := strings.SplitN(logrdr.Text(), "\n", 2)
66 flds = append(flds, "") // for Errors
68 msg, err := parselog(flds[0], flds[1])
70 return nil, fmt.Errorf("failed to parse %q: %v", logrdr.Text(), err)
73 case ClRequest, SvRequest:
74 v, err := msg.unmarshal(Requests(msg.Method))
76 return nil, fmt.Errorf("%v for %s, %T", err, msg.Method, Requests(msg.Method))
79 case ClResponse, SvResponse:
80 v, err := msg.doresponse()
82 return nil, fmt.Errorf("%v %s", err, msg.Method)
85 case ToServer, ToClient:
86 v, err := msg.unmarshal(Notifs(msg.Method))
87 if err != nil && Notifs(msg.Method) != nil {
88 return nil, fmt.Errorf("%s/%T: %v", msg.Method, Notifs(msg.Method), err)
92 msg.Body = msg.Rest // save cause
95 msgs = append(msgs, msg)
97 if err = logrdr.Err(); err != nil {
103 // parse a single log message, given first line, and the rest
104 func parselog(first, rest string) (*Logmsg, error) {
105 if strings.HasPrefix(rest, "Params: ") {
107 } else if strings.HasPrefix(rest, "Result: ") {
110 msg := &Logmsg{Hdr: first, Rest: rest}
111 fixid := func(s string) string {
112 // emacs does (n)., gopls does (n)'.
113 s = strings.Trim(s, "()'.{)")
116 flds := strings.Fields(first)
117 chk := func(s string, n int) bool { return strings.Contains(first, s) && len(flds) == n }
118 // gopls and emacs differ in how they report elapsed time
120 case chk("Sending request", 9):
122 msg.Method = flds[6][1:]
123 msg.ID = fixid(flds[8][:len(flds[8])-2])
124 case chk("Received response", 11):
125 msg.Type = ClResponse
126 msg.Method = flds[6][1:]
127 msg.ID = fixid(flds[8])
128 msg.Elapsed = flds[10]
129 case chk("Received request", 9):
131 msg.Method = flds[6][1:]
132 msg.ID = fixid(flds[8])
133 case chk("Sending response", 11), // gopls
134 chk("Sending response", 13): // emacs
135 msg.Type = SvResponse
136 msg.Method = flds[6][1:]
137 msg.ID = fixid(flds[8][:len(flds[8])-1])
138 msg.Elapsed = flds[10]
139 case chk("Sending notification", 7):
141 msg.Method = strings.Trim(flds[6], ".'")
143 log.Printf("len=%d method=%s %q", len(flds), msg.Method, first)
145 case chk("Received notification", 7):
147 msg.Method = flds[6][1 : len(flds[6])-2]
148 case strings.HasPrefix(first, "[Error - "):
151 idx := strings.Index(both, "#") // relies on ID.Number
152 msg.Method = both[:idx]
153 msg.ID = fixid(both[idx+1:])
154 msg.Rest = strings.Join(flds[6:], " ")
155 msg.Rest = `"` + msg.Rest + `"`
157 return nil, fmt.Errorf("surprise, first=%q with %d flds", first, len(flds))
162 // unmarshal into a proposed type
163 func (l *Logmsg) unmarshal(p interface{}) (interface{}, error) {
165 if err := json.Unmarshal(r, p); err != nil {
166 // need general alternatives, but for now
167 // if p is *[]foo and rest is {}, return an empty p (or *p?)
177 func (l *Logmsg) doresponse() (interface{}, error) {
178 for _, x := range Responses(l.Method) {
179 v, err := l.unmarshal(x)
184 return new(interface{}), nil
188 rr := Responses(l.Method)
189 for _, x := range rr {
190 log.Printf("tried %T", x)
192 log.Fatalf("(%d) doresponse failed for %s %q", len(rr), l.Method, l.Rest)
196 // be a little forgiving in separating log records
197 var recSep = regexp.MustCompile("\n\n\n|\r\n\r\n\r\n")
199 // return offset of start of next record, contents of record, error
200 func scanLogs(b []byte, atEOF bool) (int, []byte, error) { //bufio.SplitFunc
201 got := recSep.FindIndex(b)
203 if atEOF && len(b) > 0 {
204 return 0, nil, errors.New("malformed log: all logs should end with a separator")
208 return got[1], b[:got[0]], nil
211 // String returns a user-useful versin of a Direction
212 func (d MsgType) String() string {
229 return fmt.Sprintf("dirname: %d unknown", d)