Skip to content

Commit 677202a

Browse files
authored
Add --proxy flag to thv logs command to view proxy logs (#1230)
Signed-off-by: Juan Antonio Osorio <[email protected]>
1 parent e548195 commit 677202a

File tree

2 files changed

+96
-2
lines changed

2 files changed

+96
-2
lines changed

cmd/thv/app/logs.go

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"path/filepath"
99
"strings"
10+
"time"
1011

1112
"github.com/adrg/xdg"
1213
"github.com/spf13/cobra"
@@ -19,14 +20,18 @@ import (
1920

2021
var (
2122
followFlag bool
23+
proxyFlag bool
2224
)
2325

2426
func logsCommand() *cobra.Command {
2527
logsCommand := &cobra.Command{
2628
Use: "logs [workload-name|prune]",
2729
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),
3035
RunE: func(cmd *cobra.Command, args []string) error {
3136
// Check if the argument is "prune"
3237
if args[0] == "prune" {
@@ -38,11 +43,18 @@ func logsCommand() *cobra.Command {
3843
}
3944

4045
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+
4148
err := viper.BindPFlag("follow", logsCommand.Flags().Lookup("follow"))
4249
if err != nil {
4350
logger.Errorf("failed to bind flag: %v", err)
4451
}
4552

53+
err = viper.BindPFlag("proxy", logsCommand.Flags().Lookup("proxy"))
54+
if err != nil {
55+
logger.Errorf("failed to bind flag: %v", err)
56+
}
57+
4658
// Add prune subcommand for better discoverability
4759
pruneCmd := &cobra.Command{
4860
Use: "prune",
@@ -64,6 +76,11 @@ func logsCmdFunc(cmd *cobra.Command, args []string) error {
6476
// Get workload name
6577
workloadName := args[0]
6678
follow := viper.GetBool("follow")
79+
proxy := viper.GetBool("proxy")
80+
81+
if proxy {
82+
return getProxyLogs(workloadName, follow)
83+
}
6784

6885
manager, err := workloads.NewManager(ctx)
6986
if err != nil {
@@ -199,3 +216,76 @@ func reportPruneResults(prunedFiles, errs []string) {
199216
}
200217
}
201218
}
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+
}

docs/cli/thv_logs.md

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)