ginlogrus

package module
v1.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2019 License: BSD-3-Clause Imports: 13 Imported by: 3

README

go-gin-logrus

Go Report Card Release

Gin Web Framework Open Tracing middleware.

This middleware also support aggregate logging: the ability to aggregate all log entries into just one write. This aggregation is helpful when your logs are being sent to Kibana and you only want to index one log per request.

Installation

$ go get github.com/Bose/go-gin-logrus

If you want to use it with opentracing you could consider installing:

$ go get github.com/Bose/go-gin-opentracing

Dependencies - for local development

If you want to see your traces on your local system, you'll need to run a tracing backend like Jaeger. You'll find info about how-to in the Jaeger Tracing github repo docs Basically, you can run the Jaeger opentracing backend under docker via:

docker run -d -e \
  COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 9411:9411 \
  jaegertracing/all-in-one:latest

Usage

# example aggregated log entry for a request with UseBanner == true
{
  "new-header-index-name": "this is how you set new header level data",
  "request-summary-info": {
    "comment": "",
    "ip": "::1",
    "latency": "     98.217µs",
    "method": "GET",
    "path": "/",
    "requestID": "4b4fb22ef51cc540:4b4fb22ef51cc540:0:1",
    "status": 200,
    "time": "2019-02-06T13:24:06Z",
    "user-agent": "curl/7.54.0"
  },
  "entries": [
    {
      "level": "info",
      "msg": "this will be aggregated into one write with the access log and will show up when the request is completed",
      "time": "2019-02-06T08:24:06-05:00"
    },
    {
      "comment": "this is an aggregated log entry with initial comment field",
      "level": "debug",
      "msg": "aggregated entry with new comment field",
      "time": "2019-02-06T08:24:06-05:00"
    },
    {
      "level": "error",
      "msg": "aggregated error entry with new-comment field",
      "new-comment": "this is an aggregated log entry with reset comment field",
      "time": "2019-02-06T08:24:06-05:00"
    }
  ],
  "banner": "[GIN] --------------------------------------------------------------- GinLogrusWithTracing ----------------------------------------------------------------"
}

package main

import (
	"fmt"
	"os"
	"runtime"
	"time"

	"github.com/opentracing/opentracing-go/ext"

	ginlogrus "github.com/Bose/go-gin-logrus"
	ginopentracing "github.com/Bose/go-gin-opentracing"
	"github.com/gin-gonic/gin"
	opentracing "github.com/opentracing/opentracing-go"
	"github.com/sirupsen/logrus"
)

func main() {
	// use the JSON formatter
	logrus.SetFormatter(&logrus.JSONFormatter{})

	r := gin.New() // don't use the Default(), since it comes with a logger

	// setup tracing...
	hostName, err := os.Hostname()
	if err != nil {
		hostName = "unknown"
	}

	tracer, reporter, closer, err := ginopentracing.InitTracing(
		fmt.Sprintf("go-gin-logrus-example::%s", hostName), // service name for the traces
		"localhost:5775",                        // where to send the spans
		ginopentracing.WithEnableInfoLog(false)) // WithEnableLogInfo(false) will not log info on every span sent... if set to true it will log and they won't be aggregated
	if err != nil {
		panic("unable to init tracing")
	}
	defer closer.Close()
	defer reporter.Close()
	opentracing.SetGlobalTracer(tracer)

	p := ginopentracing.OpenTracer([]byte("api-request-"))
	r.Use(p)

	r.Use(gin.Recovery()) // add Recovery middleware
	useBanner := true
	useUTC := true
	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)))

	r.GET("/", func(c *gin.Context) {
		ginlogrus.SetCtxLoggerHeader(c, "new-header-index-name", "this is how you set new header level data")

		logger := ginlogrus.GetCtxLogger(c) // will get a logger with the aggregate Logger set if it's enabled - handy if you've already set fields for the request
		logger.Info("this will be aggregated into one write with the access log and will show up when the request is completed")

		// add some new fields to the existing logger
		logger = ginlogrus.SetCtxLogger(c, logger.WithFields(logrus.Fields{"comment": "this is an aggregated log entry with initial comment field"}))
		logger.Debug("aggregated entry with new comment field")

		// replace existing logger fields with new ones (notice it's logrus.WithFields())
		logger = ginlogrus.SetCtxLogger(c, logrus.WithFields(logrus.Fields{"new-comment": "this is an aggregated log entry with reset comment field"}))
		logger.Error("aggregated error entry with new-comment field")

		logrus.Info("this will NOT be aggregated and will be logged immediately")
		span := newSpanFromContext(c, "sleep-span")
		defer span.Finish() // this will get logged because tracing was setup with ginopentracing.WithEnableInfoLog(true)

		go func() {
			// need a NewBuffer for aggregate logging of this goroutine (since the req will be done long before this thing finishes)
			// it will inherit header info from the existing request
			buff := ginlogrus.NewBuffer(logger)
			time.Sleep(1 * time.Second)
			logger.Info("Hi from a goroutine completing after the request")
			fmt.Printf(buff.String())
		}()
		c.JSON(200, "Hello world!")
	})

	r.Run(":29090")
}

