mirror of https://github.com/ethereum/go-ethereum
commit
357732a840
@ -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 |
@ -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. |
@ -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 |
@ -0,0 +1,5 @@ |
||||
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` |
||||
* Modified behavior to exit on `critical` failure |
@ -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 |
@ -0,0 +1,327 @@ |
||||
package log |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"fmt" |
||||
"reflect" |
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
timeFormat = "2006-01-02T15:04:05-0700" |
||||
termTimeFormat = "01-02|15:04:05" |
||||
floatFormat = 'f' |
||||
termMsgJust = 40 |
||||
) |
||||
|
||||
// locationTrims are trimmed for display to avoid unwieldy log lines.
|
||||
var locationTrims = []string{ |
||||
"github.com/ethereum/go-ethereum/", |
||||
"github.com/ethereum/ethash/", |
||||
} |
||||
|
||||
// PrintOrigins sets or unsets log location (file:line) printing for terminal
|
||||
// format output.
|
||||
func PrintOrigins(print bool) { |
||||
if print { |
||||
atomic.StoreUint32(&locationEnabled, 1) |
||||
} else { |
||||
atomic.StoreUint32(&locationEnabled, 0) |
||||
} |
||||
} |
||||
|
||||
// locationEnabled is an atomic flag controlling whether the terminal formatter
|
||||
// should append the log locations too when printing entries.
|
||||
var locationEnabled uint32 |
||||
|
||||
// locationLength is the maxmimum path length encountered, which all logs are
|
||||
// padded to to aid in alignment.
|
||||
var locationLength uint32 |
||||
|
||||
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 |
||||
case LvlTrace: |
||||
color = 34 |
||||
} |
||||
|
||||
b := &bytes.Buffer{} |
||||
lvl := strings.ToUpper(r.Lvl.String()) |
||||
if atomic.LoadUint32(&locationEnabled) != 0 { |
||||
// Log origin printing was requested, format the location path and line number
|
||||
location := fmt.Sprintf("%+v", r.Call) |
||||
for _, prefix := range locationTrims { |
||||
location = strings.TrimPrefix(location, prefix) |
||||
} |
||||
// Maintain the maximum location length for fancyer alignment
|
||||
align := int(atomic.LoadUint32(&locationLength)) |
||||
if align < len(location) { |
||||
align = len(location) |
||||
atomic.StoreUint32(&locationLength, uint32(align)) |
||||
} |
||||
padding := strings.Repeat(" ", align-len(location)) |
||||
|
||||
// Assemble and print the log heading
|
||||
if color > 0 { |
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg) |
||||
} else { |
||||
fmt.Fprintf(b, "[%s] [%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg) |
||||
} |
||||
} else { |
||||
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 |
||||
} |
@ -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)) |
||||
} |
@ -0,0 +1,227 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package log |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"regexp" |
||||
"runtime" |
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
// errVmoduleSyntax is returned when a user vmodule pattern is invalid.
|
||||
var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N") |
||||
|
||||
// errTraceSyntax is returned when a user backtrace pattern is invalid.
|
||||
var errTraceSyntax = errors.New("expect file.go:234") |
||||
|
||||
// GlogHandler is a log handler that mimics the filtering features of Google's
|
||||
// glog logger: setting global log levels; overriding with callsite pattern
|
||||
// matches; and requesting backtraces at certain positions.
|
||||
type GlogHandler struct { |
||||
origin Handler // The origin handler this wraps
|
||||
|
||||
level uint32 // Current log level, atomically accessible
|
||||
override uint32 // Flag whether overrides are used, atomically accessible
|
||||
backtrace uint32 // Flag whether backtrace location is set
|
||||
|
||||
patterns []pattern // Current list of patterns to override with
|
||||
siteCache map[uintptr]Lvl // Cache of callsite pattern evaluations
|
||||
location string // file:line location where to do a stackdump at
|
||||
lock sync.RWMutex // Lock protecting the override pattern list
|
||||
} |
||||
|
||||
// NewGlogHandler creates a new log handler with filtering functionality similar
|
||||
// to Google's glog logger. The returned handler implements Handler.
|
||||
func NewGlogHandler(h Handler) *GlogHandler { |
||||
return &GlogHandler{ |
||||
origin: h, |
||||
} |
||||
} |
||||
|
||||
// pattern contains a filter for the Vmodule option, holding a verbosity level
|
||||
// and a file pattern to match.
|
||||
type pattern struct { |
||||
pattern *regexp.Regexp |
||||
level Lvl |
||||
} |
||||
|
||||
// Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
|
||||
// and source files can be raised using Vmodule.
|
||||
func (h *GlogHandler) Verbosity(level Lvl) { |
||||
atomic.StoreUint32(&h.level, uint32(level)) |
||||
} |
||||
|
||||
// Vmodule sets the glog verbosity pattern.
|
||||
//
|
||||
// The syntax of the argument is a comma-separated list of pattern=N, where the
|
||||
// pattern is a literal file name or "glob" pattern matching and N is a V level.
|
||||
//
|
||||
// For instance:
|
||||
//
|
||||
// pattern="gopher.go=3"
|
||||
// sets the V level to 3 in all Go files named "gopher.go"
|
||||
//
|
||||
// pattern="foo=3"
|
||||
// sets V to 3 in all files of any packages whose import path ends in "foo"
|
||||
//
|
||||
// pattern="foo/*=3"
|
||||
// sets V to 3 in all files of any packages whose import path contains "foo"
|
||||
func (h *GlogHandler) Vmodule(ruleset string) error { |
||||
var filter []pattern |
||||
for _, rule := range strings.Split(ruleset, ",") { |
||||
// Empty strings such as from a trailing comma can be ignored
|
||||
if len(rule) == 0 { |
||||
continue |
||||
} |
||||
// Ensure we have a pattern = level filter rule
|
||||
parts := strings.Split(rule, "=") |
||||
if len(parts) != 2 { |
||||
return errVmoduleSyntax |
||||
} |
||||
parts[0] = strings.TrimSpace(parts[0]) |
||||
parts[1] = strings.TrimSpace(parts[1]) |
||||
if len(parts[0]) == 0 || len(parts[1]) == 0 { |
||||
return errVmoduleSyntax |
||||
} |
||||
// Parse the level and if correct, assemble the filter rule
|
||||
level, err := strconv.Atoi(parts[1]) |
||||
if err != nil { |
||||
return errVmoduleSyntax |
||||
} |
||||
if level <= 0 { |
||||
continue // Ignore. It's harmless but no point in paying the overhead.
|
||||
} |
||||
// Compile the rule pattern into a regular expression
|
||||
matcher := ".*" |
||||
for _, comp := range strings.Split(parts[0], "/") { |
||||
if comp == "*" { |
||||
matcher += "(/.*)?" |
||||
} else if comp != "" { |
||||
matcher += "/" + regexp.QuoteMeta(comp) |
||||
} |
||||
} |
||||
if !strings.HasSuffix(parts[0], ".go") { |
||||
matcher += "/[^/]+\\.go" |
||||
} |
||||
matcher = matcher + "$" |
||||
|
||||
re, _ := regexp.Compile(matcher) |
||||
filter = append(filter, pattern{re, Lvl(level)}) |
||||
} |
||||
// Swap out the vmodule pattern for the new filter system
|
||||
h.lock.Lock() |
||||
defer h.lock.Unlock() |
||||
|
||||
h.patterns = filter |
||||
h.siteCache = make(map[uintptr]Lvl) |
||||
atomic.StoreUint32(&h.override, uint32(len(filter))) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// BacktraceAt sets the glog backtrace location. When set to a file and line
|
||||
// number holding a logging statement, a stack trace will be written to the Info
|
||||
// log whenever execution hits that statement.
|
||||
//
|
||||
// Unlike with Vmodule, the ".go" must be present.
|
||||
func (h *GlogHandler) BacktraceAt(location string) error { |
||||
// Ensure the backtrace location contains two non-empty elements
|
||||
parts := strings.Split(location, ":") |
||||
if len(parts) != 2 { |
||||
return errTraceSyntax |
||||
} |
||||
parts[0] = strings.TrimSpace(parts[0]) |
||||
parts[1] = strings.TrimSpace(parts[1]) |
||||
if len(parts[0]) == 0 || len(parts[1]) == 0 { |
||||
return errTraceSyntax |
||||
} |
||||
// Ensure the .go prefix is present and the line is valid
|
||||
if !strings.HasSuffix(parts[0], ".go") { |
||||
return errTraceSyntax |
||||
} |
||||
if _, err := strconv.Atoi(parts[1]); err != nil { |
||||
return errTraceSyntax |
||||
} |
||||
// All seems valid
|
||||
h.lock.Lock() |
||||
defer h.lock.Unlock() |
||||
|
||||
h.location = location |
||||
atomic.StoreUint32(&h.backtrace, uint32(len(location))) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Log implements Handler.Log, filtering a log record through the global, local
|
||||
// and backtrace filters, finally emitting it if either allow it through.
|
||||
func (h *GlogHandler) Log(r *Record) error { |
||||
// If backtracing is requested, check whether this is the callsite
|
||||
if atomic.LoadUint32(&h.backtrace) > 0 { |
||||
// Everything below here is slow. Although we could cache the call sites the
|
||||
// same way as for vmodule, backtracing is so rare it's not worth the extra
|
||||
// complexity.
|
||||
h.lock.RLock() |
||||
match := h.location == r.Call.String() |
||||
h.lock.RUnlock() |
||||
|
||||
if match { |
||||
// Callsite matched, raise the log level to info and gather the stacks
|
||||
r.Lvl = LvlInfo |
||||
|
||||
buf := make([]byte, 1024*1024) |
||||
buf = buf[:runtime.Stack(buf, true)] |
||||
r.Msg += "\n\n" + string(buf) |
||||
} |
||||
} |
||||
// If the global log level allows, fast track logging
|
||||
if atomic.LoadUint32(&h.level) >= uint32(r.Lvl) { |
||||
return h.origin.Log(r) |
||||
} |
||||
// If no local overrides are present, fast track skipping
|
||||
if atomic.LoadUint32(&h.override) == 0 { |
||||
return nil |
||||
} |
||||
// Check callsite cache for previously calculated log levels
|
||||
h.lock.RLock() |
||||
lvl, ok := h.siteCache[r.Call.PC()] |
||||
h.lock.RUnlock() |
||||
|
||||
// If we didn't cache the callsite yet, calculate it
|
||||
if !ok { |
||||
h.lock.Lock() |
||||
for _, rule := range h.patterns { |
||||
if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) { |
||||
h.siteCache[r.Call.PC()], lvl, ok = rule.level, rule.level, true |
||||
break |
||||
} |
||||
} |
||||
// If no rule matched, remember to drop log the next time
|
||||
if !ok { |
||||
h.siteCache[r.Call.PC()] = 0 |
||||
} |
||||
h.lock.Unlock() |
||||
} |
||||
if lvl >= r.Lvl { |
||||
return h.origin.Log(r) |
||||
} |
||||
return nil |
||||
} |
@ -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)) |
||||
} |
@ -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) |
||||
} |
@ -0,0 +1,220 @@ |
||||
package log |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"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 |
||||
LvlTrace |
||||
) |
||||
|
||||
// Returns the name of a Lvl
|
||||
func (l Lvl) String() string { |
||||
switch l { |
||||
case LvlTrace: |
||||
return "trce" |
||||
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 "trace", "trce": |
||||
return LvlTrace, nil |
||||
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
|
||||
Trace(msg string, ctx ...interface{}) |
||||
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) Trace(msg string, ctx ...interface{}) { |
||||
l.write(msg, LvlTrace, ctx) |
||||
} |
||||
|
||||
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) |
||||
os.Exit(1) |
||||
} |
||||
|
||||
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 |
||||
} |
@ -0,0 +1,73 @@ |
||||
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(LvlFilterHandler(LvlInfo, 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.
|
||||
|
||||
// Trace is a convenient alias for Root().Trace
|
||||
func Trace(msg string, ctx ...interface{}) { |
||||
root.write(msg, LvlTrace, ctx) |
||||
} |
||||
|
||||
// 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) |
||||
os.Exit(1) |
||||
} |
@ -0,0 +1,57 @@ |
||||
// +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 |
||||
case LvlTrace: |
||||
syslogFn = func(m string) error { return nil } // There's no syslog level for trace
|
||||
} |
||||
|
||||
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)) |
||||
} |
@ -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. |
@ -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 |
||||
} |
@ -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 |
@ -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 |
||||
} |
@ -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 |
@ -0,0 +1,7 @@ |
||||
package term |
||||
|
||||
import "syscall" |
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA |
||||
|
||||
type Termios syscall.Termios |
@ -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 |
||||
} |
@ -0,0 +1,7 @@ |
||||
package term |
||||
|
||||
import "syscall" |
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA |
||||
|
||||
type Termios syscall.Termios |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -1,191 +0,0 @@ |
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and |
||||
distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright |
||||
owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities |
||||
that control, are controlled by, or are under common control with that entity. |
||||
For the purposes of this definition, "control" means (i) the power, direct or |
||||
indirect, to cause the direction or management of such entity, whether by |
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising |
||||
permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including |
||||
but not limited to software source code, documentation source, and configuration |
||||
files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or |
||||
translation of a Source form, including but not limited to compiled object code, |
||||
generated documentation, and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made |
||||
available under the License, as indicated by a copyright notice that is included |
||||
in or attached to the work (an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that |
||||
is based on (or derived from) the Work and for which the editorial revisions, |
||||
annotations, elaborations, or other modifications represent, as a whole, an |
||||
original work of authorship. For the purposes of this License, Derivative Works |
||||
shall not include works that remain separable from, or merely link (or bind by |
||||
name) to the interfaces of, the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version |
||||
of the Work and any modifications or additions to that Work or Derivative Works |
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work |
||||
by the copyright owner or by an individual or Legal Entity authorized to submit |
||||
on behalf of the copyright owner. For the purposes of this definition, |
||||
"submitted" means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, and |
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for |
||||
the purpose of discussing and improving the Work, but excluding communication |
||||
that is conspicuously marked or otherwise designated in writing by the copyright |
||||
owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf |
||||
of whom a Contribution has been received by Licensor and subsequently |
||||
incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. |
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby |
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, |
||||
irrevocable copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the Work and such |
||||
Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. |
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby |
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, |
||||
irrevocable (except as stated in this section) patent license to make, have |
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where |
||||
such license applies only to those patent claims licensable by such Contributor |
||||
that are necessarily infringed by their Contribution(s) alone or by combination |
||||
of their Contribution(s) with the Work to which such Contribution(s) was |
||||
submitted. If You institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a |
||||
Contribution incorporated within the Work constitutes direct or contributory |
||||
patent infringement, then any patent licenses granted to You under this License |
||||
for that Work shall terminate as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. |
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof |
||||
in any medium, with or without modifications, and in Source or Object form, |
||||
provided that You meet the following conditions: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
You must retain, in the Source form of any Derivative Works that You distribute, |
||||
all copyright, patent, trademark, and attribution notices from the Source form |
||||
of the Work, excluding those notices that do not pertain to any part of the |
||||
Derivative Works; and |
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any |
||||
Derivative Works that You distribute must include a readable copy of the |
||||
attribution notices contained within such NOTICE file, excluding those notices |
||||
that do not pertain to any part of the Derivative Works, in at least one of the |
||||
following places: within a NOTICE text file distributed as part of the |
||||
Derivative Works; within the Source form or documentation, if provided along |
||||
with the Derivative Works; or, within a display generated by the Derivative |
||||
Works, if and wherever such third-party notices normally appear. The contents of |
||||
the NOTICE file are for informational purposes only and do not modify the |
||||
License. You may add Your own attribution notices within Derivative Works that |
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work, |
||||
provided that such additional attribution notices cannot be construed as |
||||
modifying the License. |
||||
You may add Your own copyright statement to Your modifications and may provide |
||||
additional or different license terms and conditions for use, reproduction, or |
||||
distribution of Your modifications, or for any such Derivative Works as a whole, |
||||
provided Your use, reproduction, and distribution of the Work otherwise complies |
||||
with the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. |
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted |
||||
for inclusion in the Work by You to the Licensor shall be under the terms and |
||||
conditions of this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of |
||||
any separate license agreement you may have executed with Licensor regarding |
||||
such Contributions. |
||||
|
||||
6. Trademarks. |
||||
|
||||
This License does not grant permission to use the trade names, trademarks, |
||||
service marks, or product names of the Licensor, except as required for |
||||
reasonable and customary use in describing the origin of the Work and |
||||
reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. |
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the |
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, |
||||
including, without limitation, any warranties or conditions of TITLE, |
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are |
||||
solely responsible for determining the appropriateness of using or |
||||
redistributing the Work and assume any risks associated with Your exercise of |
||||
permissions under this License. |
||||
|
||||
8. Limitation of Liability. |
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence), |
||||
contract, or otherwise, unless required by applicable law (such as deliberate |
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, incidental, |
||||
or consequential damages of any character arising as a result of this License or |
||||
out of the use or inability to use the Work (including but not limited to |
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or |
||||
any and all other commercial damages or losses), even if such Contributor has |
||||
been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. |
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to |
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or |
||||
other liability obligations and/or rights consistent with this License. However, |
||||
in accepting such obligations, You may act only on Your own behalf and on Your |
||||
sole responsibility, not on behalf of any other Contributor, and only if You |
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason of your |
||||
accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -1,44 +0,0 @@ |
||||
glog |
||||
==== |
||||
|
||||
Leveled execution logs for Go. |
||||
|
||||
This is an efficient pure Go implementation of leveled logs in the |
||||
manner of the open source C++ package |
||||
http://code.google.com/p/google-glog |
||||
|
||||
By binding methods to booleans it is possible to use the log package |
||||
without paying the expense of evaluating the arguments to the log. |
||||
Through the -vmodule flag, the package also provides fine-grained |
||||
control over logging at the file level. |
||||
|
||||
The comment from glog.go introduces the ideas: |
||||
|
||||
Package glog implements logging analogous to the Google-internal |
||||
C++ INFO/ERROR/V setup. It provides functions Info, Warning, |
||||
Error, Fatal, plus formatting variants such as Infof. It |
||||
also provides V-style logging controlled by the -v and |
||||
-vmodule=file=2 flags. |
||||
|
||||
Basic examples: |
||||
|
||||
glog.Info("Prepare to repel boarders") |
||||
|
||||
glog.Fatalf("Initialization failed: %s", err) |
||||
|
||||
See the documentation for the V function for an explanation |
||||
of these examples: |
||||
|
||||
if glog.V(2) { |
||||
glog.Info("Starting transaction...") |
||||
} |
||||
|
||||
glog.V(2).Infoln("Processed", nItems, "elements") |
||||
|
||||
|
||||
The repository contains an open source version of the log package |
||||
used inside Google. The master copy of the source lives inside |
||||
Google, not here. The code in this repo is for export only and is not itself |
||||
under development. Feature requests will be ignored. |
||||
|
||||
Send bug reports to golang-nuts@googlegroups.com. |
File diff suppressed because it is too large
Load Diff
@ -1,128 +0,0 @@ |
||||
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// File I/O for logs.
|
||||
|
||||
package glog |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"os" |
||||
"os/user" |
||||
"path/filepath" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// MaxSize is the maximum size of a log file in bytes.
|
||||
var MaxSize uint64 = 1024 * 1024 * 1800 |
||||
|
||||
// logDirs lists the candidate directories for new log files.
|
||||
var logDirs []string |
||||
|
||||
// If non-empty, overrides the choice of directory in which to write logs.
|
||||
// See createLogDirs for the full list of possible destinations.
|
||||
//var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory")
|
||||
var logDir *string = new(string) |
||||
|
||||
func SetLogDir(str string) { |
||||
*logDir = str |
||||
} |
||||
|
||||
func createLogDirs() { |
||||
if *logDir != "" { |
||||
logDirs = append(logDirs, *logDir) |
||||
} |
||||
logDirs = append(logDirs, os.TempDir()) |
||||
} |
||||
|
||||
var ( |
||||
pid = os.Getpid() |
||||
program = filepath.Base(os.Args[0]) |
||||
host = "unknownhost" |
||||
userName = "unknownuser" |
||||
) |
||||
|
||||
func init() { |
||||
h, err := os.Hostname() |
||||
if err == nil { |
||||
host = shortHostname(h) |
||||
} |
||||
|
||||
current, err := user.Current() |
||||
if err == nil { |
||||
userName = current.Username |
||||
} |
||||
|
||||
// Sanitize userName since it may contain filepath separators on Windows.
|
||||
userName = strings.Replace(userName, `\`, "_", -1) |
||||
} |
||||
|
||||
// shortHostname returns its argument, truncating at the first period.
|
||||
// For instance, given "www.google.com" it returns "www".
|
||||
func shortHostname(hostname string) string { |
||||
if i := strings.Index(hostname, "."); i >= 0 { |
||||
return hostname[:i] |
||||
} |
||||
return hostname |
||||
} |
||||
|
||||
// logName returns a new log file name containing tag, with start time t, and
|
||||
// the name for the symlink for tag.
|
||||
func logName(tag string, t time.Time) (name, link string) { |
||||
name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d", |
||||
program, |
||||
host, |
||||
userName, |
||||
tag, |
||||
t.Year(), |
||||
t.Month(), |
||||
t.Day(), |
||||
t.Hour(), |
||||
t.Minute(), |
||||
t.Second(), |
||||
pid) |
||||
return name, program + "." + tag |
||||
} |
||||
|
||||
var onceLogDirs sync.Once |
||||
|
||||
// create creates a new log file and returns the file and its filename, which
|
||||
// contains tag ("INFO", "FATAL", etc.) and t. If the file is created
|
||||
// successfully, create also attempts to update the symlink for that tag, ignoring
|
||||
// errors.
|
||||
func create(tag string, t time.Time) (f *os.File, filename string, err error) { |
||||
onceLogDirs.Do(createLogDirs) |
||||
if len(logDirs) == 0 { |
||||
return nil, "", errors.New("log: no log dirs") |
||||
} |
||||
name, link := logName(tag, t) |
||||
var lastErr error |
||||
for _, dir := range logDirs { |
||||
fname := filepath.Join(dir, name) |
||||
f, err := os.Create(fname) |
||||
if err == nil { |
||||
symlink := filepath.Join(dir, link) |
||||
os.Remove(symlink) // ignore err
|
||||
os.Symlink(name, symlink) // ignore err
|
||||
return f, fname, nil |
||||
} |
||||
lastErr = err |
||||
} |
||||
return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr) |
||||
} |
@ -1,436 +0,0 @@ |
||||
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package glog |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
stdLog "log" |
||||
"path/filepath" |
||||
"runtime" |
||||
"strconv" |
||||
"strings" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
// Test that shortHostname works as advertised.
|
||||
func TestShortHostname(t *testing.T) { |
||||
for hostname, expect := range map[string]string{ |
||||
"": "", |
||||
"host": "host", |
||||
"host.google.com": "host", |
||||
} { |
||||
if got := shortHostname(hostname); expect != got { |
||||
t.Errorf("shortHostname(%q): expected %q, got %q", hostname, expect, got) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// flushBuffer wraps a bytes.Buffer to satisfy flushSyncWriter.
|
||||
type flushBuffer struct { |
||||
bytes.Buffer |
||||
} |
||||
|
||||
func (f *flushBuffer) Flush() error { |
||||
return nil |
||||
} |
||||
|
||||
func (f *flushBuffer) Sync() error { |
||||
return nil |
||||
} |
||||
|
||||
// swap sets the log writers and returns the old array.
|
||||
func (l *loggingT) swap(writers [numSeverity]flushSyncWriter) (old [numSeverity]flushSyncWriter) { |
||||
l.mu.Lock() |
||||
defer l.mu.Unlock() |
||||
old = l.file |
||||
for i, w := range writers { |
||||
logging.file[i] = w |
||||
} |
||||
return |
||||
} |
||||
|
||||
// newBuffers sets the log writers to all new byte buffers and returns the old array.
|
||||
func (l *loggingT) newBuffers() [numSeverity]flushSyncWriter { |
||||
return l.swap([numSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)}) |
||||
} |
||||
|
||||
// contents returns the specified log value as a string.
|
||||
func contents(s severity) string { |
||||
return logging.file[s].(*flushBuffer).String() |
||||
} |
||||
|
||||
// contains reports whether the string is contained in the log.
|
||||
func contains(s severity, str string, t *testing.T) bool { |
||||
return strings.Contains(contents(s), str) |
||||
} |
||||
|
||||
// setFlags configures the logging flags how the test expects them.
|
||||
func setFlags() { |
||||
logging.toStderr = false |
||||
} |
||||
|
||||
// Test that Info works as advertised.
|
||||
func TestInfo(t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
Info("test") |
||||
if !contains(infoLog, "I", t) { |
||||
t.Errorf("Info has wrong character: %q", contents(infoLog)) |
||||
} |
||||
if !contains(infoLog, "test", t) { |
||||
t.Error("Info failed") |
||||
} |
||||
} |
||||
|
||||
func TestInfoDepth(t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
|
||||
f := func() { InfoDepth(1, "depth-test1") } |
||||
|
||||
// The next three lines must stay together
|
||||
_, _, wantLine, _ := runtime.Caller(0) |
||||
InfoDepth(0, "depth-test0") |
||||
f() |
||||
|
||||
msgs := strings.Split(strings.TrimSuffix(contents(infoLog), "\n"), "\n") |
||||
if len(msgs) != 2 { |
||||
t.Fatalf("Got %d lines, expected 2", len(msgs)) |
||||
} |
||||
|
||||
for i, m := range msgs { |
||||
if !strings.HasPrefix(m, "I") { |
||||
t.Errorf("InfoDepth[%d] has wrong character: %q", i, m) |
||||
} |
||||
w := fmt.Sprintf("depth-test%d", i) |
||||
if !strings.Contains(m, w) { |
||||
t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m) |
||||
} |
||||
|
||||
// pull out the line number (between : and ])
|
||||
msg := m[strings.LastIndex(m, ":")+1:] |
||||
x := strings.Index(msg, "]") |
||||
if x < 0 { |
||||
t.Errorf("InfoDepth[%d]: missing ']': %q", i, m) |
||||
continue |
||||
} |
||||
line, err := strconv.Atoi(msg[:x]) |
||||
if err != nil { |
||||
t.Errorf("InfoDepth[%d]: bad line number: %q", i, m) |
||||
continue |
||||
} |
||||
wantLine++ |
||||
if wantLine != line { |
||||
t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func init() { |
||||
CopyStandardLogTo("INFO") |
||||
} |
||||
|
||||
// Test that CopyStandardLogTo panics on bad input.
|
||||
func TestCopyStandardLogToPanic(t *testing.T) { |
||||
defer func() { |
||||
if s, ok := recover().(string); !ok || !strings.Contains(s, "LOG") { |
||||
t.Errorf(`CopyStandardLogTo("LOG") should have panicked: %v`, s) |
||||
} |
||||
}() |
||||
CopyStandardLogTo("LOG") |
||||
} |
||||
|
||||
// Test that using the standard log package logs to INFO.
|
||||
func TestStandardLog(t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
stdLog.Print("test") |
||||
if !contains(infoLog, "I", t) { |
||||
t.Errorf("Info has wrong character: %q", contents(infoLog)) |
||||
} |
||||
if !contains(infoLog, "test", t) { |
||||
t.Error("Info failed") |
||||
} |
||||
} |
||||
|
||||
// Test that the header has the correct format.
|
||||
func TestHeader(t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
defer func(previous func() time.Time) { timeNow = previous }(timeNow) |
||||
timeNow = func() time.Time { |
||||
return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local) |
||||
} |
||||
pid = 1234 |
||||
Info("test") |
||||
var line int |
||||
format := "I0102 15:04:05.067890 logger/glog/glog_test.go:%d] test\n" |
||||
n, err := fmt.Sscanf(contents(infoLog), format, &line) |
||||
if n != 1 || err != nil { |
||||
t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(infoLog)) |
||||
} |
||||
// Scanf treats multiple spaces as equivalent to a single space,
|
||||
// so check for correct space-padding also.
|
||||
want := fmt.Sprintf(format, line) |
||||
if contents(infoLog) != want { |
||||
t.Errorf("log format error: got:\n\t%q\nwant:\t%q", contents(infoLog), want) |
||||
} |
||||
} |
||||
|
||||
// Test that an Error log goes to Warning and Info.
|
||||
// Even in the Info log, the source character will be E, so the data should
|
||||
// all be identical.
|
||||
func TestError(t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
Error("test") |
||||
if !contains(errorLog, "E", t) { |
||||
t.Errorf("Error has wrong character: %q", contents(errorLog)) |
||||
} |
||||
if !contains(errorLog, "test", t) { |
||||
t.Error("Error failed") |
||||
} |
||||
str := contents(errorLog) |
||||
if !contains(warningLog, str, t) { |
||||
t.Error("Warning failed") |
||||
} |
||||
if !contains(infoLog, str, t) { |
||||
t.Error("Info failed") |
||||
} |
||||
} |
||||
|
||||
// Test that a Warning log goes to Info.
|
||||
// Even in the Info log, the source character will be W, so the data should
|
||||
// all be identical.
|
||||
func TestWarning(t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
Warning("test") |
||||
if !contains(warningLog, "W", t) { |
||||
t.Errorf("Warning has wrong character: %q", contents(warningLog)) |
||||
} |
||||
if !contains(warningLog, "test", t) { |
||||
t.Error("Warning failed") |
||||
} |
||||
str := contents(warningLog) |
||||
if !contains(infoLog, str, t) { |
||||
t.Error("Info failed") |
||||
} |
||||
} |
||||
|
||||
// Test that a V log goes to Info.
|
||||
func TestV(t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
logging.verbosity.Set("2") |
||||
defer logging.verbosity.Set("0") |
||||
V(2).Info("test") |
||||
if !contains(infoLog, "I", t) { |
||||
t.Errorf("Info has wrong character: %q", contents(infoLog)) |
||||
} |
||||
if !contains(infoLog, "test", t) { |
||||
t.Error("Info failed") |
||||
} |
||||
} |
||||
|
||||
// Test that a vmodule enables a log in this file.
|
||||
func TestVmoduleOn(t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
logging.vmodule.Set("glog_test.go=2") |
||||
defer logging.vmodule.Set("") |
||||
if !V(1) { |
||||
t.Error("V not enabled for 1") |
||||
} |
||||
if !V(2) { |
||||
t.Error("V not enabled for 2") |
||||
} |
||||
if V(3) { |
||||
t.Error("V enabled for 3") |
||||
} |
||||
V(2).Info("test") |
||||
if !contains(infoLog, "I", t) { |
||||
t.Errorf("Info has wrong character: %q", contents(infoLog)) |
||||
} |
||||
if !contains(infoLog, "test", t) { |
||||
t.Error("Info failed") |
||||
} |
||||
} |
||||
|
||||
// Test that a vmodule of another file does not enable a log in this file.
|
||||
func TestVmoduleOff(t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
logging.vmodule.Set("notthisfile=2") |
||||
defer logging.vmodule.Set("") |
||||
for i := 1; i <= 3; i++ { |
||||
if V(Level(i)) { |
||||
t.Errorf("V enabled for %d", i) |
||||
} |
||||
} |
||||
V(2).Info("test") |
||||
if contents(infoLog) != "" { |
||||
t.Error("V logged incorrectly") |
||||
} |
||||
} |
||||
|
||||
var patternTests = []struct{ input, want string }{ |
||||
{"foo/bar/x.go", ".*/foo/bar/x\\.go$"}, |
||||
{"foo/*/x.go", ".*/foo(/.*)?/x\\.go$"}, |
||||
{"foo/*", ".*/foo(/.*)?/[^/]+\\.go$"}, |
||||
} |
||||
|
||||
func TestCompileModulePattern(t *testing.T) { |
||||
for _, test := range patternTests { |
||||
re, err := compileModulePattern(test.input) |
||||
if err != nil { |
||||
t.Fatalf("%s: %v", test.input, err) |
||||
} |
||||
if re.String() != test.want { |
||||
t.Errorf("mismatch for %q: got %q, want %q", test.input, re.String(), test.want) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// vGlobs are patterns that match/don't match this file at V=2.
|
||||
var vGlobs = map[string]bool{ |
||||
// Easy to test the numeric match here.
|
||||
"glog_test.go=1": false, // If -vmodule sets V to 1, V(2) will fail.
|
||||
"glog_test.go=2": true, |
||||
"glog_test.go=3": true, // If -vmodule sets V to 1, V(3) will succeed.
|
||||
|
||||
// Import path prefix matching
|
||||
"logger/glog=1": false, |
||||
"logger/glog=2": true, |
||||
"logger/glog=3": true, |
||||
|
||||
// Import path glob matching
|
||||
"logger/*=1": false, |
||||
"logger/*=2": true, |
||||
"logger/*=3": true, |
||||
|
||||
// These all use 2 and check the patterns.
|
||||
"*=2": true, |
||||
} |
||||
|
||||
// Test that vmodule globbing works as advertised.
|
||||
func testVmoduleGlob(pat string, match bool, t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
defer logging.vmodule.Set("") |
||||
logging.vmodule.Set(pat) |
||||
if V(2) != Verbose(match) { |
||||
t.Errorf("incorrect match for %q: got %t expected %t", pat, V(2), match) |
||||
} |
||||
} |
||||
|
||||
// Test that a vmodule globbing works as advertised.
|
||||
func TestVmoduleGlob(t *testing.T) { |
||||
for glob, match := range vGlobs { |
||||
testVmoduleGlob(glob, match, t) |
||||
} |
||||
} |
||||
|
||||
func TestRollover(t *testing.T) { |
||||
setFlags() |
||||
var err error |
||||
defer func(previous func(error)) { logExitFunc = previous }(logExitFunc) |
||||
logExitFunc = func(e error) { |
||||
err = e |
||||
} |
||||
defer func(previous uint64) { MaxSize = previous }(MaxSize) |
||||
MaxSize = 512 |
||||
|
||||
Info("x") // Be sure we have a file.
|
||||
info, ok := logging.file[infoLog].(*syncBuffer) |
||||
if !ok { |
||||
t.Fatal("info wasn't created") |
||||
} |
||||
if err != nil { |
||||
t.Fatalf("info has initial error: %v", err) |
||||
} |
||||
fname0 := info.file.Name() |
||||
Info(strings.Repeat("x", int(MaxSize))) // force a rollover
|
||||
if err != nil { |
||||
t.Fatalf("info has error after big write: %v", err) |
||||
} |
||||
|
||||
// Make sure the next log file gets a file name with a different
|
||||
// time stamp.
|
||||
//
|
||||
// TODO: determine whether we need to support subsecond log
|
||||
// rotation. C++ does not appear to handle this case (nor does it
|
||||
// handle Daylight Savings Time properly).
|
||||
time.Sleep(1 * time.Second) |
||||
|
||||
Info("x") // create a new file
|
||||
if err != nil { |
||||
t.Fatalf("error after rotation: %v", err) |
||||
} |
||||
fname1 := info.file.Name() |
||||
if fname0 == fname1 { |
||||
t.Errorf("info.f.Name did not change: %v", fname0) |
||||
} |
||||
if info.nbytes >= MaxSize { |
||||
t.Errorf("file size was not reset: %d", info.nbytes) |
||||
} |
||||
} |
||||
|
||||
func TestLogBacktraceAt(t *testing.T) { |
||||
setFlags() |
||||
defer logging.swap(logging.newBuffers()) |
||||
// The peculiar style of this code simplifies line counting and maintenance of the
|
||||
// tracing block below.
|
||||
var infoLine string |
||||
setTraceLocation := func(file string, line int, ok bool, delta int) { |
||||
if !ok { |
||||
t.Fatal("could not get file:line") |
||||
} |
||||
_, file = filepath.Split(file) |
||||
infoLine = fmt.Sprintf("%s:%d", file, line+delta) |
||||
err := logging.traceLocation.Set(infoLine) |
||||
if err != nil { |
||||
t.Fatal("error setting log_backtrace_at: ", err) |
||||
} |
||||
} |
||||
{ |
||||
// Start of tracing block. These lines know about each other's relative position.
|
||||
_, file, line, ok := runtime.Caller(0) |
||||
setTraceLocation(file, line, ok, +2) // Two lines between Caller and Info calls.
|
||||
Info("we want a stack trace here") |
||||
} |
||||
numAppearances := strings.Count(contents(infoLog), infoLine) |
||||
if numAppearances < 2 { |
||||
// Need 2 appearances, one in the log header and one in the trace:
|
||||
// log_test.go:281: I0511 16:36:06.952398 02238 log_test.go:280] we want a stack trace here
|
||||
// ...
|
||||
// github.com/glog/glog_test.go:280 (0x41ba91)
|
||||
// ...
|
||||
// We could be more precise but that would require knowing the details
|
||||
// of the traceback format, which may not be dependable.
|
||||
t.Fatal("got no trace back; log is ", contents(infoLog)) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkHeader(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
buf, _, _ := logging.header(infoLog, 0) |
||||
logging.putBuffer(buf) |
||||
} |
||||
} |
@ -1,27 +0,0 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package logger |
||||
|
||||
const ( |
||||
Error = iota + 1 |
||||
Warn |
||||
Info |
||||
Debug |
||||
Detail |
||||
|
||||
Ridiculousness = 100 |
||||
) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue