7
7
"os"
8
8
"path/filepath"
9
9
"strings"
10
+ "time"
10
11
11
12
"github.com/adrg/xdg"
12
13
"github.com/spf13/cobra"
@@ -19,14 +20,18 @@ import (
19
20
20
21
var (
21
22
followFlag bool
23
+ proxyFlag bool
22
24
)
23
25
24
26
func logsCommand () * cobra.Command {
25
27
logsCommand := & cobra.Command {
26
28
Use : "logs [workload-name|prune]" ,
27
29
Short : "Output the logs of an MCP server or manage log files" ,
28
- Long : `Output the logs of an MCP server managed by ToolHive, or manage log files.` ,
29
- Args : cobra .ExactArgs (1 ),
30
+ Long : `Output the logs of an MCP server managed by ToolHive, or manage log files.
31
+
32
+ By default, this command shows the logs from the MCP server container.
33
+ Use --proxy to view the logs from the ToolHive proxy process instead.` ,
34
+ Args : cobra .ExactArgs (1 ),
30
35
RunE : func (cmd * cobra.Command , args []string ) error {
31
36
// Check if the argument is "prune"
32
37
if args [0 ] == "prune" {
@@ -38,11 +43,18 @@ func logsCommand() *cobra.Command {
38
43
}
39
44
40
45
logsCommand .Flags ().BoolVarP (& followFlag , "follow" , "f" , false , "Follow log output (only for workload logs)" )
46
+ logsCommand .Flags ().BoolVarP (& proxyFlag , "proxy" , "p" , false , "Show proxy logs instead of container logs" )
47
+
41
48
err := viper .BindPFlag ("follow" , logsCommand .Flags ().Lookup ("follow" ))
42
49
if err != nil {
43
50
logger .Errorf ("failed to bind flag: %v" , err )
44
51
}
45
52
53
+ err = viper .BindPFlag ("proxy" , logsCommand .Flags ().Lookup ("proxy" ))
54
+ if err != nil {
55
+ logger .Errorf ("failed to bind flag: %v" , err )
56
+ }
57
+
46
58
// Add prune subcommand for better discoverability
47
59
pruneCmd := & cobra.Command {
48
60
Use : "prune" ,
@@ -64,6 +76,11 @@ func logsCmdFunc(cmd *cobra.Command, args []string) error {
64
76
// Get workload name
65
77
workloadName := args [0 ]
66
78
follow := viper .GetBool ("follow" )
79
+ proxy := viper .GetBool ("proxy" )
80
+
81
+ if proxy {
82
+ return getProxyLogs (workloadName , follow )
83
+ }
67
84
68
85
manager , err := workloads .NewManager (ctx )
69
86
if err != nil {
@@ -199,3 +216,76 @@ func reportPruneResults(prunedFiles, errs []string) {
199
216
}
200
217
}
201
218
}
219
+
220
+ // getProxyLogs reads and displays the proxy logs for a given workload
221
+ func getProxyLogs (workloadName string , follow bool ) error {
222
+ // Get the proxy log file path
223
+ logFilePath , err := xdg .DataFile (fmt .Sprintf ("toolhive/logs/%s.log" , workloadName ))
224
+ if err != nil {
225
+ return fmt .Errorf ("failed to get proxy log file path: %v" , err )
226
+ }
227
+
228
+ // Clean the file path to prevent path traversal
229
+ cleanLogFilePath := filepath .Clean (logFilePath )
230
+
231
+ // Check if the log file exists
232
+ if _ , err := os .Stat (cleanLogFilePath ); os .IsNotExist (err ) {
233
+ logger .Infof ("proxy log not found for workload %s" , workloadName )
234
+ return nil
235
+ }
236
+
237
+ if follow {
238
+ return followProxyLogFile (cleanLogFilePath )
239
+ }
240
+
241
+ // Read and display the entire log file
242
+ content , err := os .ReadFile (cleanLogFilePath )
243
+ if err != nil {
244
+ return fmt .Errorf ("failed to read proxy log %s: %v" , cleanLogFilePath , err )
245
+ }
246
+
247
+ fmt .Print (string (content ))
248
+ return nil
249
+ }
250
+
251
+ // followProxyLogFile implements tail -f functionality for proxy logs
252
+ func followProxyLogFile (logFilePath string ) error {
253
+ // Clean the file path to prevent path traversal
254
+ cleanLogFilePath := filepath .Clean (logFilePath )
255
+
256
+ // Open the file
257
+ file , err := os .Open (cleanLogFilePath )
258
+ if err != nil {
259
+ return fmt .Errorf ("failed to open proxy log %s: %v" , cleanLogFilePath , err )
260
+ }
261
+ defer file .Close ()
262
+
263
+ // Read existing content first
264
+ content , err := os .ReadFile (cleanLogFilePath )
265
+ if err == nil {
266
+ fmt .Print (string (content ))
267
+ }
268
+
269
+ // Seek to the end of the file for following
270
+ _ , err = file .Seek (0 , 2 )
271
+ if err != nil {
272
+ return fmt .Errorf ("failed to seek to end of proxy log: %v" , err )
273
+ }
274
+
275
+ // Follow the file for new content
276
+ for {
277
+ // Read any new content
278
+ buffer := make ([]byte , 1024 )
279
+ n , err := file .Read (buffer )
280
+ if err != nil && err .Error () != "EOF" {
281
+ return fmt .Errorf ("error reading proxy log: %v" , err )
282
+ }
283
+
284
+ if n > 0 {
285
+ fmt .Print (string (buffer [:n ]))
286
+ }
287
+
288
+ // Sleep briefly before checking for more content
289
+ time .Sleep (100 * time .Millisecond )
290
+ }
291
+ }
0 commit comments