github.com/geneva/gqlgen@v0.17.7-0.20230801155730-7b9317164836/graphql/handler/apollotracing/tracer.go (about) 1 package apollotracing 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 8 "github.com/geneva/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 (Tracer) ExtensionName() string { 50 return "ApolloTracing" 51 } 52 53 func (Tracer) Validate(graphql.ExecutableSchema) error { 54 return nil 55 } 56 57 func (Tracer) InterceptField(ctx context.Context, next graphql.Resolver) (interface{}, error) { 58 td, ok := graphql.GetExtension(ctx, "tracing").(*TracingExtension) 59 if !ok { 60 return next(ctx) 61 } 62 63 start := graphql.Now() 64 65 defer func() { 66 end := graphql.Now() 67 68 rc := graphql.GetOperationContext(ctx) 69 fc := graphql.GetFieldContext(ctx) 70 resolver := &ResolverExecution{ 71 Path: fc.Path(), 72 ParentType: fc.Object, 73 FieldName: fc.Field.Name, 74 ReturnType: fc.Field.Definition.Type.String(), 75 StartOffset: start.Sub(rc.Stats.OperationStart), 76 Duration: end.Sub(start), 77 } 78 79 td.mu.Lock() 80 td.Execution.Resolvers = append(td.Execution.Resolvers, resolver) 81 td.mu.Unlock() 82 }() 83 84 return next(ctx) 85 } 86 87 func (Tracer) InterceptResponse(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { 88 if !graphql.HasOperationContext(ctx) { 89 return next(ctx) 90 } 91 92 rc := graphql.GetOperationContext(ctx) 93 94 start := rc.Stats.OperationStart 95 96 td := &TracingExtension{ 97 Version: 1, 98 StartTime: start, 99 Parsing: Span{ 100 StartOffset: rc.Stats.Parsing.Start.Sub(start), 101 Duration: rc.Stats.Parsing.End.Sub(rc.Stats.Parsing.Start), 102 }, 103 104 Validation: Span{ 105 StartOffset: rc.Stats.Validation.Start.Sub(start), 106 Duration: rc.Stats.Validation.End.Sub(rc.Stats.Validation.Start), 107 }, 108 } 109 110 graphql.RegisterExtension(ctx, "tracing", td) 111 resp := next(ctx) 112 113 end := graphql.Now() 114 td.EndTime = end 115 td.Duration = end.Sub(start) 116 117 return resp 118 }