diff --git a/cmd/ooni/main.go b/cmd/ooni/main.go index d6567bc..0492259 100644 --- a/cmd/ooni/main.go +++ b/cmd/ooni/main.go @@ -3,6 +3,7 @@ package main import ( // commands + "github.com/apex/log" _ "github.com/ooni/probe-cli/internal/cli/geoip" _ "github.com/ooni/probe-cli/internal/cli/info" _ "github.com/ooni/probe-cli/internal/cli/list" @@ -19,5 +20,9 @@ import ( ) func main() { - crashreport.CapturePanicAndWait(app.Run, nil) + err, _ := crashreport.CapturePanic(app.Run, nil) + if err != nil { + log.WithError(err.(error)).Error("panic in app.Run") + crashreport.Wait() + } } diff --git a/internal/cli/list/list.go b/internal/cli/list/list.go index 4a8bbf4..0534a87 100644 --- a/internal/cli/list/list.go +++ b/internal/cli/list/list.go @@ -27,27 +27,41 @@ func init() { } msmtSummary := output.MeasurementSummaryData{ - TotalCount: 0, - AnomalyCount: 0, - DataUsageUp: 0.0, - DataUsageDown: 0.0, - TotalRuntime: 0, + TotalCount: 0, + AnomalyCount: 0, + DataUsageUp: 0.0, + DataUsageDown: 0.0, + TotalRuntime: 0, + ASN: 0, + NetworkName: "", + NetworkCountryCode: "ZZ", } - for _, msmt := range measurements { + isFirst := true + isLast := false + for idx, msmt := range measurements { + if idx > 0 { + isFirst = false + } + if idx == len(measurements)-1 { + isLast = true + } + // We assume that since these are summary level information the first // item will contain the information necessary. - if msmtSummary.TotalRuntime == 0 { + if isFirst { msmtSummary.TotalRuntime = msmt.ResultRuntime - } - if msmtSummary.DataUsageUp == 0 { msmtSummary.DataUsageUp = msmt.DataUsageUp msmtSummary.DataUsageDown = msmt.DataUsageDown + msmtSummary.NetworkName = msmt.NetworkName + msmtSummary.NetworkCountryCode = msmt.NetworkCountryCode + msmtSummary.ASN = msmt.ASN + msmtSummary.StartTime = msmt.MeasurementStartTime } if msmt.IsAnomaly.Bool == true { msmtSummary.AnomalyCount++ } msmtSummary.TotalCount++ - output.MeasurementItem(msmt) + output.MeasurementItem(msmt, isFirst, isLast) } output.MeasurementSummary(msmtSummary) } else { diff --git a/internal/crashreport/crashreport.go b/internal/crashreport/crashreport.go index 21e2c1c..29801f2 100644 --- a/internal/crashreport/crashreport.go +++ b/internal/crashreport/crashreport.go @@ -1,6 +1,7 @@ package crashreport import ( + "github.com/apex/log" "github.com/getsentry/raven-go" ) @@ -8,13 +9,15 @@ import ( // crash reporting logic a no-op. var Disabled = false +var client *raven.Client + // CapturePanic is a wrapper around raven.CapturePanic that becomes a noop if // `Disabled` is set to true. func CapturePanic(f func(), tags map[string]string) (interface{}, string) { if Disabled == true { return nil, "" } - return raven.CapturePanic(f, tags) + return client.CapturePanic(f, tags) } // CapturePanicAndWait is a wrapper around raven.CapturePanicAndWait that becomes a noop if @@ -23,7 +26,7 @@ func CapturePanicAndWait(f func(), tags map[string]string) (interface{}, string) if Disabled == true { return nil, "" } - return raven.CapturePanicAndWait(f, tags) + return client.CapturePanicAndWait(f, tags) } // CaptureError is a wrapper around raven.CaptureError @@ -31,7 +34,7 @@ func CaptureError(err error, tags map[string]string) string { if Disabled == true { return "" } - return raven.CaptureError(err, tags) + return client.CaptureError(err, tags) } // CaptureErrorAndWait is a wrapper around raven.CaptureErrorAndWait @@ -39,9 +42,21 @@ func CaptureErrorAndWait(err error, tags map[string]string) string { if Disabled == true { return "" } - return raven.CaptureErrorAndWait(err, tags) + return client.CaptureErrorAndWait(err, tags) +} + +// Wait will block on sending messages to the sentry server +func Wait() { + if Disabled == false { + log.Info("sending exception backtrace") + client.Wait() + } } func init() { - raven.SetDSN("https://cb4510e090f64382ac371040c19b2258:8448daeebfa643c289ef398f8645980b@sentry.io/1234954") + var err error + client, err = raven.NewClient("https://cb4510e090f64382ac371040c19b2258:8448daeebfa643c289ef398f8645980b@sentry.io/1234954", nil) + if err != nil { + log.WithError(err).Error("failed to create a raven client") + } } diff --git a/internal/log/handlers/cli/cli.go b/internal/log/handlers/cli/cli.go index 8e4c2ec..a167ad6 100644 --- a/internal/log/handlers/cli/cli.go +++ b/internal/log/handlers/cli/cli.go @@ -114,6 +114,10 @@ func (h *Handler) TypedLog(t string, e *log.Entry) error { return nil case "table": return logTable(h.Writer, e.Fields) + case "measurement_item": + return logMeasurementItem(h.Writer, e.Fields) + case "measurement_summary": + return logMeasurementSummary(h.Writer, e.Fields) case "result_item": return logResultItem(h.Writer, e.Fields) case "result_summary": diff --git a/internal/log/handlers/cli/measurements.go b/internal/log/handlers/cli/measurements.go new file mode 100644 index 0000000..e3c4f0c --- /dev/null +++ b/internal/log/handlers/cli/measurements.go @@ -0,0 +1,103 @@ +package cli + +import ( + "fmt" + "io" + "strings" + "time" + + "github.com/apex/log" + "github.com/ooni/probe-cli/internal/util" +) + +func statusIcon(ok bool) string { + if ok { + return "✓" + } + return "❌" +} +func logMeasurementItem(w io.Writer, f log.Fields) error { + colWidth := 24 + + rID := f.Get("id").(int64) + testName := f.Get("test_name").(string) + + // We currently don't use these fields in the view + //testGroupName := f.Get("test_group_name").(string) + //networkName := f.Get("network_name").(string) + //asn := fmt.Sprintf("AS%d (%s)", f.Get("asn").(uint), f.Get("network_country_code").(string)) + testKeys := f.Get("test_keys").(string) + + isAnomaly := f.Get("is_anomaly").(bool) + isFailed := f.Get("is_failed").(bool) + isUploaded := f.Get("is_uploaded").(bool) + url := f.Get("url").(string) + urlCategoryCode := f.Get("url_category_code").(string) + + isFirst := f.Get("is_first").(bool) + isLast := f.Get("is_last").(bool) + if isFirst { + fmt.Fprintf(w, "┏"+strings.Repeat("━", colWidth*2+2)+"┓\n") + } else { + fmt.Fprintf(w, "┢"+strings.Repeat("━", colWidth*2+2)+"┪\n") + } + + anomalyStr := fmt.Sprintf("ok: %s", statusIcon(!isAnomaly)) + uploadStr := fmt.Sprintf("uploaded: %s", statusIcon(isUploaded)) + failureStr := fmt.Sprintf("success: %s", statusIcon(!isFailed)) + + fmt.Fprintf(w, fmt.Sprintf("│ %s │\n", + util.RightPad( + fmt.Sprintf("#%d", rID), colWidth*2))) + + if url != "" { + fmt.Fprintf(w, fmt.Sprintf("│ %s │\n", + util.RightPad( + fmt.Sprintf("%s (%s)", url, urlCategoryCode), colWidth*2))) + } + + fmt.Fprintf(w, fmt.Sprintf("│ %s %s│\n", + util.RightPad(testName, colWidth), + util.RightPad(anomalyStr, colWidth))) + + fmt.Fprintf(w, fmt.Sprintf("│ %s │\n", + util.RightPad(testKeys, colWidth*2))) + fmt.Fprintf(w, fmt.Sprintf("│ %s %s│\n", + util.RightPad(failureStr, colWidth), + util.RightPad(uploadStr, colWidth))) + + if isLast { + fmt.Fprintf(w, "└┬────────────────────────────────────────────────┬┘\n") + } + return nil +} + +func logMeasurementSummary(w io.Writer, f log.Fields) error { + colWidth := 12 + + totalCount := f.Get("total_count").(int64) + anomalyCount := f.Get("anomaly_count").(int64) + totalRuntime := f.Get("total_runtime").(float64) + dataUp := f.Get("data_usage_up").(float64) + dataDown := f.Get("data_usage_down").(float64) + + startTime := f.Get("start_time").(time.Time) + + asn := f.Get("asn").(uint) + countryCode := f.Get("network_country_code").(string) + networkName := f.Get("network_name").(string) + + fmt.Fprintf(w, " │ %s │\n", + util.RightPad(startTime.Format(time.RFC822), (colWidth+3)*3), + ) + fmt.Fprintf(w, " │ %s │\n", + util.RightPad(fmt.Sprintf("AS%d, %s (%s)", asn, networkName, countryCode), (colWidth+3)*3), + ) + fmt.Fprintf(w, " │ %s %s %s │\n", + util.RightPad(fmt.Sprintf("%.2fs", totalRuntime), colWidth), + util.RightPad(fmt.Sprintf("%d/%d anmls", anomalyCount, totalCount), colWidth), + util.RightPad(fmt.Sprintf("⬆ %s ⬇ %s", formatSize(dataUp), formatSize(dataDown)), colWidth+4)) + fmt.Fprintf(w, " └────────────────────────────────────────────────┘\n") + + return nil +} diff --git a/internal/onboard/onboard.go b/internal/onboard/onboard.go index f34a9ff..2144d0d 100644 --- a/internal/onboard/onboard.go +++ b/internal/onboard/onboard.go @@ -126,7 +126,7 @@ func Onboarding(config *config.Config) error { config.Lock() config.InformedConsent = true - config.Advanced.IncludeCountry = settings.IncludeCountry + config.Sharing.IncludeCountry = settings.IncludeCountry config.Advanced.SendCrashReports = settings.SendCrashReports config.Sharing.IncludeIP = settings.IncludeIP config.Sharing.IncludeASN = settings.IncludeNetwork diff --git a/internal/output/output.go b/internal/output/output.go index 8a1328a..0391cd3 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -21,11 +21,15 @@ func Progress(key string, perc float64, msg string) { } type MeasurementSummaryData struct { - TotalRuntime float64 - TotalCount int64 - AnomalyCount int64 - DataUsageUp float64 - DataUsageDown float64 + TotalRuntime float64 + TotalCount int64 + AnomalyCount int64 + DataUsageUp float64 + DataUsageDown float64 + ASN uint + NetworkName string + NetworkCountryCode string + StartTime time.Time } func MeasurementSummary(msmt MeasurementSummaryData) { @@ -36,13 +40,20 @@ func MeasurementSummary(msmt MeasurementSummaryData) { "anomaly_count": msmt.AnomalyCount, "data_usage_down": msmt.DataUsageDown, "data_usage_up": msmt.DataUsageUp, + "asn": msmt.ASN, + "network_country_code": msmt.NetworkCountryCode, + "network_name": msmt.NetworkName, + "start_time": msmt.StartTime, }).Info("measurement summary") } // MeasurementItem logs a progress type event -func MeasurementItem(msmt database.MeasurementURLNetwork) { +func MeasurementItem(msmt database.MeasurementURLNetwork, isFirst bool, isLast bool) { log.WithFields(log.Fields{ - "type": "measurement_item", + "type": "measurement_item", + "is_first": isFirst, + "is_last": isLast, + "id": msmt.MsmtTblID, "test_name": msmt.TestName, "test_group_name": msmt.Result.TestGroupName, diff --git a/nettests/nettests.go b/nettests/nettests.go index c01b290..e02caaf 100644 --- a/nettests/nettests.go +++ b/nettests/nettests.go @@ -120,7 +120,7 @@ func (c *Controller) Init(nt *mk.Nettest) error { nt.Options = mk.NettestOptions{ IncludeIP: c.Ctx.Config.Sharing.IncludeIP, IncludeASN: c.Ctx.Config.Sharing.IncludeASN, - IncludeCountry: c.Ctx.Config.Advanced.IncludeCountry, + IncludeCountry: c.Ctx.Config.Sharing.IncludeCountry, LogLevel: "DEBUG", ProbeCC: c.Ctx.Location.CountryCode,