@@ -8,8 +8,10 @@ import (
88 "errors"
99 "fmt"
1010 "os"
11+ "os/signal"
1112 "path/filepath"
1213 "strings"
14+ "syscall"
1315 "time"
1416
1517 "github.com/adrg/xdg"
@@ -94,14 +96,20 @@ func logsCmdFunc(cmd *cobra.Command, args []string) error {
9496 follow := viper .GetBool ("follow" )
9597 proxy := viper .GetBool ("proxy" )
9698
99+ if follow {
100+ var cancel context.CancelFunc
101+ ctx , cancel = signal .NotifyContext (ctx , syscall .SIGINT , syscall .SIGTERM )
102+ defer cancel ()
103+ }
104+
97105 manager , err := workloads .NewManager (ctx )
98106 if err != nil {
99107 return fmt .Errorf ("failed to create workload manager: %w" , err )
100108 }
101109
102110 if proxy {
103111 if follow {
104- return getProxyLogs (workloadName )
112+ return getProxyLogs (ctx , workloadName )
105113 }
106114 // Use the shared manager method for non-follow proxy logs
107115 // CLI gets all logs (0 = unlimited)
@@ -246,7 +254,7 @@ func reportPruneResults(prunedFiles, errs []string) {
246254}
247255
248256// getProxyLogs reads and displays the proxy logs for a given workload in follow mode
249- func getProxyLogs (workloadName string ) error {
257+ func getProxyLogs (ctx context. Context , workloadName string ) error {
250258 // Get the proxy log file path
251259 logFilePath , err := xdg .DataFile (fmt .Sprintf ("toolhive/logs/%s.log" , workloadName ))
252260 if err != nil {
@@ -262,11 +270,11 @@ func getProxyLogs(workloadName string) error {
262270 return nil
263271 }
264272
265- return followProxyLogFile (cleanLogFilePath )
273+ return followProxyLogFile (ctx , cleanLogFilePath )
266274}
267275
268276// followProxyLogFile implements tail -f functionality for proxy logs
269- func followProxyLogFile (logFilePath string ) error {
277+ func followProxyLogFile (ctx context. Context , logFilePath string ) error {
270278 // Clean the file path to prevent path traversal
271279 cleanLogFilePath := filepath .Clean (logFilePath )
272280
@@ -295,6 +303,11 @@ func followProxyLogFile(logFilePath string) error {
295303 }
296304
297305 // Follow the file for new content
306+ contentCheckInterval := 100 * time .Millisecond
307+
308+ ticker := time .NewTicker (contentCheckInterval )
309+ defer ticker .Stop ()
310+
298311 for {
299312 // Read any new content
300313 buffer := make ([]byte , 1024 )
@@ -307,7 +320,12 @@ func followProxyLogFile(logFilePath string) error {
307320 fmt .Print (string (buffer [:n ]))
308321 }
309322
310- // Sleep briefly before checking for more content
311- time .Sleep (100 * time .Millisecond )
323+ // Wait for next iteration or cancellation
324+ select {
325+ case <- ctx .Done ():
326+ return nil
327+ case <- ticker .C :
328+ // Continue to next iteration
329+ }
312330 }
313331}
0 commit comments