Skip to content
This repository was archived by the owner on Jun 12, 2024. It is now read-only.

Commit 1a3b401

Browse files
authored
feat: allow skipping http loggin per request (#268)
Provide configuration points to allow application to skip logging on specific requests. Additionally, allow configuration of the log fields as well. Signed-off-by: Lucas Roesler <[email protected]>
1 parent cec496c commit 1a3b401

File tree

4 files changed

+144
-28
lines changed

4 files changed

+144
-28
lines changed

pkg/http/middlewares/logging.go

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,26 @@ package middlewares
22

33
import (
44
"net/http"
5+
"strings"
56
"time"
67

8+
"github.com/go-chi/chi/middleware"
79
"github.com/sirupsen/logrus"
8-
"github.com/urfave/negroni"
9-
10-
server "github.com/contiamo/go-base/v4/pkg/http"
1110
)
1211

1312
// WithLogging configures a logrus middleware for that server.
1413
//
14+
// You can control the log fields or if the log should be skipped by setting
15+
// the Fields and the Ignore function respectively.
16+
//
17+
// log := middlewares.WithLogging(config.ApplicationName)
18+
// log.Fields = func(r *http.Request) logrus.Fields {
19+
// // custom logic here
20+
// }
21+
// log.Ignore = func(r *http.Request) bool {
22+
// // custom logic here
23+
// }
24+
//
1525
// When `tracing.SpanHook` is enabled and the tracing middleware is enabled
1626
// before the logging middleware, the traceId and spanId are attaced the the logs.
1727
//
@@ -33,30 +43,48 @@ import (
3343
// log.WrapHandler,
3444
// metrics.WrapHandler,
3545
// )
36-
func WithLogging(app string) server.Option {
37-
return &loggingOption{app}
46+
func WithLogging(app string) *LoggingOption {
47+
return &LoggingOption{
48+
app: app,
49+
}
3850
}
3951

40-
type loggingOption struct{ app string }
52+
type LoggingOption struct {
53+
app string
54+
// Ignore determines if the log should be skipped for the given request.
55+
//
56+
// When nil, the default logic is used, which ignores requests in which the
57+
// User-Agent contains: "healthcheck" or "kube-probe".
58+
Ignore func(r *http.Request) bool
59+
// Fields extracts log fields from the given trequest to include in the log entry.
60+
//
61+
// When nil, the default logic is used, which includes the request method and path.
62+
Fields func(r *http.Request) logrus.Fields
63+
}
64+
65+
func (opt *LoggingOption) WrapHandler(handler http.Handler) http.Handler {
66+
ignore := opt.Ignore
67+
if ignore == nil {
68+
ignore = defaultIgnore
69+
}
4170

42-
func (opt *loggingOption) WrapHandler(handler http.Handler) http.Handler {
71+
fields := opt.Fields
72+
if fields == nil {
73+
fields = defaultFields
74+
}
4375
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4476
start := time.Now()
45-
handler.ServeHTTP(w, r)
77+
78+
resp := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
79+
handler.ServeHTTP(resp, r)
4680
// when the tracing middleware is initialized first _and_
4781
// the tracing.SpanHook is configured, then the logger will
4882
// also emit the trace and span ids as fields
4983
logger := logrus.
5084
WithContext(r.Context()).
51-
WithFields(logrus.Fields{
52-
"app": opt.app,
53-
"path": r.URL.EscapedPath(),
54-
})
55-
resp, ok := w.(negroni.ResponseWriter)
56-
if !ok {
57-
logger.Warn("wrong request type")
58-
return
59-
}
85+
WithField("app", opt.app).
86+
WithFields(fields(r))
87+
6088
duration := time.Since(start)
6189
status := resp.Status()
6290
if status == 0 {
@@ -66,13 +94,37 @@ func (opt *loggingOption) WrapHandler(handler http.Handler) http.Handler {
6694
"duration_millis": duration.Nanoseconds() / 1000000,
6795
"status_code": status,
6896
})
69-
if status >= 200 && status < 400 {
70-
logger.Info("successfully handled request")
71-
} else {
97+
if !isSuccess(status) {
7298
logger.Warn("problem while handling request")
99+
return
100+
}
101+
102+
if !ignore(r) {
103+
logger.Info("successfully handled request")
73104
}
74105
})
75-
n := negroni.New()
76-
n.UseHandler(h)
77-
return n
106+
107+
return h
108+
}
109+
110+
func defaultFields(r *http.Request) logrus.Fields {
111+
return logrus.Fields{
112+
"method": r.Method,
113+
"path": r.URL.EscapedPath(),
114+
}
115+
}
116+
117+
func defaultIgnore(r *http.Request) bool {
118+
agent := strings.ToLower(r.Header.Get("User-Agent"))
119+
120+
switch {
121+
case strings.Contains(agent, "healthcheck"), strings.Contains(agent, "kube-probe"):
122+
return true
123+
default:
124+
return false
125+
}
126+
}
127+
128+
func isSuccess(status int) bool {
129+
return status >= 200 && status < 400
78130
}

pkg/http/middlewares/logging_test.go

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"net/http"
66
"net/http/httptest"
7+
"strings"
78
"testing"
89
"time"
910

@@ -35,7 +36,11 @@ func Test_LoggingMiddleware(t *testing.T) {
3536
require.NoError(t, err)
3637
defer resp.Body.Close()
3738

38-
require.Contains(t, buf.String(), "successfully handled request")
39+
data := buf.String()
40+
require.Equal(t, 1, strings.Count(data, "\n"))
41+
require.Contains(t, data, `level=info`)
42+
require.Contains(t, data, `msg="successfully handled request"`)
43+
require.Contains(t, data, `app=test duration_millis=0 method=GET path=/logging status_code=200`)
3944
})
4045

4146
t.Run("should support websockets", func(t *testing.T) {
@@ -52,6 +57,61 @@ func Test_LoggingMiddleware(t *testing.T) {
5257
require.NoError(t, err)
5358

5459
time.Sleep(100 * time.Millisecond)
55-
require.Contains(t, buf.String(), "successfully handled request")
60+
data := buf.String()
61+
require.Equal(t, 1, strings.Count(data, "\n"))
62+
require.Contains(t, data, `level=info`)
63+
require.Contains(t, data, `msg="successfully handled request"`)
64+
require.Contains(t, data, `app=test duration_millis=0 method=GET path=/ws/echo status_code=200`)
65+
})
66+
67+
t.Run("should ignore healthchecks", func(t *testing.T) {
68+
buf, restore := utils.SetupLoggingBuffer()
69+
defer restore()
70+
71+
srv, err := createServer([]server.Option{WithLogging("test")})
72+
require.NoError(t, err)
73+
74+
ts := httptest.NewServer(srv.Handler)
75+
defer ts.Close()
76+
77+
req, err := http.NewRequest(http.MethodGet, ts.URL+"/logging", nil)
78+
require.NoError(t, err)
79+
req = req.WithContext(ctx)
80+
req.Header.Set("User-Agent", "ELB-HealthChecker")
81+
82+
resp, err := http.DefaultClient.Do(req)
83+
require.NoError(t, err)
84+
defer resp.Body.Close()
85+
86+
require.Equal(t, 200, resp.StatusCode)
87+
88+
require.Equal(t, buf.String(), "")
89+
})
90+
91+
t.Run("should log on error codes", func(t *testing.T) {
92+
buf, restore := utils.SetupLoggingBuffer()
93+
defer restore()
94+
95+
srv, err := createServer([]server.Option{WithLogging("test")})
96+
require.NoError(t, err)
97+
98+
ts := httptest.NewServer(srv.Handler)
99+
defer ts.Close()
100+
101+
req, err := http.NewRequest(http.MethodGet, ts.URL+"/error", nil)
102+
require.NoError(t, err)
103+
req = req.WithContext(ctx)
104+
105+
resp, err := http.DefaultClient.Do(req)
106+
require.NoError(t, err)
107+
defer resp.Body.Close()
108+
109+
require.Equal(t, 400, resp.StatusCode)
110+
111+
data := buf.String()
112+
require.Equal(t, 1, strings.Count(data, "\n"), data)
113+
require.Contains(t, data, `level=warn`)
114+
require.Contains(t, data, `msg="problem while handling request"`)
115+
require.Contains(t, data, `app=test duration_millis=0 method=GET path=/error status_code=400`)
56116
})
57117
}

pkg/http/middlewares/middleware_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ func createServer(opts []server.Option) (*http.Server, error) {
3737

3838
mux.Handle("/metrics", promhttp.Handler())
3939
mux.HandleFunc("/ws/", echoWS)
40+
mux.HandleFunc("/error", func(w http.ResponseWriter, r *http.Request) {
41+
w.WriteHeader(http.StatusBadRequest)
42+
_, _ = io.Copy(w, r.Body)
43+
})
4044
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
4145
if r.URL.Path == "/panic" {
4246
panic("PANIC!!!")

pkg/http/middlewares/tracing.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type tracingOption struct {
3131
}
3232

3333
func (opt *tracingOption) WrapHandler(handler http.Handler) http.Handler {
34-
mw := middleware(
34+
mw := traceMiddleware(
3535
opentracing.GlobalTracer(),
3636
handler,
3737
operationNameFunc(opt.opNameFunc),
@@ -90,7 +90,7 @@ func mwComponentName(componentName string) mwOption {
9090
// Example:
9191
// http.ListenAndServe("localhost:80", nethttp.Middleware(tracer, http.DefaultServeMux))
9292
//
93-
// The options allow fine tuning the behavior of the middleware.
93+
// The options allow fine tuning the behavior of the traceMiddleware.
9494
//
9595
// Example:
9696
// mw := nethttp.Middleware(
@@ -103,7 +103,7 @@ func mwComponentName(componentName string) mwOption {
103103
// sp.SetTag("http.uri", r.URL.EscapedPath())
104104
// }),
105105
// )
106-
func middleware(tr opentracing.Tracer, h http.Handler, options ...mwOption) http.Handler {
106+
func traceMiddleware(tr opentracing.Tracer, h http.Handler, options ...mwOption) http.Handler {
107107
opts := mwOptions{
108108
opNameFunc: func(r *http.Request) string {
109109
return "HTTP " + r.Method

0 commit comments

Comments
 (0)