func sampleData(c appdash.Collector) error { const numTraces = 60 log.Printf("Adding sample data (%d traces)", numTraces) for i := appdash.ID(1); i <= numTraces; i++ { traceID := appdash.NewRootSpanID() traceRec := appdash.NewRecorder(traceID, c) traceRec.Name(fakeHosts[rand.Intn(len(fakeHosts))]) // A random length for the trace. length := time.Duration(rand.Intn(1000)) * time.Millisecond startTime := time.Now().Add(-time.Duration(rand.Intn(100)) * time.Minute) traceRec.Event(&sqltrace.SQLEvent{ ClientSend: startTime, ClientRecv: startTime.Add(length), SQL: "SELECT * FROM table_name;", Tag: fmt.Sprintf("fakeTag%d", rand.Intn(10)), }) // We'll split the trace into N (3-7) spans (i.e. "N operations") each with // a random duration of time adding up to the length of the trace. numSpans := rand.Intn(7-3) + 3 times := randomSplit(int(length/time.Millisecond), numSpans) lastSpanID := traceID for j := 1; j <= numSpans; j++ { // The parent span is the predecessor. spanID := appdash.NewSpanID(lastSpanID) rec := appdash.NewRecorder(spanID, c) rec.Name(fakeNames[(j+int(i))%len(fakeNames)]) if j%3 == 0 { rec.Log("hello") } if j%5 == 0 { rec.Msg("hi") } // Generate a span event. spanDuration := time.Duration(times[j-1]) * time.Millisecond rec.Event(&sqltrace.SQLEvent{ ClientSend: startTime, ClientRecv: startTime.Add(spanDuration), SQL: "SELECT * FROM table_name;", Tag: fmt.Sprintf("fakeTag%d", rand.Intn(10)), }) // Shift the start time forward. startTime = startTime.Add(spanDuration) // Check for any recorder errors. if errs := rec.Errors(); len(errs) > 0 { return fmt.Errorf("recorder errors: %v", errs) } lastSpanID = spanID } } return nil }
// Home is the homepage handler for our app. func Home(w http.ResponseWriter, r *http.Request) { // Grab the span from the gorilla context. We do this so that we can grab // the span.Trace ID and link directly to the trace on the web-page itself! span := context.Get(r, CtxSpanID).(appdash.SpanID) // We're going to make some API requests, so we create a HTTP client using // a appdash/httptrace transport here. The transport will inform Appdash of // the HTTP events occuring. httpClient := &http.Client{ Transport: &httptrace.Transport{ Recorder: appdash.NewRecorder(span, collector), SetName: true, }, } // Make three API requests using our HTTP client. for i := 0; i < 3; i++ { resp, err := httpClient.Get("http://localhost:8699/endpoint") if err != nil { log.Println("/endpoint:", err) continue } resp.Body.Close() } // Render the page. fmt.Fprintf(w, `<p>Three API requests have been made!</p>`) fmt.Fprintf(w, `<p><a href="http://localhost:8700/traces/%s" target="_">View the trace (ID:%s)</a></p>`, span.Trace, span.Trace) }
func (a *demoApp) ServeHTTP(w http.ResponseWriter, r *http.Request) { span := requestSpans[r] switch r.URL.Path { case "/": io.WriteString(w, `<h1>Appdash demo</h1> <p>Welcome! Click some links and then view the traces for each HTTP request by following the link at the bottom of the page. <ul> <li><a href="/api-calls">Visit a page that issues some API calls</a></li> </ul>`) case "/api-calls": httpClient := &http.Client{ Transport: &httptrace.Transport{Recorder: appdash.NewRecorder(span, a.collector), SetName: true}, } resp, err := httpClient.Get(a.baseURL.ResolveReference(&url.URL{Path: "/endpoint-A"}).String()) if err == nil { defer resp.Body.Close() } resp, err = httpClient.Get(a.baseURL.ResolveReference(&url.URL{Path: "/endpoint-B"}).String()) if err == nil { defer resp.Body.Close() } resp, err = httpClient.Get(a.baseURL.ResolveReference(&url.URL{Path: "/endpoint-C"}).String()) if err == nil { defer resp.Body.Close() } io.WriteString(w, `<a href="/">Home</a><br><br><p>I just made 3 API calls. Check the trace below to see them!</p>`) case "/endpoint-A": time.Sleep(250 * time.Millisecond) io.WriteString(w, "performed an operation!") return case "/endpoint-B": time.Sleep(75 * time.Millisecond) io.WriteString(w, "performed another operation!") return case "/endpoint-C": time.Sleep(300 * time.Millisecond) io.WriteString(w, "performed yet another operation!") return } spanURL := a.appdashURL.ResolveReference(&url.URL{Path: fmt.Sprintf("/traces/%v", span.Trace)}) io.WriteString(w, fmt.Sprintf(`<br><br><hr><a href="%s">View request trace on appdash</a> (trace ID is %s)`, spanURL, span.Trace)) }
func Home(w http.ResponseWriter, r *http.Request) { span := context.Get(r, CtxSpanID).(appdash.SpanID) httpClient := &http.Client{ Transport: &httptrace.Transport{ Recorder: appdash.NewRecorder(span, collector), SetName: true, }, } for i := 0; i < 3; i++ { resp, err := httpClient.Get("http://localhost:8699/endpoint") if err != nil { log.Println("/endpoint:", err) continue } resp.Body.Close() } fmt.Fprintf(w, `<p>Three API requests have been made!</p>`) fmt.Fprintf(w, `<p><a href="http://localhost:8700/traces/%s" target="_">View the trace (ID:%s)</a></p>`, span.Trace, span.Trace) }
// Middleware creates a new http.Handler middleware // (negroni-compliant) that records incoming HTTP requests to the // collector c as "HTTPServer"-schema events. func Middleware(c appdash.Collector, conf *MiddlewareConfig) func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { return func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { spanID, spanFromHeader, err := getSpanID(r.Header) if err != nil { log.Printf("Warning: invalid %s header: %s. (Continuing with request handling.)", spanFromHeader, err) } usingProvidedSpanID := (spanFromHeader == HeaderSpanID) if conf.SetContextSpan != nil { conf.SetContextSpan(r, *spanID) } e := NewServerEvent(r) e.ServerRecv = time.Now() rr := &responseInfoRecorder{ResponseWriter: rw} next(rr, r) SetSpanIDHeader(rr.Header(), *spanID) if !usingProvidedSpanID { e.Request = requestInfo(r) } if conf.RouteName != nil { e.Route = conf.RouteName(r) } if conf.CurrentUser != nil { e.User = conf.CurrentUser(r) } e.Response = responseInfo(rr.partialResponse()) e.ServerSend = time.Now() rec := appdash.NewRecorder(*spanID, c) if e.Route != "" { rec.Name("Serve " + e.Route) } else { rec.Name("Serve " + r.URL.Host + r.URL.Path) } rec.Event(e) rec.Finish() } }
func TestCancelRequest(t *testing.T) { ms := appdash.NewMemoryStore() rec := appdash.NewRecorder(appdash.SpanID{1, 2, 3}, appdash.NewLocalCollector(ms)) req, _ := http.NewRequest("GET", "http://example.com/foo", nil) transport := &Transport{ Recorder: rec, } client := &http.Client{ Timeout: 1 * time.Millisecond, Transport: transport, } resp, err := client.Do(req) expected := "Get http://example.com/foo: net/http: request canceled while waiting for connection" if err == nil || !strings.HasPrefix(err.Error(), expected) { t.Errorf("got %#v, want %s", err, expected) } if resp != nil { t.Errorf("got http.Response %#v, want nil", resp) } }
func TestTransport(t *testing.T) { ms := appdash.NewMemoryStore() rec := appdash.NewRecorder(appdash.SpanID{1, 2, 3}, appdash.NewLocalCollector(ms)) req, _ := http.NewRequest("GET", "http://example.com/foo", nil) req.Header.Set("X-Req-Header", "a") mt := &mockTransport{ resp: &http.Response{ StatusCode: 200, ContentLength: 123, Header: http.Header{"X-Resp-Header": []string{"b"}}, }, } transport := &Transport{ Recorder: rec, Transport: mt, } _, err := transport.RoundTrip(req) if err != nil { t.Fatal(err) } spanID, err := appdash.ParseSpanID(mt.req.Header.Get("Span-ID")) if err != nil { t.Fatal(err) } if want := (appdash.SpanID{1, spanID.Span, 2}); *spanID != want { t.Errorf("got Span-ID in header %+v, want %+v", *spanID, want) } trace, err := ms.Trace(1) if err != nil { t.Fatal(err) } var e ClientEvent if err := appdash.UnmarshalEvent(trace.Span.Annotations, &e); err != nil { t.Fatal(err) } wantEvent := ClientEvent{ Request: RequestInfo{ Method: "GET", Proto: "HTTP/1.1", URI: "/foo", Host: "example.com", Headers: map[string]string{"X-Req-Header": "a"}, }, Response: ResponseInfo{ StatusCode: 200, ContentLength: 123, Headers: map[string]string{"X-Resp-Header": "b"}, }, } delete(e.Request.Headers, "Span-Id") e.ClientSend = time.Time{} e.ClientRecv = time.Time{} if !reflect.DeepEqual(e, wantEvent) { t.Errorf("got ClientEvent %+v, want %+v", e, wantEvent) } }
func (w *appdashWrapper) Setup(name string) { w.rec = appdash.NewRecorder(appdash.NewRootSpanID(), w.coll) w.rec.Name(name) }
func handleRequest(w http.ResponseWriter, r *http.Request) { var result string span := appdash.NewRootSpanID() fmt.Println("span is ", span) collector := appdash.NewRemoteCollector(":3001") httpClient := &http.Client{ Transport: &httptrace.Transport{ Recorder: appdash.NewRecorder(span, collector), SetName: true, }, } //Service A resp, err := httpClient.Get("http://localhost:6601") if err != nil { log.Println("access serviceA err:", err) } else { log.Println("access serviceA ok") resp.Body.Close() result += "access serviceA ok\n" } //Service B resp, err = httpClient.Get("http://localhost:6602") if err != nil { log.Println("access serviceB err:", err) return } else { log.Println("access serviceB ok") resp.Body.Close() result += "access serviceB ok\n" } // SQL event traceRec := appdash.NewRecorder(span, collector) traceRec.Name("sqlevent example") // A random length for the trace. length := time.Duration(rand.Intn(1000)) * time.Millisecond StartTime := time.Now().Add(-time.Duration(rand.Intn(100)) * time.Minute) traceRec.Event(&sqltrace.SQLEvent{ ClientSend: StartTime, ClientRecv: StartTime.Add(length), SQL: "SELECT * FROM table_name;", Tag: fmt.Sprintf("fakeTag%d", rand.Intn(10)), }) result += "sql event ok\n" // self-customize event - MyEvent StartTime = time.Now().Add(-time.Duration(rand.Intn(100)) * time.Minute) traceRec.Event(&MyEvent{ Name: "MyEvent example", StartTime: StartTime, EndTime: StartTime.Add(length), }) result += "MyEvent ok\n" w.Write([]byte(result)) }