Documentation ¶
Overview ¶
Package gwr provides on demand operational data sources in Go. A typical use is adding in-depth debug tracing to your program that only turns on when there are any active consumer(s).
Basics ¶
A gwr data source is a named subset data that is Get-able and/or Watch-able. The gwr library can then build reporting on top of Watch-able sources, or by falling back to polling Get-able sources.
For example a request log source would be naturally Watch-able for future requests as they come in. An implementation could go further and add a "last-10" buffer to also become Get-able.
Integrating ¶
To bootstrap gwr and start its server listening on port 4040:
gwr.Configure(&gwr.Config{ListenAddr: ":4040"})
GWR also adds a handler to the default http server; so if you already have a default http server like:
log.Fatal(http.ListenAndServe(":8080", nil))
Then gwr will already be accessible at "/gwr/..." on port 8080; you should still call gwr.Configure:
gwr.Configure(nil)
Example (Httpserver_accesslog) ¶
package main import ( "bufio" "fmt" "io" "log" "net" "net/http" "net/http/httptest" "os" "text/template" gwr "github.com/uber-go/gwr" "github.com/uber-go/gwr/source" ) type accessLogger struct { handler http.Handler watcher source.GenericDataWatcher } func logged(handler http.Handler) *accessLogger { if handler == nil { handler = http.DefaultServeMux } return &accessLogger{ handler: handler, } } type accessEntry struct { Method string `json:"method"` Path string `json:"path"` Query string `json:"query"` Code int `json:"code"` Bytes int `json:"bytes"` ContentType string `json:"content_type"` } var accessLogTextTemplate = template.Must(template.New("req_logger_text").Parse(` {{ define "item" }}{{ .Method }} {{ .Path }}{{ if .Query }}?{{ .Query }}{{ end }} {{ .Code }} {{ .Bytes }} {{ .ContentType }}{{ end }} `)) func (al *accessLogger) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !al.watcher.Active() { al.handler.ServeHTTP(w, r) return } rec := httptest.NewRecorder() al.handler.ServeHTTP(rec, r) bytes := rec.Body.Len() // finishing work first... hdr := w.Header() for key, vals := range rec.HeaderMap { hdr[key] = vals } w.WriteHeader(rec.Code) if _, err := rec.Body.WriteTo(w); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } // ...then emitting the entry may afford slightly less overhead; most // overhead, like marshalling, should be deferred by the gwr library, // watcher.HandleItem is supposed to be fast enough to not need a channel // indirection within each source. al.watcher.HandleItem(accessEntry{ Method: r.Method, Path: r.URL.Path, Query: r.URL.RawQuery, Code: rec.Code, Bytes: bytes, ContentType: rec.HeaderMap.Get("Content-Type"), }) } func (al *accessLogger) Name() string { return "/access_log" } func (al *accessLogger) TextTemplate() *template.Template { return accessLogTextTemplate } func (al *accessLogger) SetWatcher(watcher source.GenericDataWatcher) { al.watcher = watcher } // TODO: this has become more test than example; maybe just make it a test? func main() { // Uses :0 for no conflict in test. if err := gwr.Configure(&gwr.Config{ListenAddr: ":0"}); err != nil { log.Fatal(err) } defer gwr.DefaultServer().Stop() gwrAddr := gwr.DefaultServer().Addr() // a handler so we get more than just 404s http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") if _, err := io.WriteString(w, "Ok ;-)\n"); err != nil { panic(err.Error()) } }) // wraps the default serve mux in an access logging gwr source... loggedHTTPHandler := logged(nil) // ...which we then register with gwr gwr.AddGenericDataSource(loggedHTTPHandler) // Again note the :0 pattern complicates things more than normal; this is // just the default http server ln, err := net.Listen("tcp", ":0") if err != nil { log.Fatal(err) } svcAddr := ln.Addr() go http.Serve(ln, loggedHTTPHandler) // make two requests, one should get a 200, the other a 404; we should see // only their output since no watchers are going yet fmt.Println("start") httpGetStdout("http://%s/foo?num=1", svcAddr) httpGetStdout("http://%s/bar?num=2", svcAddr) // start a json watch on the access log; NOTE we don't care about any copy // error because normal termination here is closed-mid-read; TODO we could // tighten this to log fatal any non "closed" error jsonLines := newHTTPGetChan("JSON", "http://%s/access_log?format=json&watch=1", gwrAddr) fmt.Println("\nwatching json") // make two requests, now with json watcher httpGetStdout("http://%s/foo?num=3", svcAddr) httpGetStdout("http://%s/bar?num=4", svcAddr) jsonLines.printOne() jsonLines.printOne() // start a text watch on the access log textLines := newHTTPGetChan("TEXT", "http://%s/access_log?format=text&watch=1", gwrAddr) fmt.Println("\nwatching text & json") // make two requests, now with both watchers httpGetStdout("http://%s/foo?num=5", svcAddr) httpGetStdout("http://%s/bar?num=6", svcAddr) jsonLines.printOne() jsonLines.printOne() textLines.printOne() textLines.printOne() // shutdown the json watch jsonLines.close() fmt.Println("\njust text") // make two requests; we should still see the body copies, but only get the text watch data httpGetStdout("http://%s/foo?num=7", svcAddr) httpGetStdout("http://%s/bar?num=8", svcAddr) textLines.printOne() textLines.printOne() // shutdown the json watch textLines.close() fmt.Println("\nno watchers") // make two requests; we should still see the body copies, but get no watch data for them httpGetStdout("http://%s/foo?num=9", svcAddr) httpGetStdout("http://%s/bar?num=10", svcAddr) } // test brevity conveniences func httpGetStdout(format string, args ...interface{}) { url := fmt.Sprintf(format, args...) resp, err := http.Get(url) if err != nil { log.Fatal(err) } if _, err := io.Copy(os.Stdout, resp.Body); err != nil { log.Fatal(err) } if err := resp.Body.Close(); err != nil { log.Fatal(err) } } type httpGetChan struct { tag string c chan string r *http.Response } func newHTTPGetChan(tag string, format string, args ...interface{}) *httpGetChan { url := fmt.Sprintf(format, args...) resp, err := http.Get(url) if err != nil { log.Fatal(err) } hc := &httpGetChan{ tag: tag, c: make(chan string), r: resp, } go hc.scanLines() return hc } func (hc *httpGetChan) printOne() { if _, err := fmt.Printf("%s: %s\n", hc.tag, <-hc.c); err != nil { log.Fatal(err) } } func (hc *httpGetChan) close() { if err := hc.r.Body.Close(); err != nil { log.Fatal(err) } hc.printOne() } func (hc *httpGetChan) scanLines() { s := bufio.NewScanner(hc.r.Body) for s.Scan() { hc.c <- s.Text() } hc.c <- "CLOSE" close(hc.c) // NOTE: s.Err() intentionally not checking since this is a "just" // function; in particular, we expect to get a closed-during-read error }
Output: start Ok ;-) 404 page not found watching json Ok ;-) 404 page not found JSON: {"method":"GET","path":"/foo","query":"num=3","code":200,"bytes":7,"content_type":"text/plain; charset=utf-8"} JSON: {"method":"GET","path":"/bar","query":"num=4","code":404,"bytes":19,"content_type":"text/plain; charset=utf-8"} watching text & json Ok ;-) 404 page not found JSON: {"method":"GET","path":"/foo","query":"num=5","code":200,"bytes":7,"content_type":"text/plain; charset=utf-8"} JSON: {"method":"GET","path":"/bar","query":"num=6","code":404,"bytes":19,"content_type":"text/plain; charset=utf-8"} TEXT: GET /foo?num=5 200 7 text/plain; charset=utf-8 TEXT: GET /bar?num=6 404 19 text/plain; charset=utf-8 JSON: CLOSE just text Ok ;-) 404 page not found TEXT: GET /foo?num=7 200 7 text/plain; charset=utf-8 TEXT: GET /bar?num=8 404 19 text/plain; charset=utf-8 TEXT: CLOSE no watchers Ok ;-) 404 page not found
Index ¶
- Variables
- func AddDataSource(ds source.DataSource) error
- func AddGenericDataSource(gds source.GenericDataSource) error
- func Configure(config *Config) error
- func Enabled() bool
- func ListenAndServe(hostPort string, dss *source.DataSources) error
- func ListenAndServeHTTP(hostPort string, dss *source.DataSources) error
- func ListenAndServeResp(hostPort string, dss *source.DataSources) error
- func NewServer(dss *source.DataSources) stacked.Server
- type Config
- type ConfiguredServer
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrAlreadyConfigured is returned by gwr.Configure when called more than // once. ErrAlreadyConfigured = errors.New("gwr already configured") // ErrAlreadyStarted is returned by ConfiguredServer.Start if the server is // already listening. ErrAlreadyStarted = errors.New("gwr server already started") )
var DefaultDataSources *source.DataSources
DefaultDataSources is default data sources registry which data sources are added to by the module-level Add* functions. It is used by all of the protocol servers if no data sources are provided.
Functions ¶
func AddDataSource ¶
func AddDataSource(ds source.DataSource) error
AddDataSource adds a data source to the default data sources registry. It returns an error if there's already a data source defined with the same name.
func AddGenericDataSource ¶
func AddGenericDataSource(gds source.GenericDataSource) error
AddGenericDataSource adds a generic data source to the default data sources registry. It returns an error if there's already a data source defined with the same name.
func Configure ¶
Configure sets up the gwr library and starts any resources (like a listening server) if enabled. - if nil config is passed, it's a convenience for &gwr.Config{} - if called more than once, ErrAlreadyConfigured is returned - otherwise any ConfiguredServer.Start error is returned.
func Enabled ¶
func Enabled() bool
Enabled returns true if the gwr library is configured and enabled.
func ListenAndServe ¶
func ListenAndServe(hostPort string, dss *source.DataSources) error
ListenAndServe starts an "auto" protocol server that will respond to HTTP or RESP on the given hostPort.
func ListenAndServeHTTP ¶
func ListenAndServeHTTP(hostPort string, dss *source.DataSources) error
ListenAndServeHTTP starts an http protocol gwr server.
func ListenAndServeResp ¶
func ListenAndServeResp(hostPort string, dss *source.DataSources) error
ListenAndServeResp starts a resp protocol gwr server.
Types ¶
type Config ¶
type Config struct { // Enabled controls whether GWR is enabled or not, it defaults true. // Currently this only controls whether ConfiguredServer starting. Enabled *bool `yaml:"enabled"` // ListenAddr controls what address ConfiguredServer will listen on. It is // superceded by the $GWR_LISTEN environment variable. // // If no listen address is set, then GWR does not start its own listening // server; however GWR can still be accessed under "/gwr/..." from any // default http servers. ListenAddr string `yaml:"listen"` }
Config defines configuration for GWR. For now this only defines server configuration; however once we have reporting support we'll add something ReportingCofiguration here.
type ConfiguredServer ¶
type ConfiguredServer struct {
// contains filtered or unexported fields
}
ConfiguredServer manages the lifecycle of a configured GWR server, as created by gwr.NewServer.
func DefaultServer ¶
func DefaultServer() *ConfiguredServer
DefaultServer returns the configured gwr server, or nil if Configure hasn't been called yet.
func NewConfiguredServer ¶
func NewConfiguredServer(cfg Config) *ConfiguredServer
NewConfiguredServer creates a new ConfiguredServer for a given config.
func (*ConfiguredServer) Addr ¶
func (srv *ConfiguredServer) Addr() net.Addr
Addr returns the current listening address, if any.
func (*ConfiguredServer) Enabled ¶
func (srv *ConfiguredServer) Enabled() bool
Enabled return true if the server is enabled.
func (*ConfiguredServer) ListenAddr ¶
func (srv *ConfiguredServer) ListenAddr() string
ListenAddr returns the configured listen address string.
func (*ConfiguredServer) Start ¶
func (srv *ConfiguredServer) Start() error
Start starts the server by creating the listener and a server goroutine to accept connections.
- if not enabled, or if no listen address is configured, noops and returns nil
- if already listening, returns ErrAlreadyStarted
- otherwise any net.Listen error is returned.
func (*ConfiguredServer) StartOn ¶
func (srv *ConfiguredServer) StartOn(laddr string) error
StartOn starts the server on a given listening address. If the start succeeds, it also updates the configured listening address for later reference. It has all the same error cases as ConfiguredServer.Start.
func (*ConfiguredServer) Stop ¶
func (srv *ConfiguredServer) Stop() error
Stop closes the current listener and shuts down the server goroutine started by Start (if any).
Directories ¶
Path | Synopsis |
---|---|
Package report implements reporting support around watchable source.DataSource.
|
Package report implements reporting support around watchable source.DataSource. |
tap
Package tap provides a simple item emitter source and a tracing source.
|
Package tap provides a simple item emitter source and a tracing source. |