• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pace / bricks / 11250208184

09 Oct 2024 07:25AM UTC coverage: 57.466% (-13.7%) from 71.177%
11250208184

push

github

web-flow
Merge pull request #380 from pace/sentry-tracing-poc

tracing: replace Jaeger with Sentry

140 of 206 new or added lines in 19 files covered. (67.96%)

3 existing lines in 3 files now uncovered.

5515 of 9597 relevant lines covered (57.47%)

21.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.53
/maintenance/log/handler.go
1
// Copyright © 2018 by PACE Telematics GmbH. All rights reserved.
2

3
package log
4

5
import (
6
        "net"
7
        "net/http"
8
        "strings"
9
        "time"
10

11
        "github.com/getsentry/sentry-go"
12

13
        "github.com/pace/bricks/maintenance/log/hlog"
14
        "github.com/rs/xid"
15
        "github.com/rs/zerolog"
16
        "github.com/rs/zerolog/log"
17
)
18

19
// RequestIDHeader name of the header that can contain a request ID
20
const RequestIDHeader = "Request-Id"
21

22
// Handler returns a middleware that handles all of the logging aspects of
23
// any incoming http request. Optionally several path prefixes like "/health"
24
// can be provided to decrease log spamming. All url paths with these
25
// prefixes will not be logged to the standard output but still be available
26
// in the request specific Sink.
27
func Handler(silentPrefixes ...string) func(http.Handler) http.Handler {
2✔
28
        return func(next http.Handler) http.Handler {
4✔
29
                if !cfg.LogCompletedRequest {
2✔
30
                        return hlog.NewHandler(log.Logger)(
×
31
                                handlerWithSink(silentPrefixes...)(
×
32
                                        RequestIDHandler("req_id", RequestIDHeader)(next)))
×
33
                }
×
34

35
                return hlog.NewHandler(log.Logger)(
2✔
36
                        handlerWithSink(silentPrefixes...)(
2✔
37
                                hlog.AccessHandler(requestCompleted)(
2✔
38
                                        RequestIDHandler("req_id", RequestIDHeader)(next))))
2✔
39
        }
40
}
41

42
// requestCompleted logs all request related information once
43
// at the end of the request
44
var requestCompleted = func(r *http.Request, status, size int, duration time.Duration) {
2✔
45
        ctx := r.Context()
2✔
46

2✔
47
        var traceID string
2✔
48

2✔
49
        span := sentry.SpanFromContext(ctx)
2✔
50
        if span != nil {
2✔
NEW
51
                traceID = span.TraceID.String()
×
52
        }
×
53

54
        hlog.FromRequest(r).Info().
2✔
55
                Str("method", r.Method).
2✔
56
                Str("url", r.URL.String()).
2✔
57
                Int("status", status).
2✔
58
                Str("host", r.Host).
2✔
59
                Int("size", size).
2✔
60
                Dur("duration", duration).
2✔
61
                Str("ip", ProxyAwareRemote(r)).
2✔
62
                Str("referer", r.Header.Get("Referer")).
2✔
63
                Str("user_agent", r.Header.Get("User-Agent")).
2✔
64
                Str("trace_id", traceID).
2✔
65
                Msg("Request Completed")
2✔
66
}
67

68
// ProxyAwareRemote return the most likely remote address
69
func ProxyAwareRemote(r *http.Request) string {
2✔
70
        // if we get the content via a proxy, try to extract the
2✔
71
        // ip from the usual headers
2✔
72
        for _, h := range []string{"X-Forwarded-For", "X-Real-Ip"} {
6✔
73
                addresses := strings.Split(r.Header.Get(h), ",")
4✔
74
                for i := len(addresses) - 1; i >= 0; i-- {
8✔
75
                        ip := strings.TrimSpace(addresses[i])
4✔
76
                        realIP := net.ParseIP(ip)
4✔
77
                        if !realIP.IsGlobalUnicast() || isPrivate(realIP) {
8✔
78
                                continue // bad address, go to next
4✔
79
                        }
80
                        return ip
×
81
                }
82
        }
83
        // if no proxy header is present return the
84
        // regular remote address
85
        host, _, err := net.SplitHostPort(r.RemoteAddr)
2✔
86
        if err != nil {
2✔
87
                log.Ctx(r.Context()).Warn().Err(err).Msg("failed to decode the remote address")
×
88
                return ""
×
89
        }
×
90
        return host
2✔
91
}
92

93
// isPrivate reports whether `ip' is a local address, according to
94
// RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
95
// Remove as soon as https://github.com/golang/go/issues/29146 is resolved
96
func isPrivate(ip net.IP) bool {
9✔
97
        if ip4 := ip.To4(); ip4 != nil {
15✔
98
                // Local IPv4 addresses are defined in https://tools.ietf.org/html/rfc1918
6✔
99
                return ip4[0] == 10 ||
6✔
100
                        (ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
6✔
101
                        (ip4[0] == 192 && ip4[1] == 168)
6✔
102
        }
6✔
103

104
        // Local IPv6 addresses are defined in https://tools.ietf.org/html/rfc4193
105
        return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
3✔
106
}
107

108
func RequestIDHandler(fieldKey, headerName string) func(next http.Handler) http.Handler {
2✔
109
        return func(next http.Handler) http.Handler {
4✔
110
                return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4✔
111
                        ctx := r.Context()
2✔
112
                        var id xid.ID
2✔
113

2✔
114
                        // try extract of xid from header
2✔
115
                        reqID := r.Header.Get(headerName)
2✔
116
                        if reqID != "" {
2✔
117
                                id, _ = xid.FromString(reqID)
×
118
                        }
×
119

120
                        // try extract of xid context
121
                        if id.Compare(xid.NilID()) == 0 {
4✔
122
                                if nid, ok := hlog.IDFromCtx(ctx); ok {
2✔
123
                                        id = nid
×
124
                                }
×
125
                        }
126

127
                        // give up, generate a new xid
128
                        if id.Compare(xid.NilID()) == 0 {
4✔
129
                                id = xid.New()
2✔
130
                        }
2✔
131

132
                        ctx = hlog.WithValue(ctx, id)
2✔
133
                        r = r.WithContext(ctx)
2✔
134

2✔
135
                        // log requests with request id
2✔
136
                        log := zerolog.Ctx(ctx)
2✔
137
                        log.UpdateContext(func(c zerolog.Context) zerolog.Context {
4✔
138
                                return c.Str(fieldKey, id.String())
2✔
139
                        })
2✔
140

141
                        // return the request id as a header to the client
142
                        w.Header().Set(headerName, id.String())
2✔
143
                        next.ServeHTTP(w, r)
2✔
144
                })
145
        }
146
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc