Skip to content

Commit

Permalink
Merge pull request #30 from Bose/addLoggingFunction
Browse files Browse the repository at this point in the history
Addition Option for specifying a reducedLogging function
  • Loading branch information
Alex-Grant-Bose committed Jun 16, 2020
2 parents 0002f2f + 65314f0 commit 44989be
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 14 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,30 @@ func newSpanFromContext(c *gin.Context, operationName string) opentracing.Span {

See the [example.go file](https://github.com/Bose/go-gin-logrus/blob/master/example/example.go)

## Reduced Logging Options
The Options.WithReducedLoggingFunc(c *gin.Context) allows users to specify a function for determining whether or not logs will be written. This function can be used with aggregate logging in situations where users want to maintain the details and fidelity of log messages but not necessarily log on every single request. The example below allows users to maintain aggregate logs at the DEBUG level but only write logs out on non-2xx response codes.
Reduced Logging Function:
``` go
// This function will determine whether to write a log message or not.
// When the request is not a 2xx the function will return true indicating that a log message should be written.
func ProductionLogging(c *gin.Context) bool {
statusCode := c.Writer.Status()
if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
return true
}
return false
}
```

``` go
r.Use(ginlogrus.WithTracing(logrus.StandardLogger(),
useBanner,
time.RFC3339,
useUTC,
"requestID",
[]byte("uber-trace-id"), // where jaeger might have put the trace id
[]byte("RequestID"), // where the trace ID might already be populated in the headers
ginlogrus.WithAggregateLogging(true),
ginlogrus.WithReducedLoggingFunc(ProductionLogging)))
```

16 changes: 10 additions & 6 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func WithTracing(
contextTraceIDField []byte,
opt ...Option) gin.HandlerFunc {
opts := defaultOptions

for _, o := range opt {
o(&opts)
}
Expand Down Expand Up @@ -116,13 +117,16 @@ func WithTracing(
entry.Info()
}
}
// If aggregate logging is enabled, check if we have entries to log or we are not omitting empty logs
if opts.aggregateLogging {
if aggregateLoggingBuff.Length() > 0 || opts.emptyAggregateEntries {
aggregateLoggingBuff.StoreHeader("request-summary-info", fields)
// if useBanner {
// fields["banner"] = "[GIN] --------------------------------------------------------------- GinLogrusWithTracing ----------------------------------------------------------------"
// }
fmt.Fprintf(opts.writer, aggregateLoggingBuff.String())
// If we are running structured logging, execute the reduced logging function(default to true)
// if we pass the check, check if we have any entries to log or if we are logging empty entries (default to true)
executeReduced := opts.reducedLoggingFunc(c)
if executeReduced {
if aggregateLoggingBuff.Length() > 0 || opts.emptyAggregateEntries {
aggregateLoggingBuff.StoreHeader("request-summary-info", fields)
fmt.Fprintf(opts.writer, aggregateLoggingBuff.String())
}
}
}
}
Expand Down
127 changes: 127 additions & 0 deletions middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ginlogrus

import (
"bytes"
"net/http"
"net/http/httptest"
"strings"
"testing"
Expand Down Expand Up @@ -161,3 +162,129 @@ func TestBanner(t *testing.T) {
is.True(strings.Contains(l.String(), customBanner))

}

func TestLogMessageWithProductionLevelReducedLogging(t *testing.T) {
is := is.New(t)
buff := ""
getHandler := func(c *gin.Context) {
SetCtxLoggerHeader(c, "ReducedLogging", "Shouldn't have messages with a 2xx response")

logger := GetCtxLogger(c)
logger.Info("test-entry-1")
logger.Info("test-entry-2")
logger.Error("error-entry-1")
c.JSON(200, "Hello world!")
}
failHandler := func(c *gin.Context) {
SetCtxLoggerHeader(c, "ReducedLogging", "Shouldn't have messages with a 2xx response")
logger := GetCtxLogger(c)
logger.Info("test-entry-1")
logger.Info("test-entry-2")
logger.Error("error-entry-1")
c.JSON(401, "Hello fail!")
}
gin.SetMode(gin.DebugMode)
gin.DisableConsoleColor()

l := bytes.NewBufferString(buff)
r := gin.Default()
r.Use(WithTracing(logrus.StandardLogger(),
false,
time.RFC3339,
true,
"requestID",
[]byte("uber-trace-id"), // where jaeger might have put the trace id
[]byte("RequestID"), // where the trace ID might already be populated in the headers
WithAggregateLogging(true),
WithWriter(l),
WithReducedLoggingFunc(productionLoggingTestFunc),
))
r.GET("/", getHandler)
r.GET("/fail", failHandler)
w := performRequest("GET", "/", r)
is.Equal(200, w.Code)
t.Log("this is the buffer: ", l)
// Beacuase the request is a 2xx we will not have any log entries including possible errors
is.True(len(l.String()) == 0)
is.True(!strings.Contains(l.String(), "test-entry-1"))
is.True(!strings.Contains(l.String(), "test-entry-2"))
is.True(!strings.Contains(l.String(), "error-entry-1"))

w = performRequest("GET", "/fail", r)
is.Equal(401, w.Code)
t.Log("this is the buffer: ", l)
// Beacuase the request is a 401 we will have all log entries including info logs
is.True(len(l.String()) > 0)
is.True(strings.Contains(l.String(), "test-entry-1"))
is.True(strings.Contains(l.String(), "test-entry-2"))
is.True(strings.Contains(l.String(), "error-entry-1"))

}

func TestLogMessageWithProductionReducedLoggingWarnLevel(t *testing.T) {
is := is.New(t)
buff := ""
getHandler := func(c *gin.Context) {
SetCtxLoggerHeader(c, "ReducedLogging", "Shouldn't have messages with a 2xx response")

logger := GetCtxLogger(c)
logger.Info("test-entry-1")
logger.Info("test-entry-2")
logger.Error("error-entry-1")
c.JSON(200, "Hello world!")
}
failHandler := func(c *gin.Context) {
SetCtxLoggerHeader(c, "ReducedLogging", "Shouldn't have messages with a 2xx response")
logger := GetCtxLogger(c)
logger.Info("test-entry-1")
logger.Info("test-entry-2")
logger.Error("error-entry-1")
c.JSON(401, "Hello fail!")
}
gin.SetMode(gin.DebugMode)
gin.DisableConsoleColor()

l := bytes.NewBufferString(buff)
r := gin.Default()
r.Use(WithTracing(logrus.StandardLogger(),
false,
time.RFC3339,
true,
"requestID",
[]byte("uber-trace-id"), // where jaeger might have put the trace id
[]byte("RequestID"), // where the trace ID might already be populated in the headers
WithAggregateLogging(true),
WithWriter(l),
WithLogLevel(logrus.WarnLevel),
WithReducedLoggingFunc(productionLoggingTestFunc),
))
r.GET("/", getHandler)
r.GET("/fail", failHandler)
w := performRequest("GET", "/", r)
is.Equal(200, w.Code)
t.Log("this is the buffer: ", l)
// Beacuase the request is a 2xx we will not have any log entries including possible errors
is.True(len(l.String()) == 0)
is.True(!strings.Contains(l.String(), "test-entry-1"))
is.True(!strings.Contains(l.String(), "test-entry-2"))
is.True(!strings.Contains(l.String(), "error-entry-1"))

w = performRequest("GET", "/fail", r)
is.Equal(401, w.Code)
t.Log("this is the buffer: ", l)
// Beacuase the request is a 401 we will have log entries but because we have our log level at WARN we will not have info logs
is.True(len(l.String()) > 0)
is.True(!strings.Contains(l.String(), "test-entry-1"))
is.True(!strings.Contains(l.String(), "test-entry-2"))
is.True(strings.Contains(l.String(), "error-entry-1"))

}

// Same production logging function that will only log on statusCodes in a certain range
func productionLoggingTestFunc(c *gin.Context) bool {
statusCode := c.Writer.Status()
if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
return true
}
return false
}
31 changes: 23 additions & 8 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,34 @@ import (
"io"
"os"

"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)

// Option - define options for WithTracing()
type Option func(*options)

// Function definition for reduced logging. The return value of this function
// will be used to determine whether or not a log will be output.
type ReducedLoggingFunc func(c *gin.Context) bool

type options struct {
aggregateLogging bool
aggregateLogging bool
logLevel logrus.Level
emptyAggregateEntries bool
logLevel logrus.Level
writer io.Writer
banner string
reducedLoggingFunc ReducedLoggingFunc
writer io.Writer
banner string
}

// defaultOptions - some defs options to NewJWTCache()
var defaultOptions = options{
aggregateLogging: false,
aggregateLogging: false,
logLevel: logrus.DebugLevel,
emptyAggregateEntries: true,
logLevel: logrus.DebugLevel,
writer: os.Stdout,
banner: DefaultBanner,
reducedLoggingFunc: func(c *gin.Context) bool { return true },
writer: os.Stdout,
banner: DefaultBanner,
}

// WithAggregateLogging - define an Option func for passing in an optional aggregateLogging
Expand All @@ -40,6 +48,13 @@ func WithEmptyAggregateEntries(a bool) Option {
}
}

// WithReducedLoggingFunc - define an Option func for reducing logs based on a custom function
func WithReducedLoggingFunc(a ReducedLoggingFunc) Option {
return func(o *options) {
o.reducedLoggingFunc = a
}
}

// WithLogLevel - define an Option func for passing in an optional logLevel
func WithLogLevel(logLevel logrus.Level) Option {
return func(o *options) {
Expand Down

0 comments on commit 44989be

Please sign in to comment.