lager

package module
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: May 16, 2023 License: Unlicense Imports: 22 Imported by: 10

README

Lager

Logs (in golang) that are easy for computers to parse, easy for people to read, and easy for programmers to generate. It also encourages logging data over messages, which tends to make logs more useful as well as easier to generate.

You don't need to pass around a logging object so you can log information from any code. You can decorate a Go context.Context with additional data to be added to each log line written when that context applies.

The logs are written in JSON format but the items in JSON are written in a controlled order, preserving the order used in the program code. This makes the logs pretty easy for humans to scan even with no processing or tooling.

Typical logging code like:

lager.Fail(ctx).MMap("Can't merge", "dest", dest, "err", err)
// MMap() takes a message followed by a map of key/value pairs.

could output (especially when running interactively):

["2019-12-31 23:59:59.1234Z", "FAIL", "Can't merge",
    {"dest":"localhost", "err":"refused"}]

(but as a single line). If you declare that the code is running inside Google Cloud Platform (GCP), it could instead output:

{"time":"2019-12-31T23:59:59.1234Z", "severity":"500",
    "message":"Can't merge", "dest":"localhost", "err":"refused"}

(as a single line) which GCP understands well. Note that it is still easy for a human to read with the consistent order used.

Lager is efficient but not to the point of making it inconvenient to write code that uses Lager.

It also provides more granularity when controlling which log lines to write and which to suppress. It offers 11 log levels, most of which can be enabled individually (enabling "Trace" does not force you to also enable "Debug"). You can also easily allow separate log levels for specific packages or any other logical division you care to use.

Forks

If you use a fork of this repository and want to have changes you make accepted upstream, you should use the fork by adding (to go.mod in modules where you use the fork) a line like:

replace github.com/Unity-Technologies/go-lager-internal => github.com/your/lager v1.2.3

