-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy patho11ygin.go
151 lines (132 loc) · 4.63 KB
/
o11ygin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package o11ygin
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/circleci/ex/o11y"
"github.com/circleci/ex/o11y/wrappers/baggage"
)
const contextCancelledKey = "o11y-context-cancelled-key"
// Middleware for Gin router
//
//nolint:funlen
func Middleware(provider o11y.Provider, serverName string, queryParams map[string]struct{}) gin.HandlerFunc {
m := provider.MetricsProvider()
return func(c *gin.Context) {
before := time.Now()
ctx := o11y.WithProvider(c.Request.Context(), provider)
ctx = o11y.WithBaggage(ctx, baggage.Get(ctx, c.Request))
ctx, span := startSpanOrTraceFromHTTP(ctx, c, provider, serverName)
defer span.End()
c.Request = c.Request.WithContext(ctx)
// pull out any variables in the URL, add the thing we're matching, etc.
for _, param := range c.Params {
span.AddRawField("handler.vars."+param.Key, param.Value)
}
// pull out any GET query params
if queryParams != nil {
for key, value := range c.Request.URL.Query() {
if _, ok := queryParams[key]; ok {
if len(value) > 1 {
span.AddRawField("handler.query."+key, value)
} else if len(value) == 1 {
span.AddRawField("handler.query."+key, value[0])
} else {
span.AddRawField("handler.query."+key, nil)
}
}
}
}
// Server OTEL attributes
span.AddRawField("meta.type", "http_server")
span.AddRawField("http.server_name", serverName)
span.AddRawField("http.route", c.FullPath())
span.AddRawField("http.client_ip", c.ClientIP())
// Common OTEL attributes
span.AddRawField("http.method", c.Request.Method)
span.AddRawField("http.url", c.Request.URL.String())
span.AddRawField("http.target", c.Request.URL.Path)
span.AddRawField("http.host", c.Request.Host)
span.AddRawField("http.scheme", c.Request.URL.Scheme)
span.AddRawField("http.user_agent", c.Request.UserAgent())
span.AddRawField("http.request_content_length", c.Request.ContentLength)
defer func() {
// Common OTEL attributes
o11yStatus := c.Writer.Status()
if c.GetBool(contextCancelledKey) {
o11yStatus = 499
}
span.AddRawField("http.status_code", o11yStatus)
span.AddRawField("http.response_content_length", c.Writer.Size())
if m != nil {
_ = m.TimeInMilliseconds("handler",
float64(time.Since(before).Nanoseconds())/1000000.0,
[]string{
"http.server_name:" + serverName,
"http.method:" + c.Request.Method,
"http.route:" + c.FullPath(),
"http.status_code:" + strconv.Itoa(o11yStatus),
//TODO: "has_panicked:"+,
},
1,
)
}
}()
// Run the next function in the Middleware chain
c.Next()
}
}
// ClientCancelled is a gin middleware that will trap a request context cancellation
// and return a 499 (a.la. nginx).
// If the response has already been written to, for example setting a status code, then
// that code will be honoured.
func ClientCancelled() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := c.Request.Context()
defer func() {
if errors.Is(ctx.Err(), context.Canceled) {
c.Set(contextCancelledKey, true)
return
}
// check whether there were any errors within the gin handling, for instance
// during rendering, and make a not on any active span
if len(c.Errors) > 0 {
o11y.AddField(ctx, "gin_internal_error", c.Errors)
}
}()
c.Next()
}
}
func Recovery() func(c *gin.Context) {
return gin.CustomRecoveryWithWriter(nil, func(c *gin.Context, err interface{}) {
c.AbortWithStatus(http.StatusInternalServerError)
ctx := c.Request.Context()
span := o11y.FromContext(ctx).GetSpan(ctx)
// Most likely caused by one side of the proxy disappearing. Not really a panic
// https://github.com/golang/go/issues/28239
if origErr, ok := err.(error); ok && errors.Is(origErr, http.ErrAbortHandler) {
// prevent reporting to rollbar for this expected error, report as an error instead
o11y.AddResultToSpan(span, origErr)
return
}
_ = o11y.HandlePanic(ctx, span, err, c.Request)
})
}
func startSpanOrTraceFromHTTP(ctx context.Context,
c *gin.Context, p o11y.Provider, serverName string) (context.Context, o11y.Span) {
span := p.GetSpan(ctx)
if span == nil {
// there is no trace yet. We should make one! and use the root span.
ctx, span := p.Helpers().InjectPropagation(ctx, o11y.PropagationContextFromHeader(c.Request.Header))
span.AddRawField("name", fmt.Sprintf("http-server %s: %s %s", serverName, c.Request.Method, c.FullPath()))
return ctx, span
} else {
// we had a parent! let's make a new child for this handler
ctx, span = o11y.StartSpan(ctx, fmt.Sprintf("http-server %s: %s %s", serverName, c.Request.Method, c.FullPath()))
}
return ctx, span
}