Skip to content

Commit 76f3cd0

Browse files
author
Christian Weichel
committed
[misc] Make stream logging less intrusive
1 parent a21f92d commit 76f3cd0

File tree

4 files changed

+183
-111
lines changed

4 files changed

+183
-111
lines changed

Diff for: handler/handler.go

+15-16
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,23 @@ func Setup(cliPath string, _enableLogging bool) {
2626
enableLogging = _enableLogging
2727
}
2828

29+
// CLangdStarter starts clangd and returns its stdin/out/err
30+
type CLangdStarter func() (stdin io.WriteCloser, stdout io.ReadCloser, stderr io.ReadCloser)
31+
2932
// NewInoHandler creates and configures an InoHandler.
30-
func NewInoHandler(stdin io.ReadCloser, stdout io.WriteCloser, stdinLog, stdoutLog io.Writer,
31-
startClangd func() (io.WriteCloser, io.ReadCloser, io.ReadCloser),
32-
clangdinLog, clangdoutLog, clangderrLog io.Writer, board Board) *InoHandler {
33+
func NewInoHandler(stdin io.ReadCloser, stdout io.WriteCloser, logStreams *StreamLogger, startClangd CLangdStarter, board Board) *InoHandler {
3334
handler := &InoHandler{
3435
clangdProc: ClangdProc{
35-
Start: startClangd,
36-
inLog: clangdinLog,
37-
outLog: clangdoutLog,
38-
errLog: clangderrLog,
36+
Start: startClangd,
37+
Logs: logStreams,
3938
},
4039
data: make(map[lsp.DocumentURI]*FileData),
4140
config: BoardConfig{
4241
SelectedBoard: board,
4342
},
4443
}
45-
handler.StartClangd()
46-
stdStream := jsonrpc2.NewBufferedStream(StreamReadWrite{stdin, stdout, stdinLog, stdoutLog}, jsonrpc2.VSCodeObjectCodec{})
44+
handler.startClangd()
45+
stdStream := jsonrpc2.NewBufferedStream(logStreams.AttachStdInOut(stdin, stdout), jsonrpc2.VSCodeObjectCodec{})
4746
stdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromStdio))
4847
handler.StdioConn = jsonrpc2.NewConn(context.Background(), stdStream, stdHandler)
4948
if enableLogging {
@@ -63,9 +62,9 @@ type InoHandler struct {
6362

6463
// ClangdProc contains the process input / output streams for clangd.
6564
type ClangdProc struct {
66-
Start func() (io.WriteCloser, io.ReadCloser, io.ReadCloser)
67-
inLog, outLog, errLog io.Writer
68-
initParams lsp.InitializeParams
65+
Start func() (io.WriteCloser, io.ReadCloser, io.ReadCloser)
66+
Logs *StreamLogger
67+
initParams lsp.InitializeParams
6968
}
7069

7170
// FileData gathers information on a .ino source file.
@@ -79,12 +78,12 @@ type FileData struct {
7978
}
8079

8180
// StartClangd starts the clangd process and connects its input / output streams.
82-
func (handler *InoHandler) StartClangd() {
81+
func (handler *InoHandler) startClangd() {
8382
clangdWrite, clangdRead, clangdErr := handler.clangdProc.Start()
8483
if enableLogging {
85-
go io.Copy(handler.clangdProc.errLog, clangdErr)
84+
go io.Copy(handler.clangdProc.Logs.ClangdErr, clangdErr)
8685
}
87-
srw := StreamReadWrite{clangdRead, clangdWrite, handler.clangdProc.inLog, handler.clangdProc.outLog}
86+
srw := handler.clangdProc.Logs.AttachClangdInOut(clangdRead, clangdWrite)
8887
clangdStream := jsonrpc2.NewBufferedStream(srw, jsonrpc2.VSCodeObjectCodec{})
8988
clangdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromClangd))
9089
handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler)
@@ -178,7 +177,7 @@ func (handler *InoHandler) changeBoardConfig(ctx context.Context, config *BoardC
178177
}
179178

180179
// Restart the clangd process, initialize it and reopen the files
181-
handler.StartClangd()
180+
handler.startClangd()
182181
initResult := new(lsp.InitializeResult)
183182
err := handler.ClangdConn.Call(ctx, "initialize", &handler.clangdProc.initParams, initResult)
184183
if err != nil {

Diff for: handler/stream.go

-40
This file was deleted.

Diff for: handler/streamlog.go

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package handler
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"io/ioutil"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
)
11+
12+
// StreamLogger maintains log files for all streams involved in the language server
13+
type StreamLogger struct {
14+
Default io.WriteCloser
15+
Stdin io.WriteCloser
16+
Stdout io.WriteCloser
17+
ClangdIn io.WriteCloser
18+
ClangdOut io.WriteCloser
19+
ClangdErr io.WriteCloser
20+
}
21+
22+
// Close closes all logging streams
23+
func (s *StreamLogger) Close() (err error) {
24+
var errs []string
25+
for _, c := range []io.Closer{s.Default, s.Stdin, s.Stdout, s.ClangdIn, s.ClangdOut, s.ClangdErr} {
26+
if c == nil {
27+
continue
28+
}
29+
30+
err = c.Close()
31+
if err != nil {
32+
errs = append(errs, err.Error())
33+
}
34+
}
35+
if len(errs) != 0 {
36+
return fmt.Errorf(strings.Join(errs, ", "))
37+
}
38+
39+
return nil
40+
}
41+
42+
// AttachStdInOut attaches the stdin, stdout logger to the in/out channels
43+
func (s *StreamLogger) AttachStdInOut(in io.ReadCloser, out io.WriteCloser) io.ReadWriteCloser {
44+
return &streamDuplex{
45+
io.TeeReader(in, s.Stdin),
46+
in,
47+
io.MultiWriter(out, s.Stdout),
48+
out,
49+
}
50+
}
51+
52+
// AttachClangdInOut attaches the clangd in, out logger to the in/out channels
53+
func (s *StreamLogger) AttachClangdInOut(in io.ReadCloser, out io.WriteCloser) io.ReadWriteCloser {
54+
return &streamDuplex{
55+
io.TeeReader(in, s.ClangdIn),
56+
in,
57+
io.MultiWriter(out, s.ClangdOut),
58+
out,
59+
}
60+
}
61+
62+
type streamDuplex struct {
63+
in io.Reader
64+
inc io.Closer
65+
out io.Writer
66+
outc io.Closer
67+
}
68+
69+
func (sd *streamDuplex) Read(p []byte) (int, error) {
70+
return sd.in.Read(p)
71+
}
72+
73+
func (sd *streamDuplex) Write(p []byte) (int, error) {
74+
return sd.out.Write(p)
75+
}
76+
77+
func (sd *streamDuplex) Close() error {
78+
ierr := sd.inc.Close()
79+
oerr := sd.outc.Close()
80+
81+
if ierr != nil {
82+
return ierr
83+
}
84+
if oerr != nil {
85+
return oerr
86+
}
87+
return nil
88+
}
89+
90+
// NewStreamLogger creates files for all stream logs. Returns an error if opening a single stream fails.
91+
func NewStreamLogger(basepath string) (res *StreamLogger, err error) {
92+
res = &StreamLogger{}
93+
94+
res.Default, err = os.OpenFile(filepath.Join(basepath, "inols.log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
95+
if err != nil {
96+
res.Close()
97+
return
98+
}
99+
res.Stdin, err = os.OpenFile(filepath.Join(basepath, "inols-stdin.log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
100+
if err != nil {
101+
res.Close()
102+
return
103+
}
104+
res.Stdout, err = os.OpenFile(filepath.Join(basepath, "inols-stdout.log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
105+
if err != nil {
106+
res.Close()
107+
return
108+
}
109+
res.ClangdIn, err = os.OpenFile(filepath.Join(basepath, "inols-clangd-in.log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
110+
if err != nil {
111+
res.Close()
112+
return
113+
}
114+
res.ClangdOut, err = os.OpenFile(filepath.Join(basepath, "inols-clangd-out.log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
115+
if err != nil {
116+
res.Close()
117+
return
118+
}
119+
res.ClangdErr, err = os.OpenFile(filepath.Join(basepath, "inols-clangd-err.log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
120+
if err != nil {
121+
res.Close()
122+
return
123+
}
124+
125+
return
126+
}
127+
128+
// NewNoopLogger creates a logger that does nothing
129+
func NewNoopLogger() (res *StreamLogger) {
130+
noop := noopCloser{ioutil.Discard}
131+
return &StreamLogger{
132+
Default: noop,
133+
Stdin: noop,
134+
Stdout: noop,
135+
ClangdIn: noop,
136+
ClangdOut: noop,
137+
ClangdErr: noop,
138+
}
139+
}
140+
141+
type noopCloser struct {
142+
io.Writer
143+
}
144+
145+
func (noopCloser) Close() error {
146+
return nil
147+
}

Diff for: main.go

+21-55
Original file line numberDiff line numberDiff line change
@@ -15,83 +15,49 @@ var cliPath string
1515
var initialFqbn string
1616
var initialBoardName string
1717
var enableLogging bool
18+
var loggingBasePath string
1819

1920
func main() {
20-
flag.StringVar(&clangdPath, "clangd", "clangd",
21-
"Path to clangd executable")
22-
flag.StringVar(&cliPath, "cli", "arduino-cli",
23-
"Path to arduino-cli executable")
24-
flag.StringVar(&initialFqbn, "fqbn", "arduino:avr:uno",
25-
"Fully qualified board name to use initially (can be changed via JSON-RPC)")
26-
flag.StringVar(&initialBoardName, "board-name", "",
27-
"User-friendly board name to use initially (can be changed via JSON-RPC)")
28-
flag.BoolVar(&enableLogging, "log", false,
29-
"Enable logging to files")
21+
flag.StringVar(&clangdPath, "clangd", "clangd", "Path to clangd executable")
22+
flag.StringVar(&cliPath, "cli", "arduino-cli", "Path to arduino-cli executable")
23+
flag.StringVar(&initialFqbn, "fqbn", "arduino:avr:uno", "Fully qualified board name to use initially (can be changed via JSON-RPC)")
24+
flag.StringVar(&initialBoardName, "board-name", "", "User-friendly board name to use initially (can be changed via JSON-RPC)")
25+
flag.BoolVar(&enableLogging, "log", false, "Enable logging to files")
26+
flag.StringVar(&loggingBasePath, "logpath", ".", "Location where to write logging files to when logging is enabled")
3027
flag.Parse()
3128

32-
var stdinLog, stdoutLog, clangdinLog, clangdoutLog, clangderrLog io.Writer
29+
// var stdinLog, stdoutLog, clangdinLog, clangdoutLog, clangderrLog io.Writer
30+
var logStreams *handler.StreamLogger
3331
if enableLogging {
34-
logFile, stdinLogFile, stdoutLogFile, clangdinLogFile, clangdoutLogFile, clangderrLogFile := createLogFiles()
35-
defer logFile.Close()
36-
defer stdinLogFile.Close()
37-
defer stdoutLogFile.Close()
38-
defer clangdinLogFile.Close()
39-
defer clangdoutLogFile.Close()
40-
defer clangderrLogFile.Close()
41-
log.SetOutput(logFile)
42-
stdinLog, stdoutLog, clangdinLog, clangdoutLog, clangderrLog = stdinLogFile, stdoutLogFile,
43-
clangdinLogFile, clangdoutLogFile, clangderrLogFile
32+
var err error
33+
logStreams, err = handler.NewStreamLogger(loggingBasePath)
34+
if err != nil {
35+
log.Fatal(err)
36+
}
37+
defer logStreams.Close()
38+
39+
log.SetOutput(logStreams.Default)
4440
} else {
41+
logStreams = handler.NewNoopLogger()
4542
log.SetOutput(os.Stderr)
4643
}
4744

4845
handler.Setup(cliPath, enableLogging)
4946
initialBoard := handler.Board{Fqbn: initialFqbn, Name: initialBoardName}
50-
inoHandler := handler.NewInoHandler(os.Stdin, os.Stdout, stdinLog, stdoutLog, startClangd,
51-
clangdinLog, clangdoutLog, clangderrLog, initialBoard)
47+
inoHandler := handler.NewInoHandler(os.Stdin, os.Stdout, logStreams, startClangd, initialBoard)
5248
defer inoHandler.StopClangd()
5349
<-inoHandler.StdioConn.DisconnectNotify()
5450
}
5551

56-
func createLogFiles() (logFile, stdinLog, stdoutLog, clangdinLog, clangdoutLog, clangderrLog *os.File) {
57-
var err error
58-
logFile, err = os.OpenFile("inols.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
59-
if err != nil {
60-
panic(err)
61-
}
62-
stdinLog, err = os.OpenFile("inols-stdin.log", os.O_WRONLY|os.O_CREATE, 0666)
63-
if err != nil {
64-
panic(err)
65-
}
66-
stdoutLog, err = os.OpenFile("inols-stdout.log", os.O_WRONLY|os.O_CREATE, 0666)
67-
if err != nil {
68-
panic(err)
69-
}
70-
clangdinLog, err = os.OpenFile("inols-clangd-in.log", os.O_WRONLY|os.O_CREATE, 0666)
71-
if err != nil {
72-
panic(err)
73-
}
74-
clangdoutLog, err = os.OpenFile("inols-clangd-out.log", os.O_WRONLY|os.O_CREATE, 0666)
75-
if err != nil {
76-
panic(err)
77-
}
78-
clangderrLog, err = os.OpenFile("inols-clangd-err.log", os.O_WRONLY|os.O_CREATE, 0666)
79-
if err != nil {
80-
panic(err)
81-
}
82-
return
83-
}
84-
8552
func startClangd() (clangdIn io.WriteCloser, clangdOut io.ReadCloser, clangdErr io.ReadCloser) {
8653
if enableLogging {
8754
log.Println("Starting clangd process:", clangdPath)
8855
}
8956
clangdCmd := exec.Command(clangdPath)
9057
clangdIn, _ = clangdCmd.StdinPipe()
9158
clangdOut, _ = clangdCmd.StdoutPipe()
92-
if enableLogging {
93-
clangdErr, _ = clangdCmd.StderrPipe()
94-
}
59+
clangdErr, _ = clangdCmd.StderrPipe()
60+
9561
err := clangdCmd.Start()
9662
if err != nil {
9763
panic(err)

0 commit comments

Comments
 (0)