Context package to support context-aware logging
The new context package supports context-aware logging, integrating with logrus. Several utilities are provided to associate http requests with a context, ensuring that one can trace log messages all the way through a context-aware call stack. A full description of this functionality is available in doc.go. Signed-off-by: Stephen J Day <stephen.day@docker.com>master
							parent
							
								
									13382e45ba
								
							
						
					
					
						commit
						4b14eef595
					
				|  | @ -0,0 +1,76 @@ | |||
| // Package context provides several utilities for working with
 | ||||
| // golang.org/x/net/context in http requests. Primarily, the focus is on
 | ||||
| // logging relevent request information but this package is not limited to
 | ||||
| // that purpose.
 | ||||
| //
 | ||||
| // Logging
 | ||||
| //
 | ||||
| // The most useful aspect of this package is GetLogger. This function takes
 | ||||
| // any context.Context interface and returns the current logger from the
 | ||||
| // context. Canonical usage looks like this:
 | ||||
| //
 | ||||
| // 	GetLogger(ctx).Infof("something interesting happened")
 | ||||
| //
 | ||||
| // GetLogger also takes optional key arguments. The keys will be looked up in
 | ||||
| // the context and reported with the logger. The following example would
 | ||||
| // return a logger that prints the version with each log message:
 | ||||
| //
 | ||||
| // 	ctx := context.Context(context.Background(), "version", version)
 | ||||
| // 	GetLogger(ctx, "version").Infof("this log message has a version field")
 | ||||
| //
 | ||||
| // The above would print out a log message like this:
 | ||||
| //
 | ||||
| // 	INFO[0000] this log message has a version field        version=v2.0.0-alpha.2.m
 | ||||
| //
 | ||||
| // When used with WithLogger, we gain the ability to decorate the context with
 | ||||
| // loggers that have information from disparate parts of the call stack.
 | ||||
| // Following from the version example, we can build a new context with the
 | ||||
| // configured logger such that we always print the version field:
 | ||||
| //
 | ||||
| // 	ctx = WithLogger(ctx, GetLogger(ctx, "version"))
 | ||||
| //
 | ||||
| // Since the logger has been pushed to the context, we can now get the version
 | ||||
| // field for free with our log messages. Future calls to GetLogger on the new
 | ||||
| // context will have the version field:
 | ||||
| //
 | ||||
| // 	GetLogger(ctx).Infof("this log message has a version field")
 | ||||
| //
 | ||||
| // This becomes more powerful when we start stacking loggers. Let's say we
 | ||||
| // have the version logger from above but also want a request id. Using the
 | ||||
| // context above, in our request scoped function, we place another logger in
 | ||||
| // the context:
 | ||||
| //
 | ||||
| // 	ctx = context.WithValue(ctx, "http.request.id", "unique id") // called when building request context
 | ||||
| // 	ctx = WithLogger(ctx, GetLogger(ctx, "http.request.id"))
 | ||||
| //
 | ||||
| // When GetLogger is called on the new context, "http.request.id" will be
 | ||||
| // included as a logger field, along with the original "version" field:
 | ||||
| //
 | ||||
| // 	INFO[0000] this log message has a version field        http.request.id=unique id version=v2.0.0-alpha.2.m
 | ||||
| //
 | ||||
| // Note that this only affects the new context, the previous context, with the
 | ||||
| // version field, can be used independently. Put another way, the new logger,
 | ||||
| // added to the request context, is unique to that context and can have
 | ||||
| // request scoped varaibles.
 | ||||
| //
 | ||||
| // HTTP Requests
 | ||||
| //
 | ||||
| // This package also contains several methods for working with http requests.
 | ||||
| // The concepts are very similar to those described above. We simply place the
 | ||||
| // request in the context using WithRequest. This makes the request variables
 | ||||
| // available. GetRequestLogger can then be called to get request specific
 | ||||
| // variables in a log line:
 | ||||
| //
 | ||||
