diff --git a/.gitignore b/.gitignore index 8c72f74..a3d85a1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ .vscode main +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 998cff1..a8919e6 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,7 @@ Configuration files can either be in YAML or JSON their names are derived from t host_port: 0.0.0.0:8080 web_ui: true debug_level: 1 +enable_tracing: true # When to throw a 401 on auth failure # valid values: always, per_query, never diff --git a/dev.yml b/dev.yml index b7c78e4..2e2a3ba 100644 --- a/dev.yml +++ b/dev.yml @@ -2,6 +2,7 @@ title: Super Graph Development host_port: 0.0.0.0:8080 web_ui: true debug_level: 1 +enable_tracing: true # Throw a 401 on auth failure for queries that need auth # valid values: always, per_query, never diff --git a/prod.yml b/prod.yml index 9d10ff9..f00adc9 100644 --- a/prod.yml +++ b/prod.yml @@ -2,6 +2,7 @@ title: Super Graph Production host_port: 0.0.0.0:8080 web_ui: false debug_level: 0 +enable_tracing: false auth_fail_block: always # Postgres related environment Variables diff --git a/serv/http.go b/serv/http.go index 4fa773e..e74b62e 100644 --- a/serv/http.go +++ b/serv/http.go @@ -9,7 +9,9 @@ import ( "io/ioutil" "net/http" "strings" + "time" + "github.com/dosco/super-graph/qcode" "github.com/go-pg/pg" "github.com/gorilla/websocket" "github.com/valyala/fasttemplate" @@ -33,8 +35,34 @@ type gqlReq struct { } type gqlResp struct { - Error string `json:"error,omitempty"` - Data json.RawMessage `json:"data,omitempty"` + Error string `json:"error,omitempty"` + Data json.RawMessage `json:"data,omitempty"` + Extensions extensions `json:"extensions"` +} + +type extensions struct { + Tracing *trace `json:"tracing"` +} + +type trace struct { + Version int `json:"version"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + Duration time.Duration `json:"duration"` + Execution execution `json:"execution"` +} + +type execution struct { + Resolvers []resolver `json:"resolvers"` +} + +type resolver struct { + Path []string `json:"path"` + ParentType string `json:"parentType"` + FieldName string `json:"fieldName"` + ReturnType string `json:"returnType"` + StartOffset int `json:"startOffset"` + Duration time.Duration `json:"duration"` } func apiv1Http(w http.ResponseWriter, r *http.Request) { @@ -99,6 +127,7 @@ func apiv1Http(w http.ResponseWriter, r *http.Request) { if debug > 0 { fmt.Println(finalSQL) } + st := time.Now() var root json.RawMessage _, err = db.Query(pg.Scan(&root), finalSQL) @@ -108,7 +137,15 @@ func apiv1Http(w http.ResponseWriter, r *http.Request) { return } - json.NewEncoder(w).Encode(gqlResp{Data: json.RawMessage(root)}) + et := time.Now() + resp := gqlResp{} + + if tracing { + resp.Extensions = extensions{newTrace(st, et, qc)} + } + + resp.Data = json.RawMessage(root) + json.NewEncoder(w).Encode(resp) } /* @@ -174,3 +211,28 @@ func varValues(ctx context.Context) map[string]interface{} { "user_id_provider": uidpFn, } } + +func newTrace(st, et time.Time, qc *qcode.QCode) *trace { + du := et.Sub(et) + + t := &trace{ + Version: 1, + StartTime: st, + EndTime: et, + Duration: du, + Execution: execution{ + []resolver{ + resolver{ + Path: []string{qc.Query.Select.Table}, + ParentType: "Query", + FieldName: qc.Query.Select.Table, + ReturnType: "object", + StartOffset: 1, + Duration: du, + }, + }, + }, + } + + return t +} diff --git a/serv/serv.go b/serv/serv.go index 598da8f..fda1f97 100644 --- a/serv/serv.go +++ b/serv/serv.go @@ -32,6 +32,7 @@ var ( pcompile *psql.Compiler qcompile *qcode.Compiler authFailBlock int + tracing bool ) func initLog() { @@ -74,6 +75,7 @@ func initConf() { conf.SetDefault("host_port", "0.0.0.0:8080") conf.SetDefault("web_ui", false) conf.SetDefault("debug_level", 0) + conf.SetDefault("enable_tracing", false) conf.SetDefault("database.type", "postgres") conf.SetDefault("database.host", "localhost") @@ -84,6 +86,8 @@ func initConf() { conf.SetDefault("env", "development") conf.BindEnv("env", "GO_ENV") + tracing = conf.GetBool("enable_tracing") + switch conf.GetString("auth_fail_block") { case "always": authFailBlock = authFailBlockAlways diff --git a/web/public/super-graph-web-ui.png b/web/public/super-graph-web-ui.png index a6fc652..32152fd 100644 Binary files a/web/public/super-graph-web-ui.png and b/web/public/super-graph-web-ui.png differ