diff --git a/README.md b/README.md index fe5ccf2..1ff0a1b 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,14 @@ Flags: --config string config file (default is $HOME/.spinner.yaml) -d, --debug Print debug logging -h, --help help for spinner - -t, --tail string Path to file to tail and pipe to STDOUT. + -t, --tail string Path to file to tail and pipe to STDOUT + -o, --out Path to file to write response body if site response status >= 300 Service Usage: spinner service [name] [flags] Site Usage: - spinner site [url] [flags] + spinner site [url] [counter limit] [time to exit] [flags] ``` @@ -51,4 +52,4 @@ Spinner. spinner.exe service W3SVC -t c:\\iislog\\W3SVC\\u_extend1.log ``` -The Linux build is experimental and does not include the `service` command. \ No newline at end of file +The Linux build is experimental and does not include the `service` command. diff --git a/cmd/root.go b/cmd/root.go index 33a4440..f0724c3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,6 +25,7 @@ import ( var cfgFile string var tailFile string +var outFile string var debugFlag bool //func Kill(err error) { @@ -72,6 +73,7 @@ func init() { RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.spinner.yaml)") RootCmd.PersistentFlags().StringVarP(&tailFile, "tail", "t", "", "Path to file to tail and pipe to STDOUT.") + RootCmd.PersistentFlags().StringVarP(&outFile, "out", "o", "", "Path to file to write errors out to.") RootCmd.PersistentFlags().BoolVarP(&debugFlag, "debug", "d", false, "Print debug logging") } diff --git a/cmd/site.go b/cmd/site.go index 02236c5..756a48c 100644 --- a/cmd/site.go +++ b/cmd/site.go @@ -14,11 +14,14 @@ package cmd import ( + "bufio" "fmt" "io/ioutil" "log" "net/http" "os" + "path/filepath" + "strconv" s "strings" "time" @@ -27,8 +30,8 @@ import ( var urlFlag string -func queryPage(u string) { - +func queryPage(u, cl, tte string) { + var c int64 var fullURL string if s.HasPrefix(u, "http://") || s.HasPrefix(u, "https://") { @@ -37,41 +40,110 @@ func queryPage(u string) { fullURL = "http://" + u } - fmt.Println("Full URL being monitored:", fullURL) + log.Println("Full URL being monitored:", fullURL, "Counter limit:", cl) + for { + cl, err := strconv.ParseInt(cl, 10, 8) + if err != nil { + log.Fatal(err) + } + + tte, err := strconv.ParseInt(tte, 10, 8) + if err != nil { + log.Fatal(err) + } - resp, err := http.Get(fullURL) + // Set 30 second timeout for page GET + tr := &http.Transport{ + IdleConnTimeout: 30 * time.Second, + DisableCompression: true, + } + client := &http.Client{Transport: tr} + resp, err := client.Get(fullURL) if err != nil { log.Fatal("An error occurred during the request:", err) } if resp.StatusCode >= 300 { - rd, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) + c++ + if c >= cl { + rd, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + // Convert rd to string and remove carriage returns from output + rs := s.Replace(string(rd), "\r", "", -1) + // Remove line feeds from output + rs = s.Replace(rs, "\n", "", -1) + + // Output to file if path is set + if outFile != "" { + d, _ := filepath.Split(outFile) + err := os.MkdirAll(d, os.ModePerm) + if err != nil { + panic(err) + } + + f, err := os.OpenFile(outFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) + if err != nil { + panic(err) + } + defer f.Close() + + ls := time.Now().Format("2006-01-02 15:04:05") + " [spinner-app] " + rs + "\n" + + b := bufio.NewWriterSize(f, 10000) + _, err = b.WriteString(ls) + if err != nil { + log.Println(err) + } + + err = b.Flush() + if err != nil { + log.Println(err) + } + } + + // Output to stdout + log.Println("Status Code:", resp.StatusCode, " Body:", rs) + // Sleep for n second(s) allowing output error to stdout to be picked up + // by monitoring software/container (if needed) + time.Sleep(time.Duration(tte) * time.Second) + log.Fatalln("Spinner shutting down, status code was:", resp.StatusCode) + } else { + log.Println("Status Code:", resp.StatusCode, "Counter count:", c) } - // Convert rd to string and remove line breaks to output on single line - rs := s.Replace(string(rd), "\r\n", "", -1) - log.Fatalf("Status Code: %v Body: %s", resp.StatusCode, rs) } else if debugFlag { + c = 0 log.Println("Status Code:", resp.StatusCode) } resp.Body.Close() - time.Sleep(1000 * time.Millisecond) + time.Sleep(1 * time.Second) } } // siteCmd represents the site command var siteCmd = &cobra.Command{ - Use: "site [url]", + Use: "site [url] [counter limit] [time to exit]", Short: "Watch a Site", Aliases: []string{"url", "address"}, - Example: "spinner.exe site http://localhost -t c:\\iislog\\W3SVC\\u_extend1.log", - Long: `Poll Web Site by Get request and terminate this process if -the a >300 status code is returned. + Example: "spinner.exe site http://localhost 5 5 -t c:\\iislog\\W3SVC\\u_extend1.log -o c:\\logs\\spinner.log", + Long: `Poll Web Site by Get request and terminate this process if the a >300 +status code is returned. + +Counter Limit (default 1) is the number of times the site being monitored can +be down before spinner exits. + +Time to Exit (default 1) is the time (in seconds) after the response body is +logged to stdout before spinner will shutdown. This can be useful +if the monitoring software does not catch the error quick enough. + +OutFile is the location path to a file to write out the response body if +the response status is >= 300. This can be useful in troubleshooting why +the application is exiting. Use this as the entrypoint for a container to stop the container if the given service stops.`, @@ -86,9 +158,13 @@ the given service stops.`, if tailFile != "" { go TailLog() } - - queryPage(args[0]) - + if len(args) == 1 { + queryPage(args[0], "1", "1") + } else if len(args) == 2 { + queryPage(args[0], args[1], "1") + } else { + queryPage(args[0], args[1], args[2]) + } }, }