diff options
-rw-r--r-- | log/CONTRIBUTORS | 11 | ||||
-rw-r--r-- | log/LICENSE | 13 | ||||
-rw-r--r-- | log/README.md | 77 | ||||
-rw-r--r-- | log/README_ETHEREUM.md | 4 | ||||
-rw-r--r-- | log/doc.go | 333 | ||||
-rw-r--r-- | log/format.go | 279 | ||||
-rw-r--r-- | log/handler.go | 356 | ||||
-rw-r--r-- | log/handler_go13.go | 26 | ||||
-rw-r--r-- | log/handler_go14.go | 23 | ||||
-rw-r--r-- | log/logger.go | 208 | ||||
-rw-r--r-- | log/root.go | 67 | ||||
-rw-r--r-- | log/syslog.go | 55 | ||||
-rw-r--r-- | log/term/LICENSE | 21 | ||||
-rw-r--r-- | log/term/terminal_appengine.go | 13 | ||||
-rw-r--r-- | log/term/terminal_darwin.go | 13 | ||||
-rw-r--r-- | log/term/terminal_freebsd.go | 18 | ||||
-rw-r--r-- | log/term/terminal_linux.go | 14 | ||||
-rw-r--r-- | log/term/terminal_netbsd.go | 7 | ||||
-rw-r--r-- | log/term/terminal_notwindows.go | 20 | ||||
-rw-r--r-- | log/term/terminal_openbsd.go | 7 | ||||
-rw-r--r-- | log/term/terminal_solaris.go | 9 | ||||
-rw-r--r-- | log/term/terminal_windows.go | 26 | ||||
-rw-r--r-- | vendor/github.com/go-stack/stack/LICENSE.md | 13 | ||||
-rw-r--r-- | vendor/github.com/go-stack/stack/README.md | 38 | ||||
-rw-r--r-- | vendor/github.com/go-stack/stack/stack.go | 349 | ||||
-rw-r--r-- | vendor/vendor.json | 6 |
26 files changed, 2006 insertions, 0 deletions
diff --git a/log/CONTRIBUTORS b/log/CONTRIBUTORS new file mode 100644 index 000000000..a0866713b --- /dev/null +++ b/log/CONTRIBUTORS @@ -0,0 +1,11 @@ +Contributors to log15: + +- Aaron L +- Alan Shreve +- Chris Hines +- Ciaran Downey +- Dmitry Chestnykh +- Evan Shaw +- Péter Szilágyi +- Trevor Gattis +- Vincent Vanackere diff --git a/log/LICENSE b/log/LICENSE new file mode 100644 index 000000000..5f0d1fb6a --- /dev/null +++ b/log/LICENSE @@ -0,0 +1,13 @@ +Copyright 2014 Alan Shreve + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/log/README.md b/log/README.md new file mode 100644 index 000000000..0951b21cb --- /dev/null +++ b/log/README.md @@ -0,0 +1,77 @@ +![obligatory xkcd](http://imgs.xkcd.com/comics/standards.png) + +# log15 [![godoc reference](https://godoc.org/github.com/inconshreveable/log15?status.png)](https://godoc.org/github.com/inconshreveable/log15) [![Build Status](https://travis-ci.org/inconshreveable/log15.svg?branch=master)](https://travis-ci.org/inconshreveable/log15) + +Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is modeled after the Go standard library's [`io`](http://golang.org/pkg/io/) and [`net/http`](http://golang.org/pkg/net/http/) packages and is an alternative to the standard library's [`log`](http://golang.org/pkg/log/) package. + +## Features +- A simple, easy-to-understand API +- Promotes structured logging by encouraging use of key/value pairs +- Child loggers which inherit and add their own private context +- Lazy evaluation of expensive operations +- Simple Handler interface allowing for construction of flexible, custom logging configurations with a tiny API. +- Color terminal support +- Built-in support for logging to files, streams, syslog, and the network +- Support for forking records to multiple handlers, buffering records for output, failing over from failed handler writes, + more + +## Versioning +The API of the master branch of log15 should always be considered unstable. If you want to rely on a stable API, +you must vendor the library. + +## Importing + +```go +import log "github.com/inconshreveable/log15" +``` + +## Examples + +```go +// all loggers can have key/value context +srvlog := log.New("module", "app/server") + +// all log messages can have key/value context +srvlog.Warn("abnormal conn rate", "rate", curRate, "low", lowRate, "high", highRate) + +// child loggers with inherited context +connlog := srvlog.New("raddr", c.RemoteAddr()) +connlog.Info("connection open") + +// lazy evaluation +connlog.Debug("ping remote", "latency", log.Lazy{pingRemote}) + +// flexible configuration +srvlog.SetHandler(log.MultiHandler( + log.StreamHandler(os.Stderr, log.LogfmtFormat()), + log.LvlFilterHandler( + log.LvlError, + log.Must.FileHandler("errors.json", log.JsonFormat())))) +``` + +Will result in output that looks like this: + +``` +WARN[06-17|21:58:10] abnormal conn rate module=app/server rate=0.500 low=0.100 high=0.800 +INFO[06-17|21:58:10] connection open module=app/server raddr=10.0.0.1 +``` + +## Breaking API Changes +The following commits broke API stability. This reference is intended to help you understand the consequences of updating to a newer version +of log15. + +- 57a084d014d4150152b19e4e531399a7145d1540 - Added a `Get()` method to the `Logger` interface to retrieve the current handler +- 93404652ee366648fa622b64d1e2b67d75a3094a - `Record` field `Call` changed to `stack.Call` with switch to `github.com/go-stack/stack` +- a5e7613673c73281f58e15a87d2cf0cf111e8152 - Restored `syslog.Priority` argument to the `SyslogXxx` handler constructors + +## FAQ + +### The varargs style is brittle and error prone! Can I have type safety please? +Yes. Use `log.Ctx`: + +```go +srvlog := log.New(log.Ctx{"module": "app/server"}) +srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate}) +``` + +## License +Apache diff --git a/log/README_ETHEREUM.md b/log/README_ETHEREUM.md new file mode 100644 index 000000000..6a0b0141b --- /dev/null +++ b/log/README_ETHEREUM.md @@ -0,0 +1,4 @@ +This package is a fork of https://github.com/inconshreveable/log15, with some +minor modifications required by the go-ethereum codebase: + + * Support for log level `trace` diff --git a/log/doc.go b/log/doc.go new file mode 100644 index 000000000..83ad8c54f --- /dev/null +++ b/log/doc.go @@ -0,0 +1,333 @@ +/* +Package log15 provides an opinionated, simple toolkit for best-practice logging that is +both human and machine readable. It is modeled after the standard library's io and net/http +packages. + +This package enforces you to only log key/value pairs. Keys must be strings. Values may be +any type that you like. The default output format is logfmt, but you may also choose to use +JSON instead if that suits you. Here's how you log: + + log.Info("page accessed", "path", r.URL.Path, "user_id", user.id) + +This will output a line that looks like: + + lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9 + +Getting Started + +To get started, you'll want to import the library: + + import log "github.com/inconshreveable/log15" + + +Now you're ready to start logging: + + func main() { + log.Info("Program starting", "args", os.Args()) + } + + +Convention + +Because recording a human-meaningful message is common and good practice, the first argument to every +logging method is the value to the *implicit* key 'msg'. + +Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so +will the current timestamp with key 't'. + +You may supply any additional context as a set of key/value pairs to the logging function. log15 allows +you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for +logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate +in the variadic argument list: + + log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val) + +If you really do favor your type-safety, you may choose to pass a log.Ctx instead: + + log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val}) + + +Context loggers + +Frequently, you want to add context to a logger so that you can track actions associated with it. An http +request is a good example. You can easily create new loggers that have context that is automatically included +with each log line: + + requestlogger := log.New("path", r.URL.Path) + + // later + requestlogger.Debug("db txn commit", "duration", txnTimer.Finish()) + +This will output a log line that includes the path context that is attached to the logger: + + lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12 + + +Handlers + +The Handler interface defines where log lines are printed to and how they are formated. Handler is a +single interface that is inspired by net/http's handler interface: + + type Handler interface { + Log(r *Record) error + } + + +Handlers can filter records, format them, or dispatch to multiple other Handlers. +This package implements a number of Handlers for common logging patterns that are +easily composed to create flexible, custom logging structures. + +Here's an example handler that prints logfmt output to Stdout: + + handler := log.StreamHandler(os.Stdout, log.LogfmtFormat()) + +Here's an example handler that defers to two other handlers. One handler only prints records +from the rpc package in logfmt to standard out. The other prints records at Error level +or above in JSON formatted output to the file /var/log/service.json + + handler := log.MultiHandler( + log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JsonFormat())), + log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler()) + ) + +Logging File Names and Line Numbers + +This package implements three Handlers that add debugging information to the +context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's +an example that adds the source file and line number of each logging call to +the context. + + h := log.CallerFileHandler(log.StdoutHandler) + log.Root().SetHandler(h) + ... + log.Error("open file", "err", err) + +This will output a line that looks like: + + lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42 + +Here's an example that logs the call stack rather than just the call site. + + h := log.CallerStackHandler("%+v", log.StdoutHandler) + log.Root().SetHandler(h) + ... + log.Error("open file", "err", err) + +This will output a line that looks like: + + lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" stack="[pkg/data.go:42 pkg/cmd/main.go]" + +The "%+v" format instructs the handler to include the path of the source file +relative to the compile time GOPATH. The github.com/go-stack/stack package +documents the full list of formatting verbs and modifiers available. + +Custom Handlers + +The Handler interface is so simple that it's also trivial to write your own. Let's create an +example handler which tries to write to one handler, but if that fails it falls back to +writing to another handler and includes the error that it encountered when trying to write +to the primary. This might be useful when trying to log over a network socket, but if that +fails you want to log those records to a file on disk. + + type BackupHandler struct { + Primary Handler + Secondary Handler + } + + func (h *BackupHandler) Log (r *Record) error { + err := h.Primary.Log(r) + if err != nil { + r.Ctx = append(ctx, "primary_err", err) + return h.Secondary.Log(r) + } + return nil + } + +This pattern is so useful that a generic version that handles an arbitrary number of Handlers +is included as part of this library called FailoverHandler. + +Logging Expensive Operations + +Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay +the price of computing them if you haven't turned up your logging level to a high level of detail. + +This package provides a simple type to annotate a logging operation that you want to be evaluated +lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler +filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example: + + func factorRSAKey() (factors []int) { + // return the factors of a very large number + } + + log.Debug("factors", log.Lazy{factorRSAKey}) + +If this message is not logged for any reason (like logging at the Error level), then +factorRSAKey is never evaluated. + +Dynamic context values + +The same log.Lazy mechanism can be used to attach context to a logger which you want to be +evaluated when the message is logged, but not when the logger is created. For example, let's imagine +a game where you have Player objects: + + type Player struct { + name string + alive bool + log.Logger + } + +You always want to log a player's name and whether they're alive or dead, so when you create the player +object, you might do: + + p := &Player{name: name, alive: true} + p.Logger = log.New("name", p.name, "alive", p.alive) + +Only now, even after a player has died, the logger will still report they are alive because the logging +context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation +of whether the player is alive or not to each log message, so that the log records will reflect the player's +current state no matter when the log message is written: + + p := &Player{name: name, alive: true} + isAlive := func() bool { return p.alive } + player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive}) + +Terminal Format + +If log15 detects that stdout is a terminal, it will configure the default +handler for it (which is log.StdoutHandler) to use TerminalFormat. This format +logs records nicely for your terminal, including color-coded output based +on log level. + +Error Handling + +Becasuse log15 allows you to step around the type system, there are a few ways you can specify +invalid arguments to the logging functions. You could, for example, wrap something that is not +a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries +are typically the mechanism by which errors are reported, it would be onerous for the logging functions +to return errors. Instead, log15 handles errors by making these guarantees to you: + +- Any log record containing an error will still be printed with the error explained to you as part of the log record. + +- Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily +(and if you like, automatically) detect if any of your logging calls are passing bad values. + +Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers +are encouraged to return errors only if they fail to write their log records out to an external source like if the +syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures +like the FailoverHandler. + +Library Use + +log15 is intended to be useful for library authors as a way to provide configurable logging to +users of their library. Best practice for use in a library is to always disable all output for your logger +by default and to provide a public Logger instance that consumers of your library can configure. Like so: + + package yourlib + + import "github.com/inconshreveable/log15" + + var Log = log.New() + + func init() { + Log.SetHandler(log.DiscardHandler()) + } + +Users of your library may then enable it if they like: + + import "github.com/inconshreveable/log15" + import "example.com/yourlib" + + func main() { + handler := // custom handler setup + yourlib.Log.SetHandler(handler) + } + +Best practices attaching logger context + +The ability to attach context to a logger is a powerful one. Where should you do it and why? +I favor embedding a Logger directly into any persistent object in my application and adding +unique, tracing context keys to it. For instance, imagine I am writing a web browser: + + type Tab struct { + url string + render *RenderingContext + // ... + + Logger + } + + func NewTab(url string) *Tab { + return &Tab { + // ... + url: url, + + Logger: log.New("url", url), + } + } + +When a new tab is created, I assign a logger to it with the url of +the tab as context so it can easily be traced through the logs. +Now, whenever we perform any operation with the tab, we'll log with its +embedded logger and it will include the tab title automatically: + + tab.Debug("moved position", "idx", tab.idx) + +There's only one problem. What if the tab url changes? We could +use log.Lazy to make sure the current url is always written, but that +would mean that we couldn't trace a tab's full lifetime through our +logs after the user navigate to a new URL. + +Instead, think about what values to attach to your loggers the +same way you think about what to use as a key in a SQL database schema. +If it's possible to use a natural key that is unique for the lifetime of the +object, do so. But otherwise, log15's ext package has a handy RandId +function to let you generate what you might call "surrogate keys" +They're just random hex identifiers to use for tracing. Back to our +Tab example, we would prefer to set up our Logger like so: + + import logext "github.com/inconshreveable/log15/ext" + + t := &Tab { + // ... + url: url, + } + + t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl}) + return t + +Now we'll have a unique traceable identifier even across loading new urls, but +we'll still be able to see the tab's current url in the log messages. + +Must + +For all Handler functions which can return an error, there is a version of that +function which will return no error but panics on failure. They are all available +on the Must object. For example: + + log.Must.FileHandler("/path", log.JsonFormat) + log.Must.NetHandler("tcp", ":1234", log.JsonFormat) + +Inspiration and Credit + +All of the following excellent projects inspired the design of this library: + +code.google.com/p/log4go + +github.com/op/go-logging + +github.com/technoweenie/grohl + +github.com/Sirupsen/logrus + +github.com/kr/logfmt + +github.com/spacemonkeygo/spacelog + +golang's stdlib, notably io and net/http + +The Name + +https://xkcd.com/927/ + +*/ +package log diff --git a/log/format.go b/log/format.go new file mode 100644 index 000000000..376376183 --- /dev/null +++ b/log/format.go @@ -0,0 +1,279 @@ +package log + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" +) + +const ( + timeFormat = "2006-01-02T15:04:05-0700" + termTimeFormat = "01-02|15:04:05" + floatFormat = 'f' + termMsgJust = 40 +) + +type Format interface { + Format(r *Record) []byte +} + +// FormatFunc returns a new Format object which uses +// the given function to perform record formatting. +func FormatFunc(f func(*Record) []byte) Format { + return formatFunc(f) +} + +type formatFunc func(*Record) []byte + +func (f formatFunc) Format(r *Record) []byte { + return f(r) +} + +// TerminalFormat formats log records optimized for human readability on +// a terminal with color-coded level output and terser human friendly timestamp. +// This format should only be used for interactive programs or while developing. +// +// [TIME] [LEVEL] MESAGE key=value key=value ... +// +// Example: +// +// [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002 +// +func TerminalFormat() Format { + return FormatFunc(func(r *Record) []byte { + var color = 0 + switch r.Lvl { + case LvlCrit: + color = 35 + case LvlError: + color = 31 + case LvlWarn: + color = 33 + case LvlInfo: + color = 32 + case LvlDebug: + color = 36 + } + + b := &bytes.Buffer{} + lvl := strings.ToUpper(r.Lvl.String()) + if color > 0 { + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg) + } else { + fmt.Fprintf(b, "[%s] [%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg) + } + + // try to justify the log output for short messages + if len(r.Ctx) > 0 && len(r.Msg) < termMsgJust { + b.Write(bytes.Repeat([]byte{' '}, termMsgJust-len(r.Msg))) + } + + // print the keys logfmt style + logfmt(b, r.Ctx, color) + return b.Bytes() + }) +} + +// LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable +// format for key/value pairs. +// +// For more details see: http://godoc.org/github.com/kr/logfmt +// +func LogfmtFormat() Format { + return FormatFunc(func(r *Record) []byte { + common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg} + buf := &bytes.Buffer{} + logfmt(buf, append(common, r.Ctx...), 0) + return buf.Bytes() + }) +} + +func logfmt(buf *bytes.Buffer, ctx []interface{}, color int) { + for i := 0; i < len(ctx); i += 2 { + if i != 0 { + buf.WriteByte(' ') + } + + k, ok := ctx[i].(string) + v := formatLogfmtValue(ctx[i+1]) + if !ok { + k, v = errorKey, formatLogfmtValue(k) + } + + // XXX: we should probably check that all of your key bytes aren't invalid + if color > 0 { + fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=%s", color, k, v) + } else { + buf.WriteString(k) + buf.WriteByte('=') + buf.WriteString(v) + } + } + + buf.WriteByte('\n') +} + +// JsonFormat formats log records as JSON objects separated by newlines. +// It is the equivalent of JsonFormatEx(false, true). +func JsonFormat() Format { + return JsonFormatEx(false, true) +} + +// JsonFormatEx formats log records as JSON objects. If pretty is true, +// records will be pretty-printed. If lineSeparated is true, records +// will be logged with a new line between each record. +func JsonFormatEx(pretty, lineSeparated bool) Format { + jsonMarshal := json.Marshal + if pretty { + jsonMarshal = func(v interface{}) ([]byte, error) { + return json.MarshalIndent(v, "", " ") + } + } + + return FormatFunc(func(r *Record) []byte { + props := make(map[string]interface{}) + + props[r.KeyNames.Time] = r.Time + props[r.KeyNames.Lvl] = r.Lvl.String() + props[r.KeyNames.Msg] = r.Msg + + for i := 0; i < len(r.Ctx); i += 2 { + k, ok := r.Ctx[i].(string) + if !ok { + props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) + } + props[k] = formatJsonValue(r.Ctx[i+1]) + } + + b, err := jsonMarshal(props) + if err != nil { + b, _ = jsonMarshal(map[string]string{ + errorKey: err.Error(), + }) + return b + } + + if lineSeparated { + b = append(b, '\n') + } + + return b + }) +} + +func formatShared(value interface{}) (result interface{}) { + defer func() { + if err := recover(); err != nil { + if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() { + result = "nil" + } else { + panic(err) + } + } + }() + + switch v := value.(type) { + case time.Time: + return v.Format(timeFormat) + + case error: + return v.Error() + + case fmt.Stringer: + return v.String() + + default: + return v + } +} + +func formatJsonValue(value interface{}) interface{} { + value = formatShared(value) + switch value.(type) { + case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: + return value + default: + return fmt.Sprintf("%+v", value) + } +} + +// formatValue formats a value for serialization +func formatLogfmtValue(value interface{}) string { + if value == nil { + return "nil" + } + + if t, ok := value.(time.Time); ok { + // Performance optimization: No need for escaping since the provided + // timeFormat doesn't have any escape characters, and escaping is + // expensive. + return t.Format(timeFormat) + } + value = formatShared(value) + switch v := value.(type) { + case bool: + return strconv.FormatBool(v) + case float32: + return strconv.FormatFloat(float64(v), floatFormat, 3, 64) + case float64: + return strconv.FormatFloat(v, floatFormat, 3, 64) + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + return fmt.Sprintf("%d", value) + case string: + return escapeString(v) + default: + return escapeString(fmt.Sprintf("%+v", value)) + } +} + +var stringBufPool = sync.Pool{ + New: func() interface{} { return new(bytes.Buffer) }, +} + +func escapeString(s string) string { + needsQuotes := false + needsEscape := false + for _, r := range s { + if r <= ' ' || r == '=' || r == '"' { + needsQuotes = true + } + if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' { + needsEscape = true + } + } + if needsEscape == false && needsQuotes == false { + return s + } + e := stringBufPool.Get().(*bytes.Buffer) + e.WriteByte('"') + for _, r := range s { + switch r { + case '\\', '"': + e.WriteByte('\\') + e.WriteByte(byte(r)) + case '\n': + e.WriteString("\\n") + case '\r': + e.WriteString("\\r") + case '\t': + e.WriteString("\\t") + default: + e.WriteRune(r) + } + } + e.WriteByte('"') + var ret string + if needsQuotes { + ret = e.String() + } else { + ret = string(e.Bytes()[1 : e.Len()-1]) + } + e.Reset() + stringBufPool.Put(e) + return ret +} diff --git a/log/handler.go b/log/handler.go new file mode 100644 index 000000000..abb17b4c4 --- /dev/null +++ b/log/handler.go @@ -0,0 +1,356 @@ +package log + +import ( + "fmt" + "io" + "net" + "os" + "reflect" + "sync" + + "github.com/go-stack/stack" +) + +// A Logger prints its log records by writing to a Handler. +// The Handler interface defines where and how log records are written. +// Handlers are composable, providing you great flexibility in combining +// them to achieve the logging structure that suits your applications. +type Handler interface { + Log(r *Record) error +} + +// FuncHandler returns a Handler that logs records with the given +// function. +func FuncHandler(fn func(r *Record) error) Handler { + return funcHandler(fn) +} + +type funcHandler func(r *Record) error + +func (h funcHandler) Log(r *Record) error { + return h(r) +} + +// StreamHandler writes log records to an io.Writer +// with the given format. StreamHandler can be used +// to easily begin writing log records to other +// outputs. +// +// StreamHandler wraps itself with LazyHandler and SyncHandler +// to evaluate Lazy objects and perform safe concurrent writes. +func StreamHandler(wr io.Writer, fmtr Format) Handler { + h := FuncHandler(func(r *Record) error { + _, err := wr.Write(fmtr.Format(r)) + return err + }) + return LazyHandler(SyncHandler(h)) +} + +// SyncHandler can be wrapped around a handler to guarantee that +// only a single Log operation can proceed at a time. It's necessary +// for thread-safe concurrent writes. +func SyncHandler(h Handler) Handler { + var mu sync.Mutex + return FuncHandler(func(r *Record) error { + defer mu.Unlock() + mu.Lock() + return h.Log(r) + }) +} + +// FileHandler returns a handler which writes log records to the give file +// using the given format. If the path +// already exists, FileHandler will append to the given file. If it does not, +// FileHandler will create the file with mode 0644. +func FileHandler(path string, fmtr Format) (Handler, error) { + f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return nil, err + } + return closingHandler{f, StreamHandler(f, fmtr)}, nil +} + +// NetHandler opens a socket to the given address and writes records +// over the connection. +func NetHandler(network, addr string, fmtr Format) (Handler, error) { + conn, err := net.Dial(network, addr) + if err != nil { + return nil, err + } + + return closingHandler{conn, StreamHandler(conn, fmtr)}, nil +} + +// XXX: closingHandler is essentially unused at the moment +// it's meant for a future time when the Handler interface supports +// a possible Close() operation +type closingHandler struct { + io.WriteCloser + Handler +} + +func (h *closingHandler) Close() error { + return h.WriteCloser.Close() +} + +// CallerFileHandler returns a Handler that adds the line number and file of +// the calling function to the context with key "caller". +func CallerFileHandler(h Handler) Handler { + return FuncHandler(func(r *Record) error { + r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call)) + return h.Log(r) + }) +} + +// CallerFuncHandler returns a Handler that adds the calling function name to +// the context with key "fn". +func CallerFuncHandler(h Handler) Handler { + return FuncHandler(func(r *Record) error { + r.Ctx = append(r.Ctx, "fn", fmt.Sprintf("%+n", r.Call)) + return h.Log(r) + }) +} + +// CallerStackHandler returns a Handler that adds a stack trace to the context +// with key "stack". The stack trace is formated as a space separated list of +// call sites inside matching []'s. The most recent call site is listed first. +// Each call site is formatted according to format. See the documentation of +// package github.com/go-stack/stack for the list of supported formats. +func CallerStackHandler(format string, h Handler) Handler { + return FuncHandler(func(r *Record) error { + s := stack.Trace().TrimBelow(r.Call).TrimRuntime() + if len(s) > 0 { + r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s)) + } + return h.Log(r) + }) +} + +// FilterHandler returns a Handler that only writes records to the +// wrapped Handler if the given function evaluates true. For example, +// to only log records where the 'err' key is not nil: +// +// logger.SetHandler(FilterHandler(func(r *Record) bool { +// for i := 0; i < len(r.Ctx); i += 2 { +// if r.Ctx[i] == "err" { +// return r.Ctx[i+1] != nil +// } +// } +// return false +// }, h)) +// +func FilterHandler(fn func(r *Record) bool, h Handler) Handler { + return FuncHandler(func(r *Record) error { + if fn(r) { + return h.Log(r) + } + return nil + }) +} + +// MatchFilterHandler returns a Handler that only writes records +// to the wrapped Handler if the given key in the logged +// context matches the value. For example, to only log records +// from your ui package: +// +// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler) +// +func MatchFilterHandler(key string, value interface{}, h Handler) Handler { + return FilterHandler(func(r *Record) (pass bool) { + switch key { + case r.KeyNames.Lvl: + return r.Lvl == value + case r.KeyNames.Time: + return r.Time == value + case r.KeyNames.Msg: + return r.Msg == value + } + + for i := 0; i < len(r.Ctx); i += 2 { + if r.Ctx[i] == key { + return r.Ctx[i+1] == value + } + } + return false + }, h) +} + +// LvlFilterHandler returns a Handler that only writes +// records which are less than the given verbosity +// level to the wrapped Handler. For example, to only +// log Error/Crit records: +// +// log.LvlFilterHandler(log.LvlError, log.StdoutHandler) +// +func LvlFilterHandler(maxLvl Lvl, h Handler) Handler { + return FilterHandler(func(r *Record) (pass bool) { + return r.Lvl <= maxLvl + }, h) +} + +// A MultiHandler dispatches any write to each of its handlers. +// This is useful for writing different types of log information +// to different locations. For example, to log to a file and +// standard error: +// +// log.MultiHandler( +// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), +// log.StderrHandler) +// +func MultiHandler(hs ...Handler) Handler { + return FuncHandler(func(r *Record) error { + for _, h := range hs { + // what to do about failures? + h.Log(r) + } + return nil + }) +} + +// A FailoverHandler writes all log records to the first handler +// specified, but will failover and write to the second handler if +// the first handler has failed, and so on for all handlers specified. +// For example you might want to log to a network socket, but failover +// to writing to a file if the network fails, and then to +// standard out if the file write fails: +// +// log.FailoverHandler( +// log.Must.NetHandler("tcp", ":9090", log.JsonFormat()), +// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), +// log.StdoutHandler) +// +// All writes that do not go to the first handler will add context with keys of +// the form "failover_err_{idx}" which explain the error encountered while +// trying to write to the handlers before them in the list. +func FailoverHandler(hs ...Handler) Handler { + return FuncHandler(func(r *Record) error { + var err error + for i, h := range hs { + err = h.Log(r) + if err == nil { + return nil + } else { + r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) + } + } + + return err + }) +} + +// ChannelHandler writes all records to the given channel. +// It blocks if the channel is full. Useful for async processing +// of log messages, it's used by BufferedHandler. +func ChannelHandler(recs chan<- *Record) Handler { + return FuncHandler(func(r *Record) error { + recs <- r + return nil + }) +} + +// BufferedHandler writes all records to a buffered +// channel of the given size which flushes into the wrapped +// handler whenever it is available for writing. Since these +// writes happen asynchronously, all writes to a BufferedHandler +// never return an error and any errors from the wrapped handler are ignored. +func BufferedHandler(bufSize int, h Handler) Handler { + recs := make(chan *Record, bufSize) + go func() { + for m := range recs { + _ = h.Log(m) + } + }() + return ChannelHandler(recs) +} + +// LazyHandler writes all values to the wrapped handler after evaluating +// any lazy functions in the record's context. It is already wrapped +// around StreamHandler and SyslogHandler in this library, you'll only need +// it if you write your own Handler. +func LazyHandler(h Handler) Handler { + return FuncHandler(func(r *Record) error { + // go through the values (odd indices) and reassign + // the values of any lazy fn to the result of its execution + hadErr := false + for i := 1; i < len(r.Ctx); i += 2 { + lz, ok := r.Ctx[i].(Lazy) + if ok { + v, err := evaluateLazy(lz) + if err != nil { + hadErr = true + r.Ctx[i] = err + } else { + if cs, ok := v.(stack.CallStack); ok { + v = cs.TrimBelow(r.Call).TrimRuntime() + } + r.Ctx[i] = v + } + } + } + + if hadErr { + r.Ctx = append(r.Ctx, errorKey, "bad lazy") + } + + return h.Log(r) + }) +} + +func evaluateLazy(lz Lazy) (interface{}, error) { + t := reflect.TypeOf(lz.Fn) + + if t.Kind() != reflect.Func { + return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) + } + + if t.NumIn() > 0 { + return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) + } + + if t.NumOut() == 0 { + return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) + } + + value := reflect.ValueOf(lz.Fn) + results := value.Call([]reflect.Value{}) + if len(results) == 1 { + return results[0].Interface(), nil + } else { + values := make([]interface{}, len(results)) + for i, v := range results { + values[i] = v.Interface() + } + return values, nil + } +} + +// DiscardHandler reports success for all writes but does nothing. +// It is useful for dynamically disabling logging at runtime via +// a Logger's SetHandler method. +func DiscardHandler() Handler { + return FuncHandler(func(r *Record) error { + return nil + }) +} + +// The Must object provides the following Handler creation functions +// which instead of returning an error parameter only return a Handler +// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler +var Must muster + +func must(h Handler, err error) Handler { + if err != nil { + panic(err) + } + return h +} + +type muster struct{} + +func (m muster) FileHandler(path string, fmtr Format) Handler { + return must(FileHandler(path, fmtr)) +} + +func (m muster) NetHandler(network, addr string, fmtr Format) Handler { + return must(NetHandler(network, addr, fmtr)) +} diff --git a/log/handler_go13.go b/log/handler_go13.go new file mode 100644 index 000000000..0843ed0e5 --- /dev/null +++ b/log/handler_go13.go @@ -0,0 +1,26 @@ +// +build !go1.4 + +package log + +import ( + "sync/atomic" + "unsafe" +) + +// swapHandler wraps another handler that may be swapped out +// dynamically at runtime in a thread-safe fashion. +type swapHandler struct { + handler unsafe.Pointer +} + +func (h *swapHandler) Log(r *Record) error { + return h.Get().Log(r) +} + +func (h *swapHandler) Get() Handler { + return *(*Handler)(atomic.LoadPointer(&h.handler)) +} + +func (h *swapHandler) Swap(newHandler Handler) { + atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler)) +} diff --git a/log/handler_go14.go b/log/handler_go14.go new file mode 100644 index 000000000..05dedbf2a --- /dev/null +++ b/log/handler_go14.go @@ -0,0 +1,23 @@ +// +build go1.4 + +package log + +import "sync/atomic" + +// swapHandler wraps another handler that may be swapped out +// dynamically at runtime in a thread-safe fashion. +type swapHandler struct { + handler atomic.Value +} + +func (h *swapHandler) Log(r *Record) error { + return (*h.handler.Load().(*Handler)).Log(r) +} + +func (h *swapHandler) Swap(newHandler Handler) { + h.handler.Store(&newHandler) +} + +func (h *swapHandler) Get() Handler { + return *h.handler.Load().(*Handler) +} diff --git a/log/logger.go b/log/logger.go new file mode 100644 index 000000000..a9d42b8da --- /dev/null +++ b/log/logger.go @@ -0,0 +1,208 @@ +package log + +import ( + "fmt" + "time" + + "github.com/go-stack/stack" +) + +const timeKey = "t" +const lvlKey = "lvl" +const msgKey = "msg" +const errorKey = "LOG15_ERROR" + +type Lvl int + +const ( + LvlCrit Lvl = iota + LvlError + LvlWarn + LvlInfo + LvlDebug +) + +// Returns the name of a Lvl +func (l Lvl) String() string { + switch l { + case LvlDebug: + return "dbug" + case LvlInfo: + return "info" + case LvlWarn: + return "warn" + case LvlError: + return "eror" + case LvlCrit: + return "crit" + default: + panic("bad level") + } +} + +// Returns the appropriate Lvl from a string name. +// Useful for parsing command line args and configuration files. +func LvlFromString(lvlString string) (Lvl, error) { + switch lvlString { + case "debug", "dbug": + return LvlDebug, nil + case "info": + return LvlInfo, nil + case "warn": + return LvlWarn, nil + case "error", "eror": + return LvlError, nil + case "crit": + return LvlCrit, nil + default: + return LvlDebug, fmt.Errorf("Unknown level: %v", lvlString) + } +} + +// A Record is what a Logger asks its handler to write +type Record struct { + Time time.Time + Lvl Lvl + Msg string + Ctx []interface{} + Call stack.Call + KeyNames RecordKeyNames +} + +type RecordKeyNames struct { + Time string + Msg string + Lvl string +} + +// A Logger writes key/value pairs to a Handler +type Logger interface { + // New returns a new Logger that has this logger's context plus the given context + New(ctx ...interface{}) Logger + + // GetHandler gets the handler associated with the logger. + GetHandler() Handler + + // SetHandler updates the logger to write records to the specified handler. + SetHandler(h Handler) + + // Log a message at the given level with context key/value pairs + Debug(msg string, ctx ...interface{}) + Info(msg string, ctx ...interface{}) + Warn(msg string, ctx ...interface{}) + Error(msg string, ctx ...interface{}) + Crit(msg string, ctx ...interface{}) +} + +type logger struct { + ctx []interface{} + h *swapHandler +} + +func (l *logger) write(msg string, lvl Lvl, ctx []interface{}) { + l.h.Log(&Record{ + Time: time.Now(), + Lvl: lvl, + Msg: msg, + Ctx: newContext(l.ctx, ctx), + Call: stack.Caller(2), + KeyNames: RecordKeyNames{ + Time: timeKey, + Msg: msgKey, + Lvl: lvlKey, + }, + }) +} + +func (l *logger) New(ctx ...interface{}) Logger { + child := &logger{newContext(l.ctx, ctx), new(swapHandler)} + child.SetHandler(l.h) + return child +} + +func newContext(prefix []interface{}, suffix []interface{}) []interface{} { + normalizedSuffix := normalize(suffix) + newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix)) + n := copy(newCtx, prefix) + copy(newCtx[n:], normalizedSuffix) + return newCtx +} + +func (l *logger) Debug(msg string, ctx ...interface{}) { + l.write(msg, LvlDebug, ctx) +} + +func (l *logger) Info(msg string, ctx ...interface{}) { + l.write(msg, LvlInfo, ctx) +} + +func (l *logger) Warn(msg string, ctx ...interface{}) { + l.write(msg, LvlWarn, ctx) +} + +func (l *logger) Error(msg string, ctx ...interface{}) { + l.write(msg, LvlError, ctx) +} + +func (l *logger) Crit(msg string, ctx ...interface{}) { + l.write(msg, LvlCrit, ctx) +} + +func (l *logger) GetHandler() Handler { + return l.h.Get() +} + +func (l *logger) SetHandler(h Handler) { + l.h.Swap(h) +} + +func normalize(ctx []interface{}) []interface{} { + // if the caller passed a Ctx object, then expand it + if len(ctx) == 1 { + if ctxMap, ok := ctx[0].(Ctx); ok { + ctx = ctxMap.toArray() + } + } + + // ctx needs to be even because it's a series of key/value pairs + // no one wants to check for errors on logging functions, + // so instead of erroring on bad input, we'll just make sure + // that things are the right length and users can fix bugs + // when they see the output looks wrong + if len(ctx)%2 != 0 { + ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil") + } + + return ctx +} + +// Lazy allows you to defer calculation of a logged value that is expensive +// to compute until it is certain that it must be evaluated with the given filters. +// +// Lazy may also be used in conjunction with a Logger's New() function +// to generate a child logger which always reports the current value of changing +// state. +// +// You may wrap any function which takes no arguments to Lazy. It may return any +// number of values of any type. +type Lazy struct { + Fn interface{} +} + +// Ctx is a map of key/value pairs to pass as context to a log function +// Use this only if you really need greater safety around the arguments you pass +// to the logging functions. +type Ctx map[string]interface{} + +func (c Ctx) toArray() []interface{} { + arr := make([]interface{}, len(c)*2) + + i := 0 + for k, v := range c { + arr[i] = k + arr[i+1] = v + i += 2 + } + + return arr +} diff --git a/log/root.go b/log/root.go new file mode 100644 index 000000000..39b4c9429 --- /dev/null +++ b/log/root.go @@ -0,0 +1,67 @@ +package log + +import ( + "os" + + "github.com/ethereum/go-ethereum/log/term" + "github.com/mattn/go-colorable" +) + +var ( + root *logger + StdoutHandler = StreamHandler(os.Stdout, LogfmtFormat()) + StderrHandler = StreamHandler(os.Stderr, LogfmtFormat()) +) + +func init() { + if term.IsTty(os.Stdout.Fd()) { + StdoutHandler = StreamHandler(colorable.NewColorableStdout(), TerminalFormat()) + } + + if term.IsTty(os.Stderr.Fd()) { + StderrHandler = StreamHandler(colorable.NewColorableStderr(), TerminalFormat()) + } + + root = &logger{[]interface{}{}, new(swapHandler)} + root.SetHandler(StdoutHandler) +} + +// New returns a new logger with the given context. +// New is a convenient alias for Root().New +func New(ctx ...interface{}) Logger { + return root.New(ctx...) +} + +// Root returns the root logger +func Root() Logger { + return root +} + +// The following functions bypass the exported logger methods (logger.Debug, +// etc.) to keep the call depth the same for all paths to logger.write so +// runtime.Caller(2) always refers to the call site in client code. + +// Debug is a convenient alias for Root().Debug +func Debug(msg string, ctx ...interface{}) { + root.write(msg, LvlDebug, ctx) +} + +// Info is a convenient alias for Root().Info +func Info(msg string, ctx ...interface{}) { + root.write(msg, LvlInfo, ctx) +} + +// Warn is a convenient alias for Root().Warn +func Warn(msg string, ctx ...interface{}) { + root.write(msg, LvlWarn, ctx) +} + +// Error is a convenient alias for Root().Error +func Error(msg string, ctx ...interface{}) { + root.write(msg, LvlError, ctx) +} + +// Crit is a convenient alias for Root().Crit +func Crit(msg string, ctx ...interface{}) { + root.write(msg, LvlCrit, ctx) +} diff --git a/log/syslog.go b/log/syslog.go new file mode 100644 index 000000000..4f1097ff9 --- /dev/null +++ b/log/syslog.go @@ -0,0 +1,55 @@ +// +build !windows,!plan9 + +package log + +import ( + "log/syslog" + "strings" +) + +// SyslogHandler opens a connection to the system syslog daemon by calling +// syslog.New and writes all records to it. +func SyslogHandler(priority syslog.Priority, tag string, fmtr Format) (Handler, error) { + wr, err := syslog.New(priority, tag) + return sharedSyslog(fmtr, wr, err) +} + +// SyslogNetHandler opens a connection to a log daemon over the network and writes +// all log records to it. +func SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) (Handler, error) { + wr, err := syslog.Dial(net, addr, priority, tag) + return sharedSyslog(fmtr, wr, err) +} + +func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) { + if err != nil { + return nil, err + } + h := FuncHandler(func(r *Record) error { + var syslogFn = sysWr.Info + switch r.Lvl { + case LvlCrit: + syslogFn = sysWr.Crit + case LvlError: + syslogFn = sysWr.Err + case LvlWarn: + syslogFn = sysWr.Warning + case LvlInfo: + syslogFn = sysWr.Info + case LvlDebug: + syslogFn = sysWr.Debug + } + + s := strings.TrimSpace(string(fmtr.Format(r))) + return syslogFn(s) + }) + return LazyHandler(&closingHandler{sysWr, h}), nil +} + +func (m muster) SyslogHandler(priority syslog.Priority, tag string, fmtr Format) Handler { + return must(SyslogHandler(priority, tag, fmtr)) +} + +func (m muster) SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) Handler { + return must(SyslogNetHandler(net, addr, priority, tag, fmtr)) +} diff --git a/log/term/LICENSE b/log/term/LICENSE new file mode 100644 index 000000000..f090cb42f --- /dev/null +++ b/log/term/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Simon Eskildsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/log/term/terminal_appengine.go b/log/term/terminal_appengine.go new file mode 100644 index 000000000..c1b5d2a3b --- /dev/null +++ b/log/term/terminal_appengine.go @@ -0,0 +1,13 @@ +// Based on ssh/terminal: +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appengine + +package term + +// IsTty always returns false on AppEngine. +func IsTty(fd uintptr) bool { + return false +} diff --git a/log/term/terminal_darwin.go b/log/term/terminal_darwin.go new file mode 100644 index 000000000..d8f351b1b --- /dev/null +++ b/log/term/terminal_darwin.go @@ -0,0 +1,13 @@ +// Based on ssh/terminal: +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +build !appengine + +package term + +import "syscall" + +const ioctlReadTermios = syscall.TIOCGETA + +type Termios syscall.Termios diff --git a/log/term/terminal_freebsd.go b/log/term/terminal_freebsd.go new file mode 100644 index 000000000..cfaceab33 --- /dev/null +++ b/log/term/terminal_freebsd.go @@ -0,0 +1,18 @@ +package term + +import ( + "syscall" +) + +const ioctlReadTermios = syscall.TIOCGETA + +// Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin. +type Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]uint8 + Ispeed uint32 + Ospeed uint32 +} diff --git a/log/term/terminal_linux.go b/log/term/terminal_linux.go new file mode 100644 index 000000000..5290468d6 --- /dev/null +++ b/log/term/terminal_linux.go @@ -0,0 +1,14 @@ +// Based on ssh/terminal: +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !appengine + +package term + +import "syscall" + +const ioctlReadTermios = syscall.TCGETS + +type Termios syscall.Termios diff --git a/log/term/terminal_netbsd.go b/log/term/terminal_netbsd.go new file mode 100644 index 000000000..f9bb9e1c2 --- /dev/null +++ b/log/term/terminal_netbsd.go @@ -0,0 +1,7 @@ +package term + +import "syscall" + +const ioctlReadTermios = syscall.TIOCGETA + +type Termios syscall.Termios diff --git a/log/term/terminal_notwindows.go b/log/term/terminal_notwindows.go new file mode 100644 index 000000000..c9af534f6 --- /dev/null +++ b/log/term/terminal_notwindows.go @@ -0,0 +1,20 @@ +// Based on ssh/terminal: +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux,!appengine darwin freebsd openbsd netbsd + +package term + +import ( + "syscall" + "unsafe" +) + +// IsTty returns true if the given file descriptor is a terminal. +func IsTty(fd uintptr) bool { + var termios Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/log/term/terminal_openbsd.go b/log/term/terminal_openbsd.go new file mode 100644 index 000000000..f9bb9e1c2 --- /dev/null +++ b/log/term/terminal_openbsd.go @@ -0,0 +1,7 @@ +package term + +import "syscall" + +const ioctlReadTermios = syscall.TIOCGETA + +type Termios syscall.Termios diff --git a/log/term/terminal_solaris.go b/log/term/terminal_solaris.go new file mode 100644 index 000000000..033c16324 --- /dev/null +++ b/log/term/terminal_solaris.go @@ -0,0 +1,9 @@ +package term + +import "golang.org/x/sys/unix" + +// IsTty returns true if the given file descriptor is a terminal. +func IsTty(fd uintptr) bool { + _, err := unix.IoctlGetTermios(int(fd), unix.TCGETA) + return err == nil +} diff --git a/log/term/terminal_windows.go b/log/term/terminal_windows.go new file mode 100644 index 000000000..df3c30c15 --- /dev/null +++ b/log/term/terminal_windows.go @@ -0,0 +1,26 @@ +// Based on ssh/terminal: +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package term + +import ( + "syscall" + "unsafe" +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") +) + +// IsTty returns true if the given file descriptor is a terminal. +func IsTty(fd uintptr) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} diff --git a/vendor/github.com/go-stack/stack/LICENSE.md b/vendor/github.com/go-stack/stack/LICENSE.md new file mode 100644 index 000000000..c8ca66c5e --- /dev/null +++ b/vendor/github.com/go-stack/stack/LICENSE.md @@ -0,0 +1,13 @@ +Copyright 2014 Chris Hines + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/go-stack/stack/README.md b/vendor/github.com/go-stack/stack/README.md new file mode 100644 index 000000000..f11ccccaa --- /dev/null +++ b/vendor/github.com/go-stack/stack/README.md @@ -0,0 +1,38 @@ +[![GoDoc](https://godoc.org/github.com/go-stack/stack?status.svg)](https://godoc.org/github.com/go-stack/stack) +[![Go Report Card](https://goreportcard.com/badge/go-stack/stack)](https://goreportcard.com/report/go-stack/stack) +[![TravisCI](https://travis-ci.org/go-stack/stack.svg?branch=master)](https://travis-ci.org/go-stack/stack) +[![Coverage Status](https://coveralls.io/repos/github/go-stack/stack/badge.svg?branch=master)](https://coveralls.io/github/go-stack/stack?branch=master) + +# stack + +Package stack implements utilities to capture, manipulate, and format call +stacks. It provides a simpler API than package runtime. + +The implementation takes care of the minutia and special cases of interpreting +the program counter (pc) values returned by runtime.Callers. + +## Versioning + +Package stack publishes releases via [semver](http://semver.org/) compatible Git +tags prefixed with a single 'v'. The master branch always contains the latest +release. The develop branch contains unreleased commits. + +## Formatting + +Package stack's types implement fmt.Formatter, which provides a simple and +flexible way to declaratively configure formatting when used with logging or +error tracking packages. + +```go +func DoTheThing() { + c := stack.Caller(0) + log.Print(c) // "source.go:10" + log.Printf("%+v", c) // "pkg/path/source.go:10" + log.Printf("%n", c) // "DoTheThing" + + s := stack.Trace().TrimRuntime() + log.Print(s) // "[source.go:15 caller.go:42 main.go:14]" +} +``` + +See the docs for all of the supported formatting options. diff --git a/vendor/github.com/go-stack/stack/stack.go b/vendor/github.com/go-stack/stack/stack.go new file mode 100644 index 000000000..a614eeebf --- /dev/null +++ b/vendor/github.com/go-stack/stack/stack.go @@ -0,0 +1,349 @@ +// Package stack implements utilities to capture, manipulate, and format call +// stacks. It provides a simpler API than package runtime. +// +// The implementation takes care of the minutia and special cases of +// interpreting the program counter (pc) values returned by runtime.Callers. +// +// Package stack's types implement fmt.Formatter, which provides a simple and +// flexible way to declaratively configure formatting when used with logging +// or error tracking packages. +package stack + +import ( + "bytes" + "errors" + "fmt" + "io" + "runtime" + "strconv" + "strings" +) + +// Call records a single function invocation from a goroutine stack. +type Call struct { + fn *runtime.Func + pc uintptr +} + +// Caller returns a Call from the stack of the current goroutine. The argument +// skip is the number of stack frames to ascend, with 0 identifying the +// calling function. +func Caller(skip int) Call { + var pcs [2]uintptr + n := runtime.Callers(skip+1, pcs[:]) + + var c Call + + if n < 2 { + return c + } + + c.pc = pcs[1] + if runtime.FuncForPC(pcs[0]) != sigpanic { + c.pc-- + } + c.fn = runtime.FuncForPC(c.pc) + return c +} + +// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c). +func (c Call) String() string { + return fmt.Sprint(c) +} + +// MarshalText implements encoding.TextMarshaler. It formats the Call the same +// as fmt.Sprintf("%v", c). +func (c Call) MarshalText() ([]byte, error) { + if c.fn == nil { + return nil, ErrNoFunc + } + buf := bytes.Buffer{} + fmt.Fprint(&buf, c) + return buf.Bytes(), nil +} + +// ErrNoFunc means that the Call has a nil *runtime.Func. The most likely +// cause is a Call with the zero value. +var ErrNoFunc = errors.New("no call stack information") + +// Format implements fmt.Formatter with support for the following verbs. +// +// %s source file +// %d line number +// %n function name +// %v equivalent to %s:%d +// +// It accepts the '+' and '#' flags for most of the verbs as follows. +// +// %+s path of source file relative to the compile time GOPATH +// %#s full path of source file +// %+n import path qualified function name +// %+v equivalent to %+s:%d +// %#v equivalent to %#s:%d +func (c Call) Format(s fmt.State, verb rune) { + if c.fn == nil { + fmt.Fprintf(s, "%%!%c(NOFUNC)", verb) + return + } + + switch verb { + case 's', 'v': + file, line := c.fn.FileLine(c.pc) + switch { + case s.Flag('#'): + // done + case s.Flag('+'): + file = file[pkgIndex(file, c.fn.Name()):] + default: + const sep = "/" + if i := strings.LastIndex(file, sep); i != -1 { + file = file[i+len(sep):] + } + } + io.WriteString(s, file) + if verb == 'v' { + buf := [7]byte{':'} + s.Write(strconv.AppendInt(buf[:1], int64(line), 10)) + } + + case 'd': + _, line := c.fn.FileLine(c.pc) + buf := [6]byte{} + s.Write(strconv.AppendInt(buf[:0], int64(line), 10)) + + case 'n': + name := c.fn.Name() + if !s.Flag('+') { + const pathSep = "/" + if i := strings.LastIndex(name, pathSep); i != -1 { + name = name[i+len(pathSep):] + } + const pkgSep = "." + if i := strings.Index(name, pkgSep); i != -1 { + name = name[i+len(pkgSep):] + } + } + io.WriteString(s, name) + } +} + +// PC returns the program counter for this call frame; multiple frames may +// have the same PC value. +func (c Call) PC() uintptr { + return c.pc +} + +// name returns the import path qualified name of the function containing the +// call. +func (c Call) name() string { + if c.fn == nil { + return "???" + } + return c.fn.Name() +} + +func (c Call) file() string { + if c.fn == nil { + return "???" + } + file, _ := c.fn.FileLine(c.pc) + return file +} + +func (c Call) line() int { + if c.fn == nil { + return 0 + } + _, line := c.fn.FileLine(c.pc) + return line +} + +// CallStack records a sequence of function invocations from a goroutine +// stack. +type CallStack []Call + +// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs). +func (cs CallStack) String() string { + return fmt.Sprint(cs) +} + +var ( + openBracketBytes = []byte("[") + closeBracketBytes = []byte("]") + spaceBytes = []byte(" ") +) + +// MarshalText implements encoding.TextMarshaler. It formats the CallStack the +// same as fmt.Sprintf("%v", cs). +func (cs CallStack) MarshalText() ([]byte, error) { + buf := bytes.Buffer{} + buf.Write(openBracketBytes) + for i, pc := range cs { + if pc.fn == nil { + return nil, ErrNoFunc + } + if i > 0 { + buf.Write(spaceBytes) + } + fmt.Fprint(&buf, pc) + } + buf.Write(closeBracketBytes) + return buf.Bytes(), nil +} + +// Format implements fmt.Formatter by printing the CallStack as square brackets +// ([, ]) surrounding a space separated list of Calls each formatted with the +// supplied verb and options. +func (cs CallStack) Format(s fmt.State, verb rune) { + s.Write(openBracketBytes) + for i, pc := range cs { + if i > 0 { + s.Write(spaceBytes) + } + pc.Format(s, verb) + } + s.Write(closeBracketBytes) +} + +// findSigpanic intentionally executes faulting code to generate a stack trace +// containing an entry for runtime.sigpanic. +func findSigpanic() *runtime.Func { + var fn *runtime.Func + var p *int + func() int { + defer func() { + if p := recover(); p != nil { + var pcs [512]uintptr + n := runtime.Callers(2, pcs[:]) + for _, pc := range pcs[:n] { + f := runtime.FuncForPC(pc) + if f.Name() == "runtime.sigpanic" { + fn = f + break + } + } + } + }() + // intentional nil pointer dereference to trigger sigpanic + return *p + }() + return fn +} + +var sigpanic = findSigpanic() + +// Trace returns a CallStack for the current goroutine with element 0 +// identifying the calling function. +func Trace() CallStack { + var pcs [512]uintptr + n := runtime.Callers(2, pcs[:]) + cs := make([]Call, n) + + for i, pc := range pcs[:n] { + pcFix := pc + if i > 0 && cs[i-1].fn != sigpanic { + pcFix-- + } + cs[i] = Call{ + fn: runtime.FuncForPC(pcFix), + pc: pcFix, + } + } + + return cs +} + +// TrimBelow returns a slice of the CallStack with all entries below c +// removed. +func (cs CallStack) TrimBelow(c Call) CallStack { + for len(cs) > 0 && cs[0].pc != c.pc { + cs = cs[1:] + } + return cs +} + +// TrimAbove returns a slice of the CallStack with all entries above c +// removed. +func (cs CallStack) TrimAbove(c Call) CallStack { + for len(cs) > 0 && cs[len(cs)-1].pc != c.pc { + cs = cs[:len(cs)-1] + } + return cs +} + +// pkgIndex returns the index that results in file[index:] being the path of +// file relative to the compile time GOPATH, and file[:index] being the +// $GOPATH/src/ portion of file. funcName must be the name of a function in +// file as returned by runtime.Func.Name. +func pkgIndex(file, funcName string) int { + // As of Go 1.6.2 there is no direct way to know the compile time GOPATH + // at runtime, but we can infer the number of path segments in the GOPATH. + // We note that runtime.Func.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // file[:idx] == /home/user/src/ + // file[idx:] == pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired result for file[idx:]. We count separators from the + // end of the file path until it finds two more than in the function name + // and then move one character forward to preserve the initial path + // segment without a leading separator. + const sep = "/" + i := len(file) + for n := strings.Count(funcName, sep) + 2; n > 0; n-- { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + return i + len(sep) +} + +var runtimePath string + +func init() { + var pcs [1]uintptr + runtime.Callers(0, pcs[:]) + fn := runtime.FuncForPC(pcs[0]) + file, _ := fn.FileLine(pcs[0]) + + idx := pkgIndex(file, fn.Name()) + + runtimePath = file[:idx] + if runtime.GOOS == "windows" { + runtimePath = strings.ToLower(runtimePath) + } +} + +func inGoroot(c Call) bool { + file := c.file() + if len(file) == 0 || file[0] == '?' { + return true + } + if runtime.GOOS == "windows" { + file = strings.ToLower(file) + } + return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go") +} + +// TrimRuntime returns a slice of the CallStack with the topmost entries from +// the go runtime removed. It considers any calls originating from unknown +// files, files under GOROOT, or _testmain.go as part of the runtime. +func (cs CallStack) TrimRuntime() CallStack { + for len(cs) > 0 && inGoroot(cs[len(cs)-1]) { + cs = cs[:len(cs)-1] + } + return cs +} diff --git a/vendor/vendor.json b/vendor/vendor.json index e19d011f1..eda007745 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -53,6 +53,12 @@ "revisionTime": "2017-01-17T22:23:42Z" }, { + "checksumSHA1": "2sj/DbXoXdnPAfjAEyhS0Jj5QL0=", + "path": "github.com/go-stack/stack", + "revision": "100eb0c0a9c5b306ca2fb4f165df21d80ada4b82", + "revisionTime": "2016-05-14T03:44:11Z" + }, + { "checksumSHA1": "p/8vSviYF91gFflhrt5vkyksroo=", "path": "github.com/golang/snappy", "revision": "553a641470496b2327abcac10b36396bd98e45c9", |