| // 	ctx = WithRequest(ctx, req)
 | ||||
| // 	GetRequestLogger(ctx).Infof("request variables")
 | ||||
| //
 | ||||
| // Like above, if we want to include the request data in all log messages in
 | ||||
| // the context, we push the logger to a new context and use that one:
 | ||||
| //
 | ||||
| // 	ctx = WithLogger(ctx, GetRequestLogger(ctx))
 | ||||
| //
 | ||||
| // The concept is fairly powerful and ensures that calls throughout the stack
 | ||||
| // can be traced in log messages. Using the fields like "http.request.id", one
 | ||||
| // can analyze call flow for a particular request with a simple grep of the
 | ||||
| // logs.
 | ||||
| package context | ||||
|  | @ -0,0 +1,270 @@ | |||
| package context | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"code.google.com/p/go-uuid/uuid" | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"golang.org/x/net/context" | ||||
| ) | ||||
| 
 | ||||
| // Common errors used with this package.
 | ||||
| var ( | ||||
| 	ErrNoRequestContext = errors.New("no http request in context") | ||||
| ) | ||||
| 
 | ||||
| // WithRequest places the request on the context. The context of the request
 | ||||
| // is assigned a unique id, available at "http.request.id". The request itself
 | ||||
| // is available at "http.request". Other common attributes are available under
 | ||||
| // the prefix "http.request.". If a request is already present on the context,
 | ||||
| // this method will panic.
 | ||||
