fix: add http tracing so end-to-end tracing is possible
This commit is contained in:
parent
7b25873438
commit
1344246287
|
@ -77,6 +77,8 @@ cors_debug: true
|
||||||
# exporter: "zipkin"
|
# exporter: "zipkin"
|
||||||
# endpoint: "http://zipkin:9411/api/v2/spans"
|
# endpoint: "http://zipkin:9411/api/v2/spans"
|
||||||
# sample: 0.2
|
# sample: 0.2
|
||||||
|
# include_query: false
|
||||||
|
# include_params: false
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
# Can be 'rails' or 'jwt'
|
# Can be 'rails' or 'jwt'
|
||||||
|
|
|
@ -17,10 +17,6 @@ const (
|
||||||
closeBlock = 500
|
closeBlock = 500
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrAllTablesSkipped = errors.New("all tables skipped. cannot render query")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Variables map[string]json.RawMessage
|
type Variables map[string]json.RawMessage
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -92,30 +88,35 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer, vars Variables) (
|
||||||
|
|
||||||
io.WriteString(c.w, `SELECT jsonb_build_object(`)
|
io.WriteString(c.w, `SELECT jsonb_build_object(`)
|
||||||
for _, id := range qc.Roots {
|
for _, id := range qc.Roots {
|
||||||
root := &qc.Selects[id]
|
|
||||||
if root.SkipRender || len(root.Cols) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
st.Push(root.ID + closeBlock)
|
|
||||||
st.Push(root.ID)
|
|
||||||
|
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
io.WriteString(c.w, `, `)
|
io.WriteString(c.w, `, `)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.renderRootSelect(root)
|
root := &qc.Selects[id]
|
||||||
|
|
||||||
|
if root.SkipRender || len(root.Cols) == 0 {
|
||||||
|
squoted(c.w, root.FieldName)
|
||||||
|
io.WriteString(c.w, `, `)
|
||||||
|
io.WriteString(c.w, `NULL`)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
st.Push(root.ID + closeBlock)
|
||||||
|
st.Push(root.ID)
|
||||||
|
c.renderRootSelect(root)
|
||||||
|
}
|
||||||
|
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
io.WriteString(c.w, `) as "__root" FROM `)
|
|
||||||
|
|
||||||
if i == 0 {
|
|
||||||
return 0, ErrAllTablesSkipped
|
|
||||||
}
|
|
||||||
|
|
||||||
var ignored uint32
|
var ignored uint32
|
||||||
|
|
||||||
|
if st.Len() != 0 {
|
||||||
|
io.WriteString(c.w, `) as "__root" FROM `)
|
||||||
|
} else {
|
||||||
|
io.WriteString(c.w, `) as "__root"`)
|
||||||
|
return ignored, nil
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if st.Len() == 0 {
|
if st.Len() == 0 {
|
||||||
break
|
break
|
||||||
|
|
|
@ -333,59 +333,82 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
trv := com.getRole(role, field.Name)
|
trv := com.getRole(role, field.Name)
|
||||||
|
skipRender := false
|
||||||
|
|
||||||
switch action {
|
if trv != nil {
|
||||||
case QTQuery:
|
switch action {
|
||||||
if trv.query.block {
|
case QTQuery:
|
||||||
continue
|
if trv.query.block {
|
||||||
|
skipRender = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case QTInsert:
|
||||||
|
if trv.insert.block {
|
||||||
|
return fmt.Errorf("insert blocked: %s", field.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
case QTUpdate:
|
||||||
|
if trv.update.block {
|
||||||
|
return fmt.Errorf("update blocked: %s", field.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
case QTDelete:
|
||||||
|
if trv.delete.block {
|
||||||
|
return fmt.Errorf("delete blocked: %s", field.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case QTInsert:
|
} else if role == "anon" {
|
||||||
if trv.insert.block {
|
skipRender = true
|
||||||
return fmt.Errorf("insert blocked: %s", field.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
case QTUpdate:
|
|
||||||
if trv.update.block {
|
|
||||||
return fmt.Errorf("update blocked: %s", field.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
case QTDelete:
|
|
||||||
if trv.delete.block {
|
|
||||||
return fmt.Errorf("delete blocked: %s", field.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selects = append(selects, Select{
|
selects = append(selects, Select{
|
||||||
ID: id,
|
ID: id,
|
||||||
ParentID: parentID,
|
ParentID: parentID,
|
||||||
Name: field.Name,
|
Name: field.Name,
|
||||||
Children: make([]int32, 0, 5),
|
SkipRender: skipRender,
|
||||||
Allowed: trv.allowedColumns(action),
|
|
||||||
Functions: true,
|
|
||||||
})
|
})
|
||||||
s := &selects[(len(selects) - 1)]
|
s := &selects[(len(selects) - 1)]
|
||||||
|
|
||||||
switch action {
|
|
||||||
case QTQuery:
|
|
||||||
s.Functions = !trv.query.disable.funcs
|
|
||||||
s.Paging.Limit = trv.query.limit
|
|
||||||
|
|
||||||
case QTInsert:
|
|
||||||
s.PresetMap = trv.insert.psmap
|
|
||||||
s.PresetList = trv.insert.pslist
|
|
||||||
|
|
||||||
case QTUpdate:
|
|
||||||
s.PresetMap = trv.update.psmap
|
|
||||||
s.PresetList = trv.update.pslist
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(field.Alias) != 0 {
|
if len(field.Alias) != 0 {
|
||||||
s.FieldName = field.Alias
|
s.FieldName = field.Alias
|
||||||
} else {
|
} else {
|
||||||
s.FieldName = s.Name
|
s.FieldName = s.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.ParentID == -1 {
|
||||||
|
qc.Roots = append(qc.Roots, s.ID)
|
||||||
|
} else {
|
||||||
|
p := &selects[s.ParentID]
|
||||||
|
p.Children = append(p.Children, s.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if skipRender {
|
||||||
|
id++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Children = make([]int32, 0, 5)
|
||||||
|
s.Functions = true
|
||||||
|
|
||||||
|
if trv != nil {
|
||||||
|
s.Allowed = trv.allowedColumns(action)
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case QTQuery:
|
||||||
|
s.Functions = !trv.query.disable.funcs
|
||||||
|
s.Paging.Limit = trv.query.limit
|
||||||
|
|
||||||
|
case QTInsert:
|
||||||
|
s.PresetMap = trv.insert.psmap
|
||||||
|
s.PresetList = trv.insert.pslist
|
||||||
|
|
||||||
|
case QTUpdate:
|
||||||
|
s.PresetMap = trv.update.psmap
|
||||||
|
s.PresetList = trv.update.pslist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := com.compileArgs(qc, s, field.Args, role)
|
err := com.compileArgs(qc, s, field.Args, role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -394,13 +417,6 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
||||||
// Order is important AddFilters must come after compileArgs
|
// Order is important AddFilters must come after compileArgs
|
||||||
com.AddFilters(qc, s, role)
|
com.AddFilters(qc, s, role)
|
||||||
|
|
||||||
if s.ParentID == -1 {
|
|
||||||
qc.Roots = append(qc.Roots, s.ID)
|
|
||||||
} else {
|
|
||||||
p := &selects[s.ParentID]
|
|
||||||
p.Children = append(p.Children, s.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Cols = make([]Column, 0, len(field.Children))
|
s.Cols = make([]Column, 0, len(field.Children))
|
||||||
action = QTQuery
|
action = QTQuery
|
||||||
|
|
||||||
|
@ -440,14 +456,10 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
||||||
|
|
||||||
func (com *Compiler) AddFilters(qc *QCode, sel *Select, role string) {
|
func (com *Compiler) AddFilters(qc *QCode, sel *Select, role string) {
|
||||||
var fil *Exp
|
var fil *Exp
|
||||||
var nu bool // user required (or not) in this filter
|
var nu bool // need user_id (or not) in this filter
|
||||||
|
|
||||||
if trv, ok := com.tr[role][sel.Name]; ok {
|
if trv, ok := com.tr[role][sel.Name]; ok {
|
||||||
fil, nu = trv.filter(qc.Type)
|
fil, nu = trv.filter(qc.Type)
|
||||||
|
|
||||||
} else if com.db && role == "anon" {
|
|
||||||
// Tables not defined under the anon role will not be rendered
|
|
||||||
sel.SkipRender = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fil == nil {
|
if fil == nil {
|
||||||
|
@ -838,14 +850,17 @@ func (com *Compiler) compileArgAfterBefore(sel *Select, arg *Arg, pt PagingType)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
var zeroTrv = &trval{}
|
// var zeroTrv = &trval{}
|
||||||
|
|
||||||
func (com *Compiler) getRole(role, field string) *trval {
|
func (com *Compiler) getRole(role, field string) *trval {
|
||||||
if trv, ok := com.tr[role][field]; ok {
|
if trv, ok := com.tr[role][field]; ok {
|
||||||
return trv
|
return trv
|
||||||
} else {
|
|
||||||
return zeroTrv
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
// } else {
|
||||||
|
// return zeroTrv
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddFilter(sel *Select, fil *Exp) {
|
func AddFilter(sel *Select, fil *Exp) {
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/core/internal/allow"
|
"github.com/dosco/super-graph/core/internal/allow"
|
||||||
"github.com/dosco/super-graph/core/internal/psql"
|
|
||||||
"github.com/dosco/super-graph/core/internal/qcode"
|
"github.com/dosco/super-graph/core/internal/qcode"
|
||||||
"github.com/valyala/fasttemplate"
|
"github.com/valyala/fasttemplate"
|
||||||
)
|
)
|
||||||
|
@ -103,9 +102,6 @@ func (sg *SuperGraph) prepareStmt(item allow.Item) error {
|
||||||
// logger.Debug().Msgf("Prepared statement 'query %s' (anon)", item.Name)
|
// logger.Debug().Msgf("Prepared statement 'query %s' (anon)", item.Name)
|
||||||
|
|
||||||
stmts2, err := sg.buildRoleStmt(qb, vars, "anon")
|
stmts2, err := sg.buildRoleStmt(qb, vars, "anon")
|
||||||
if err == psql.ErrAllTablesSkipped {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -121,9 +117,6 @@ func (sg *SuperGraph) prepareStmt(item allow.Item) error {
|
||||||
// logger.Debug().Msgf("Prepared statement 'mutation %s' (%s)", item.Name, role.Name)
|
// logger.Debug().Msgf("Prepared statement 'mutation %s' (%s)", item.Name, role.Name)
|
||||||
|
|
||||||
stmts, err := sg.buildRoleStmt(qb, vars, role.Name)
|
stmts, err := sg.buildRoleStmt(qb, vars, role.Name)
|
||||||
if err == psql.ErrAllTablesSkipped {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,9 +62,11 @@ type Serv struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
Tracing struct {
|
Tracing struct {
|
||||||
Exporter string
|
Exporter string
|
||||||
Endpoint string
|
Endpoint string
|
||||||
Sample string
|
Sample string
|
||||||
|
IncludeQuery bool `mapstructure:"include_query"`
|
||||||
|
IncludeParams bool `mapstructure:"include_params"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"github.com/dosco/super-graph/core"
|
"github.com/dosco/super-graph/core"
|
||||||
"github.com/dosco/super-graph/internal/serv/internal/auth"
|
"github.com/dosco/super-graph/internal/serv/internal/auth"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
|
"go.opencensus.io/plugin/ochttp"
|
||||||
|
"go.opencensus.io/trace"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,7 +46,7 @@ func apiV1Handler() http.Handler {
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
Debug: conf.DebugCORS,
|
Debug: conf.DebugCORS,
|
||||||
})
|
})
|
||||||
h = c.Handler(h)
|
return c.Handler(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
return h
|
return h
|
||||||
|
@ -78,6 +80,22 @@ func apiV1(w http.ResponseWriter, r *http.Request) {
|
||||||
doLog := true
|
doLog := true
|
||||||
res, err := sg.GraphQL(ct, req.Query, req.Vars)
|
res, err := sg.GraphQL(ct, req.Query, req.Vars)
|
||||||
|
|
||||||
|
if conf.telemetryEnabled() {
|
||||||
|
span := trace.FromContext(ct)
|
||||||
|
|
||||||
|
span.AddAttributes(
|
||||||
|
trace.StringAttribute("operation", res.OperationName()),
|
||||||
|
trace.StringAttribute("query_name", res.QueryName()),
|
||||||
|
trace.StringAttribute("role", res.Role()),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
span.AddAttributes(trace.StringAttribute("error", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
ochttp.SetRoute(ct, apiRoute)
|
||||||
|
}
|
||||||
|
|
||||||
if !conf.Production && res.QueryName() == introspectionQuery {
|
if !conf.Production && res.QueryName() == introspectionQuery {
|
||||||
doLog = false
|
doLog = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,7 +215,20 @@ func initDB(c *Config, useDB, useTelemetry bool) (*sql.DB, error) {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if useTelemetry && conf.telemetryEnabled() {
|
if useTelemetry && conf.telemetryEnabled() {
|
||||||
driverName, err = ocsql.Register(driverName, ocsql.WithAllTraceOptions(), ocsql.WithInstanceName(conf.AppName))
|
opts := ocsql.TraceOptions{
|
||||||
|
AllowRoot: false,
|
||||||
|
Ping: true,
|
||||||
|
RowsNext: true,
|
||||||
|
RowsClose: true,
|
||||||
|
RowsAffected: true,
|
||||||
|
LastInsertID: true,
|
||||||
|
Query: conf.Telemetry.Tracing.IncludeQuery,
|
||||||
|
QueryParams: conf.Telemetry.Tracing.IncludeParams,
|
||||||
|
}
|
||||||
|
opt := ocsql.WithOptions(opts)
|
||||||
|
name := ocsql.WithInstanceName(conf.AppName)
|
||||||
|
|
||||||
|
driverName, err = ocsql.Register(driverName, opt, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to register ocsql driver: %v", err)
|
return nil, fmt.Errorf("unable to register ocsql driver: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,11 @@ import (
|
||||||
rice "github.com/GeertJohan/go.rice"
|
rice "github.com/GeertJohan/go.rice"
|
||||||
"github.com/NYTimes/gziphandler"
|
"github.com/NYTimes/gziphandler"
|
||||||
"github.com/dosco/super-graph/internal/serv/internal/auth"
|
"github.com/dosco/super-graph/internal/serv/internal/auth"
|
||||||
|
"go.opencensus.io/plugin/ochttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
apiRoute string = "/api/v1/graphql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initWatcher() {
|
func initWatcher() {
|
||||||
|
@ -76,6 +81,10 @@ func startHTTP() {
|
||||||
MaxHeaderBytes: 1 << 20,
|
MaxHeaderBytes: 1 << 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.telemetryEnabled() {
|
||||||
|
srv.Handler = &ochttp.Handler{Handler: routes}
|
||||||
|
}
|
||||||
|
|
||||||
idleConnsClosed := make(chan struct{})
|
idleConnsClosed := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
sigint := make(chan os.Signal, 1)
|
sigint := make(chan os.Signal, 1)
|
||||||
|
@ -114,8 +123,6 @@ func routeHandler() (http.Handler, error) {
|
||||||
return mux, nil
|
return mux, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
apiRoute := "/api/v1/graphql"
|
|
||||||
|
|
||||||
if len(conf.APIPath) != 0 {
|
if len(conf.APIPath) != 0 {
|
||||||
apiRoute = path.Join("/", conf.APIPath, "/v1/graphql")
|
apiRoute = path.Join("/", conf.APIPath, "/v1/graphql")
|
||||||
}
|
}
|
||||||
|
@ -178,6 +185,10 @@ func setActionRoutes(routes map[string]http.Handler) error {
|
||||||
routes[p] = fn
|
routes[p] = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.telemetryEnabled() {
|
||||||
|
routes[p] = ochttp.WithRouteTag(routes[p], p)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,9 @@ func enableObservability(mux *http.ServeMux) (func(), error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ERR OpenCensus: %s: %v", conf.Telemetry.Tracing, err)
|
return nil, fmt.Errorf("ERR OpenCensus: %s: %v",
|
||||||
|
conf.Telemetry.Tracing.Exporter,
|
||||||
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tex != nil {
|
if tex != nil {
|
||||||
|
|
Loading…
Reference in New Issue