Skip to content

Commit

Permalink
feat: finish pr #72
Browse files Browse the repository at this point in the history
  • Loading branch information
jesusprubio committed Nov 19, 2024
1 parent c344987 commit 2692ae9
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 62 deletions.
6 changes: 3 additions & 3 deletions internal/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ type Options struct {
// Output flags.
// Output in JSON format.
JSONOutput bool
// Output in Grepable format.
GrepFormat bool
// Output in grepable format.
GrepOutput bool
// Disable color output.
NoColor bool
// Enable debugging.
Expand All @@ -49,7 +49,7 @@ func (opts *Options) Parse() {
)
flag.StringVar(&opts.DNSResolver, "dr", "", "DNS resolution server")
flag.BoolVar(&opts.JSONOutput, "j", false, "Output in JSON format")
flag.BoolVar(&opts.GrepFormat, "g", false, "Output in Grepable format")
flag.BoolVar(&opts.GrepOutput, "g", false, "Output in grepable format")
flag.BoolVar(&opts.NoColor, "nc", false, "Disable color output")
flag.BoolVar(&opts.Debug, "dbg", false, "Verbose output")
flag.BoolVar(&opts.Help, "h", false, "Show app documentation")
Expand Down
11 changes: 9 additions & 2 deletions internal/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,21 @@ func (p Probe) Do(ctx context.Context) error {
p.Logger.Debug(
"New protocol", "count", count, "protocol", proto,
)
var errMessage string
rhost, extra, err := proto.Probe("")
if err != nil {
errMessage = err.Error()
}
report := Report{
ProtocolID: proto.String(),
Time: time.Since(start),
Error: err,
RHost: rhost,
Time: time.Since(start),
Error: errMessage,
Extra: extra,
}
if err != nil {
errMessage = err.Error()

Check failure on line 92 in internal/probe.go

View workflow job for this annotation

GitHub Actions / Unit tests

this value of errMessage is never used (SA4006)
}
p.Logger.Debug(
"Sending report back",
"count", count, "report", report,
Expand Down
2 changes: 1 addition & 1 deletion internal/probe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestProbeDo(t *testing.T) {
if report.Time == 0 {
t.Errorf("got %q, want > 0", report.Time)
}
if report.Error != nil {
if report.Error != "" {
t.Errorf("got %q, want nil", report.Error)
}
}
Expand Down
83 changes: 43 additions & 40 deletions internal/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import (
"github.com/fatih/color"
)

// Report is the result of a connection attempt.
//
// Only one of the properties 'Response' or 'Error' is set.

// Format is the output format of the report.
type Format int

const (
JSONFormat Format = iota
HumanFormat
HumanFormat Format = iota
JSONFormat
GrepFormat
)

// Report is the result of a connection attempt.
//
// Only one of the properties 'Response' or 'Error' is set.
type Report struct {
// Protocol used to connect to.
ProtocolID string `json:"protocol"`
Expand All @@ -28,72 +28,75 @@ type Report struct {
// Response time.
Time time.Duration `json:"time"`
// Network error.
Error error `json:"error,omitempty"`
Error string `json:"error,omitempty"`
// Extra information. Depends on the protocol.
Extra string `json:"extra,omitempty"`
Format Format
Extra string `json:"extra,omitempty"`
}

func (r *Report) NewLine(f Format) (string, error) {
switch f {
// String returns a string representation of the report.
func (r *Report) String(format Format) (string, error) {
switch format {
case HumanFormat:
return r.newLineHuman(), nil
return r.stringHuman(), nil
case JSONFormat:
line, err := r.newLineJSON()
line, err := r.stringJSON()
if err != nil {
return "", fmt.Errorf("error generating JSON report: %w", err)
}
return line, nil
case GrepFormat:
return r.newLineGrep(), nil
return r.stringGrep(), nil
default:
return "", fmt.Errorf("unsupported format: %v", f)
return "", fmt.Errorf("unsupported format: %v", format)
}
}

func (r *Report) newLineJSON() (string, error) {

// Returns the report in JSON format.
// Example:
// '{"protocol":"tcp","rhost":"64.6.65.6:53","time":13433165,"extra":"192.168.1.177:39384"}'
func (r *Report) stringJSON() (string, error) {
reportJSON, err := json.Marshal(r)
if err != nil {
return "", fmt.Errorf("marshaling report: %w", err)

}
return string(reportJSON), nil
}

var (
green = color.New(color.FgGreen).SprintFunc()
red = color.New(color.FgRed).SprintFunc()
bold = color.New(color.Bold).SprintFunc()
faint = color.New(color.Faint).SprintFunc()
)

func (r *Report) newLineHuman() string {
// Returns the report in human readable format.
// Example: '✔ tcp 100.077875ms 77.88.8.8:53 (192.168.1.177:43586)
func (r *Report) stringHuman() string {
line := fmt.Sprintf("%-15s %-14s %s", bold(r.ProtocolID), r.Time, r.RHost)
suffix := r.Extra
prefix := green("✔")
if r.Error != nil {
if r.Error != "" {
prefix = red("✘")
suffix = r.Error.Error()
suffix = r.Error
}
suffix = fmt.Sprintf("(%s)", suffix)

return fmt.Sprintf("%s %s %s", prefix, line, faint(suffix))
}

// Output: HTTP/1.1 2024-11-18T15:00:00Z 192.168.1.1 success Request
// processed successfully
func (r *Report) newLineGrep() string {
status := "success"
if r.Error != nil {
status = "failure"
var (
green = color.New(color.FgGreen).SprintFunc()
red = color.New(color.FgRed).SprintFunc()
bold = color.New(color.Bold).SprintFunc()
faint = color.New(color.Faint).SprintFunc()
)

// Returns the report in a grepable format.
//
// Example: 'tcp 13.944825ms 195.46.39.40:53 success 192.168.1.177:43296
func (r *Report) stringGrep() string {
status := "ok"
if r.Error != "" {
status = "error"
}
suffix := r.Extra
if r.Error != "" {
suffix = r.Error
}
line := fmt.Sprintf("%s\t%s\t%s\t%s\t%s",
r.ProtocolID,
r.Time,
r.RHost,
status,
r.Extra,
r.ProtocolID, r.Time, r.RHost, status, suffix,
)
return line
}
137 changes: 137 additions & 0 deletions internal/report_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package internal

import (
"testing"
)

func TestReportString(t *testing.T) {
r := Report{
ProtocolID: "tcp",
RHost: "127.0.0.1:80",
Time: 1,
Extra: "extra-0",
}
t.Run("returns a report using human format", func(t *testing.T) {
got, err := r.String(HumanFormat)
if err != nil {
t.Fatal(err)
}
want := "✔ tcp 1ns 127.0.0.1:80 (extra-0)"
if got != want {
t.Fatalf("got %q, want %q", got, want)
}
})
t.Run("returns a report using JSON format", func(t *testing.T) {
got, err := r.String(JSONFormat)
if err != nil {
t.Fatal(err)
}
want := `{"protocol":"tcp","rhost":"127.0.0.1:80","time":1,"extra":"extra-0"}`
if got != want {
t.Fatalf("got %q, want %q", got, want)
}
})
t.Run("returns a report using a grepable format", func(t *testing.T) {
got, err := r.String(GrepFormat)
if err != nil {
t.Fatal(err)
}
want := "tcp\t1ns\t127.0.0.1:80\tok\textra-0"
if got != want {
t.Fatalf("got %q, want %q", got, want)
}
})

}

func TestStringJSON(t *testing.T) {
r := Report{
ProtocolID: "tcp",
RHost: "127.0.0.1:80",
Time: 1,
Extra: "extra-0",
}
t.Run("returns JSON format for successful probes", func(t *testing.T) {
got, err := r.stringJSON()
if err != nil {
t.Fatal(err)
}
want := `{"protocol":"tcp","rhost":"127.0.0.1:80","time":1,"extra":"extra-0"}`
if got != want {
t.Fatalf("got %q, want %q", got, want)
}
},
)
t.Run("returns JSON format for failed probes", func(t *testing.T) {
rErr := r
rErr.Extra = ""
rErr.Error = "error-0"
got, err := rErr.stringJSON()
if err != nil {
t.Fatal(err)
}
want := `{"protocol":"tcp","rhost":"127.0.0.1:80","time":1,"error":"error-0"}`
if got != want {
t.Fatalf("got %q, want %q", got, want)
}
},
)
}

func TestStringHuman(t *testing.T) {
r := Report{
ProtocolID: "tcp",
RHost: "127.0.0.1:80",
Time: 1,
Extra: "extra-0",
}
t.Run("returns human readable format for successful probes",
func(t *testing.T) {
got := r.stringHuman()
want := "✔ tcp 1ns 127.0.0.1:80 (extra-0)"
if got != want {
t.Fatalf("got %q, want %q", got, want)
}
},
)
t.Run("returns human readable format for failed probes",
func(t *testing.T) {
rErr := r
rErr.Extra = ""
rErr.Error = "error-0"
got := rErr.stringHuman()
want := "✘ tcp 1ns 127.0.0.1:80 (error-0)"
if got != want {
t.Fatalf("got %q, want %q", got, want)
}
},
)
}

func TestStringGrep(t *testing.T) {
r := Report{
ProtocolID: "tcp",
RHost: "127.0.0.1:80",
Time: 1,
Extra: "extra-0",
}
t.Run("returns grep format for successful probes", func(t *testing.T) {
got := r.stringGrep()
want := "tcp\t1ns\t127.0.0.1:80\tok\textra-0"
if got != want {
t.Fatalf("got %q, want %q", got, want)
}
},
)
t.Run("returns grep format for failed probes", func(t *testing.T) {
rErr := r
rErr.Extra = ""
rErr.Error = "error-0"
got := rErr.stringGrep()
want := "tcp\t1ns\t127.0.0.1:80\terror\terror-0"
if got != want {
t.Fatalf("got %q, want %q", got, want)
}
},
)
}
28 changes: 12 additions & 16 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,29 +99,25 @@ func main() {
Logger: logger,
ReportCh: reportCh,
}
var format internal.Format
switch {
case opts.JSONOutput:
format = internal.JSONFormat
case opts.GrepOutput:
format = internal.GrepFormat
default:
format = internal.HumanFormat
}
go func() {
logger.Debug("Listening for reports ...")
for report := range probe.ReportCh {
logger.Debug("New report", "report", *report)

var format internal.Format
switch {
case opts.JSONOutput:
format = internal.JSONFormat
case opts.GrepFormat:
format = internal.GrepFormat
default:
format = internal.HumanFormat
}
repLine, err := report.NewLine(format)

repLine, err := report.String(format)
if err != nil {
fatal(err)
}

fmt.Println(repLine)

if report.Error == nil {
if report.Error == "" {
if opts.Stop {
logger.Debug("Stopping after first successful request")
cancel()
Expand All @@ -136,7 +132,7 @@ func main() {
}
}

// Logs the error to the standard output and exits with status 1.
// Prints the error to the standard output and exits with status 1.
func fatal(err error) {
fmt.Fprintf(os.Stderr, "%s: %s\n", appName, err)
os.Exit(1)
Expand Down

0 comments on commit 2692ae9

Please sign in to comment.