func newSpanFromContext(c *gin.Context, operationName string) opentracing.Span {
	parentSpan, _ := c.Get("tracing-context")
	options := []opentracing.StartSpanOption{
		opentracing.Tag{Key: ext.SpanKindRPCServer.Key, Value: ext.SpanKindRPCServer.Value},
		opentracing.Tag{Key: string(ext.HTTPMethod), Value: c.Request.Method},
		opentracing.Tag{Key: string(ext.HTTPUrl), Value: c.Request.URL.Path},
		opentracing.Tag{Key: "current-goroutines", Value: runtime.NumGoroutine()},
	}

	if parentSpan != nil {
		options = append(options, opentracing.ChildOf(parentSpan.(opentracing.Span).Context()))
	}

	return opentracing.StartSpan(operationName, options...)
}


See the example.go file

Documentation

Index

Constants

View Source
const DefaultBanner = "" /* 155-byte string literal not displayed */
View Source
const DefaultLogBufferMaxSize = 100000

DefaultLogBufferMaxSize - avg single spaced page contains 3k chars, so 100k == 33 pages which is a reasonable max

Variables

View Source
var ContextTraceIDField string

ContextTraceIDField - used to find the trace id in the gin.Context - optional

Functions

func CopyHeader added in v1.0.3

func CopyHeader(dst *LogBuffer, src *LogBuffer)

CopyHeader - copy a header

func CxtRequestID

func CxtRequestID(c *gin.Context) string

CxtRequestID - if not already set, then add logrus Field to the entry with the tracing ID for the request. then return the trace/request id

func GetCtxLogger

func GetCtxLogger(c *gin.Context) *logrus.Entry

GetCtxLogger - get the *logrus.Entry for this request from the gin.Context

func GetCxtRequestID

func GetCxtRequestID(c *gin.Context) string

GetCxtRequestID - dig the request ID out of the *logrus.Entry in the gin.Context

func SetCtxLogger

func SetCtxLogger(c *gin.Context, logger *logrus.Entry) *logrus.Entry

SetCtxLogger - used when you want to set the *logrus.Entry with new logrus.WithFields{} for this request in the gin.Context so it can be used going forward for the request

func SetCtxLoggerHeader

func SetCtxLoggerHeader(c *gin.Context, name string, data interface{})

SetCtxLoggerHeader - if aggregate logging, add header info... otherwise just info log the data passed

func WithTracing

func WithTracing(
	logger loggerEntryWithFields,
	useBanner bool,
	timeFormat string,
	utc bool,
	logrusFieldNameForTraceID string,
	traceIDHeader []byte,
	contextTraceIDField []byte,
	opt ...Option) gin.HandlerFunc

WithTracing returns a gin.HandlerFunc (middleware) that logs requests using logrus.

Requests with errors are logged using logrus.Error(). Requests without errors are logged using logrus.Info().

It receives:

  1. A logrus.Entry with fields
  2. A boolean stating whether to use a BANNER in the log entry
  3. A time package format string (e.g. time.RFC3339).
  4. A boolean stating whether to use UTC time zone or local.
  5. A string to use for Trace ID the Logrus log field.
  6. A []byte for the request header that contains the trace id
  7. A []byte for "getting" the requestID out of the gin.Context
  8. A list of possible ginlogrus.Options to apply

