diff --git a/api/features.go b/api/features.go new file mode 100644 index 0000000..470fd8f --- /dev/null +++ b/api/features.go @@ -0,0 +1,34 @@ +package api + +import ( + "time" + "net/http" + "github.com/gorilla/context" +) + +func AddFeature(r *http.Request, feature string) { + rawFeatures, ok := context.GetOk(r, "features") + + if ok { + features := rawFeatures.(map[string]int) + features[feature] += 1 + } else { + context.Set(r, "features", map[string]int{ feature: 1 }) + } +} + +func GetFeatures(r *http.Request) map[string]interface{} { + result := map[string]interface{} {} + + if features, ok := context.GetOk(r, "features"); ok { + result["features"] = features + } + + if identifier, ok := context.GetOk(r, "api_key"); ok { + result["identifier"] = identifier + } + + result["timestamp"] = time.Now().UTC().Format(time.RFC3339Nano) + + return result +} \ No newline at end of file diff --git a/api/log.go b/api/log.go new file mode 100644 index 0000000..db0c3a0 --- /dev/null +++ b/api/log.go @@ -0,0 +1,32 @@ +package api + +import ( + "os" + "log" + "io" + "io/ioutil" + "log/syslog" + "github.com/spf13/viper" +) + +var statsLogger *log.Logger + +func StatsLogger() *log.Logger { + if statsLogger == nil { + var statsWriter io.Writer + + logStyle := viper.GetString("statsLogger") + switch logStyle { + case "syslog": + statsWriter, _ = syslog.New(syslog.LOG_NOTICE, "bloomapi-stats") + case "stdout": + statsWriter = os.Stdout + default: + statsWriter = ioutil.Discard + } + + statsLogger = log.New(statsWriter, "", 0) + } + + return statsLogger +} \ No newline at end of file diff --git a/bloomapi.go b/bloomapi.go index b9d2df5..9559011 100644 --- a/bloomapi.go +++ b/bloomapi.go @@ -3,6 +3,8 @@ package main import ( "os" "fmt" + "log" + "log/syslog" "github.com/spf13/viper" "github.com/untoldone/bloomapi/cmd" ) @@ -33,6 +35,21 @@ func main() { os.Exit(1) } + logStyle := viper.GetString("generalLogger") + switch logStyle { + case "syslog": + logwriter, e := syslog.New(syslog.LOG_NOTICE, "bloomapi") + if e != nil { + fmt.Println("Error initializing syslog") + os.Exit(1) + } + log.SetOutput(logwriter) + case "stdout": + default: + fmt.Println("Invalid logger:", logStyle, "please select 'syslog' or 'stdout'") + os.Exit(1) + } + switch arg { case "server": cmd.Server() diff --git a/cmd/server.go b/cmd/server.go index aaf6a70..717cd15 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -20,6 +20,7 @@ func Server() { // Router setup router := mux.NewRouter() + router.KeepContext = true // For Backwards Compatibility Feb 13, 2015 router.HandleFunc("/api/search", handler_compat.NpiSearchHandler).Methods("GET") @@ -35,6 +36,7 @@ func Server() { n.UseHandler(router) // Post-Router Middleware setup + n.Use(middleware.NewRecordFeatures()) n.Use(middleware.NewClearContext()) log.Println("Running Server") diff --git a/config.toml b/config.toml index 8c07a17..da18336 100644 --- a/config.toml +++ b/config.toml @@ -3,3 +3,5 @@ searchHosts = [ "localhost" ] bloomapiPort = 3005 +statsLogger = "syslog" # values can be: 'syslog' or 'stdout' +generalLogger = "syslog" \ No newline at end of file diff --git a/handler/item_handler.go b/handler/item_handler.go index cde299c..d48ffc5 100644 --- a/handler/item_handler.go +++ b/handler/item_handler.go @@ -20,6 +20,9 @@ func ItemHandler (w http.ResponseWriter, req *http.Request) { source := strings.ToLower(vars["source"]) id := vars["id"] + api.AddFeature(req, "handler:item") + api.AddFeature(req, source) + bloomConn := api.Conn() searchConn := bloomConn.SearchConnection() diff --git a/handler/search_helper.go b/handler/search_helper.go index f31618e..cdc00eb 100644 --- a/handler/search_helper.go +++ b/handler/search_helper.go @@ -19,6 +19,7 @@ func phraseMatches (paramSets []*SearchParamSet, r *http.Request) []interface{} elasticPhrases := make([]interface{}, len(paramSets)) for index, set := range paramSets { shouldValues := make([]map[string]interface{}, len(set.Values)) + api.AddFeature(r, "op:" + set.Op) for vIndex, value := range set.Values { switch (set.Op) { case "eq": @@ -118,6 +119,8 @@ func Search(sourceType string, params *SearchParams, r *http.Request) (map[strin }, } + + api.AddFeature(r, "sort") api.AddMessage(r, experimentalSort) } diff --git a/handler/search_source_handler.go b/handler/search_source_handler.go index e07d20b..620db11 100644 --- a/handler/search_source_handler.go +++ b/handler/search_source_handler.go @@ -14,6 +14,9 @@ func SearchSourceHandler (w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) source := strings.ToLower(vars["source"]) + api.AddFeature(req, source) + api.AddFeature(req, "handler:search") + conn := api.Conn() apiKey, ok := context.Get(req, "api_key").(string) if !ok { diff --git a/handler/sources_handler.go b/handler/sources_handler.go index 9809be0..bd39e24 100644 --- a/handler/sources_handler.go +++ b/handler/sources_handler.go @@ -19,7 +19,9 @@ type dataSource struct { func SourcesHandler (w http.ResponseWriter, req *http.Request) { conn := api.Conn() apiKey, ok := context.Get(req, "api_key").(string) - log.Println(apiKey) + + api.AddFeature(req, "handler:sources") + if !ok { apiKey = "" } diff --git a/handler_compat/npi_item_handler.go b/handler_compat/npi_item_handler.go index 213885e..eccd242 100644 --- a/handler_compat/npi_item_handler.go +++ b/handler_compat/npi_item_handler.go @@ -13,6 +13,9 @@ func NpiItemHandler (w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) npi := vars["npi"] + api.AddFeature(req, "usgov.hhs.npi") + api.AddFeature(req, "handler:legacynpi:item") + conn := api.Conn().SearchConnection() result, err := conn.Search("usgov.hhs.npi", "main", nil, map[string]interface{} { diff --git a/handler_compat/npi_search_handler.go b/handler_compat/npi_search_handler.go index 8478686..4b0f82a 100644 --- a/handler_compat/npi_search_handler.go +++ b/handler_compat/npi_search_handler.go @@ -15,6 +15,9 @@ func NpiSearchHandler (w http.ResponseWriter, req *http.Request) { return } + api.AddFeature(req, "usgov.hhs.npi") + api.AddFeature(req, "handler:legacynpi:search") + results, err := handler.Search("usgov.hhs.npi", params, req) if err != nil { switch err.(type) { diff --git a/middleware/record_features.go b/middleware/record_features.go new file mode 100644 index 0000000..b6b485c --- /dev/null +++ b/middleware/record_features.go @@ -0,0 +1,21 @@ +package middleware + +import ( + "net/http" + "encoding/json" + "github.com/untoldone/bloomapi/api" +) + +type RecordFeatures struct {} + +func NewRecordFeatures() *RecordFeatures { + return &RecordFeatures{} +} + +func (s *RecordFeatures) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + features := api.GetFeatures(r) + j, _ := json.Marshal(features) + featureJson := string(j) + api.StatsLogger().Println(featureJson) + next(rw, r) +} \ No newline at end of file