feat: tracing improvements (#4988)

This commit is contained in:
Dean Sheather
2022-11-29 07:22:10 +10:00
committed by GitHub
parent d402914eb7
commit 15f8967a8a
10 changed files with 342 additions and 34 deletions
+13 -3
View File
@@ -1,6 +1,8 @@
package httpmw
import (
"context"
"fmt"
"net/http"
"time"
@@ -11,9 +13,13 @@ import (
func Logger(log slog.Logger) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
start := time.Now()
sw := &tracing.StatusWriter{ResponseWriter: w}
sw, ok := rw.(*tracing.StatusWriter)
if !ok {
panic(fmt.Sprintf("ResponseWriter not a *tracing.StatusWriter; got %T", rw))
}
httplog := log.With(
slog.F("host", httpapi.RequestHost(r)),
@@ -51,7 +57,11 @@ func Logger(log slog.Logger) func(next http.Handler) http.Handler {
logLevelFn = httplog.Warn
}
logLevelFn(r.Context(), r.Method)
// We already capture most of this information in the span (minus
// the response body which we don't want to capture anyways).
tracing.RunWithoutSpan(r.Context(), func(ctx context.Context) {
logLevelFn(ctx, r.Method)
})
})
}
}
@@ -0,0 +1,50 @@
package patternmatcher
import (
"fmt"
"regexp"
"strings"
"golang.org/x/xerrors"
)
// RoutePatterns provides a method to generate a regex which will match a URL
// path against a collection of patterns. If any of the patterns match the path,
// the regex will return a successful match.
//
// Multiple patterns can be provided and they are matched in order. Example:
// - /api/* matches /api/1 but not /api or /api/1/2
// - /api/*/2 matches /api/1/2 but not /api/2 /api/1
// - /api/** matches /api/1, /api/1/2, /api/1/2/3 but not /api
// - /api/**/3 matches /api/1/2, /api/1/2/3 but not /api, /api/1 or /api/1/2
//
// All patterns support an optional trailing slash.
type RoutePatterns []string
func (rp RoutePatterns) MustCompile() *regexp.Regexp {
re, err := rp.Compile()
if err != nil {
panic(err)
}
return re
}
func (rp RoutePatterns) Compile() (*regexp.Regexp, error) {
patterns := make([]string, len(rp))
for i, p := range rp {
p = strings.ReplaceAll(p, "**", ".+")
p = strings.ReplaceAll(p, "*", "[^/]+")
if !strings.HasSuffix(p, "/") {
p += "/?"
}
patterns[i] = p
}
pattern := fmt.Sprintf("^(%s)$", strings.Join(patterns, "|"))
re, err := regexp.Compile(pattern)
if err != nil {
return nil, xerrors.Errorf("compile regex %q: %w", pattern, err)
}
return re, nil
}
@@ -0,0 +1,135 @@
package patternmatcher_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/httpmw/patternmatcher"
)
func Test_RoutePatterns(t *testing.T) {
t.Parallel()
cases := []struct {
name string
patterns []string
errContains string
output string
}{
{
name: "Empty",
patterns: []string{},
output: "^()$",
},
{
name: "Single",
patterns: []string{
"/api",
},
output: "^(/api/?)$",
},
{
name: "TrailingSlash",
patterns: []string{
"/api/",
},
output: "^(/api/)$",
},
{
name: "Multiple",
patterns: []string{
"/api",
"/api2",
},
output: "^(/api/?|/api2/?)$",
},
{
name: "Star",
patterns: []string{
"/api/*",
},
output: "^(/api/[^/]+/?)$",
},
{
name: "StarStar",
patterns: []string{
"/api/**",
},
output: "^(/api/.+/?)$",
},
{
name: "TelemetryPatterns",
patterns: []string{
"/api",
"/api/**",
"/@*/*/apps/**",
"/%40*/*/apps/**",
"/gitauth/*/callback",
},
output: "^(/api/?|/api/.+/?|/@[^/]+/[^/]+/apps/.+/?|/%40[^/]+/[^/]+/apps/.+/?|/gitauth/[^/]+/callback/?)$",
},
{
name: "Slash",
patterns: []string{
"/",
},
output: "^(/)$",
},
{
name: "SlashStar",
patterns: []string{
"/*",
},
output: "^(/[^/]+/?)$",
},
{
name: "SlashStarStar",
patterns: []string{
"/**",
},
output: "^(/.+/?)$",
},
{
name: "SlashSlash",
patterns: []string{
"//",
"/api//v1",
},
output: "^(//|/api//v1/?)$",
},
{
name: "Invalid",
patterns: []string{
"/api(",
},
errContains: "compile regex",
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
rp := patternmatcher.RoutePatterns(c.patterns)
re, err := rp.Compile()
if c.errContains != "" {
require.Error(t, err)
require.ErrorContains(t, err, c.errContains)
require.Panics(t, func() {
_ = rp.MustCompile()
})
} else {
require.NoError(t, err)
require.Equal(t, c.output, re.String())
require.NotPanics(t, func() {
re := rp.MustCompile()
require.Equal(t, c.output, re.String())
})
}
})
}
}