Types

type LogBuffer

type LogBuffer struct {
	Buff strings.Builder

	AddBanner bool

	MaxSize uint
	// contains filtered or unexported fields
}

LogBuffer - implement io.Writer inferface to append to a string

func NewBuffer

func NewBuffer(l *logrus.Entry) *LogBuffer

NewBuffer - create a new aggregate logging buffer for the *logrus.Entry , which can be flushed by the consumer how-to, when to use this:

		the request level log entry is written when the request is over, so you need this thing to
		write go routine logs that complete AFTER the request is completed.
     careful: the loggers will share a ref to the same Header (writes to one will affect the other)

example:

go func() {
		buff := NewBuffer(logger) // logger is an existing *logrus.Entry
		// do somem work here and write some logs via the logger.  Like logger.Info("hi mom! I'm a go routine that finished after the request")
		fmt.Printf(buff.String()) // this will write the aggregated buffered logs to stdout
}()

func NewLogBuffer added in v1.0.3

func NewLogBuffer(opt ...LogBufferOption) LogBuffer

NewLogBuffer - create a LogBuffer and initialize it

func (*LogBuffer) DeleteHeader added in v1.0.3

func (b *LogBuffer) DeleteHeader(k string)

DeleteHeader - delete a header

func (*LogBuffer) GetAllHeaders added in v1.0.3

func (b *LogBuffer) GetAllHeaders() (map[string]interface{}, error)

GetAllHeaders - return all the headers

func (*LogBuffer) GetHeader added in v1.0.3

func (b *LogBuffer) GetHeader(k string) (interface{}, bool)

GetHeader - get a header

func (*LogBuffer) SetCustomBanner added in v1.0.3

func (b *LogBuffer) SetCustomBanner(banner string)

SetCustomBanner allows a custom banner to be set after the NewLogBuffer() has been used

func (*LogBuffer) StoreHeader added in v1.0.3

func (b *LogBuffer) StoreHeader(k string, v interface{})

StoreHeader - store a header

func (*LogBuffer) String

func (b *LogBuffer) String() string

String - output the strings.Builder as one aggregated JSON object

func (*LogBuffer) Write

func (b *LogBuffer) Write(data []byte) (n int, err error)

Write - simply append to the strings.Buffer but add a comma too

type LogBufferOption added in v1.0.3

type LogBufferOption func(*logBufferOptions)

LogBufferOption - define options for LogBuffer

func WithBanner added in v1.0.3

func WithBanner(a bool) LogBufferOption

WithBanner - define an Option func for passing in an optional add Banner

func WithCustomBanner added in v1.0.3

func WithCustomBanner(b string) LogBufferOption

WithCustomBanner allows users to define their own custom banner

func WithHeader added in v1.0.3

func WithHeader(k string, v interface{}) LogBufferOption

WithHeader - define an Option func for passing in a set of optional header

func WithMaxSize added in v1.0.3

func WithMaxSize(s uint) LogBufferOption

WithMaxSize specifies the bounded max size the logBuffer can grow to

type Option

type Option func(*options)

Option - define options for WithTracing()

func WithAggregateLogging

func WithAggregateLogging(a bool) Option

WithAggregateLogging - define an Option func for passing in an optional aggregateLogging

func WithLogCustomBanner added in v1.0.3

func WithLogCustomBanner(b string) Option

WithLogCustomBanner allows users to define their own custom banner. There is some overlap with this name and the LogBufferOption.CustomBanner and yes, they are related options, but I didn't want to make a breaking API change to support this new option... so we'll have to live with a bit of confusion/overlap in option names

func WithLogLevel added in v1.0.3

func WithLogLevel(logLevel logrus.Level) Option

WithLogLevel - define an Option func for passing in an optional logLevel

func WithWriter added in v1.0.3

func WithWriter(w io.Writer) Option

WithWriter allows users to define the writer used for middlware aggregagte logging, the default writer is os.Stdout

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL