Skip to content

Commit d5bd646

Browse files
feat: add deamon debug mode
1 parent 9dc2860 commit d5bd646

2 files changed

Lines changed: 157 additions & 2 deletions

File tree

cmd/arduino-flasher-cli/daemon/daemon.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,22 @@ import (
2929
"github.com/arduino/arduino-flasher-cli/cmd/feedback"
3030
"github.com/arduino/arduino-flasher-cli/cmd/i18n"
3131
flasher "github.com/arduino/arduino-flasher-cli/rpc/cc/arduino/flasher/v1"
32+
"github.com/arduino/go-paths-helper"
3233
)
3334

3435
func NewDaemonCommand(srv flasher.FlasherServer) *cobra.Command {
3536
var daemonPort string
3637
var maxGRPCRecvMsgSize int
38+
var debugFile string
39+
var debug bool
40+
var debugFiltersArg []string
3741
daemonCommand := &cobra.Command{
3842
Use: "daemon",
3943
Short: i18n.Tr("Run the Arduino Flasher CLI as a gRPC daemon."),
4044
Example: " " + os.Args[0] + " daemon",
4145
Args: cobra.NoArgs,
4246
Run: func(cmd *cobra.Command, args []string) {
43-
runDaemonCommand(srv, daemonPort, maxGRPCRecvMsgSize)
47+
runDaemonCommand(srv, daemonPort, maxGRPCRecvMsgSize, debugFile, debug, debugFiltersArg)
4448
},
4549
}
4650

@@ -52,11 +56,49 @@ func NewDaemonCommand(srv flasher.FlasherServer) *cobra.Command {
5256
"max-grpc-recv-message-size", 16*1024*1024,
5357
i18n.Tr("Sets the maximum message size in bytes the daemon can receive"))
5458

59+
daemonCommand.Flags().BoolVar(&debug,
60+
"debug", false,
61+
i18n.Tr("Enable debug logging of gRPC calls"))
62+
daemonCommand.Flags().StringVar(&debugFile,
63+
"debug-file", "",
64+
i18n.Tr("Append debug logging to the specified file"))
65+
66+
daemonCommand.Flags().StringSliceVar(&debugFiltersArg,
67+
"debug-filter", []string{},
68+
i18n.Tr("Display only the provided gRPC calls"))
69+
5570
return daemonCommand
5671
}
5772

58-
func runDaemonCommand(srv flasher.FlasherServer, daemonPort string, maxGRPCRecvMsgSize int) {
73+
func runDaemonCommand(srv flasher.FlasherServer, daemonPort string, maxGRPCRecvMsgSize int, debugFile string, debug bool, debugFiltersArg []string) {
5974
gRPCOptions := []grpc.ServerOption{}
75+
if debugFile != "" {
76+
if !debug {
77+
feedback.Fatal(i18n.Tr("The flag --debug-file must be used with --debug."), feedback.ErrBadArgument)
78+
}
79+
}
80+
if debug {
81+
if debugFile != "" {
82+
outFile := paths.New(debugFile)
83+
f, err := outFile.Append()
84+
if err != nil {
85+
feedback.Fatal(i18n.Tr("Error opening debug logging file: %s", err), feedback.ErrGeneric)
86+
}
87+
defer f.Close()
88+
debugStdOut = f
89+
} else {
90+
if out, _, err := feedback.DirectStreams(); err != nil {
91+
feedback.Fatal(i18n.Tr("Can't write debug log: %s", err), feedback.ErrBadArgument)
92+
} else {
93+
debugStdOut = out
94+
}
95+
}
96+
debugFilters = debugFiltersArg
97+
gRPCOptions = append(gRPCOptions,
98+
grpc.UnaryInterceptor(unaryLoggerInterceptor),
99+
grpc.StreamInterceptor(streamLoggerInterceptor),
100+
)
101+
}
60102
gRPCOptions = append(gRPCOptions, grpc.MaxRecvMsgSize(maxGRPCRecvMsgSize))
61103
s := grpc.NewServer(gRPCOptions...)
62104

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// This file is part of arduino-flasher-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-flasher-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package daemon
17+
18+
import (
19+
"context"
20+
"encoding/json"
21+
"fmt"
22+
"io"
23+
"strings"
24+
"sync/atomic"
25+
26+
"google.golang.org/grpc"
27+
)
28+
29+
var debugStdOut io.Writer
30+
var debugSeq uint32
31+
var debugFilters []string
32+
33+
func log(isRequest bool, seq uint32, msg interface{}) {
34+
prefix := fmt.Sprint(seq, " | ")
35+
j, _ := json.MarshalIndent(msg, prefix, " ")
36+
inOut := "RESP: "
37+
if isRequest {
38+
inOut = "REQ: "
39+
}
40+
fmt.Fprintln(debugStdOut, prefix+inOut+string(j))
41+
}
42+
43+
func logError(seq uint32, err error) {
44+
if err != nil {
45+
fmt.Fprintln(debugStdOut, seq, "| ERROR: ", err)
46+
}
47+
}
48+
49+
func logSelector(method string) bool {
50+
if len(debugFilters) == 0 {
51+
return true
52+
}
53+
for _, filter := range debugFilters {
54+
if strings.Contains(method, filter) {
55+
return true
56+
}
57+
}
58+
return false
59+
}
60+
61+
func unaryLoggerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
62+
if !logSelector(info.FullMethod) {
63+
return handler(ctx, req)
64+
}
65+
seq := atomic.AddUint32(&debugSeq, 1)
66+
fmt.Fprintln(debugStdOut, seq, "CALLED:", info.FullMethod)
67+
log(true, seq, req)
68+
resp, err := handler(ctx, req)
69+
logError(seq, err)
70+
log(false, seq, resp)
71+
fmt.Fprintln(debugStdOut, seq, "CALL END")
72+
fmt.Fprintln(debugStdOut)
73+
return resp, err
74+
}
75+
76+
func streamLoggerInterceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
77+
if !logSelector(info.FullMethod) {
78+
return handler(srv, stream)
79+
}
80+
seq := atomic.AddUint32(&debugSeq, 1)
81+
streamReq := ""
82+
if info.IsClientStream {
83+
streamReq = "STREAM_REQ "
84+
}
85+
if info.IsServerStream {
86+
streamReq += "STREAM_RESP"
87+
}
88+
fmt.Fprintln(debugStdOut, seq, "CALLED:", info.FullMethod, streamReq)
89+
err := handler(srv, &loggingServerStream{ServerStream: stream, seq: seq})
90+
logError(seq, err)
91+
fmt.Fprintln(debugStdOut, seq, "STREAM CLOSED")
92+
fmt.Fprintln(debugStdOut)
93+
return err
94+
}
95+
96+
type loggingServerStream struct {
97+
grpc.ServerStream
98+
seq uint32
99+
}
100+
101+
func (l *loggingServerStream) RecvMsg(m interface{}) error {
102+
err := l.ServerStream.RecvMsg(m)
103+
logError(l.seq, err)
104+
log(true, l.seq, m)
105+
return err
106+
}
107+
108+
func (l *loggingServerStream) SendMsg(m interface{}) error {
109+
err := l.ServerStream.SendMsg(m)
110+
logError(l.seq, err)
111+
log(false, l.seq, m)
112+
return err
113+
}

0 commit comments

Comments
 (0)