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