And then use "github.com/Unity-Technologies/go-lager-internal" in 'import' statements and in a 'require' directive in the go.mod. (See "replace directive" in https://go.dev/ref/mod.)

Documentation

Overview

Lager makes logs that are easy for computers to parse, easy for people to read, and easy for programmers to generate. It also encourages logging data over messages, which tends to make logs more useful as well as easier to generate.

You don't need to pass around a logging object so you can log information from any code. You can decorate a Go context.Context with additional data to be added to each log line written when that context applies.

The logs are written in JSON format but the items in JSON are written in a controlled order, preserving the order used in the program code. This makes the logs pretty easy for humans to scan even with no processing or tooling.

Typical logging code like:

lager.Fail(ctx).MMap("Can't merge", "dest", dest, "err", err)
// MMap() takes a message followed by a map of key/value pairs.

could output (especially when running interactively):

["2019-12-31 23:59:59.1234Z", "FAIL", "Can't merge",
	{"dest":"localhost", "err":"refused"}]

(but as a single line). If you declare that the code is running inside Google Cloud Platform (GCP), it could instead output:

{"time":"2019-12-31T23:59:59.1234Z", "severity":"500",
	"message":"Can't merge", "dest":"localhost", "err":"refused"}

(as a single line) which GCP understands well but note that it is still easy for a human to read with the consistent order used.

You don't even need to take the time to compose and type labels for data items, if it doesn't seem worth it in some cases:

lager.Fail(ctx).MList("Can't merge", dest, err)
// MList() takes a message followed by arbitrary data items.

There are 11 log levels and 9 can be independently enabled or disabled. You usually use them via code similar to:

lager.Panic().MMap(...) // Calls panic(), always enabled.
lager.Exit().MMap(...)  // To Stderr, calls os.Exit(1), always enabled.
lager.Fail().MMap(...)  // For non-normal failures (default on).
lager.Warn().MMap(...)  // For warnings (default on).
lager.Note().MMap(...)  // For normal, important notices (default on).
lager.Acc().MMap(...)   // For access logs (default on).
lager.Info().MMap(...)  // For normal events (default off).
laber.Trace().MMap(...) // For tracing code flow (default off).
laber.Debug().MMap(...) // For debugging details (default off).
laber.Obj().MMap(....)  // For object dumps (default off).
lager.Guts().MMap(...)  // For volumious data dumps (default off).

Panic and Exit cannot be disabled. Fail, Warn, Note, and Acc are enabled by default.

If you want to decorate each log line with additional key/value pairs, then you can accumulate those in a context.Context value that gets passed around and then pass that Context in when logging:

// In a method dispatcher, for example:
ctx = lager.AddPairs(ctx, "srcIP", ip, "user", username)

// In a method or code that a method calls, for example:
lager.Info(ctx).MMap(msg, pairs...)

// Or, if you have multiple log lines at the same level:
debug := lager.Debug(ctx)
debug.MMap(msg, pairs...)
debug.MList(msg, args...)

Most log archiving systems expect JSON log lines to be a map (object/hash) not a list (array). To get that you just declare what labels to use for: timestamp, level, message, list data, context, and module.

// Example choice of logging keys:
lager.Keys("t", "l", "msg", "a", "", "mod")

Support for GCP Cloud Logging and Cloud Trace is integrated.

Index

Constants

View Source
const GcpSpanKey = "logging.googleapis.com/spanId"
View Source
const GcpTraceKey = "logging.googleapis.com/trace"
View Source
const InlinePairs = inlinePairs("")

InlinePairs can be used as a "label" to indicate that the following value that contains label-subvalue pairs (a value of type AMap or RawMap) should be treated as if the pairs had been passed in at that higher level.

func Assert(pairs ...interface{}) {
    lager.Fail().MMap("Assertion failed", lager.InlinePairs, pairs)
}
View Source
const SkipThisPair = skipThisPair("")

SkipThisPair can be used as a "label" to indicate that the following value should just be ignored. You would usually call lager.Unless() rather than use this directly.

Variables

This section is empty.

Functions

func AutoLock

func AutoLock(l sync.Locker) func()

AutoLock() can be used on any sync.Locker (anything with Lock and Unlock methods, like a *sync.Mutex). Call it like:

defer lager.AutoLock(locker)()
//                          ^^ The 2nd set of parens is important!

and the Locker will be locked immediately and unlocked when your function ends.

If 'mu' is of type sync.Mutex, then you would have to call:

defer lager.AutoLock(&mu)()

as a *sync.Mutex is a Locker but a sync.Mutex is not.

func ExitNotExpected

func ExitNotExpected(unexpected bool)

ExitNotExpected(true) causes any subsequent uses of lager.Exit() to include a full stack trace. You usually call ExitNotExpected() at the point where process initialization has completed. If you had not previously called 'defer lager.ExitViaPanic()()', then a warning is logged [catches accidentally writing 'defer lager.ExitViaPanic()'].

ExitNotExpected(false) disables the added stack trace [and never logs a warning].

func ExitViaPanic

func ExitViaPanic() func(...func(*int))

ExitViaPanic() improves the way lager.Exit() works so that uses of it in inappropriate places are less problematic. Using lager.Exit() causes 'os.Exit(1)' to be called, which prevents any 'defer'ed code from doing important clean-up. One should only use lager.Exit() in code that will only run during process initialization, which means it should only be called when there is nothing important that needs to be cleaned up. But mistakes are sometimes made.

Doing:

defer lager.ExitViaPanic()()
//                        ^^ The 2nd set of parens is important!

very early in your main() function will mean that uses of lager.Exit() will only skip clean-up in items that were 'defer'ed before that point. The call to 'os.Exit(1)' will be done in the function returned from ExitViaPanic() rather than at the point where lager.Exit()'s return value was used. See also ExitNotExpected().

You can change the exit status or prevent the call to os.Exit() (such as for testing); see RecoverPanicToExit() for details.

If you would instead like lager.Exit() to terminate the process with a plain panic(), then omit the 'defer' and the 2nd set of parens:

_ = lager.ExitViaPanic()

func GcpFakeResponse

func GcpFakeResponse(status int, size int64, desc string) *http.Response

GcpFakeResponse() creates an http.Response suitable for passing to GcpHttp() [or similar] when you just have a status code (and/or a response size) and not a http.Response.

Pass 'size' as -1 to omit this information. Passing 'status' as 0 will cause an explicit 0 to be logged. Pass 'status' as -1 to omit it. Pass 'desc' as "" to have it set based on 'status'.

func GcpFinishSpan

func GcpFinishSpan(span spans.Factory, resp *http.Response) time.Duration

GcpFinishSpan() updates a span with the status information from a http.Response and Finish()es the span (which registers it with GCP).

func GcpHttpF

func GcpHttpF(
	req *http.Request, resp *http.Response, start *time.Time,
) func() interface{}

GcpHttpF() can be used for logging just like GcpHttp(), it just returns a function so that the work is only performed if the logging level is enabled.

If you are including GcpHttp() information in a lot of log lines [which can be quite useful], then you can get even more efficiency by adding the pair ' "httpRequest", GcpHttp(req, nil, nil) ' to your Context [which you then pass to 'lager.Warn(ctx)', for example] so the data is only calculated once. You can add this to an *http.Request's Context by calling GcpRequestAddTrace().

For this to work best, you should specify "" as the key name for context information; which is automatically done if LAGER_GCP is non-empty in the environment and LAGER_KEYS is not set.

You'll likely include 'GcpHttp(req, resp, &start)' in one log line [to record the response information and latency, not just the request]. If you added "httpRequest" to your context, then that logging is best done via:

lager.Acc(
    lager.AddPairs(ctx, "httpRequest", GcpHttp(req, resp, &start)),
).List("Response sent")

so that the request-only information is not also output. Doing this via GcpLogAccess() is easier.

func GcpLevelName

func GcpLevelName(lev string) string

GcpLevelName takes a Lager level name (only the first letter matters and it must be upper case) and returns the corresponding value GCP uses in structured logging to represent the severity of such logs. Levels are mapped as:

Not used: Alert ("700") and Emergency ("800")
Panic, Exit - Critical ("600")
Fail - Error ("500")
Warn - Warning ("400")
Note - Notice ("300")
Access, Info - Info ("200")
Trace, Debug, Obj, Guts - Debug ("100")
If an invalid level name is passed: Default ("0")

If the environment variable LAGER_GCP is not empty, then lager.LevelNotation will be initalized to lager.GcpLevelName.

func GcpProjectID

func GcpProjectID(ctx Ctx) (string, error)

GcpProjectID() returns the current GCP project ID [which is not the project number]. Once the lookup succeeds, that value is saved and returned for subsequent calls. The lookup times out after 0.1s.

Set GCP_PROJECT_ID in your environment to avoid the more complex lookup.

func GcpReceivedRequest

func GcpReceivedRequest(pReq **http.Request) spans.Factory

GcpReceivedRequest() gets the Context from '*pReq' and uses it to call GcpContextReceivedRequest(). Then it replaces '*pReq' with a version of the request with the new Context attached. Then it returns the Factory.

It is usually called in a manner similar to:

defer spans.FinishSpan(lager.GcpReceivedRequest(&req))

or

var resp *http.Response // Will be set in later code
defer lager.GcpSendingResponse(
    lager.GcpReceivedRequest(&req), req, resp)

Using GcpContextReceivedRequest() can be slightly more efficient if you either start with a Context different from the one attached to the Request or will not attach the new Context to the Request (or will adjust it further before attaching it) since each time WithContext() is called on a Request, the Request must be copied.

func GcpReceivedResponse

func GcpReceivedResponse(
	span spans.Factory,
	req *http.Request,
	resp *http.Response,
	pairs ...interface{},
)

GcpReceivedResponse() combines GcpLogAccess() and GcpFinishSpan(). The access log line written will use the message "Received response" and will include the passed-in 'pairs' which should be zero or more pairs of a string key followed by an arbitrary value. However, logging every response received from a dependent service may be excessive.

func GcpSendingRequest

func GcpSendingRequest(pReq **http.Request) spans.Factory

GcpSendingRequest() does several things that are useful when a server is about to send a request to a dependent service, by calling GcpContextSendingRequest(). It uses the Context from '*pReq' and then replaces '*pReq' with a copy of the original Request but with the new Context attached.

It is usually called in a manner similar to:

defer spans.FinishSpan(lager.GcpSendingRequest(&req))

func GcpSendingResponse

func GcpSendingResponse(
	span spans.Factory,
	req *http.Request,
	resp *http.Response,
	pairs ...interface{},
)

GcpSendingResponse() does several things that are useful when a server is about to send a response to a request it received. It combines GcpLogAccess() and GcpFinishSpan(). The access log line written will use the message "Sending response" and will include the passed-in 'pairs' which should be zero or more pairs of a string key followed by an arbitrary value.

'resp' will often be constructed via GcpFakeResponse().

func GetModuleLevels

func GetModuleLevels(name string) string

En-/disables log levels for the named module. If no module by that name exists yet, then "n/a" is returned. Otherwise returns the enabled levels.

func GetModules

func GetModules() map[string]string

Returns a map[string]string where the keys are all of the module names and the values are their enabled levels.

func GetSpanPrefix

func GetSpanPrefix() string

GetSpanPrefix() returns a string to be used as the prefix for the Display Name of trace spans. It defaults to os.Getenv("LAGER_SPAN_PREFIX") or, if that is not set, to the basename of 'os.Args[0]'.

func Init

func Init(levels string)

Init() en-/disables log levels. Pass in a string of letters from "FWNAITDOG" to indicate which log levels should be the only ones that produce output. Each letter is the first letter of a log level (Fail, Warn, Note, Acc, Info, Trace, Debug, Obj, or Guts). Levels Panic and Exit are always enabled. Init("") acts like Init("FWNA"), the default setting. To disable all optional logs, you can use Init("-") as any characters not from "FWNAITDOG" are silently ignored. So you can also call Init("Fail Warn Note Access Info").

Rather than calling Init(), you may prefer to set enabled levels via the LAGER_LEVELS environment variable since that initialization is guaranteed to happen before any logging takes place, even if logging ends up being done in code called from initialization code.

func Keys

func Keys(when, lev, msg, args, ctx, mod string)

Keys() provides keys to be used to write each JSON log line as a map (object or hash) instead of as a list (array).

'when' is used for the timestamp. 'lev' is used for the log level name. 'msg' is either "" or will be used for the first argument to MMap() or MList() (and similar methods). 'msg' is also used when a single argument is passed to List(). 'args' is used for the arguments to List() when 'msg' is not. 'mod' is used for the module name (if any).

'ctx' is used for the key/value pairs added from contexts. Specify "" for 'ctx' to have any context key/value pairs included in-line in the top-level JSON map. In this case, care should be taken to avoid using the same key name both in a context pair and in a pair being passed to, for example, MMap(). If you do that, both pairs will be output but anything parsing the log line will only remember one of the pairs.

If the environment variable LAGER_KEYS is set it must contain 6 key names separated by commas and those become the keys to use. Otherwise, if the environment variable LAGER_GCP is not empty, then it is as if you had the following set (among other changes):

LAGER_KEYS="time,severity,message,data,,module"

Pass in 6 empty strings to revert to logging a JSON list (array).

func RecoverPanicToExit

func RecoverPanicToExit(handlers ...func(*int))

RecoverPanicToExit is the func pointer that is returned by ExitViaPanic(). It must be called via 'defer' and will call os.Exit(1) if lager.Exit() has invoked panic() because of ExitViaPanic().

If you pass in one or more 'func(*int)' arguments, then they will each be called and passed a pointer to the exit status (initially 1) so that they can change it or just note the impending Exit. If the final value is negative, then os.Exit() will not be called (useful when testing).

func RequestUrl

func RequestUrl(req *http.Request) *url.URL

RequestUrl() returns a *url.URL more appropriate for logging, based on an *http.Request. For server Requests, the missing Host and Scheme are populated. Any query parameters are omitted (but the trailing "?" is preserved, if present).

func RunningInGcp

func RunningInGcp()

RunningInGcp() tells Lager to log messages in a format that works best when running inside of the Google Cloud Platform (when using GCP Cloud Logging). You can call this so you don't have to set LAGER_GCP=1 in your environment, but note that it will be possible for logging to happen before this call is executed [such as some logging triggered, perhaps indirectly, by some code in an Init() function] and such logs would not be in the desired format. Even calling RunningInGcp() from your own Init() function will not guarantee it happens before any logging. For this reason, using LAGER_GCP=1 is preferred.

In particular, RunningInGcp() is equivalent to running:

if "" == os.Getenv("LAGER_KEYS") {
    // LAGER_KEYS has precedence over LAGER_GCP.
    lager.Keys("time", "severity", "message", "data", "", "module")
}
lager.SetLevelNotation(lager.GcpLevelName)

It also arranges for an extra element to be added to the JSON if nothing but a message is logged so that jsonPayload.message does not get transformed into textPayload when the log is ingested into Cloud Logging.

func S

func S(arg interface{}) string

S() converts an arbitrary value to a string. It is very similar to 'fmt.Sprintf("%v", arg)' but treats []byte values the same as strings rather then dumping them as a list of byte values in base 10.

func SetLevelNotation

func SetLevelNotation(mapper func(string) string)

SetLevelNotation() installs a function to map from Lager's level names (like "DEBUG") to other values to indicate log levels. An example of such a function is GcpLevelName(). If you write such a function, you would usually just key off the first letter of the passed-in level name.

Passing in 'nil' for 'mapper' resets to the default behavior.

func SetModuleLevels

func SetModuleLevels(name, levels string) bool

En-/disables log levels for the named module. If no module by that name exists yet, then false is returned.

func SetOutput

func SetOutput(writer io.Writer) func()

SetOutput() causes all future log lines to be written to the passed-in io.Writer. If 'nil' is passed in, then log lines return to being written to os.Stdout (for most log levels) and to os.Stderr (for Panic and Exit levels).

You can temporarily redirect logs via:

defer lager.SetOutput(writer)()
//                           ^^ Note required final parens!

func SetPathParts

func SetPathParts(pathParts int)

SetPathParts() sets how many path components to include in the source code file names when recording caller information or a stack trace. Passing in 1 will cause only the source code file name to be included. A 2 will include the file name and the name of the directory it is in. A 3 adds the directory above that, etc. A value of 0 (or -1) will include the full path.

If you have not called SetPathParts(), it defaults to 3.

func SetSpanPrefix

func SetSpanPrefix(prefix string)

SetSpanPrefix() sets the span name prefix [see GetSpanPrefix()].

func Unless

func Unless(cond bool, label string) interface{}

Unless() is used to pass an optional label+value pair to Map(). Use Unless() to specify the label and, if the value is unsafe or expensive to compute, then wrap it in a deferring function:

lager.Debug().Map(
    "Ran", stage,
    // Don't include `"Error": null,` in the log:
    lager.Unless(nil == err, "Error"), err,
    // Don't call config.Proxy() if config is nil:
    lager.Unless(nil == config, "Proxy"),
        func() interface{} { return config.Proxy() },
)

Types

type AList

type AList = []interface{}

A list type that we efficiently convert to JSON.

func List

func List(args ...interface{}) AList

lager.List() returns a slice (lager.AList) that can be passed as an argument to a Lager's [C][M]Map() or [C][M]List() method to construct nested data that can be quickly serialized to JSON. For example:

lager.Info().Map("User", u, "not in", lager.List(one, two, three))

type AMap

type AMap = *KVPairs

A processed list of key/value pairs we can efficiently convert to JSON.

func ContextPairs

func ContextPairs(ctx Ctx) AMap

Fetches the lager key/value pairs stored in a context.Context.

func Pairs

func Pairs(pairs ...interface{}) AMap

lager.Pairs() returns a (processed) list of key/value pairs (lager.AMap) that can be added to a context.Context to specify additional data to append to each log line. It can also be used similar to lager.Map().

func (AMap) AddPairs

func (p AMap) AddPairs(pairs ...interface{}) AMap

Return an AMap with the passed-in key/value pairs added to and/or replacing the keys/values from the method receiver.

func (AMap) InContext

func (p AMap) InContext(ctx Ctx) Ctx

Get a new context with this map stored in it.

func (AMap) Merge

func (a AMap) Merge(b AMap) AMap

Return an AMap with the keys/values from the passed-in AMap added to and/or replacing the keys/values from the method receiver.

type Ctx

type Ctx = context.Context

Ctx is just an alias for context.Context that takes up less space in function signatures. You never need to use lager.Ctx in your code.

func AddPairs

func AddPairs(ctx Ctx, pairs ...interface{}) Ctx

Add/update Lager key/value pairs to/in a context.Context.

func GcpContextAddTrace

func GcpContextAddTrace(ctx Ctx, span spans.Factory) Ctx

GcpContextAddTrace() takes a Context and returns one that has the span added as 2 pairs that will be logged and recognized by GCP when that Context is passed to lager.Warn() or similar methods. If 'span' is 'nil' or an empty Factory, then the original 'ctx' is just returned.

'ctx' is the Context from which the new Context is created. 'span' contains the GCP CloudTrace span to be added.

See also GcpContextReceivedRequest() and/or GcpContextSendingRequest() which call this and do several other useful things.

func GcpContextReceivedRequest

func GcpContextReceivedRequest(
	ctx Ctx, req *http.Request,
) (Ctx, spans.Factory)

GcpContextReceivedRequest() does several things that are useful when a server receives a new request. 'ctx' is the Context passed to the request handler and 'req' is the received request.

An "httpRequest" key/value pair is added to the Context so that the request details will be included in any subsequent log lines [when the returned Context is passed to lager.Warn() or similar methods].

If the request headers include GCP trace information, then that is extracted [see spans.Factory.ImportFromHeaders()].

If 'ctx' contains a spans.Factory, then that is fetched and used to create either a new sub-span or (if there is no CloudTrace context in the headers) a new trace (and span). If the Factory is able to create a new span, then it is marked as a "SERVER" span, its Display Name is set to GetSpanPrefix() + ".in.request", and it is stored in the context via spans.ContextStoreSpan(). Also, an "http.url" attribute is set to the request's URL (minus query parameters), and if the request method is not "GET", then an "http.method" attribute is set to that.

If a span was imported or created, then the span information is added to the Context as pairs to be logged [see GcpContextAddTrace()] and a span will be contained in the returned Factory.

The updated Context is returned (Contexts are immutable).

It is usually called in a manner similar to:

ctx, span := lager.GcpContextReceivedRequest(ctx, req)
defer spans.FinishSpan(span)

or

ctx, span := lager.GcpContextReceivedRequest(ctx, req)
var resp *http.Response
defer lager.GcpSendingResponse(span, req, resp)

See also GcpReceivedRequest().

The order of arguments is 'ctx' then 'req' as information moves only in the direction ctx <- req (if we consider 'ctx' to represent both the argument and the returned value) and information always moves right-to- left in Go (in assignment statements and when using channels).

func GcpContextSendingRequest

func GcpContextSendingRequest(
	req *http.Request, ctx Ctx,
) (Ctx, spans.Factory)

GcpContextSendingRequest() does several things that are useful when a server is about to send a request to a dependent service. 'req' is the Request that is about to be sent. 'ctx' is the server's current Context.

The current span is fetched from 'ctx' [such as the one placed there by GcpReceivedRequest() when the original request was received]. A new sub-span is created, if possible. If so, then it is marked as a "CLIENT" span, its Display Name is set to GetSpanPrefix() + ".out.request", attributes for "http.url" and maybe "http.method" are added to it, it is stored in the Context via spans.ContextStoreSpan(), the returned Factory will contain the new span, and the updated Context will contain 2 pairs (to be logged) from the new span. Note that the original Context is not (cannot be) modified, so the trace/span pair logged after the request-sending function returns will revert to the prior span.

If a span was found or created, then its CloudContext is added to the headers for 'req' so that the dependent service can log it and add its own spans to the trace (unless 'req' is 'nil').

The updated Context is returned (Contexts are immutable).

The order of arguments is 'req' then 'ctx' as information moves only in the direction req <- ctx and information always moves right-to-left in Go (in assignment statements and when using channels).

It is usually called in a manner similar to:

ctx, span := lager.GcpContextSendingRequest(req, ctx)
defer spans.FinishSpan(span)

See also GcpSendingRequest().

func GcpSendingNewRequest

func GcpSendingNewRequest(
	ctx Ctx, method, url string, body io.Reader,
) (*http.Request, Ctx, spans.Factory, error)

GcpSendingNewRequest() does several things that are useful when a server is about to send a request to a dependent service, by calling GcpContextSendingRequest(). It takes the same arguments as http.NewRequestWithContext() but returns extra values.

It is usually called in a manner similar to:

req, ctx, span, err := lager.GcpSendingNewRequest(ctx, "GET", url, nil)
if nil != err { ... }
defer spans.FinishSpan(span)

The returned Context is the same as 'req.Context()' but it is returned separately to ease updating 'ctx' such as shown above.

type Flusher

type Flusher struct {
	Lager   *logger
	Filters []func(Lager, []byte) []byte
}

Flusher is an io.Writer that will use a Lager to log each buffer written to it. Filters are called in order. See lager.Lager.LogLogger() for more details.

func (Flusher) Write

func (f Flusher) Write(buf []byte) (int, error)

type KVPairs

type KVPairs struct {
	// contains filtered or unexported fields
}

Storage for an ordered list of key/value pairs (without duplicate keys).

type Lager

type Lager interface {

	// The List() method writes a single log line in JSON format including a
	// UTC timestamp, log level, and the passed-in values.  Or logs nothing
	// if the corresponding log level is not enabled.
	//
	// You may prefer MMap() if using common log processors.
	//
	List(args ...interface{})

	// CList() is the same as '.WithCaller(0).List(...)'.
	CList(args ...interface{})

	// MList() takes a message string followed by 0 or more arbitrary values.
	// Avoid interpreting values into the message string, passing them as
	// additional values instead so they can be extracted if needed.
	//
	// If Keys() have not been set, then MList() acts similar to List().  If
	// Keys() have been set, then MList() acts similar to:
	//
	//      MMap(message, "data", lager.List(args...))
	//
	// except the "data" key is taken from the Keys() config.
	//
	MList(message string, args ...interface{})

	// CMList() is the same as '.WithCaller(0).MList(...)'.
	CMList(message string, args ...interface{})

	// The Map() method takes a list of key/value pairs and writes a single
	// log line in JSON format including a UTC timestamp, the log level, and
	// the passed-in key/value pairs.  Or logs nothing if the corresponding
	// log level is not enabled.
	//
	// You may prefer MMap() if using common log processors.
	//
	Map(pairs ...interface{})

	// CMap() is the same as '.WithCaller(0).Map(...)'.
	CMap(pairs ...interface{})

	// MMap() takes a message string followed by zero or more key/value
	// pairs.  It is the logging method that is most compatible with the
	// most log processors.  It acts like:
	//
	//      Map("msg", message, pairs...)
	//
	// except the "msg" key is taken from the Keys() config.
	//
	// Avoid interpreting values into the message string, passing them
	// as additional key/value pairs instead so that they can be matched
	// or extracted more reliably.  For example:
	//
	//      // Don't do this:
	//      lager.Fail().MMap(fmt.Sprintf(
	//          "Failed connecting to %s: %v", url, err))
	//
	// is better written as:
	//
	//      lager.Fail().MMap("Failed connecting", "dest", url, "error", err)
	//
	MMap(message string, pairs ...interface{})

	// Same as '.WithCaller(0).MMap(...)'.
	CMMap(message string, pairs ...interface{})

	// With() returns a new Lager that adds to each log line the key/value
	// pairs from zero or more context.Context values.
	//
	With(ctxs ...context.Context) Lager

	// Enabled() returns 'false' only if this Lager will log nothing.
	Enabled() bool

	// WithStack() adds a "_stack" key/value pair to the logged context.  The
	// value is a list of strings where each string is a line number (base
	// 10) followed by a space and then the code file name (shortened to the
	// last 'PathParts' components) followed by a space and then the function
	// name (with package prefix stripped).
	//
	// If 'stackLen' is 0 (or negative), then the full stack trace will be
	// included.  Otherwise, the list will contain at most 'stackLen' strings.
	// The first string will always be for depth 'minDepth'.
	//
	// A 'minDepth' of 0 starts at the line where WithStack() was called and
	// 1 starts at the line of the caller of the caller of WithStack(), etc.
	//
	WithStack(minDepth, stackLen int) Lager

	// WithCaller() adds "_file", "_line", and "_func" key/value pairs to the
	// logged context.  A 'depth' of 0 means the line where WithCaller() was
	// called, and 1 is the line of the caller of the caller of WithCaller(),
	// etc.
	//
	WithCaller(depth int) Lager

	// The Println() method is provided for minimal compatibility with
	// log.Logger, as this method is the one most used by other modules.
	// It is just an alias for the List() method.
	//
	Println(...interface{})

	// LogLogger() returns a *log.Logger that uses the receiver to log
	// the constructed message.  You can pass 0 or more message filter
	// functions to modify the message before logging or to perform
	// additional actions.  If the final filter returns an empty []byte
	// value, then nothing will be logged.  A Flusher object is used as
	// the io.Writer for the created log.Logger.
	//
	LogLogger(...func(Lager, []byte) []byte) *log.Logger
}

'Lager' is the interface returned from lager.Warn() and the other log-level selectors. Of the several of its methods that can write log lines, MMap() is often the one you should use.

For strings that contain bytes that do not form valid UTF-8, Lager will produce valid JSON output. Since logs can be important in diagnosing problems, the non-UTF-8 parts of such strings are not replaced with the Unicode Replacement character ('\uFFFD') so any such characters in the logs indicate that '\uFFFD' was in the original string. Instead, each run of non-UTF-8 bytes is replaced by a string like "«xABC0»" that will contain 2 base-16 digits per byte.

The [C][M]Map() log-writing methods can take a list of key/value pairs as their final arguments. There are special keys and types of values that get special handling. The [C][M]List() log-writing methods can take a list of arbitrary values as their final arguments and the special value types apply to those as well.

You can use lager.InlinePairs as a key to have a pair-containing value be treated as if its pairs were passed in directly.

You can use a call to lager.Unless() as a key to make inclusion of that key/value pair optional.

A value of type 'func() interface{}' will be called so its return value can be logged; potentially saving an expensive call when the log level is disabled or when lager.Unless() causes the key/value pair to be ignored. [Note: If more than about 16KiB of that log line has been generated before such a value is reached, then we only wait 10ms for the function to finish as a lock is held in that case.]

func Acc

func Acc(cs ...Ctx) Lager

Acc() returns a Lager object. If the Acc log level has been disabled, then the returned Lager will be one that does nothing (produces no output). Otherwise it incorporates pairs from any contexts passed in. Holding on to the returned object may ignore future config updates.

Use this to write access logs. The level is recorded as "ACCESS".

func Debug

func Debug(cs ...Ctx) Lager

Debug() returns a Lager object. If the Debug log level is not enabled, then the returned Lager will be one that does nothing (produces no output). Otherwise it incorporates pairs from any contexts passed in. Holding on to the returned object may ignore future config updates.

Use this to log important details that may help in debugging.

func Exit

func Exit(cs ...Ctx) Lager

Exit() returns a Lager object that writes to os.Stderr, incorporating pairs from any contexts passed in, then calls os.Exit(1). Holding on to the returned object may ignore future config updates.

This log level is often called "Fatal" but loggers are inconsistent as to whether logging at the Fatal level causes the process to exit. By naming this level Exit, that ambiguity is removed.

Exit() should only be used during process initialization as os.Exit() will prevent any 'defer'ed clean-up operations from running. You can use ExitNotExpected() and ExitViaPanic() to find problematic uses of lager.Exit() and mitigate their impact.

func Fail

func Fail(cs ...Ctx) Lager

Fail() returns a Lager object. If the Fail log level has been disabled, then the returned Lager will be one that does nothing (produces no output). Otherwise it incorporates pairs from any contexts passed in. Holding on to the returned object may ignore future config updates.

Use this to report errors that are not part of the normal flow.

func GcpLogAccess

func GcpLogAccess(
	req *http.Request, resp *http.Response, pStart *time.Time,
) Lager

GcpLogAccess() creates a standard "access log" entry. It is just a handy shortcut for:

lager.Acc(
    lager.AddPairs(req.Context(),
        "httpRequest", GcpHttp(req, resp, pStart)))

You would use it like, for example:

lager.GcpLogAccess(req, resp, &start).MMap(
    "Response sent", "User", userID)

func Guts

func Guts(cs ...Ctx) Lager

Guts() returns a Lager object. If the Guts log level is not enabled, then the returned Lager will be one that does nothing (produces no output). Otherwise it incorporates pairs from any contexts passed in. Holding on to the returned object may ignore future config updates.

Use this for debugging data that is too voluminous to always include when debugging.

func Info

func Info(cs ...Ctx) Lager

Info() returns a Lager object. If the Info log level is not enabled, then the returned Lager will be one that does nothing (produces no output). Otherwise it incorporates pairs from any contexts passed in. Holding on to the returned object may ignore future config updates.

Use this to report minor milestones that are part of normal flow.

func Level

func Level(lev byte, cs ...Ctx) Lager

Level() takes one letter from "PEFWNAITDOG" and returns a Lager object that either logs or doesn't, depending on whether the specified log level is enabled, incorporating any key/value pairs from the passed-in contexts. Passing in any other character calls panic().

func Note

func Note(cs ...Ctx) Lager

Note() returns a Lager object. If the Note log level has been disabled, then the returned Lager will be one that does nothing (produces no output). Otherwise it incorporates pairs from any contexts passed in. Holding on to the returned object may ignore future config updates.

Use this to report major milestones that are part of normal flow.

func Obj

func Obj(cs ...Ctx) Lager

Obj() returns a Lager object. If the Obj log level is not enabled, then the returned Lager will be one that does nothing (produces no output). Otherwise it incorporates pairs from any contexts passed in. Holding on to the returned object may ignore future config updates.

Use this to log the details of internal data structures.

func Panic

func Panic(cs ...Ctx) Lager

Panic() returns a Lager object that calls panic(), incorporating pairs from any contexts passed in. The JSON is output to os.Stderr and then

panic("lager.Panic() logged (see above)")

is called. Holding on to the returned object may ignore future config updates.

func Trace

func Trace(cs ...Ctx) Lager

Trace() returns a Lager object. If the Trace log level is not enabled, then the returned Lager will be one that does nothing (produces no output). Otherwise it incorporates pairs from any contexts passed in. Holding on to the returned object may ignore future config updates.

Use this to trace how execution is flowing through the code.

func Warn

func Warn(cs ...Ctx) Lager

Warn() returns a Lager object. If the Warn log level has been disabled, then the returned Lager will be one that does nothing (produces no output). Otherwise it incorporates pairs from any contexts passed in. Holding on to the returned object may ignore future config updates.

Use this to report unusual conditions that may be signs of problems.

type Module

type Module struct {
	// contains filtered or unexported fields
}

A named module that allows separate log levels to be en-/disabled.

func NewModule

func NewModule(name string, defaultLevels ...string) *Module

Create a new Module with the given name. Default log levels can also be passed in as an optional second argument. The initial log levels enabled are taken from the last item in the list that is not "":

The current globally enabled levels.
The (optional) passed-in defaultLevels.
The value of the LAGER_{module_name}_LEVELS environment variable.

If you wish to ignore the LAGER_{module_name}_LEVELS environment varible, then write code similar to:

mod := lager.NewModule("mymod").Init("FW")

func (*Module) Acc

func (m *Module) Acc(cs ...Ctx) Lager

Returns a Lager object. If Acc log level has been disabled, then the returned Lager will be one that does nothing (produces no output). Use this log level to write access logs.

func (*Module) Debug

func (m *Module) Debug(cs ...Ctx) Lager

Returns a Lager object. If Debug log level is not enabled, then the returned Lager will be one that does nothing (produces no output). Use this log level to log important details that may help in debugging.

func (*Module) Exit

func (m *Module) Exit(cs ...Ctx) Lager

Returns a Lager object that writes to os.Stderr then calls os.Exit(1). This log level is often called "Fatal" but loggers are inconsistent as to whether logging at the Fatal level causes the process to exit. By naming this level Exit, that ambiguity is removed.

func (*Module) Fail

func (m *Module) Fail(cs ...Ctx) Lager

Returns a Lager object. If Fail log level has been disabled, then the returned Lager will be one that does nothing (produces no output). Use this log level to report errors that are not part of the normal flow.

func (*Module) Guts

func (m *Module) Guts(cs ...Ctx) Lager

Returns a Lager object. If Guts log level is not enabled, then the returned Lager will be one that does nothing (produces no output). Use this log level for debugging data that is too voluminous to always include when debugging.

func (*Module) Info

func (m *Module) Info(cs ...Ctx) Lager

Returns a Lager object. If Info log level is not enabled, then the returned Lager will be one that does nothing (produces no output). Use this log level to report minor milestones that are part of normal flow.

func (*Module) Init

func (m *Module) Init(levels string) *Module

En-/disables log levels. Pass in a string of letters from "FWNAITDOG" to indicate which log levels should be the only ones that produce output. Each letter is the first letter of a log level (Fail, Warn, Note, Acc, Info, Trace, Debug, Obj, or Guts). Levels Panic and Exit are always enabled. Init("") copies the current globally enabled levels. To disable all optional logs, you can use Init("-") as any characters not from "FWNAITDOG" are silently ignored. So you can also call Init("Fail Warn Note Acc Info").

func (*Module) Level

func (m *Module) Level(lev byte, cs ...Ctx) Lager

Pass in one character from "PEFWITDOG" to get a Lager object that either logs or doesn't, depending on whether the specified log level is enabled.

func (*Module) Note

func (m *Module) Note(cs ...Ctx) Lager

Returns a Lager object. If Note log level has been disabled, then the returned Lager will be one that does nothing (produces no output). Use this log level to report major milestones that are part of normal flow.

func (*Module) Obj

func (m *Module) Obj(cs ...Ctx) Lager

Returns a Lager object. If Obj log level is not enabled, then the returned Lager will be one that does nothing (produces no output). Use this log level to log the details of internal data structures.

func (*Module) Panic

func (m *Module) Panic(cs ...Ctx) Lager

Returns a Lager object that calls panic(). The JSON log line is first output to os.Stderr and then

panic("lager.Panic() logged (see above)")

is called.

func (*Module) Trace

func (m *Module) Trace(cs ...Ctx) Lager

Returns a Lager object. If Trace log level is not enabled, then the returned Lager will be one that does nothing (produces no output). Use this log level to trace how execution is flowing through the code.

func (*Module) Warn

func (m *Module) Warn(cs ...Ctx) Lager

Returns a Lager object. If Warn log level has been disabled, then the returned Lager will be one that does nothing (produces no output). Use this log level to report unusual conditions that may be signs of problems.

type RawMap

type RawMap []interface{}

A raw list of key/value pairs we can efficiently convert to JSON as a map.

func GcpHttp

func GcpHttp(req *http.Request, resp *http.Response, start *time.Time) RawMap

GcpHtttp() returns a value for logging that GCP will recognize as details about an HTTP(S) request (and perhaps its response), if placed under the key "httpRequest".

'req' must not be 'nil' but 'resp' and 'start' can be. None of the arguments passed will be modified; 'start' is of type '*time.Time' only to make it simple to omit latency calculations by passing in 'nil'. If 'start' points to a 'time.Time' that .IsZero(), then it is ignored.

When using tracing, this allows GCP logging to display log lines for the same request (if each includes this block) together. So this can be a good thing to add to a context.Context used with your logging. For this to work, you must log a final message that includes all three arguments (as well as using GCP-compatible tracing).

The following items will be logged (in order in the original JSON, but GCP does not preserve order of JSON keys, understandably), except that some can be omitted depending on what you pass in.

"requestMethod"     E.g. "GET"
"requestUrl"        E.g. "https://cool.me/api/v1" (no query params)
"protocol"          E.g. "HTTP/1.1"
"status"            E.g. 403
"requestSize"       Omitted if the request body size is not yet known.
"responseSize"      Omitted if 'resp' is 'nil' or body size not known.
"latency"           E.g. "0.1270s".  Omitted if 'start' is 'nil'.
"remoteIp"          E.g. "127.0.0.1"
"serverIp"          Not currently ever included.
"referer"           Omitted if there is no Referer[sic] header.
"userAgent"         Omitted if there is no User-Agent header.

Note that "status" is logged as "0" in the special case where 'resp' is 'nil' but 'start' is not 'nil'. This allows you to make an "access log" entry for cases where you got an error that prevents you from either making or getting an http.Response.

See also GcpHttpF() and GcpRequestAddTrace().

func Map

func Map(pairs ...interface{}) RawMap

lager.Map() returns a raw list of key/value pairs (lager.RawMap) that can be passed as an argument to a Lager's [C][M]Map() or [C][M]List() method to construct nested data that can be quickly serialized to JSON. I.e.:

lager.Info().List("Using", lager.Map("name", name, "age", age))

Dupliate keys all get output. If you need to squash duplicate keys, then call lager.Pairs() instead.

type Stringer

type Stringer interface {
	String() string
}

A Stringer just has a String() method that returns its stringification.

Directories

Path Synopsis
This package is still in beta and the public interface may undergo changes without a full deprecation cycle.
This package is still in beta and the public interface may undergo changes without a full deprecation cycle.
grpc_lager is package for gRPC logging middlewares for the Lager library.
grpc_lager is package for gRPC logging middlewares for the Lager library.

Jump to

Keyboard shortcuts

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