forked from sourcegraph/appdash
/
main.go
142 lines (126 loc) · 4.97 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// webapp: a standalone example Negroni / Gorilla based webapp.
//
// This example demonstrates basic usage of Appdash in a Negroni / Gorilla
// based web application. The entire application is ran locally (i.e. on the
// same server) -- even the Appdash web UI.
package main
import (
"fmt"
"log"
"net/http"
"net/url"
"time"
"sourcegraph.com/sourcegraph/appdash"
"sourcegraph.com/sourcegraph/appdash/httptrace"
"sourcegraph.com/sourcegraph/appdash/traceapp"
"github.com/codegangsta/negroni"
"github.com/gorilla/context"
"github.com/gorilla/mux"
)
// Used to store the SpanID in a request's context (see gorilla/context docs
// for more information).
const CtxSpanID = 0
// We want to create HTTP clients recording to this collector inside our Home
// handler below, so we use a global variable (for simplicity sake) to store
// the collector in use. We could also use gorilla/context to store it.
var collector appdash.Collector
func main() {
// Create a recent in-memory store, evicting data after 20s.
//
// The store defines where information about traces (i.e. spans and
// annotations) will be stored during the lifetime of the application. This
// application uses a MemoryStore store wrapped by a RecentStore with an
// eviction time of 20s (i.e. all data after 20s is deleted from memory).
memStore := appdash.NewMemoryStore()
store := &appdash.RecentStore{
MinEvictAge: 20 * time.Second,
DeleteStore: memStore,
}
// Start the Appdash web UI on port 8700.
//
// This is the actual Appdash web UI -- usable as a Go package itself, We
// embed it directly into our application such that visiting the web server
// on HTTP port 8700 will bring us to the web UI, displaying information
// about this specific web-server (another alternative would be to connect
// to a centralized Appdash collection server).
url, err := url.Parse("http://localhost:8700")
if err != nil {
log.Fatal(err)
}
tapp, err := traceapp.New(nil, url)
if err != nil {
log.Fatal(err)
}
tapp.Store = store
tapp.Queryer = memStore
log.Println("Appdash web UI running on HTTP :8700")
go func() {
log.Fatal(http.ListenAndServe(":8700", tapp))
}()
// We will use a local collector (as we are running the Appdash web UI
// embedded within our app).
//
// A collector is responsible for collecting the information about traces
// (i.e. spans and annotations) and placing them into a store. In this app
// we use a local collector (we could also use a remote collector, sending
// the information to a remote Appdash collection server).
collector = appdash.NewLocalCollector(store)
// Create the appdash/httptrace middleware.
//
// Here we initialize the appdash/httptrace middleware. It is a Negroni
// compliant HTTP middleware that will generate HTTP events for Appdash to
// display. We could also instruct Appdash with events manually, if we
// wanted to.
tracemw := httptrace.Middleware(collector, &httptrace.MiddlewareConfig{
RouteName: func(r *http.Request) string { return r.URL.Path },
SetContextSpan: func(r *http.Request, spanID appdash.SpanID) {
context.Set(r, CtxSpanID, spanID)
},
})
// Setup our router (for information, see the gorilla/mux docs):
router := mux.NewRouter()
router.HandleFunc("/", Home)
router.HandleFunc("/endpoint", Endpoint)
// Setup Negroni for our app (for information, see the negroni docs):
n := negroni.Classic()
n.Use(negroni.HandlerFunc(tracemw)) // Register appdash's HTTP middleware.
n.UseHandler(router)
n.Run(":8699")
}
// 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)
}
// Endpoint is an example API endpoint. In a real application, the backend of
// your service would be contacting several external and internal API endpoints
// which may be the bottleneck of your application.
//
// For example purposes we just sleep for 200ms before responding to simulate a
// slow API endpoint as the bottleneck of your application.
func Endpoint(w http.ResponseWriter, r *http.Request) {
time.Sleep(200 * time.Millisecond)
fmt.Fprintf(w, "Slept for 200ms!")
}