-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Initial import, minus logrus and zap * GitHub Actions for CI * gofmt * Satisfy staticcheck * It was a nice idea * Satisfy staticcheck
- Loading branch information
1 parent
1df7c19
commit 5739c26
Showing
45 changed files
with
4,046 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
on: push | ||
name: Test | ||
jobs: | ||
test: | ||
strategy: | ||
matrix: | ||
go-version: [1.16.x] | ||
platform: [ubuntu-latest, macos-latest, windows-latest] | ||
runs-on: ${{ matrix.platform }} | ||
steps: | ||
- name: Install Go | ||
uses: actions/setup-go@v1 | ||
with: | ||
go-version: ${{ matrix.go-version }} | ||
- name: Install staticcheck | ||
run: go install honnef.co/go/tools/cmd/staticcheck@latest | ||
shell: bash | ||
- name: Install golint | ||
run: go install golang.org/x/lint/golint@latest | ||
shell: bash | ||
- name: Update PATH | ||
run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH | ||
shell: bash | ||
- name: Checkout code | ||
uses: actions/checkout@v1 | ||
- name: Fmt | ||
if: matrix.platform != 'windows-latest' # :( | ||
run: "diff <(gofmt -d .) <(printf '')" | ||
shell: bash | ||
- name: Vet | ||
run: go vet ./... | ||
- name: Staticcheck | ||
run: staticcheck ./... | ||
- name: Lint | ||
run: golint ./... | ||
- name: Test | ||
run: go test -race ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,155 @@ | ||
# log | ||
A minimal and extensible structured logger | ||
<h3 align="center">:warning: PRE-RELEASE :warning:</h3> | ||
<h4 align="center">DO NOT IMPORT THIS MODULE</h4> | ||
<h4 align="center">YOUR PROJECT WILL BREAK</h4> | ||
|
||
# package log | ||
|
||
`package log` provides a minimal interface for structured logging in services. | ||
It may be wrapped to encode conventions, enforce type-safety, provide leveled | ||
logging, and so on. It can be used for both typical application log events, | ||
and log-structured data streams. | ||
|
||
## Structured logging | ||
|
||
Structured logging is, basically, conceding to the reality that logs are | ||
_data_, and warrant some level of schematic rigor. Using a stricter, | ||
key/value-oriented message format for our logs, containing contextual and | ||
semantic information, makes it much easier to get insight into the | ||
operational activity of the systems we build. Consequently, `package log` is | ||
of the strong belief that "[the benefits of structured logging outweigh the | ||
minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)". | ||
|
||
Migrating from unstructured to structured logging is probably a lot easier | ||
than you'd expect. | ||
|
||
```go | ||
// Unstructured | ||
log.Printf("HTTP server listening on %s", addr) | ||
|
||
// Structured | ||
logger.Log("transport", "HTTP", "addr", addr, "msg", "listening") | ||
``` | ||
|
||
## Usage | ||
|
||
### Typical application logging | ||
|
||
```go | ||
w := log.NewSyncWriter(os.Stderr) | ||
logger := log.NewLogfmtLogger(w) | ||
logger.Log("question", "what is the meaning of life?", "answer", 42) | ||
|
||
// Output: | ||
// question="what is the meaning of life?" answer=42 | ||
``` | ||
|
||
### Contextual Loggers | ||
|
||
```go | ||
func main() { | ||
var logger log.Logger | ||
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) | ||
logger = log.With(logger, "instance_id", 123) | ||
|
||
logger.Log("msg", "starting") | ||
NewWorker(log.With(logger, "component", "worker")).Run() | ||
NewSlacker(log.With(logger, "component", "slacker")).Run() | ||
} | ||
|
||
// Output: | ||
// instance_id=123 msg=starting | ||
// instance_id=123 component=worker msg=running | ||
// instance_id=123 component=slacker msg=running | ||
``` | ||
|
||
### Interact with stdlib logger | ||
|
||
Redirect stdlib logger to Go kit logger. | ||
|
||
```go | ||
import ( | ||
"os" | ||
stdlog "log" | ||
kitlog "github.com/go-kit/log" | ||
) | ||
|
||
func main() { | ||
logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout)) | ||
stdlog.SetOutput(kitlog.NewStdlibAdapter(logger)) | ||
stdlog.Print("I sure like pie") | ||
} | ||
|
||
// Output: | ||
// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"} | ||
``` | ||
|
||
Or, if, for legacy reasons, you need to pipe all of your logging through the | ||
stdlib log package, you can redirect Go kit logger to the stdlib logger. | ||
|
||
```go | ||
logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{}) | ||
logger.Log("legacy", true, "msg", "at least it's something") | ||
|
||
// Output: | ||
// 2016/01/01 12:34:56 legacy=true msg="at least it's something" | ||
``` | ||
|
||
### Timestamps and callers | ||
|
||
```go | ||
var logger log.Logger | ||
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) | ||
logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) | ||
|
||
logger.Log("msg", "hello") | ||
|
||
// Output: | ||
// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello | ||
``` | ||
|
||
## Levels | ||
|
||
Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/log/level). | ||
|
||
## Supported output formats | ||
|
||
- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write)) | ||
- JSON | ||
|
||
## Enhancements | ||
|
||
`package log` is centered on the one-method Logger interface. | ||
|
||
```go | ||
type Logger interface { | ||
Log(keyvals ...interface{}) error | ||
} | ||
``` | ||
|
||
This interface, and its supporting code like is the product of much iteration | ||
and evaluation. For more details on the evolution of the Logger interface, | ||
see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1), | ||
a talk by [Chris Hines](https://github.com/ChrisHines). | ||
Also, please see | ||
[#63](https://github.com/go-kit/kit/issues/63), | ||
[#76](https://github.com/go-kit/kit/pull/76), | ||
[#131](https://github.com/go-kit/kit/issues/131), | ||
[#157](https://github.com/go-kit/kit/pull/157), | ||
[#164](https://github.com/go-kit/kit/issues/164), and | ||
[#252](https://github.com/go-kit/kit/pull/252) | ||
to review historical conversations about package log and the Logger interface. | ||
|
||
Value-add packages and suggestions, | ||
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/log/level), | ||
are of course welcome. Good proposals should | ||
|
||
- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/log#With), | ||
- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/log#Caller) in any wrapped contextual loggers, and | ||
- Be friendly to packages that accept only an unadorned log.Logger. | ||
|
||
## Benchmarks & comparisons | ||
|
||
There are a few Go logging benchmarks and comparisons that include Go kit's package log. | ||
|
||
- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log | ||
- [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package log_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/go-kit/log" | ||
) | ||
|
||
func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) { | ||
lc := log.With(logger, "common_key", "common_value") | ||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
f(lc) | ||
} | ||
} | ||
|
||
var ( | ||
baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") } | ||
withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") } | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package log_test | ||
|
||
import ( | ||
"math" | ||
"testing" | ||
|
||
"github.com/go-kit/log" | ||
) | ||
|
||
// These test are designed to be run with the race detector. | ||
|
||
func testConcurrency(t *testing.T, logger log.Logger, total int) { | ||
n := int(math.Sqrt(float64(total))) | ||
share := total / n | ||
|
||
errC := make(chan error, n) | ||
|
||
for i := 0; i < n; i++ { | ||
go func() { | ||
errC <- spam(logger, share) | ||
}() | ||
} | ||
|
||
for i := 0; i < n; i++ { | ||
err := <-errC | ||
if err != nil { | ||
t.Fatalf("concurrent logging error: %v", err) | ||
} | ||
} | ||
} | ||
|
||
func spam(logger log.Logger, count int) error { | ||
for i := 0; i < count; i++ { | ||
err := logger.Log("key", i) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// Package log provides a structured logger. | ||
// | ||
// Structured logging produces logs easily consumed later by humans or | ||
// machines. Humans might be interested in debugging errors, or tracing | ||
// specific requests. Machines might be interested in counting interesting | ||
// events, or aggregating information for off-line processing. In both cases, | ||
// it is important that the log messages are structured and actionable. | ||
// Package log is designed to encourage both of these best practices. | ||
// | ||
// Basic Usage | ||
// | ||
// The fundamental interface is Logger. Loggers create log events from | ||
// key/value data. The Logger interface has a single method, Log, which | ||
// accepts a sequence of alternating key/value pairs, which this package names | ||
// keyvals. | ||
// | ||
// type Logger interface { | ||
// Log(keyvals ...interface{}) error | ||
// } | ||
// | ||
// Here is an example of a function using a Logger to create log events. | ||
// | ||
// func RunTask(task Task, logger log.Logger) string { | ||
// logger.Log("taskID", task.ID, "event", "starting task") | ||
// ... | ||
// logger.Log("taskID", task.ID, "event", "task complete") | ||
// } | ||
// | ||
// The keys in the above example are "taskID" and "event". The values are | ||
// task.ID, "starting task", and "task complete". Every key is followed | ||
// immediately by its value. | ||
// | ||
// Keys are usually plain strings. Values may be any type that has a sensible | ||
// encoding in the chosen log format. With structured logging it is a good | ||
// idea to log simple values without formatting them. This practice allows | ||
// the chosen logger to encode values in the most appropriate way. | ||
// | ||
// Contextual Loggers | ||
// | ||
// A contextual logger stores keyvals that it includes in all log events. | ||
// Building appropriate contextual loggers reduces repetition and aids | ||
// consistency in the resulting log output. With, WithPrefix, and WithSuffix | ||
// add context to a logger. We can use With to improve the RunTask example. | ||
// | ||
// func RunTask(task Task, logger log.Logger) string { | ||
// logger = log.With(logger, "taskID", task.ID) | ||
// logger.Log("event", "starting task") | ||
// ... | ||
// taskHelper(task.Cmd, logger) | ||
// ... | ||
// logger.Log("event", "task complete") | ||
// } | ||
// | ||
// The improved version emits the same log events as the original for the | ||
// first and last calls to Log. Passing the contextual logger to taskHelper | ||
// enables each log event created by taskHelper to include the task.ID even | ||
// though taskHelper does not have access to that value. Using contextual | ||
// loggers this way simplifies producing log output that enables tracing the | ||
// life cycle of individual tasks. (See the Contextual example for the full | ||
// code of the above snippet.) | ||
// | ||
// Dynamic Contextual Values | ||
// | ||
// A Valuer function stored in a contextual logger generates a new value each | ||
// time an event is logged. The Valuer example demonstrates how this feature | ||
// works. | ||
// | ||
// Valuers provide the basis for consistently logging timestamps and source | ||
// code location. The log package defines several valuers for that purpose. | ||
// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and | ||
// DefaultCaller. A common logger initialization sequence that ensures all log | ||
// entries contain a timestamp and source location looks like this: | ||
// | ||
// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) | ||
// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) | ||
// | ||
// Concurrent Safety | ||
// | ||
// Applications with multiple goroutines want each log event written to the | ||
// same logger to remain separate from other log events. Package log provides | ||
// two simple solutions for concurrent safe logging. | ||
// | ||
// NewSyncWriter wraps an io.Writer and serializes each call to its Write | ||
// method. Using a SyncWriter has the benefit that the smallest practical | ||
// portion of the logging logic is performed within a mutex, but it requires | ||
// the formatting Logger to make only one call to Write per log event. | ||
// | ||
// NewSyncLogger wraps any Logger and serializes each call to its Log method. | ||
// Using a SyncLogger has the benefit that it guarantees each log event is | ||
// handled atomically within the wrapped logger, but it typically serializes | ||
// both the formatting and output logic. Use a SyncLogger if the formatting | ||
// logger may perform multiple writes per log event. | ||
// | ||
// Error Handling | ||
// | ||
// This package relies on the practice of wrapping or decorating loggers with | ||
// other loggers to provide composable pieces of functionality. It also means | ||
// that Logger.Log must return an error because some | ||
// implementations—especially those that output log data to an io.Writer—may | ||
// encounter errors that cannot be handled locally. This in turn means that | ||
// Loggers that wrap other loggers should return errors from the wrapped | ||
// logger up the stack. | ||
// | ||
// Fortunately, the decorator pattern also provides a way to avoid the | ||
// necessity to check for errors every time an application calls Logger.Log. | ||
// An application required to panic whenever its Logger encounters | ||
// an error could initialize its logger as follows. | ||
// | ||
// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) | ||
// logger := log.LoggerFunc(func(keyvals ...interface{}) error { | ||
// if err := fmtlogger.Log(keyvals...); err != nil { | ||
// panic(err) | ||
// } | ||
// return nil | ||
// }) | ||
package log |
Oops, something went wrong.