func main() { //The store will be used by receivers and outlets. var st store.Store if conf.UsingRedis { st = store.NewRedisStore(conf.RedisHost, conf.RedisPass, conf.MaxPartitions, conf.MaxRedisConns) fmt.Printf("at=initialized-redis-store\n") } else { st = store.NewMemStore() fmt.Printf("at=initialized-mem-store\n") } //It is not possible to run both librato and graphite outlets //in the same process. switch conf.Outlet { case "librato": rdr := outlet.NewBucketReader(conf.BufferSize, conf.Concurrency, conf.FlushtInterval, st) outlet := outlet.NewLibratoOutlet(conf.BufferSize, conf.Concurrency, conf.NumOutletRetry, rdr) outlet.Start() if conf.Verbose { go outlet.Report() } case "graphite": rdr := &outlet.BucketReader{Store: st, Interval: conf.FlushtInterval} outlet := outlet.NewGraphiteOutlet(conf.BufferSize, rdr) outlet.Start() default: fmt.Println("No outlet running. Run `l2met -h` for outlet help.") } //The HTTP Outlet can be ran in addition to the librato or graphite outlet. if conf.UsingHttpOutlet { httpOutlet := new(outlet.HttpOutlet) httpOutlet.Store = st http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) { httpOutlet.ServeReadBucket(w, r) }) } if conf.UsingReciever { recv := receiver.NewReceiver(conf.BufferSize, conf.Concurrency, conf.FlushtInterval, st) recv.Start() if conf.Verbose { go recv.Report() } http.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) { startReceiveT := time.Now() if r.Method != "POST" { http.Error(w, "Invalid Request", 400) return } user, pass, err := auth.Parse(r) if err != nil { fmt.Printf("measure.failed-auth erro=%s user=%s pass=%s user-agent=%s token=%s client=%s\n", err, user, pass, r.Header.Get("User-Agent"), r.Header.Get("Logplex-Drain-Token"), r.Header.Get("X-Forwarded-For")) http.Error(w, "Invalid Request", 400) return } b, err := ioutil.ReadAll(r.Body) r.Body.Close() if err != nil { http.Error(w, "Invalid Request", 400) return } v := r.URL.Query() v.Add("user", user) v.Add("password", pass) recv.Receive(b, v) utils.MeasureT("http-receiver", startReceiveT) }) } //The only thing that constitutes a healthy l2met //is the health of the store. In some cases, this might mean //a redis health check. http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { ok := st.Health() if !ok { msg := "Store is unavailable." fmt.Printf("error=%q\n", msg) http.Error(w, msg, 500) } }) http.HandleFunc("/sign", func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Invalid Method. Must be POST.", 400) return } user, pass, err := auth.ParseRaw(r.Header.Get("Authorization")) if err != nil { fmt.Printf("measure.failed-auth erro=%s user=%s pass=%s user-agent=%s token=%s client=%s\n", err, user, pass, r.Header.Get("User-Agent"), r.Header.Get("Logplex-Drain-Token"), r.Header.Get("X-Forwarded-For")) http.Error(w, "Unable to parse auth headers.", 400) return } matched := false for i := range conf.Secrets { if user == conf.Secrets[i] { matched = true break } } if !matched { http.Error(w, "Authentication failed.", 401) return } b, err := ioutil.ReadAll(r.Body) r.Body.Close() if err != nil { http.Error(w, "Unable to read body of POST.", 400) return } signed, err := auth.Sign(b) if err != nil { http.Error(w, "Unable to sign body.", 500) return } fmt.Fprint(w, string(signed)) }) //Start the HTTP server. if e := http.ListenAndServe(fmt.Sprintf(":%d", conf.Port), nil); e != nil { log.Fatal("Unable to start HTTP server.") } fmt.Printf("at=l2met-initialized port=%d\n", conf.Port) }
func (h *HttpOutlet) ServeReadBucket(w http.ResponseWriter, r *http.Request) { // need to extract: token, source, name, time // https://l2met:[email protected]/buckets/:name user, pass, err := auth.Parse(r) if err != nil { http.Error(w, "Inavalid Authentication", 401) return } // Shortcut so we can quickly access query params. h.Query = r.URL.Query() //We need to build the identity of a bucket before we can fetch //it from the store. Thus, the following attrs are parsed and held //for the bucket.Id. src := h.Query.Get("source") name := h.Query.Get("name") if len(name) == 0 { http.Error(w, "Invalid Request. Name is required.", 400) return } res, err := h.parseAssertion("resolution", 60) if err != nil { http.Error(w, "Invalid Request.", 400) return } resolution := time.Second * time.Duration(res) units := h.Query.Get("units") if len(units) == 0 { units = bucket.DefaultUnit } //The limit and offset are shortcuts to work with the time //field on the bucket. This makes it easy for the client to not have //to worry about keeping correct time. limit, err := h.parseAssertion("limit", 1) if err != nil { http.Error(w, "Invalid Request.", 400) return } //The offset is handy because you may not want to take the most recent //bucket. For instance, the current minute will not have a complete view //of the data; however, the last minute should. offset, err := h.parseAssertion("offset", 1) if err != nil { http.Error(w, "Invalid Request.", 400) return } //The API supports the ability to assert what metrics should be. //If the value of the assertion is negative, the assertion can //be skipped. By default, the value is negative. countAssertion, err := h.parseAssertion("count", -1) if err != nil { http.Error(w, "Invalid Request.", 400) return } meanAssertion, err := h.parseAssertion("mean", -1) if err != nil { http.Error(w, "Invalid Request.", 400) return } sumAssertion, err := h.parseAssertion("sum", -1) if err != nil { http.Error(w, "Invalid Request.", 400) return } //The tolerance is a way to work with assertions that would like to use //less than or greater than operators. tol, err := h.parseAssertion("tol", 0) if err != nil { http.Error(w, "Invalid Request.", 400) return } //Build one bucket.Id to share across all the buckets that we fetch //with respect to the limit. //We will set the time in proceeding for loop. id := &bucket.Id{ User: user, Pass: pass, Name: name, Source: src, Resolution: resolution, Units: units, } resBucket := &bucket.Bucket{Id: id} anchorTime := time.Now() for i := 0; i < limit; i++ { x := time.Duration((i+offset)*-1) * resolution id.Time = utils.RoundTime(anchorTime.Add(x), resolution) b := &bucket.Bucket{Id: id} //Fetch the bucket from our store. //This will fill in the vals. h.Store.Get(b) //We are only returning 1 bucket from the API. The //bucket will contain an aggregate view of the data based on limit. resBucket.Add(b) } //If any of the assertion values are -1 then they were not //defined in the request query params. Thus, we only do our assertions //if the assertion parameter is > 0. if countAssertion > 0 { if math.Abs(float64(resBucket.Count()-countAssertion)) > float64(tol) { http.Error(w, "Count assertion failed.", 404) return } } if meanAssertion > 0 { if math.Abs(float64(resBucket.Mean()-float64(meanAssertion))) > float64(tol) { http.Error(w, "Mean assertion failed.", 404) return } } if sumAssertion > 0 { if math.Abs(float64(resBucket.Sum()-float64(sumAssertion))) > float64(tol) { http.Error(w, "Sum assertion failed.", 404) return } } //Assuming there was not a failed assertion, we can return the result //bucket which may contain an aggregate of other buckets via bucket.Add() utils.WriteJson(w, 200, resBucket) }