| func WithRequest(ctx context.Context, r *http.Request) context.Context { | ||||
| 	if ctx.Value("http.request") != nil { | ||||
| 		// NOTE(stevvooe): This needs to be considered a programming error. It
 | ||||
| 		// is unlikely that we'd want to have more than one request in
 | ||||
| 		// context.
 | ||||
| 		panic("only one request per context") | ||||
| 	} | ||||
| 
 | ||||
| 	return &httpRequestContext{ | ||||
| 		Context:   ctx, | ||||
| 		startedAt: time.Now(), | ||||
| 		id:        uuid.New(), // assign the request a unique.
 | ||||
| 		r:         r, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // GetRequest returns the http request in the given context. Returns
 | ||||
| // ErrNoRequestContext if the context does not have an http request associated
 | ||||
| // with it.
 | ||||
| func GetRequest(ctx context.Context) (*http.Request, error) { | ||||
| 	if r, ok := ctx.Value("http.request").(*http.Request); r != nil && ok { | ||||
| 		return r, nil | ||||
| 	} | ||||
| 	return nil, ErrNoRequestContext | ||||
| } | ||||
| 
 | ||||
| // GetRequestID attempts to resolve the current request id, if possible. An
 | ||||
| // error is return if it is not available on the context.
 | ||||
| func GetRequestID(ctx context.Context) string { | ||||
| 	return GetStringValue(ctx, "http.request.id") | ||||
| } | ||||
| 
 | ||||
| // WithResponseWriter returns a new context and response writer that makes
 | ||||
| // interesting response statistics available within the context.
 | ||||
| func WithResponseWriter(ctx context.Context, w http.ResponseWriter) (context.Context, http.ResponseWriter) { | ||||
| 	irw := &instrumentedResponseWriter{ | ||||
| 		ResponseWriter: w, | ||||
| 		Context:        ctx, | ||||
| 	} | ||||
| 
 | ||||
| 	return irw, irw | ||||
| } | ||||
| 
 | ||||
| // getVarsFromRequest let's us change request vars implementation for testing
 | ||||
| // and maybe future changes.
 | ||||
| var getVarsFromRequest = mux.Vars | ||||
| 
 | ||||
| // WithVars extracts gorilla/mux vars and makes them available on the returned
 | ||||
| // context. Variables are available at keys with the prefix "vars.". For
 | ||||
| // example, if looking for the variable "name", it can be accessed as
 | ||||
| // "vars.name". Implementations that are accessing values need not know that
 | ||||
| // the underlying context is implemented with gorilla/mux vars.
 | ||||
| func WithVars(ctx context.Context, r *http.Request) context.Context { | ||||
| 	return &muxVarsContext{ | ||||
| 		Context: ctx, | ||||
| 		vars:    getVarsFromRequest(r), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // GetRequestLogger returns a logger that contains fields from the request in
 | ||||
| // the current context. If the request is not available in the context, no
 | ||||
| // fields will display. Request loggers can safely be pushed onto the context.
 | ||||
| func GetRequestLogger(ctx context.Context) Logger { | ||||
| 	return GetLogger(ctx, | ||||
| 		"http.request.id", | ||||
| 		"http.request.method", | ||||
| 		"http.request.host", | ||||
| 		"http.request.uri", | ||||
| 		"http.request.referer", | ||||
| 		"http.request.useragent", | ||||
| 		"http.request.remoteaddr", | ||||
| 		"http.request.contenttype") | ||||
| } | ||||
| 
 | ||||
| // GetResponseLogger reads the current response stats and builds a logger.
 | ||||
| // Because the values are read at call time, pushing a logger returned from
 | ||||
| // this function on the context will lead to missing or invalid data. Only
 | ||||
| // call this at the end of a request, after the response has been written.
 | ||||
| func GetResponseLogger(ctx context.Context) Logger { | ||||
| 	l := getLogrusLogger(ctx, | ||||
| 		"http.response.written", | ||||
| 		"http.response.status", | ||||
| 		"http.response.contenttype") | ||||
| 
 | ||||
| 	duration := Since(ctx, "http.request.startedat") | ||||
| 
 | ||||
| 	if duration > 0 { | ||||
| 		l = l.WithField("http.response.duration", duration) | ||||
| 	} | ||||
| 
 | ||||
| 	return l | ||||
| } | ||||
| 
 | ||||
| // httpRequestContext makes information about a request available to context.
 | ||||
| type httpRequestContext struct { | ||||
| 	context.Context | ||||
| 
 | ||||
| 	startedAt time.Time | ||||
| 	id        string | ||||
| 	r         *http.Request | ||||
| } | ||||
| 
 | ||||
| // Value returns a keyed element of the request for use in the context. To get
 | ||||
| // the request itself, query "request". For other components, access them as
 | ||||
| // "request.<component>". For example, r.RequestURI
 | ||||
| func (ctx *httpRequestContext) Value(key interface{}) interface{} { | ||||
| 	if keyStr, ok := key.(string); ok { | ||||
| 		if keyStr == "http.request" { | ||||
| 			return ctx.r | ||||
| 		} | ||||
| 
 | ||||
| 		parts := strings.Split(keyStr, ".") | ||||
| 
 | ||||
| 		if len(parts) != 3 { | ||||
| 			goto fallback | ||||
| 		} | ||||
| 
 | ||||
| 		switch parts[2] { | ||||
| 		case "uri": | ||||
| 			return ctx.r.RequestURI | ||||
| 		case "remoteaddr": | ||||
| 			return ctx.r.RemoteAddr | ||||
| 		case "method": | ||||
| 			return ctx.r.Method | ||||
| 		case "host": | ||||
| 			return ctx.r.Host | ||||
| 		case "referer": | ||||
| 			referer := ctx.r.Referer() | ||||
| 			if referer != "" { | ||||
| 				return referer | ||||
| 			} | ||||
| 		case "useragent": | ||||
| 			return ctx.r.UserAgent() | ||||
| 		case "id": | ||||
| 			return ctx.id | ||||
| 		case "startedat": | ||||
| 			return ctx.startedAt | ||||
| 		case "contenttype": | ||||
| 			ct := ctx.r.Header.Get("Content-Type") | ||||
| 			if ct != "" { | ||||
| 				return ct | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| fallback: | ||||
| 	return ctx.Context.Value(key) | ||||
| } | ||||
| 
 | ||||
| type muxVarsContext struct { | ||||
| 	context.Context | ||||
| 	vars map[string]string | ||||
| } | ||||
| 
 | ||||
| func (ctx *muxVarsContext) Value(key interface{}) interface{} { | ||||
| 	if keyStr, ok := key.(string); ok { | ||||
| 		if keyStr == "vars" { | ||||
| 			return ctx.vars | ||||
| 		} | ||||
| 
 | ||||
| 		if strings.HasPrefix(keyStr, "vars.") { | ||||
| 			keyStr = strings.TrimPrefix(keyStr, "vars.") | ||||
| 		} | ||||
| 
 | ||||
| 		if v, ok := ctx.vars[keyStr]; ok { | ||||
| 			return v | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ctx.Context.Value(key) | ||||
| } | ||||
| 
 | ||||
| // instrumentedResponseWriter provides response writer information in a
 | ||||
| // context.
 | ||||
| type instrumentedResponseWriter struct { | ||||
| 	http.ResponseWriter | ||||
| 	context.Context | ||||
| 
 | ||||
| 	mu      sync.Mutex | ||||
| 	status  int | ||||
| 	written int64 | ||||
| } | ||||
| 
 | ||||
| func (irw *instrumentedResponseWriter) Write(p []byte) (n int, err error) { | ||||
| 	n, err = irw.ResponseWriter.Write(p) | ||||
| 
 | ||||
| 	irw.mu.Lock() | ||||
| 	irw.written += int64(n) | ||||
| 
 | ||||
| 	// Guess the likely status if not set.
 | ||||
| 	if irw.status == 0 { | ||||
| 		irw.status = http.StatusOK | ||||
| 	} | ||||
| 
 | ||||
| 	irw.mu.Unlock() | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (irw *instrumentedResponseWriter) WriteHeader(status int) { | ||||
| 	irw.ResponseWriter.WriteHeader(status) | ||||
| 
 | ||||
| 	irw.mu.Lock() | ||||
| 	irw.status = status | ||||
| 	irw.mu.Unlock() | ||||
| } | ||||
| 
 | ||||
| func (irw *instrumentedResponseWriter) Flush() { | ||||
| 	if flusher, ok := irw.ResponseWriter.(http.Flusher); ok { | ||||
| 		flusher.Flush() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} { | ||||
| 	if keyStr, ok := key.(string); ok { | ||||
| 		if keyStr == "http.response" { | ||||
| 			return irw.ResponseWriter | ||||
| 		} | ||||
| 
 | ||||
| 		parts := strings.Split(keyStr, ".") | ||||
| 
 | ||||
| 		if len(parts) != 3 { | ||||
| 			goto fallback | ||||
| 		} | ||||
| 
 | ||||
| 		irw.mu.Lock() | ||||
| 		defer irw.mu.Unlock() | ||||
| 
 | ||||
| 		switch parts[2] { | ||||
| 		case "written": | ||||
| 			return irw.written | ||||
| 		case "status": | ||||
| 			if irw.status != 0 { | ||||
| 				return irw.status | ||||
| 			} | ||||
| 		case "contenttype": | ||||
| 			contentType := irw.Header().Get("Content-Type") | ||||
| 			if contentType != "" { | ||||
| 				return contentType | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| fallback: | ||||
| 	return irw.Context.Value(key) | ||||
| } | ||||
|  | @ -0,0 +1,207 @@ | |||
| package context | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"golang.org/x/net/context" | ||||
| ) | ||||
| 
 | ||||
| func TestWithRequest(t *testing.T) { | ||||
| 	var req http.Request | ||||
| 
 | ||||
| 	start := time.Now() | ||||
| 	req.Method = "GET" | ||||
| 	req.Host = "example.com" | ||||
| 	req.RequestURI = "/test-test" | ||||
| 	req.Header = make(http.Header) | ||||
| 	req.Header.Set("Referer", "foo.com/referer") | ||||
| 	req.Header.Set("User-Agent", "test/0.1") | ||||
| 
 | ||||
| 	ctx := WithRequest(context.Background(), &req) | ||||
| 	for _, testcase := range []struct { | ||||
| 		key      string | ||||
| 		expected interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			key:      "http.request", | ||||
| 			expected: &req, | ||||
| 		}, | ||||
| 		{ | ||||
| 			key: "http.request.id", | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:      "http.request.method", | ||||
| 			expected: req.Method, | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:      "http.request.host", | ||||
| 			expected: req.Host, | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:      "http.request.uri", | ||||
| 			expected: req.RequestURI, | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:      "http.request.referer", | ||||
| 			expected: req.Referer(), | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:      "http.request.useragent", | ||||
| 			expected: req.UserAgent(), | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:      "http.request.remoteaddr", | ||||
| 			expected: req.RemoteAddr, | ||||
| 		}, | ||||
| 		{ | ||||
| 			key: "http.request.startedat", | ||||
| 		}, | ||||
| 	} { | ||||
| 		v := ctx.Value(testcase.key) | ||||
| 
 | ||||
| 		if v == nil { | ||||
| 			t.Fatalf("value not found for %q", testcase.key) | ||||
| 		} | ||||
| 
 | ||||
| 		if testcase.expected != nil && v != testcase.expected { | ||||
| 			t.Fatalf("%s: %v != %v", testcase.key, v, testcase.expected) | ||||
| 		} | ||||
| 
 | ||||
| 		// Key specific checks!
 | ||||
| 		switch testcase.key { | ||||
| 		case "http.request.id": | ||||
| 			if _, ok := v.(string); !ok { | ||||
| 				t.Fatalf("request id not a string: %v", v) | ||||
| 			} | ||||
| 		case "http.request.startedat": | ||||
| 			vt, ok := v.(time.Time) | ||||
| 			if !ok { | ||||
| 				t.Fatalf("value not a time: %v", v) | ||||
| 			} | ||||
| 
 | ||||
| 			now := time.Now() | ||||
| 			if vt.After(now) { | ||||
| 				t.Fatalf("time generated too late: %v > %v", vt, now) | ||||
| 			} | ||||
| 
 | ||||
| 			if vt.Before(start) { | ||||
| 				t.Fatalf("time generated too early: %v < %v", vt, start) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type testResponseWriter struct { | ||||
| 	flushed bool | ||||
| 	status  int | ||||
| 	written int64 | ||||
| 	header  http.Header | ||||
| } | ||||
| 
 | ||||
| func (trw *testResponseWriter) Header() http.Header { | ||||
| 	if trw.header == nil { | ||||
| 		trw.header = make(http.Header) | ||||
| 	} | ||||
| 
 | ||||
| 	return trw.header | ||||
| } | ||||
| 
 | ||||
| func (trw *testResponseWriter) Write(p []byte) (n int, err error) { | ||||
| 	if trw.status == 0 { | ||||
| 		trw.status = http.StatusOK | ||||
| 	} | ||||
| 
 | ||||
| 	n = len(p) | ||||
| 	trw.written += int64(n) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (trw *testResponseWriter) WriteHeader(status int) { | ||||
| 	trw.status = status | ||||
| } | ||||
| 
 | ||||
| func (trw *testResponseWriter) Flush() { | ||||
| 	trw.flushed = true | ||||
| } | ||||
| 
 | ||||
| func TestWithResponseWriter(t *testing.T) { | ||||
| 	trw := testResponseWriter{} | ||||
| 	ctx, rw := WithResponseWriter(context.Background(), &trw) | ||||
| 
 | ||||
| 	if ctx.Value("http.response") != &trw { | ||||
| 		t.Fatalf("response not available in context: %v != %v", ctx.Value("http.response"), &trw) | ||||
| 	} | ||||
| 
 | ||||
| 	if n, err := rw.Write(make([]byte, 1024)); err != nil { | ||||
| 		t.Fatalf("unexpected error writing: %v", err) | ||||
| 	} else if n != 1024 { | ||||
| 		t.Fatalf("unexpected number of bytes written: %v != %v", n, 1024) | ||||
| 	} | ||||
| 
 | ||||
| 	if ctx.Value("http.response.status") != http.StatusOK { | ||||
| 		t.Fatalf("unexpected response status in context: %v != %v", ctx.Value("http.response.status"), http.StatusOK) | ||||
| 	} | ||||
| 
 | ||||
| 	if ctx.Value("http.response.written") != int64(1024) { | ||||
| 		t.Fatalf("unexpected number reported bytes written: %v != %v", ctx.Value("http.response.written"), 1024) | ||||
| 	} | ||||
| 
 | ||||
| 	// Make sure flush propagates
 | ||||
| 	rw.(http.Flusher).Flush() | ||||
| 
 | ||||
| 	if !trw.flushed { | ||||
| 		t.Fatalf("response writer not flushed") | ||||
| 	} | ||||
| 
 | ||||
| 	// Write another status and make sure context is correct. This normally
 | ||||
| 	// wouldn't work except for in this contrived testcase.
 | ||||
| 	rw.WriteHeader(http.StatusBadRequest) | ||||
| 
 | ||||
| 	if ctx.Value("http.response.status") != http.StatusBadRequest { | ||||
| 		t.Fatalf("unexpected response status in context: %v != %v", ctx.Value("http.response.status"), http.StatusBadRequest) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestWithVars(t *testing.T) { | ||||
| 	var req http.Request | ||||
| 	vars := map[string]string{ | ||||
| 		"foo": "asdf", | ||||
| 		"bar": "qwer", | ||||
| 	} | ||||
| 
 | ||||
| 	getVarsFromRequest = func(r *http.Request) map[string]string { | ||||
| 		if r != &req { | ||||
| 			t.Fatalf("unexpected request: %v != %v", r, req) | ||||
| 		} | ||||
| 
 | ||||
| 		return vars | ||||
| 	} | ||||
| 
 | ||||
| 	ctx := WithVars(context.Background(), &req) | ||||
| 	for _, testcase := range []struct { | ||||
| 		key      string | ||||
| 		expected interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			key:      "vars", | ||||
| 			expected: vars, | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:      "vars.foo", | ||||
| 			expected: "asdf", | ||||
| 		}, | ||||
| 		{ | ||||
| 			key:      "vars.bar", | ||||
| 			expected: "qwer", | ||||
| 		}, | ||||
| 	} { | ||||
| 		v := ctx.Value(testcase.key) | ||||
| 
 | ||||
| 		if !reflect.DeepEqual(v, testcase.expected) { | ||||
| 			t.Fatalf("%q: %v != %v", testcase.key, v, testcase.expected) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,88 @@ | |||
| package context | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/Sirupsen/logrus" | ||||
| 	"golang.org/x/net/context" | ||||
| ) | ||||
| 
 | ||||
| // Logger provides a leveled-logging interface.
 | ||||
| type Logger interface { | ||||
| 	// standard logger methods
 | ||||
| 	Print(args ...interface{}) | ||||
| 	Printf(format string, args ...interface{}) | ||||
| 	Println(args ...interface{}) | ||||
| 
 | ||||
| 	Fatal(args ...interface{}) | ||||
| 	Fatalf(format string, args ...interface{}) | ||||
| 	Fatalln(args ...interface{}) | ||||
| 
 | ||||
| 	Panic(args ...interface{}) | ||||
| 	Panicf(format string, args ...interface{}) | ||||
| 	Panicln(args ...interface{}) | ||||
| 
 | ||||
| 	// Leveled methods, from logrus
 | ||||
| 	Debug(args ...interface{}) | ||||
| 	Debugf(format string, args ...interface{}) | ||||
| 	Debugln(args ...interface{}) | ||||
| 
 | ||||
| 	Error(args ...interface{}) | ||||
| 	Errorf(format string, args ...interface{}) | ||||
| 	Errorln(args ...interface{}) | ||||
| 
 | ||||
| 	Info(args ...interface{}) | ||||
| 	Infof(format string, args ...interface{}) | ||||
| 	Infoln(args ...interface{}) | ||||
| 
 | ||||
| 	Warn(args ...interface{}) | ||||
| 	Warnf(format string, args ...interface{}) | ||||
| 	Warnln(args ...interface{}) | ||||
| } | ||||
| 
 | ||||
| // WithLogger creates a new context with provided logger.
 | ||||
| func WithLogger(ctx context.Context, logger Logger) context.Context { | ||||
| 	return context.WithValue(ctx, "logger", logger) | ||||
| } | ||||
| 
 | ||||
| // GetLogger returns the logger from the current context, if present. If one
 | ||||
| // or more keys are provided, they will be resolved on the context and
 | ||||
| // included in the logger. While context.Value takes an interface, any key
 | ||||
| // argument passed to GetLogger will be passed to fmt.Sprint when expanded as
 | ||||
| // a logging key field. If context keys are integer constants, for example,
 | ||||
| // its recommended that a String method is implemented.
 | ||||
| func GetLogger(ctx context.Context, keys ...interface{}) Logger { | ||||
| 	return getLogrusLogger(ctx, keys...) | ||||
| } | ||||
| 
 | ||||
| // GetLogrusLogger returns the logrus logger for the context. If one more keys
 | ||||
| // are provided, they will be resolved on the context and included in the
 | ||||
| // logger. Only use this function if specific logrus functionality is
 | ||||
| // required.
 | ||||
| func getLogrusLogger(ctx context.Context, keys ...interface{}) *logrus.Entry { | ||||
| 	var logger *logrus.Entry | ||||
| 
 | ||||
| 	// Get a logger, if it is present.
 | ||||
| 	loggerInterface := ctx.Value("logger") | ||||
| 	if loggerInterface != nil { | ||||
| 		if lgr, ok := loggerInterface.(*logrus.Entry); ok { | ||||
| 			logger = lgr | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if logger == nil { | ||||
| 		// If no logger is found, just return the standard logger.
 | ||||
| 		logger = logrus.NewEntry(logrus.StandardLogger()) | ||||
| 	} | ||||
| 
 | ||||
| 	fields := logrus.Fields{} | ||||
| 
 | ||||
| 	for _, key := range keys { | ||||
| 		v := ctx.Value(key) | ||||
| 		if v != nil { | ||||
| 			fields[fmt.Sprint(key)] = v | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return logger.WithFields(fields) | ||||
| } | ||||
|  | @ -0,0 +1,34 @@ | |||
| package context | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"golang.org/x/net/context" | ||||
| ) | ||||
| 
 | ||||
| // Since looks up key, which should be a time.Time, and returns the duration
 | ||||
| // since that time. If the key is not found, the value returned will be zero.
 | ||||
| // This is helpful when inferring metrics related to context execution times.
 | ||||
| func Since(ctx context.Context, key interface{}) time.Duration { | ||||
| 	startedAtI := ctx.Value(key) | ||||
| 	if startedAtI != nil { | ||||
| 		if startedAt, ok := startedAtI.(time.Time); ok { | ||||
| 			return time.Since(startedAt) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return 0 | ||||
| } | ||||
| 
 | ||||
| // GetStringValue returns a string value from the context. The empty string
 | ||||
| // will be returned if not found.
 | ||||
| func GetStringValue(ctx context.Context, key string) (value string) { | ||||
| 	stringi := ctx.Value(key) | ||||
| 	if stringi != nil { | ||||
| 		if valuev, ok := stringi.(string); ok { | ||||
| 			value = valuev | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return value | ||||
| } | ||||
		Loading…
	
		Reference in New Issue