git.sr.ht/~sircmpwn/gqlgen@v0.0.0-20200522192042-c84d29a1c940/graphql/handler/apollotracing/tracer.go (about) 1 package apollotracing 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 8 "git.sr.ht/~sircmpwn/gqlgen/graphql" 9 "github.com/vektah/gqlparser/v2/ast" 10 ) 11 12 type ( 13 Tracer struct{} 14 15 TracingExtension struct { 16 mu sync.Mutex 17 Version int `json:"version"` 18 StartTime time.Time `json:"startTime"` 19 EndTime time.Time `json:"endTime"` 20 Duration time.Duration `json:"duration"` 21 Parsing Span `json:"parsing"` 22 Validation Span `json:"validation"` 23 Execution struct { 24 Resolvers []ResolverExecution `json:"resolvers"` 25 } `json:"execution"` 26 } 27 28 Span struct { 29 StartOffset time.Duration `json:"startOffset"` 30 Duration time.Duration `json:"duration"` 31 } 32 33 ResolverExecution struct { 34 Path ast.Path `json:"path"` 35 ParentType string `json:"parentType"` 36 FieldName string `json:"fieldName"` 37 ReturnType string `json:"returnType"` 38 StartOffset time.Duration `json:"startOffset"` 39 Duration time.Duration `json:"duration"` 40 } 41 ) 42 43 var _ interface { 44 graphql.HandlerExtension 45 graphql.ResponseInterceptor 46 graphql.FieldInterceptor 47 } = Tracer{} 48 49 func (a Tracer) ExtensionName() string { 50 return "ApolloTracing" 51 } 52 53 func (a Tracer) Validate(schema graphql.ExecutableSchema) error { 54 return nil 55 } 56 57 func (a Tracer) InterceptField(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { 58 rc := graphql.GetOperationContext(ctx) 59 td, ok := graphql.GetExtension(ctx, "tracing").(*TracingExtension) 60 if !ok { 61 panic("missing tracing extension") 62 } 63 64 start := graphql.Now() 65 66 defer func() { 67 td.mu.Lock() 68 defer td.mu.Unlock() 69 fc := graphql.GetFieldContext(ctx) 70 71 end := graphql.Now() 72 73 td.Execution.Resolvers = append(td.Execution.Resolvers, ResolverExecution{ 74 Path: fc.Path(), 75 ParentType: fc.Object, 76 FieldName: fc.Field.Name, 77 ReturnType: fc.Field.Definition.Type.String(), 78 StartOffset: start.Sub(rc.Stats.OperationStart), 79 Duration: end.Sub(start), 80 }) 81 }() 82 83 return next(ctx) 84 } 85 86 func (a Tracer) InterceptResponse(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { 87 rc := graphql.GetOperationContext(ctx) 88 89 start := rc.Stats.OperationStart 90 91 td := &TracingExtension{ 92 Version: 1, 93 StartTime: start, 94 Parsing: Span{ 95 StartOffset: rc.Stats.Parsing.Start.Sub(start), 96 Duration: rc.Stats.Parsing.End.Sub(rc.Stats.Parsing.Start), 97 }, 98 99 Validation: Span{ 100 StartOffset: rc.Stats.Validation.Start.Sub(start), 101 Duration: rc.Stats.Validation.End.Sub(rc.Stats.Validation.Start), 102 }, 103 104 Execution: struct { 105 Resolvers []ResolverExecution `json:"resolvers"` 106 }{}, 107 } 108 109 graphql.RegisterExtension(ctx, "tracing", td) 110 resp := next(ctx) 111 112 end := graphql.Now() 113 td.EndTime = end 114 td.Duration = end.Sub(start) 115 116 return resp 117 }