mirror of https://github.com/docker/docs.git
Refactor logging to focus on simple STDOUT/STDERR
This also lays the foundation for the possibility of log drivers in the future, if it is decided that is a direction to pursue. Signed-off-by: Nathan LeClaire <nathan.leclaire@gmail.com>
This commit is contained in:
parent
03c245cf4f
commit
142ffadc2c
|
@ -12,11 +12,6 @@
|
||||||
"Comment": "v1.1-17-g515f3ec",
|
"Comment": "v1.1-17-g515f3ec",
|
||||||
"Rev": "515f3ec74ce6a5b31e934cefae997c97bd0a1b1e"
|
"Rev": "515f3ec74ce6a5b31e934cefae997c97bd0a1b1e"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/Sirupsen/logrus",
|
|
||||||
"Comment": "v0.6.1",
|
|
||||||
"Rev": "1f2ba2c6317323dd667bd266c1e8ebffc4a4c62f"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cenkalti/backoff",
|
"ImportPath": "github.com/cenkalti/backoff",
|
||||||
"Rev": "9831e1e25c874e0a0601b6dc43641071414eec7a"
|
"Rev": "9831e1e25c874e0a0601b6dc43641071414eec7a"
|
||||||
|
@ -31,11 +26,6 @@
|
||||||
"Comment": "v0.5.0",
|
"Comment": "v0.5.0",
|
||||||
"Rev": "5478aae80694de1d2d0e02c386bbedd201266234"
|
"Rev": "5478aae80694de1d2d0e02c386bbedd201266234"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/api",
|
|
||||||
"Comment": "v1.5.0",
|
|
||||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/dockerversion",
|
"ImportPath": "github.com/docker/docker/dockerversion",
|
||||||
"Comment": "v1.5.0",
|
"Comment": "v1.5.0",
|
||||||
|
@ -106,11 +96,6 @@
|
||||||
"Comment": "v1.5.0",
|
"Comment": "v1.5.0",
|
||||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/utils",
|
|
||||||
"Comment": "v1.5.0",
|
|
||||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar",
|
"ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar",
|
||||||
"Comment": "v1.5.0",
|
"Comment": "v1.5.0",
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
logrus
|
|
|
@ -1,9 +0,0 @@
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.2
|
|
||||||
- 1.3
|
|
||||||
- tip
|
|
||||||
install:
|
|
||||||
- go get github.com/stretchr/testify
|
|
||||||
- go get github.com/stvp/go-udp-testing
|
|
||||||
- go get github.com/tobi/airbrake-go
|
|
|
@ -1,21 +0,0 @@
|
||||||
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.
|
|
|
@ -1,349 +0,0 @@
|
||||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/Sirupsen/logrus)
|
|
||||||
|
|
||||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
|
||||||
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
|
||||||
yet stable (pre 1.0), the core API is unlikely change much but please version
|
|
||||||
control your Logrus to make sure you aren't fetching latest `master` on every
|
|
||||||
build.**
|
|
||||||
|
|
||||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
|
||||||
plain text):
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
|
|
||||||
or Splunk:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
|
||||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
|
||||||
|
|
||||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
|
||||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
|
||||||
|
|
||||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
|
||||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
|
||||||
|
|
||||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
|
||||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
|
||||||
|
|
||||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
|
||||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
|
||||||
```
|
|
||||||
|
|
||||||
With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not
|
|
||||||
attached, the output is compatible with the
|
|
||||||
[l2met](http://r.32k.io/l2met-introduction) format:
|
|
||||||
|
|
||||||
```text
|
|
||||||
time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10
|
|
||||||
time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122
|
|
||||||
time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10
|
|
||||||
time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9
|
|
||||||
time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
The simplest way to use Logrus is simply the package-level exported logger:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
}).Info("A walrus appears")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
|
||||||
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
|
|
||||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
|
||||||
want:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/Sirupsen/logrus/hooks/airbrake"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Log as JSON instead of the default ASCII formatter.
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
|
|
||||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
|
||||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
|
||||||
log.AddHook(&logrus_airbrake.AirbrakeHook{})
|
|
||||||
|
|
||||||
// Output to stderr instead of stdout, could also be a file.
|
|
||||||
log.SetOutput(os.Stderr)
|
|
||||||
|
|
||||||
// Only log the warning severity or above.
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 122,
|
|
||||||
}).Warn("The group's number increased tremendously!")
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 100,
|
|
||||||
}).Fatal("The ice breaks!")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For more advanced usage such as logging to multiple locations from the same
|
|
||||||
application, you can also create an instance of the `logrus` Logger:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create a new instance of the logger. You can have any number of instances.
|
|
||||||
var log = logrus.New()
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// The API for setting attributes is a little different than the package level
|
|
||||||
// exported logger. See Godoc.
|
|
||||||
log.Out = os.Stderr
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fields
|
|
||||||
|
|
||||||
Logrus encourages careful, structured logging though logging fields instead of
|
|
||||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
|
||||||
to send event %s to topic %s with key %d")`, you should log the much more
|
|
||||||
discoverable:
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"event": event,
|
|
||||||
"topic": topic,
|
|
||||||
"key": key,
|
|
||||||
}).Fatal("Failed to send event")
|
|
||||||
```
|
|
||||||
|
|
||||||
We've found this API forces you to think about logging in a way that produces
|
|
||||||
much more useful logging messages. We've been in countless situations where just
|
|
||||||
a single added field to a log statement that was already there would've saved us
|
|
||||||
hours. The `WithFields` call is optional.
|
|
||||||
|
|
||||||
In general, with Logrus using any of the `printf`-family functions should be
|
|
||||||
seen as a hint you should add a field, however, you can still use the
|
|
||||||
`printf`-family functions with Logrus.
|
|
||||||
|
|
||||||
#### Hooks
|
|
||||||
|
|
||||||
You can add hooks for logging levels. For example to send errors to an exception
|
|
||||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
|
||||||
multiple places simultaneously, e.g. syslog.
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Not the real implementation of the Airbrake hook. Just a simple sample.
|
|
||||||
import (
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.AddHook(new(AirbrakeHook))
|
|
||||||
}
|
|
||||||
|
|
||||||
type AirbrakeHook struct{}
|
|
||||||
|
|
||||||
// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains
|
|
||||||
// the fields for the entry. See the Fields section of the README.
|
|
||||||
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
|
||||||
err := airbrake.Notify(entry.Data["error"].(error))
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"source": "airbrake",
|
|
||||||
"endpoint": airbrake.Endpoint,
|
|
||||||
}).Info("Failed to send error to Airbrake")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// `Levels()` returns a slice of `Levels` the hook is fired for.
|
|
||||||
func (hook *AirbrakeHook) Levels() []log.Level {
|
|
||||||
return []log.Level{
|
|
||||||
log.ErrorLevel,
|
|
||||||
log.FatalLevel,
|
|
||||||
log.PanicLevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Logrus comes with built-in hooks. Add those, or your custom hook, in `init`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/Sirupsen/logrus/hooks/airbrake"
|
|
||||||
"github.com/Sirupsen/logrus/hooks/syslog"
|
|
||||||
"log/syslog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.AddHook(new(logrus_airbrake.AirbrakeHook))
|
|
||||||
|
|
||||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to connect to local syslog daemon")
|
|
||||||
} else {
|
|
||||||
log.AddHook(hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
|
|
||||||
Send errors to an exception tracking service compatible with the Airbrake API.
|
|
||||||
Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
|
|
||||||
|
|
||||||
* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
|
|
||||||
Send errors to the Papertrail hosted logging service via UDP.
|
|
||||||
|
|
||||||
* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
|
|
||||||
Send errors to remote syslog server.
|
|
||||||
Uses standard library `log/syslog` behind the scenes.
|
|
||||||
|
|
||||||
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
|
|
||||||
Send errors to a channel in hipchat.
|
|
||||||
|
|
||||||
#### Level logging
|
|
||||||
|
|
||||||
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.Debug("Useful debugging information.")
|
|
||||||
log.Info("Something noteworthy happened!")
|
|
||||||
log.Warn("You should probably take a look at this.")
|
|
||||||
log.Error("Something failed but I'm not quitting.")
|
|
||||||
// Calls os.Exit(1) after logging
|
|
||||||
log.Fatal("Bye.")
|
|
||||||
// Calls panic() after logging
|
|
||||||
log.Panic("I'm bailing.")
|
|
||||||
```
|
|
||||||
|
|
||||||
You can set the logging level on a `Logger`, then it will only log entries with
|
|
||||||
that severity or anything above it:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
```
|
|
||||||
|
|
||||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
|
||||||
environment if your application has that.
|
|
||||||
|
|
||||||
#### Entries
|
|
||||||
|
|
||||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
|
||||||
automatically added to all logging events:
|
|
||||||
|
|
||||||
1. `time`. The timestamp when the entry was created.
|
|
||||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
|
||||||
the `AddFields` call. E.g. `Failed to send event.`
|
|
||||||
3. `level`. The logging level. E.g. `info`.
|
|
||||||
|
|
||||||
#### Environments
|
|
||||||
|
|
||||||
Logrus has no notion of environment.
|
|
||||||
|
|
||||||
If you wish for hooks and formatters to only be used in specific environments,
|
|
||||||
you should handle that yourself. For example, if your application has a global
|
|
||||||
variable `Environment`, which is a string representation of the environment you
|
|
||||||
could do:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// do something here to set environment depending on an environment variable
|
|
||||||
// or command-line flag
|
|
||||||
if Environment == "production" {
|
|
||||||
log.SetFormatter(logrus.JSONFormatter)
|
|
||||||
} else {
|
|
||||||
// The TextFormatter is default, you don't actually have to do this.
|
|
||||||
log.SetFormatter(logrus.TextFormatter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This configuration is how `logrus` was intended to be used, but JSON in
|
|
||||||
production is mostly only useful if you do log aggregation with tools like
|
|
||||||
Splunk or Logstash.
|
|
||||||
|
|
||||||
#### Formatters
|
|
||||||
|
|
||||||
The built-in logging formatters are:
|
|
||||||
|
|
||||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
|
||||||
without colors.
|
|
||||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
|
||||||
field to `true`. To force no colored output even if there is a TTY set the
|
|
||||||
`DisableColors` field to `true`
|
|
||||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
|
||||||
|
|
||||||
Third party logging formatters:
|
|
||||||
|
|
||||||
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
|
||||||
|
|
||||||
You can define your formatter by implementing the `Formatter` interface,
|
|
||||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
|
||||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
|
||||||
default ones (see Entries section above):
|
|
||||||
|
|
||||||
```go
|
|
||||||
type MyJSONFormatter struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetFormatter(new(MyJSONFormatter))
|
|
||||||
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
// Note this doesn't include Time, Level and Message which are available on
|
|
||||||
// the Entry. Consult `godoc` on information about those fields or read the
|
|
||||||
// source of the official loggers.
|
|
||||||
serialized, err := json.Marshal(entry.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
|
||||||
}
|
|
||||||
return append(serialized, '\n'), nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Rotation
|
|
||||||
|
|
||||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
|
||||||
external program (like `logrotated(8)`) that can compress and delete old log
|
|
||||||
entries. It should not be a feature of the application-level logger.
|
|
||||||
|
|
||||||
|
|
||||||
[godoc]: https://godoc.org/github.com/Sirupsen/logrus
|
|
|
@ -1,248 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
|
||||||
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
|
||||||
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
|
||||||
// passed around as much as you wish to avoid field duplication.
|
|
||||||
type Entry struct {
|
|
||||||
Logger *Logger
|
|
||||||
|
|
||||||
// Contains all the fields set by the user.
|
|
||||||
Data Fields
|
|
||||||
|
|
||||||
// Time at which the log entry was created
|
|
||||||
Time time.Time
|
|
||||||
|
|
||||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
|
||||||
Level Level
|
|
||||||
|
|
||||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEntry(logger *Logger) *Entry {
|
|
||||||
return &Entry{
|
|
||||||
Logger: logger,
|
|
||||||
// Default is three fields, give a little extra room
|
|
||||||
Data: make(Fields, 5),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a reader for the entry, which is a proxy to the formatter.
|
|
||||||
func (entry *Entry) Reader() (*bytes.Buffer, error) {
|
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
|
||||||
return bytes.NewBuffer(serialized), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the string representation from the reader and ultimately the
|
|
||||||
// formatter.
|
|
||||||
func (entry *Entry) String() (string, error) {
|
|
||||||
reader, err := entry.Reader()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return reader.String(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a single field to the Entry.
|
|
||||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
|
||||||
return entry.WithFields(Fields{key: value})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a map of fields to the Entry.
|
|
||||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
|
||||||
data := Fields{}
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range fields {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
return &Entry{Logger: entry.Logger, Data: data}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) log(level Level, msg string) {
|
|
||||||
entry.Time = time.Now()
|
|
||||||
entry.Level = level
|
|
||||||
entry.Message = msg
|
|
||||||
|
|
||||||
if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
|
||||||
entry.Logger.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
reader, err := entry.Reader()
|
|
||||||
if err != nil {
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
|
||||||
entry.Logger.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
defer entry.Logger.mu.Unlock()
|
|
||||||
|
|
||||||
_, err = io.Copy(entry.Logger.Out, reader)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// To avoid Entry#log() returning a value that only would make sense for
|
|
||||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
|
||||||
// directly here.
|
|
||||||
if level <= PanicLevel {
|
|
||||||
panic(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Debug(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= DebugLevel {
|
|
||||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Print(args ...interface{}) {
|
|
||||||
entry.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Info(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= InfoLevel {
|
|
||||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warn(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= WarnLevel {
|
|
||||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Error(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= ErrorLevel {
|
|
||||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatal(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= FatalLevel {
|
|
||||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panic(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= PanicLevel {
|
|
||||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
panic(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry Printf family functions
|
|
||||||
|
|
||||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= DebugLevel {
|
|
||||||
entry.Debug(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= InfoLevel {
|
|
||||||
entry.Info(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
|
||||||
entry.Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= WarnLevel {
|
|
||||||
entry.Warn(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
|
||||||
entry.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= ErrorLevel {
|
|
||||||
entry.Error(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= FatalLevel {
|
|
||||||
entry.Fatal(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= PanicLevel {
|
|
||||||
entry.Panic(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry Println family functions
|
|
||||||
|
|
||||||
func (entry *Entry) Debugln(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= DebugLevel {
|
|
||||||
entry.Debug(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Infoln(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= InfoLevel {
|
|
||||||
entry.Info(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Println(args ...interface{}) {
|
|
||||||
entry.Infoln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warnln(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= WarnLevel {
|
|
||||||
entry.Warn(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warningln(args ...interface{}) {
|
|
||||||
entry.Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Errorln(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= ErrorLevel {
|
|
||||||
entry.Error(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= FatalLevel {
|
|
||||||
entry.Fatal(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panicln(args ...interface{}) {
|
|
||||||
if entry.Logger.Level >= PanicLevel {
|
|
||||||
entry.Panic(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
|
||||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
|
||||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
|
||||||
// string allocation, we do the simplest thing.
|
|
||||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
|
||||||
msg := fmt.Sprintln(args...)
|
|
||||||
return msg[:len(msg)-1]
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEntryPanicln(t *testing.T) {
|
|
||||||
errBoom := fmt.Errorf("boom time")
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
p := recover()
|
|
||||||
assert.NotNil(t, p)
|
|
||||||
|
|
||||||
switch pVal := p.(type) {
|
|
||||||
case *Entry:
|
|
||||||
assert.Equal(t, "kaboom", pVal.Message)
|
|
||||||
assert.Equal(t, errBoom, pVal.Data["err"])
|
|
||||||
default:
|
|
||||||
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
logger := New()
|
|
||||||
logger.Out = &bytes.Buffer{}
|
|
||||||
entry := NewEntry(logger)
|
|
||||||
entry.WithField("err", errBoom).Panicln("kaboom")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEntryPanicf(t *testing.T) {
|
|
||||||
errBoom := fmt.Errorf("boom again")
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
p := recover()
|
|
||||||
assert.NotNil(t, p)
|
|
||||||
|
|
||||||
switch pVal := p.(type) {
|
|
||||||
case *Entry:
|
|
||||||
assert.Equal(t, "kaboom true", pVal.Message)
|
|
||||||
assert.Equal(t, errBoom, pVal.Data["err"])
|
|
||||||
default:
|
|
||||||
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
logger := New()
|
|
||||||
logger.Out = &bytes.Buffer{}
|
|
||||||
entry := NewEntry(logger)
|
|
||||||
entry.WithField("err", errBoom).Panicf("kaboom %v", true)
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logrus.New()
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.Formatter = new(logrus.JSONFormatter)
|
|
||||||
log.Formatter = new(logrus.TextFormatter) // default
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
defer func() {
|
|
||||||
err := recover()
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"err": err,
|
|
||||||
"number": 100,
|
|
||||||
}).Fatal("The ice breaks!")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 122,
|
|
||||||
}).Warn("The group's number increased tremendously!")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "orca",
|
|
||||||
"size": 9009,
|
|
||||||
}).Panic("It's over 9000!")
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/Sirupsen/logrus/hooks/airbrake"
|
|
||||||
"github.com/tobi/airbrake-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logrus.New()
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.Formatter = new(logrus.TextFormatter) // default
|
|
||||||
log.Hooks.Add(new(logrus_airbrake.AirbrakeHook))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml"
|
|
||||||
airbrake.ApiKey = "whatever"
|
|
||||||
airbrake.Environment = "production"
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 122,
|
|
||||||
}).Warn("The group's number increased tremendously!")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 100,
|
|
||||||
}).Fatal("The ice breaks!")
|
|
||||||
}
|
|
|
@ -1,182 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// std is the name of the standard logger in stdlib `log`
|
|
||||||
std = New()
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetOutput sets the standard logger output.
|
|
||||||
func SetOutput(out io.Writer) {
|
|
||||||
std.mu.Lock()
|
|
||||||
defer std.mu.Unlock()
|
|
||||||
std.Out = out
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFormatter sets the standard logger formatter.
|
|
||||||
func SetFormatter(formatter Formatter) {
|
|
||||||
std.mu.Lock()
|
|
||||||
defer std.mu.Unlock()
|
|
||||||
std.Formatter = formatter
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLevel sets the standard logger level.
|
|
||||||
func SetLevel(level Level) {
|
|
||||||
std.mu.Lock()
|
|
||||||
defer std.mu.Unlock()
|
|
||||||
std.Level = level
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLevel returns the standard logger level.
|
|
||||||
func GetLevel() Level {
|
|
||||||
return std.Level
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHook adds a hook to the standard logger hooks.
|
|
||||||
func AddHook(hook Hook) {
|
|
||||||
std.mu.Lock()
|
|
||||||
defer std.mu.Unlock()
|
|
||||||
std.Hooks.Add(hook)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithField creates an entry from the standard logger and adds a field to
|
|
||||||
// it. If you want multiple fields, use `WithFields`.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithField(key string, value interface{}) *Entry {
|
|
||||||
return std.WithField(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFields creates an entry from the standard logger and adds multiple
|
|
||||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
|
||||||
// once for each field.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithFields(fields Fields) *Entry {
|
|
||||||
return std.WithFields(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug logs a message at level Debug on the standard logger.
|
|
||||||
func Debug(args ...interface{}) {
|
|
||||||
std.Debug(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print logs a message at level Info on the standard logger.
|
|
||||||
func Print(args ...interface{}) {
|
|
||||||
std.Print(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info logs a message at level Info on the standard logger.
|
|
||||||
func Info(args ...interface{}) {
|
|
||||||
std.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn logs a message at level Warn on the standard logger.
|
|
||||||
func Warn(args ...interface{}) {
|
|
||||||
std.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning logs a message at level Warn on the standard logger.
|
|
||||||
func Warning(args ...interface{}) {
|
|
||||||
std.Warning(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error logs a message at level Error on the standard logger.
|
|
||||||
func Error(args ...interface{}) {
|
|
||||||
std.Error(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panic logs a message at level Panic on the standard logger.
|
|
||||||
func Panic(args ...interface{}) {
|
|
||||||
std.Panic(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal logs a message at level Fatal on the standard logger.
|
|
||||||
func Fatal(args ...interface{}) {
|
|
||||||
std.Fatal(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf logs a message at level Debug on the standard logger.
|
|
||||||
func Debugf(format string, args ...interface{}) {
|
|
||||||
std.Debugf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf logs a message at level Info on the standard logger.
|
|
||||||
func Printf(format string, args ...interface{}) {
|
|
||||||
std.Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infof logs a message at level Info on the standard logger.
|
|
||||||
func Infof(format string, args ...interface{}) {
|
|
||||||
std.Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warnf logs a message at level Warn on the standard logger.
|
|
||||||
func Warnf(format string, args ...interface{}) {
|
|
||||||
std.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningf logs a message at level Warn on the standard logger.
|
|
||||||
func Warningf(format string, args ...interface{}) {
|
|
||||||
std.Warningf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf logs a message at level Error on the standard logger.
|
|
||||||
func Errorf(format string, args ...interface{}) {
|
|
||||||
std.Errorf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panicf logs a message at level Panic on the standard logger.
|
|
||||||
func Panicf(format string, args ...interface{}) {
|
|
||||||
std.Panicf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalf logs a message at level Fatal on the standard logger.
|
|
||||||
func Fatalf(format string, args ...interface{}) {
|
|
||||||
std.Fatalf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugln logs a message at level Debug on the standard logger.
|
|
||||||
func Debugln(args ...interface{}) {
|
|
||||||
std.Debugln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println logs a message at level Info on the standard logger.
|
|
||||||
func Println(args ...interface{}) {
|
|
||||||
std.Println(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infoln logs a message at level Info on the standard logger.
|
|
||||||
func Infoln(args ...interface{}) {
|
|
||||||
std.Infoln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warnln logs a message at level Warn on the standard logger.
|
|
||||||
func Warnln(args ...interface{}) {
|
|
||||||
std.Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningln logs a message at level Warn on the standard logger.
|
|
||||||
func Warningln(args ...interface{}) {
|
|
||||||
std.Warningln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorln logs a message at level Error on the standard logger.
|
|
||||||
func Errorln(args ...interface{}) {
|
|
||||||
std.Errorln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panicln logs a message at level Panic on the standard logger.
|
|
||||||
func Panicln(args ...interface{}) {
|
|
||||||
std.Panicln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalln logs a message at level Fatal on the standard logger.
|
|
||||||
func Fatalln(args ...interface{}) {
|
|
||||||
std.Fatalln(args...)
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
|
||||||
// `Entry`. It exposes all the fields, including the default ones:
|
|
||||||
//
|
|
||||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
|
||||||
// * `entry.Data["time"]`. The timestamp.
|
|
||||||
// * `entry.Data["level"]. The level the entry was logged at.
|
|
||||||
//
|
|
||||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
|
||||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
|
||||||
// logged to `logger.Out`.
|
|
||||||
type Formatter interface {
|
|
||||||
Format(*Entry) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
|
||||||
// dumping it. If this code wasn't there doing:
|
|
||||||
//
|
|
||||||
// logrus.WithField("level", 1).Info("hello")
|
|
||||||
//
|
|
||||||
// Would just silently drop the user provided level. Instead with this code
|
|
||||||
// it'll logged as:
|
|
||||||
//
|
|
||||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
|
||||||
//
|
|
||||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
|
||||||
// avoid code duplication between the two default formatters.
|
|
||||||
func prefixFieldClashes(entry *Entry) {
|
|
||||||
_, ok := entry.Data["time"]
|
|
||||||
if ok {
|
|
||||||
entry.Data["fields.time"] = entry.Data["time"]
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok = entry.Data["msg"]
|
|
||||||
if ok {
|
|
||||||
entry.Data["fields.msg"] = entry.Data["msg"]
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok = entry.Data["level"]
|
|
||||||
if ok {
|
|
||||||
entry.Data["fields.level"] = entry.Data["level"]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// smallFields is a small size data set for benchmarking
|
|
||||||
var smallFields = Fields{
|
|
||||||
"foo": "bar",
|
|
||||||
"baz": "qux",
|
|
||||||
"one": "two",
|
|
||||||
"three": "four",
|
|
||||||
}
|
|
||||||
|
|
||||||
// largeFields is a large size data set for benchmarking
|
|
||||||
var largeFields = Fields{
|
|
||||||
"foo": "bar",
|
|
||||||
"baz": "qux",
|
|
||||||
"one": "two",
|
|
||||||
"three": "four",
|
|
||||||
"five": "six",
|
|
||||||
"seven": "eight",
|
|
||||||
"nine": "ten",
|
|
||||||
"eleven": "twelve",
|
|
||||||
"thirteen": "fourteen",
|
|
||||||
"fifteen": "sixteen",
|
|
||||||
"seventeen": "eighteen",
|
|
||||||
"nineteen": "twenty",
|
|
||||||
"a": "b",
|
|
||||||
"c": "d",
|
|
||||||
"e": "f",
|
|
||||||
"g": "h",
|
|
||||||
"i": "j",
|
|
||||||
"k": "l",
|
|
||||||
"m": "n",
|
|
||||||
"o": "p",
|
|
||||||
"q": "r",
|
|
||||||
"s": "t",
|
|
||||||
"u": "v",
|
|
||||||
"w": "x",
|
|
||||||
"y": "z",
|
|
||||||
"this": "will",
|
|
||||||
"make": "thirty",
|
|
||||||
"entries": "yeah",
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSmallTextFormatter(b *testing.B) {
|
|
||||||
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLargeTextFormatter(b *testing.B) {
|
|
||||||
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
|
|
||||||
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
|
|
||||||
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSmallJSONFormatter(b *testing.B) {
|
|
||||||
doBenchmark(b, &JSONFormatter{}, smallFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLargeJSONFormatter(b *testing.B) {
|
|
||||||
doBenchmark(b, &JSONFormatter{}, largeFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
|
|
||||||
entry := &Entry{
|
|
||||||
Time: time.Time{},
|
|
||||||
Level: InfoLevel,
|
|
||||||
Message: "message",
|
|
||||||
Data: fields,
|
|
||||||
}
|
|
||||||
var d []byte
|
|
||||||
var err error
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
d, err = formatter.Format(entry)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
b.SetBytes(int64(len(d)))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestHook struct {
|
|
||||||
Fired bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hook *TestHook) Fire(entry *Entry) error {
|
|
||||||
hook.Fired = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hook *TestHook) Levels() []Level {
|
|
||||||
return []Level{
|
|
||||||
DebugLevel,
|
|
||||||
InfoLevel,
|
|
||||||
WarnLevel,
|
|
||||||
ErrorLevel,
|
|
||||||
FatalLevel,
|
|
||||||
PanicLevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHookFires(t *testing.T) {
|
|
||||||
hook := new(TestHook)
|
|
||||||
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Hooks.Add(hook)
|
|
||||||
assert.Equal(t, hook.Fired, false)
|
|
||||||
|
|
||||||
log.Print("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, hook.Fired, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModifyHook struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hook *ModifyHook) Fire(entry *Entry) error {
|
|
||||||
entry.Data["wow"] = "whale"
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hook *ModifyHook) Levels() []Level {
|
|
||||||
return []Level{
|
|
||||||
DebugLevel,
|
|
||||||
InfoLevel,
|
|
||||||
WarnLevel,
|
|
||||||
ErrorLevel,
|
|
||||||
FatalLevel,
|
|
||||||
PanicLevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHookCanModifyEntry(t *testing.T) {
|
|
||||||
hook := new(ModifyHook)
|
|
||||||
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Hooks.Add(hook)
|
|
||||||
log.WithField("wow", "elephant").Print("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["wow"], "whale")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCanFireMultipleHooks(t *testing.T) {
|
|
||||||
hook1 := new(ModifyHook)
|
|
||||||
hook2 := new(TestHook)
|
|
||||||
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Hooks.Add(hook1)
|
|
||||||
log.Hooks.Add(hook2)
|
|
||||||
|
|
||||||
log.WithField("wow", "elephant").Print("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["wow"], "whale")
|
|
||||||
assert.Equal(t, hook2.Fired, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrorHook struct {
|
|
||||||
Fired bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hook *ErrorHook) Fire(entry *Entry) error {
|
|
||||||
hook.Fired = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hook *ErrorHook) Levels() []Level {
|
|
||||||
return []Level{
|
|
||||||
ErrorLevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
|
|
||||||
hook := new(ErrorHook)
|
|
||||||
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Hooks.Add(hook)
|
|
||||||
log.Info("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, hook.Fired, false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorHookShouldFireOnError(t *testing.T) {
|
|
||||||
hook := new(ErrorHook)
|
|
||||||
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Hooks.Add(hook)
|
|
||||||
log.Error("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, hook.Fired, true)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
// A hook to be fired when logging on the logging levels returned from
|
|
||||||
// `Levels()` on your implementation of the interface. Note that this is not
|
|
||||||
// fired in a goroutine or a channel with workers, you should handle such
|
|
||||||
// functionality yourself if your call is non-blocking and you don't wish for
|
|
||||||
// the logging calls for levels returned from `Levels()` to block.
|
|
||||||
type Hook interface {
|
|
||||||
Levels() []Level
|
|
||||||
Fire(*Entry) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal type for storing the hooks on a logger instance.
|
|
||||||
type levelHooks map[Level][]Hook
|
|
||||||
|
|
||||||
// Add a hook to an instance of logger. This is called with
|
|
||||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
|
||||||
func (hooks levelHooks) Add(hook Hook) {
|
|
||||||
for _, level := range hook.Levels() {
|
|
||||||
hooks[level] = append(hooks[level], hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
|
||||||
// appropriate hooks for a log entry.
|
|
||||||
func (hooks levelHooks) Fire(level Level, entry *Entry) error {
|
|
||||||
for _, hook := range hooks[level] {
|
|
||||||
if err := hook.Fire(entry); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package logrus_airbrake
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/tobi/airbrake-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AirbrakeHook to send exceptions to an exception-tracking service compatible
|
|
||||||
// with the Airbrake API. You must set:
|
|
||||||
// * airbrake.Endpoint
|
|
||||||
// * airbrake.ApiKey
|
|
||||||
// * airbrake.Environment (only sends exceptions when set to "production")
|
|
||||||
//
|
|
||||||
// Before using this hook, to send an error. Entries that trigger an Error,
|
|
||||||
// Fatal or Panic should now include an "error" field to send to Airbrake.
|
|
||||||
type AirbrakeHook struct{}
|
|
||||||
|
|
||||||
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
|
||||||
if entry.Data["error"] == nil {
|
|
||||||
entry.Logger.WithFields(logrus.Fields{
|
|
||||||
"source": "airbrake",
|
|
||||||
"endpoint": airbrake.Endpoint,
|
|
||||||
}).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err, ok := entry.Data["error"].(error)
|
|
||||||
if !ok {
|
|
||||||
entry.Logger.WithFields(logrus.Fields{
|
|
||||||
"source": "airbrake",
|
|
||||||
"endpoint": airbrake.Endpoint,
|
|
||||||
}).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
airErr := airbrake.Notify(err)
|
|
||||||
if airErr != nil {
|
|
||||||
entry.Logger.WithFields(logrus.Fields{
|
|
||||||
"source": "airbrake",
|
|
||||||
"endpoint": airbrake.Endpoint,
|
|
||||||
"error": airErr,
|
|
||||||
}).Warn("Failed to send error to Airbrake")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hook *AirbrakeHook) Levels() []logrus.Level {
|
|
||||||
return []logrus.Level{
|
|
||||||
logrus.ErrorLevel,
|
|
||||||
logrus.FatalLevel,
|
|
||||||
logrus.PanicLevel,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
# Papertrail Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
|
|
||||||
|
|
||||||
[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts).
|
|
||||||
|
|
||||||
In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`.
|
|
||||||
|
|
||||||
For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs.
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"log/syslog"
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/Sirupsen/logrus/hooks/papertrail"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log := logrus.New()
|
|
||||||
hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
log.Hooks.Add(hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go
generated
vendored
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go
generated
vendored
|
@ -1,54 +0,0 @@
|
||||||
package logrus_papertrail
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
format = "Jan 2 15:04:05"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PapertrailHook to send logs to a logging service compatible with the Papertrail API.
|
|
||||||
type PapertrailHook struct {
|
|
||||||
Host string
|
|
||||||
Port int
|
|
||||||
AppName string
|
|
||||||
UDPConn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPapertrailHook creates a hook to be added to an instance of logger.
|
|
||||||
func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) {
|
|
||||||
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port))
|
|
||||||
return &PapertrailHook{host, port, appName, conn}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire is called when a log event is fired.
|
|
||||||
func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
|
|
||||||
date := time.Now().Format(format)
|
|
||||||
payload := fmt.Sprintf("<22> %s %s: [%s] %s", date, hook.AppName, entry.Data["level"], entry.Message)
|
|
||||||
|
|
||||||
bytesWritten, err := hook.UDPConn.Write([]byte(payload))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Levels returns the available logging levels.
|
|
||||||
func (hook *PapertrailHook) Levels() []logrus.Level {
|
|
||||||
return []logrus.Level{
|
|
||||||
logrus.PanicLevel,
|
|
||||||
logrus.FatalLevel,
|
|
||||||
logrus.ErrorLevel,
|
|
||||||
logrus.WarnLevel,
|
|
||||||
logrus.InfoLevel,
|
|
||||||
logrus.DebugLevel,
|
|
||||||
}
|
|
||||||
}
|
|
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go
generated
vendored
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go
generated
vendored
|
@ -1,26 +0,0 @@
|
||||||
package logrus_papertrail
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/stvp/go-udp-testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWritingToUDP(t *testing.T) {
|
|
||||||
port := 16661
|
|
||||||
udp.SetAddr(fmt.Sprintf(":%d", port))
|
|
||||||
|
|
||||||
hook, err := NewPapertrailHook("localhost", port, "test")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unable to connect to local UDP server.")
|
|
||||||
}
|
|
||||||
|
|
||||||
log := logrus.New()
|
|
||||||
log.Hooks.Add(hook)
|
|
||||||
|
|
||||||
udp.ShouldReceive(t, "foo", func() {
|
|
||||||
log.Info("foo")
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"log/syslog"
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/Sirupsen/logrus/hooks/syslog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log := logrus.New()
|
|
||||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
log.Hooks.Add(hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,59 +0,0 @@
|
||||||
package logrus_syslog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"log/syslog"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SyslogHook to send logs via syslog.
|
|
||||||
type SyslogHook struct {
|
|
||||||
Writer *syslog.Writer
|
|
||||||
SyslogNetwork string
|
|
||||||
SyslogRaddr string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a hook to be added to an instance of logger. This is called with
|
|
||||||
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
|
|
||||||
// `if err == nil { log.Hooks.Add(hook) }`
|
|
||||||
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
|
|
||||||
w, err := syslog.Dial(network, raddr, priority, tag)
|
|
||||||
return &SyslogHook{w, network, raddr}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
|
|
||||||
line, err := entry.String()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch entry.Level {
|
|
||||||
case logrus.PanicLevel:
|
|
||||||
return hook.Writer.Crit(line)
|
|
||||||
case logrus.FatalLevel:
|
|
||||||
return hook.Writer.Crit(line)
|
|
||||||
case logrus.ErrorLevel:
|
|
||||||
return hook.Writer.Err(line)
|
|
||||||
case logrus.WarnLevel:
|
|
||||||
return hook.Writer.Warning(line)
|
|
||||||
case logrus.InfoLevel:
|
|
||||||
return hook.Writer.Info(line)
|
|
||||||
case logrus.DebugLevel:
|
|
||||||
return hook.Writer.Debug(line)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hook *SyslogHook) Levels() []logrus.Level {
|
|
||||||
return []logrus.Level{
|
|
||||||
logrus.PanicLevel,
|
|
||||||
logrus.FatalLevel,
|
|
||||||
logrus.ErrorLevel,
|
|
||||||
logrus.WarnLevel,
|
|
||||||
logrus.InfoLevel,
|
|
||||||
logrus.DebugLevel,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package logrus_syslog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"log/syslog"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLocalhostAddAndPrint(t *testing.T) {
|
|
||||||
log := logrus.New()
|
|
||||||
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unable to connect to local syslog.")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Hooks.Add(hook)
|
|
||||||
|
|
||||||
for _, level := range hook.Levels() {
|
|
||||||
if len(log.Hooks[level]) != 1 {
|
|
||||||
t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Congratulations!")
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JSONFormatter struct{}
|
|
||||||
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
prefixFieldClashes(entry)
|
|
||||||
entry.Data["time"] = entry.Time.Format(time.RFC3339)
|
|
||||||
entry.Data["msg"] = entry.Message
|
|
||||||
entry.Data["level"] = entry.Level.String()
|
|
||||||
|
|
||||||
serialized, err := json.Marshal(entry.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
|
||||||
}
|
|
||||||
return append(serialized, '\n'), nil
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
|
||||||
// file, or leave it default which is `os.Stdout`. You can also set this to
|
|
||||||
// something more adventorous, such as logging to Kafka.
|
|
||||||
Out io.Writer
|
|
||||||
// Hooks for the logger instance. These allow firing events based on logging
|
|
||||||
// levels and log entries. For example, to send errors to an error tracking
|
|
||||||
// service, log to StatsD or dump the core on fatal errors.
|
|
||||||
Hooks levelHooks
|
|
||||||
// All log entries pass through the formatter before logged to Out. The
|
|
||||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
|
||||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
|
||||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
|
||||||
// own that implements the `Formatter` interface, see the `README` or included
|
|
||||||
// formatters for examples.
|
|
||||||
Formatter Formatter
|
|
||||||
// The logging level the logger should log at. This is typically (and defaults
|
|
||||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
|
||||||
// logged. `logrus.Debug` is useful in
|
|
||||||
Level Level
|
|
||||||
// Used to sync writing to the log.
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
|
||||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
|
||||||
// instantiate your own:
|
|
||||||
//
|
|
||||||
// var log = &Logger{
|
|
||||||
// Out: os.Stderr,
|
|
||||||
// Formatter: new(JSONFormatter),
|
|
||||||
// Hooks: make(levelHooks),
|
|
||||||
// Level: logrus.Debug,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// It's recommended to make this a global instance called `log`.
|
|
||||||
func New() *Logger {
|
|
||||||
return &Logger{
|
|
||||||
Out: os.Stdout,
|
|
||||||
Formatter: new(TextFormatter),
|
|
||||||
Hooks: make(levelHooks),
|
|
||||||
Level: InfoLevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a field to the log entry, note that you it doesn't log until you call
|
|
||||||
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
|
|
||||||
// Ff you want multiple fields, use `WithFields`.
|
|
||||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
|
||||||
return NewEntry(logger).WithField(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
|
||||||
// each `Field`.
|
|
||||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
|
||||||
return NewEntry(logger).WithFields(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
|
||||||
NewEntry(logger).Debugf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
|
||||||
NewEntry(logger).Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
|
||||||
NewEntry(logger).Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
|
||||||
NewEntry(logger).Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
|
||||||
NewEntry(logger).Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
|
||||||
NewEntry(logger).Errorf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
|
||||||
NewEntry(logger).Fatalf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
|
||||||
NewEntry(logger).Panicf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debug(args ...interface{}) {
|
|
||||||
NewEntry(logger).Debug(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Info(args ...interface{}) {
|
|
||||||
NewEntry(logger).Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Print(args ...interface{}) {
|
|
||||||
NewEntry(logger).Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warn(args ...interface{}) {
|
|
||||||
NewEntry(logger).Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warning(args ...interface{}) {
|
|
||||||
NewEntry(logger).Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Error(args ...interface{}) {
|
|
||||||
NewEntry(logger).Error(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatal(args ...interface{}) {
|
|
||||||
NewEntry(logger).Fatal(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panic(args ...interface{}) {
|
|
||||||
NewEntry(logger).Panic(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugln(args ...interface{}) {
|
|
||||||
NewEntry(logger).Debugln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infoln(args ...interface{}) {
|
|
||||||
NewEntry(logger).Infoln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Println(args ...interface{}) {
|
|
||||||
NewEntry(logger).Println(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnln(args ...interface{}) {
|
|
||||||
NewEntry(logger).Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningln(args ...interface{}) {
|
|
||||||
NewEntry(logger).Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorln(args ...interface{}) {
|
|
||||||
NewEntry(logger).Errorln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
|
||||||
NewEntry(logger).Fatalln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicln(args ...interface{}) {
|
|
||||||
NewEntry(logger).Panicln(args...)
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fields type, used to pass to `WithFields`.
|
|
||||||
type Fields map[string]interface{}
|
|
||||||
|
|
||||||
// Level type
|
|
||||||
type Level uint8
|
|
||||||
|
|
||||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
|
||||||
func (level Level) String() string {
|
|
||||||
switch level {
|
|
||||||
case DebugLevel:
|
|
||||||
return "debug"
|
|
||||||
case InfoLevel:
|
|
||||||
return "info"
|
|
||||||
case WarnLevel:
|
|
||||||
return "warning"
|
|
||||||
case ErrorLevel:
|
|
||||||
return "error"
|
|
||||||
case FatalLevel:
|
|
||||||
return "fatal"
|
|
||||||
case PanicLevel:
|
|
||||||
return "panic"
|
|
||||||
}
|
|
||||||
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
|
||||||
func ParseLevel(lvl string) (Level, error) {
|
|
||||||
switch lvl {
|
|
||||||
case "panic":
|
|
||||||
return PanicLevel, nil
|
|
||||||
case "fatal":
|
|
||||||
return FatalLevel, nil
|
|
||||||
case "error":
|
|
||||||
return ErrorLevel, nil
|
|
||||||
case "warn", "warning":
|
|
||||||
return WarnLevel, nil
|
|
||||||
case "info":
|
|
||||||
return InfoLevel, nil
|
|
||||||
case "debug":
|
|
||||||
return DebugLevel, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var l Level
|
|
||||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the different logging levels. You can set the logging level to log
|
|
||||||
// on your instance of logger, obtained with `logrus.New()`.
|
|
||||||
const (
|
|
||||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
|
||||||
// message passed to Debug, Info, ...
|
|
||||||
PanicLevel Level = iota
|
|
||||||
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
|
||||||
// logging level is set to Panic.
|
|
||||||
FatalLevel
|
|
||||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
|
||||||
// Commonly used for hooks to send errors to an error tracking service.
|
|
||||||
ErrorLevel
|
|
||||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
|
||||||
WarnLevel
|
|
||||||
// InfoLevel level. General operational entries about what's going on inside the
|
|
||||||
// application.
|
|
||||||
InfoLevel
|
|
||||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
|
||||||
DebugLevel
|
|
||||||
)
|
|
||||||
|
|
||||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
|
||||||
var _ StdLogger = &log.Logger{}
|
|
||||||
|
|
||||||
// StdLogger is what your logrus-enabled library should take, that way
|
|
||||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
|
||||||
// interface, this is the closest we get, unfortunately.
|
|
||||||
type StdLogger interface {
|
|
||||||
Print(...interface{})
|
|
||||||
Printf(string, ...interface{})
|
|
||||||
Println(...interface{})
|
|
||||||
|
|
||||||
Fatal(...interface{})
|
|
||||||
Fatalf(string, ...interface{})
|
|
||||||
Fatalln(...interface{})
|
|
||||||
|
|
||||||
Panic(...interface{})
|
|
||||||
Panicf(string, ...interface{})
|
|
||||||
Panicln(...interface{})
|
|
||||||
}
|
|
|
@ -1,247 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
var fields Fields
|
|
||||||
|
|
||||||
logger := New()
|
|
||||||
logger.Out = &buffer
|
|
||||||
logger.Formatter = new(JSONFormatter)
|
|
||||||
|
|
||||||
log(logger)
|
|
||||||
|
|
||||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
assertions(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
|
|
||||||
logger := New()
|
|
||||||
logger.Out = &buffer
|
|
||||||
logger.Formatter = &TextFormatter{
|
|
||||||
DisableColors: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
log(logger)
|
|
||||||
|
|
||||||
fields := make(map[string]string)
|
|
||||||
for _, kv := range strings.Split(buffer.String(), " ") {
|
|
||||||
if !strings.Contains(kv, "=") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
kvArr := strings.Split(kv, "=")
|
|
||||||
key := strings.TrimSpace(kvArr[0])
|
|
||||||
val, err := strconv.Unquote(kvArr[1])
|
|
||||||
assert.NoError(t, err)
|
|
||||||
fields[key] = val
|
|
||||||
}
|
|
||||||
assertions(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrint(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Print("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "test")
|
|
||||||
assert.Equal(t, fields["level"], "info")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfo(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Info("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "test")
|
|
||||||
assert.Equal(t, fields["level"], "info")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWarn(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Warn("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "test")
|
|
||||||
assert.Equal(t, fields["level"], "warning")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Infoln("test", "test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "test test")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Infoln("test", 10)
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "test 10")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Infoln(10, 10)
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "10 10")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Infoln(10, 10)
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "10 10")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Info("test", 10)
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "test10")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.Info("test", "test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "testtest")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
var fields Fields
|
|
||||||
|
|
||||||
logger := New()
|
|
||||||
logger.Out = &buffer
|
|
||||||
logger.Formatter = new(JSONFormatter)
|
|
||||||
|
|
||||||
localLog := logger.WithFields(Fields{
|
|
||||||
"key1": "value1",
|
|
||||||
})
|
|
||||||
|
|
||||||
localLog.WithField("key2", "value2").Info("test")
|
|
||||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, "value2", fields["key2"])
|
|
||||||
assert.Equal(t, "value1", fields["key1"])
|
|
||||||
|
|
||||||
buffer = bytes.Buffer{}
|
|
||||||
fields = Fields{}
|
|
||||||
localLog.Info("test")
|
|
||||||
err = json.Unmarshal(buffer.Bytes(), &fields)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
_, ok := fields["key2"]
|
|
||||||
assert.Equal(t, false, ok)
|
|
||||||
assert.Equal(t, "value1", fields["key1"])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.WithField("msg", "hello").Info("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "test")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.WithField("msg", "hello").Info("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["msg"], "test")
|
|
||||||
assert.Equal(t, fields["fields.msg"], "hello")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.WithField("time", "hello").Info("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["fields.time"], "hello")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
|
|
||||||
LogAndAssertJSON(t, func(log *Logger) {
|
|
||||||
log.WithField("level", 1).Info("test")
|
|
||||||
}, func(fields Fields) {
|
|
||||||
assert.Equal(t, fields["level"], "info")
|
|
||||||
assert.Equal(t, fields["fields.level"], 1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
|
|
||||||
LogAndAssertText(t, func(log *Logger) {
|
|
||||||
ll := log.WithField("herp", "derp")
|
|
||||||
ll.Info("hello")
|
|
||||||
ll.Info("bye")
|
|
||||||
}, func(fields map[string]string) {
|
|
||||||
for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} {
|
|
||||||
if _, ok := fields[fieldName]; ok {
|
|
||||||
t.Fatalf("should not have prefixed %q: %v", fieldName, fields)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConvertLevelToString(t *testing.T) {
|
|
||||||
assert.Equal(t, "debug", DebugLevel.String())
|
|
||||||
assert.Equal(t, "info", InfoLevel.String())
|
|
||||||
assert.Equal(t, "warning", WarnLevel.String())
|
|
||||||
assert.Equal(t, "error", ErrorLevel.String())
|
|
||||||
assert.Equal(t, "fatal", FatalLevel.String())
|
|
||||||
assert.Equal(t, "panic", PanicLevel.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseLevel(t *testing.T) {
|
|
||||||
l, err := ParseLevel("panic")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, PanicLevel, l)
|
|
||||||
|
|
||||||
l, err = ParseLevel("fatal")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, FatalLevel, l)
|
|
||||||
|
|
||||||
l, err = ParseLevel("error")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, ErrorLevel, l)
|
|
||||||
|
|
||||||
l, err = ParseLevel("warn")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, WarnLevel, l)
|
|
||||||
|
|
||||||
l, err = ParseLevel("warning")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, WarnLevel, l)
|
|
||||||
|
|
||||||
l, err = ParseLevel("info")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, InfoLevel, l)
|
|
||||||
|
|
||||||
l, err = ParseLevel("debug")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, DebugLevel, l)
|
|
||||||
|
|
||||||
l, err = ParseLevel("invalid")
|
|
||||||
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA
|
|
||||||
|
|
||||||
type Termios syscall.Termios
|
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
|
|
||||||
*/
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA
|
|
||||||
|
|
||||||
type Termios struct {
|
|
||||||
Iflag uint32
|
|
||||||
Oflag uint32
|
|
||||||
Cflag uint32
|
|
||||||
Lflag uint32
|
|
||||||
Cc [20]uint8
|
|
||||||
Ispeed uint32
|
|
||||||
Ospeed uint32
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TCGETS
|
|
||||||
|
|
||||||
type Termios syscall.Termios
|
|
|
@ -1,21 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
fd := syscall.Stdout
|
|
||||||
var termios Termios
|
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
|
||||||
return err == 0
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
// 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 logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
|
|
||||||
var (
|
|
||||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
fd := syscall.Stdout
|
|
||||||
var st uint32
|
|
||||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
|
||||||
return r != 0 && e == 0
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
nocolor = 0
|
|
||||||
red = 31
|
|
||||||
green = 32
|
|
||||||
yellow = 33
|
|
||||||
blue = 34
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
baseTimestamp time.Time
|
|
||||||
isTerminal bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
baseTimestamp = time.Now()
|
|
||||||
isTerminal = IsTerminal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func miniTS() int {
|
|
||||||
return int(time.Since(baseTimestamp) / time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TextFormatter struct {
|
|
||||||
// Set to true to bypass checking for a TTY before outputting colors.
|
|
||||||
ForceColors bool
|
|
||||||
DisableColors bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
|
|
||||||
var keys []string
|
|
||||||
for k := range entry.Data {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
b := &bytes.Buffer{}
|
|
||||||
|
|
||||||
prefixFieldClashes(entry)
|
|
||||||
|
|
||||||
isColored := (f.ForceColors || isTerminal) && !f.DisableColors
|
|
||||||
|
|
||||||
if isColored {
|
|
||||||
printColored(b, entry, keys)
|
|
||||||
} else {
|
|
||||||
f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
|
|
||||||
f.appendKeyValue(b, "level", entry.Level.String())
|
|
||||||
f.appendKeyValue(b, "msg", entry.Message)
|
|
||||||
for _, key := range keys {
|
|
||||||
f.appendKeyValue(b, key, entry.Data[key])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteByte('\n')
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
|
|
||||||
var levelColor int
|
|
||||||
switch entry.Level {
|
|
||||||
case WarnLevel:
|
|
||||||
levelColor = yellow
|
|
||||||
case ErrorLevel, FatalLevel, PanicLevel:
|
|
||||||
levelColor = red
|
|
||||||
default:
|
|
||||||
levelColor = blue
|
|
||||||
}
|
|
||||||
|
|
||||||
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
|
||||||
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
|
|
||||||
for _, k := range keys {
|
|
||||||
v := entry.Data[k]
|
|
||||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) {
|
|
||||||
switch value.(type) {
|
|
||||||
case string, error:
|
|
||||||
fmt.Fprintf(b, "%v=%q ", key, value)
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(b, "%v=%v ", key, value)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
Victor Vieux <vieux@docker.com> (@vieux)
|
|
||||||
Jessie Frazelle <jess@docker.com> (@jfrazelle)
|
|
|
@ -1,5 +0,0 @@
|
||||||
This directory contains code pertaining to the Docker API:
|
|
||||||
|
|
||||||
- Used by the docker client when communicating with the docker daemon
|
|
||||||
|
|
||||||
- Used by third party tools wishing to interface with the docker daemon
|
|
|
@ -1,19 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestJsonContentType(t *testing.T) {
|
|
||||||
if !MatchesContentType("application/json", "application/json") {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !MatchesContentType("application/json; charset=utf-8", "application/json") {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
if MatchesContentType("dockerapplication/json", "application/json") {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,188 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/docker/pkg/term"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DockerCli struct {
|
|
||||||
proto string
|
|
||||||
addr string
|
|
||||||
configFile *registry.ConfigFile
|
|
||||||
in io.ReadCloser
|
|
||||||
out io.Writer
|
|
||||||
err io.Writer
|
|
||||||
keyFile string
|
|
||||||
tlsConfig *tls.Config
|
|
||||||
scheme string
|
|
||||||
// inFd holds file descriptor of the client's STDIN, if it's a valid file
|
|
||||||
inFd uintptr
|
|
||||||
// outFd holds file descriptor of the client's STDOUT, if it's a valid file
|
|
||||||
outFd uintptr
|
|
||||||
// isTerminalIn describes if client's STDIN is a TTY
|
|
||||||
isTerminalIn bool
|
|
||||||
// isTerminalOut describes if client's STDOUT is a TTY
|
|
||||||
isTerminalOut bool
|
|
||||||
transport *http.Transport
|
|
||||||
}
|
|
||||||
|
|
||||||
var funcMap = template.FuncMap{
|
|
||||||
"json": func(v interface{}) string {
|
|
||||||
a, _ := json.Marshal(v)
|
|
||||||
return string(a)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) {
|
|
||||||
camelArgs := make([]string, len(args))
|
|
||||||
for i, s := range args {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
|
||||||
}
|
|
||||||
methodName := "Cmd" + strings.Join(camelArgs, "")
|
|
||||||
method := reflect.ValueOf(cli).MethodByName(methodName)
|
|
||||||
if !method.IsValid() {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return method.Interface().(func(...string) error), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cmd executes the specified command
|
|
||||||
func (cli *DockerCli) Cmd(args ...string) error {
|
|
||||||
if len(args) > 1 {
|
|
||||||
method, exists := cli.getMethod(args[:2]...)
|
|
||||||
if exists {
|
|
||||||
return method(args[2:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(args) > 0 {
|
|
||||||
method, exists := cli.getMethod(args[0])
|
|
||||||
if !exists {
|
|
||||||
fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0])
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return method(args[1:]...)
|
|
||||||
}
|
|
||||||
return cli.CmdHelp()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bool) *flag.FlagSet {
|
|
||||||
var errorHandling flag.ErrorHandling
|
|
||||||
if exitOnError {
|
|
||||||
errorHandling = flag.ExitOnError
|
|
||||||
} else {
|
|
||||||
errorHandling = flag.ContinueOnError
|
|
||||||
}
|
|
||||||
flags := flag.NewFlagSet(name, errorHandling)
|
|
||||||
flags.Usage = func() {
|
|
||||||
options := ""
|
|
||||||
if flags.FlagCountUndeprecated() > 0 {
|
|
||||||
options = "[OPTIONS] "
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "\nUsage: docker %s %s%s\n\n%s\n\n", name, options, signature, description)
|
|
||||||
flags.SetOutput(cli.out)
|
|
||||||
flags.PrintDefaults()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
return flags
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) LoadConfigFile() (err error) {
|
|
||||||
cli.configFile, err = registry.LoadConfig(os.Getenv("HOME"))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(cli.err, "WARNING: %s\n", err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
|
||||||
// In order to attach to a container tty, input stream for the client must
|
|
||||||
// be a tty itself: redirecting or piping the client standard input is
|
|
||||||
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
|
|
||||||
if ttyMode && attachStdin && !cli.isTerminalIn {
|
|
||||||
return errors.New("cannot enable tty mode on non tty input")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
|
||||||
var (
|
|
||||||
inFd uintptr
|
|
||||||
outFd uintptr
|
|
||||||
isTerminalIn = false
|
|
||||||
isTerminalOut = false
|
|
||||||
scheme = "http"
|
|
||||||
)
|
|
||||||
|
|
||||||
if tlsConfig != nil {
|
|
||||||
scheme = "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
if in != nil {
|
|
||||||
if file, ok := in.(*os.File); ok {
|
|
||||||
inFd = file.Fd()
|
|
||||||
isTerminalIn = term.IsTerminal(inFd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if out != nil {
|
|
||||||
if file, ok := out.(*os.File); ok {
|
|
||||||
outFd = file.Fd()
|
|
||||||
isTerminalOut = term.IsTerminal(outFd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
err = out
|
|
||||||
}
|
|
||||||
|
|
||||||
// The transport is created here for reuse during the client session
|
|
||||||
tr := &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
TLSClientConfig: tlsConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Why 32? See issue 8035
|
|
||||||
timeout := 32 * time.Second
|
|
||||||
if proto == "unix" {
|
|
||||||
// no need in compressing for local communications
|
|
||||||
tr.DisableCompression = true
|
|
||||||
tr.Dial = func(_, _ string) (net.Conn, error) {
|
|
||||||
return net.DialTimeout(proto, addr, timeout)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DockerCli{
|
|
||||||
proto: proto,
|
|
||||||
addr: addr,
|
|
||||||
in: in,
|
|
||||||
out: out,
|
|
||||||
err: err,
|
|
||||||
keyFile: keyFile,
|
|
||||||
inFd: inFd,
|
|
||||||
outFd: outFd,
|
|
||||||
isTerminalIn: isTerminalIn,
|
|
||||||
isTerminalOut: isTerminalOut,
|
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
scheme: scheme,
|
|
||||||
transport: tr,
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,250 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/dockerversion"
|
|
||||||
"github.com/docker/docker/pkg/promise"
|
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
|
||||||
"github.com/docker/docker/pkg/term"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tlsClientCon struct {
|
|
||||||
*tls.Conn
|
|
||||||
rawConn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tlsClientCon) CloseWrite() error {
|
|
||||||
// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
|
|
||||||
// on its underlying connection.
|
|
||||||
if cwc, ok := c.rawConn.(interface {
|
|
||||||
CloseWrite() error
|
|
||||||
}); ok {
|
|
||||||
return cwc.CloseWrite()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
|
|
||||||
return tlsDialWithDialer(new(net.Dialer), network, addr, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
|
|
||||||
// order to return our custom tlsClientCon struct which holds both the tls.Conn
|
|
||||||
// object _and_ its underlying raw connection. The rationale for this is that
|
|
||||||
// we need to be able to close the write end of the connection when attaching,
|
|
||||||
// which tls.Conn does not provide.
|
|
||||||
func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
|
|
||||||
// We want the Timeout and Deadline values from dialer to cover the
|
|
||||||
// whole process: TCP connection and TLS handshake. This means that we
|
|
||||||
// also need to start our own timers now.
|
|
||||||
timeout := dialer.Timeout
|
|
||||||
|
|
||||||
if !dialer.Deadline.IsZero() {
|
|
||||||
deadlineTimeout := dialer.Deadline.Sub(time.Now())
|
|
||||||
if timeout == 0 || deadlineTimeout < timeout {
|
|
||||||
timeout = deadlineTimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errChannel chan error
|
|
||||||
|
|
||||||
if timeout != 0 {
|
|
||||||
errChannel = make(chan error, 2)
|
|
||||||
time.AfterFunc(timeout, func() {
|
|
||||||
errChannel <- errors.New("")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
rawConn, err := dialer.Dial(network, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// When we set up a TCP connection for hijack, there could be long periods
|
|
||||||
// of inactivity (a long running command with no output) that in certain
|
|
||||||
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
|
||||||
// state. Setting TCP KeepAlive on the socket connection will prohibit
|
|
||||||
// ECONNTIMEOUT unless the socket connection truly is broken
|
|
||||||
if tcpConn, ok := rawConn.(*net.TCPConn); ok {
|
|
||||||
tcpConn.SetKeepAlive(true)
|
|
||||||
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
colonPos := strings.LastIndex(addr, ":")
|
|
||||||
if colonPos == -1 {
|
|
||||||
colonPos = len(addr)
|
|
||||||
}
|
|
||||||
hostname := addr[:colonPos]
|
|
||||||
|
|
||||||
// If no ServerName is set, infer the ServerName
|
|
||||||
// from the hostname we're connecting to.
|
|
||||||
if config.ServerName == "" {
|
|
||||||
// Make a copy to avoid polluting argument or default.
|
|
||||||
c := *config
|
|
||||||
c.ServerName = hostname
|
|
||||||
config = &c
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := tls.Client(rawConn, config)
|
|
||||||
|
|
||||||
if timeout == 0 {
|
|
||||||
err = conn.Handshake()
|
|
||||||
} else {
|
|
||||||
go func() {
|
|
||||||
errChannel <- conn.Handshake()
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = <-errChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
rawConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is Docker difference with standard's crypto/tls package: returned a
|
|
||||||
// wrapper which holds both the TLS and raw connections.
|
|
||||||
return &tlsClientCon{conn, rawConn}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) dial() (net.Conn, error) {
|
|
||||||
if cli.tlsConfig != nil && cli.proto != "unix" {
|
|
||||||
// Notice this isn't Go standard's tls.Dial function
|
|
||||||
return tlsDial(cli.proto, cli.addr, cli.tlsConfig)
|
|
||||||
}
|
|
||||||
return net.Dial(cli.proto, cli.addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error {
|
|
||||||
defer func() {
|
|
||||||
if started != nil {
|
|
||||||
close(started)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
params, err := cli.encodeData(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
|
||||||
req.Header.Set("Content-Type", "text/plain")
|
|
||||||
req.Header.Set("Connection", "Upgrade")
|
|
||||||
req.Header.Set("Upgrade", "tcp")
|
|
||||||
req.Host = cli.addr
|
|
||||||
|
|
||||||
dial, err := cli.dial()
|
|
||||||
// When we set up a TCP connection for hijack, there could be long periods
|
|
||||||
// of inactivity (a long running command with no output) that in certain
|
|
||||||
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
|
||||||
// state. Setting TCP KeepAlive on the socket connection will prohibit
|
|
||||||
// ECONNTIMEOUT unless the socket connection truly is broken
|
|
||||||
if tcpConn, ok := dial.(*net.TCPConn); ok {
|
|
||||||
tcpConn.SetKeepAlive(true)
|
|
||||||
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
|
||||||
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
clientconn := httputil.NewClientConn(dial, nil)
|
|
||||||
defer clientconn.Close()
|
|
||||||
|
|
||||||
// Server hijacks the connection, error 'connection closed' expected
|
|
||||||
clientconn.Do(req)
|
|
||||||
|
|
||||||
rwc, br := clientconn.Hijack()
|
|
||||||
defer rwc.Close()
|
|
||||||
|
|
||||||
if started != nil {
|
|
||||||
started <- rwc
|
|
||||||
}
|
|
||||||
|
|
||||||
var receiveStdout chan error
|
|
||||||
|
|
||||||
var oldState *term.State
|
|
||||||
|
|
||||||
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
|
||||||
oldState, err = term.SetRawTerminal(cli.inFd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer term.RestoreTerminal(cli.inFd, oldState)
|
|
||||||
}
|
|
||||||
|
|
||||||
if stdout != nil || stderr != nil {
|
|
||||||
receiveStdout = promise.Go(func() (err error) {
|
|
||||||
defer func() {
|
|
||||||
if in != nil {
|
|
||||||
if setRawTerminal && cli.isTerminalIn {
|
|
||||||
term.RestoreTerminal(cli.inFd, oldState)
|
|
||||||
}
|
|
||||||
// For some reason this Close call blocks on darwin..
|
|
||||||
// As the client exists right after, simply discard the close
|
|
||||||
// until we find a better solution.
|
|
||||||
if runtime.GOOS != "darwin" {
|
|
||||||
in.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// When TTY is ON, use regular copy
|
|
||||||
if setRawTerminal && stdout != nil {
|
|
||||||
_, err = io.Copy(stdout, br)
|
|
||||||
} else {
|
|
||||||
_, err = stdcopy.StdCopy(stdout, stderr, br)
|
|
||||||
}
|
|
||||||
log.Debugf("[hijack] End of stdout")
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sendStdin := promise.Go(func() error {
|
|
||||||
if in != nil {
|
|
||||||
io.Copy(rwc, in)
|
|
||||||
log.Debugf("[hijack] End of stdin")
|
|
||||||
}
|
|
||||||
|
|
||||||
if conn, ok := rwc.(interface {
|
|
||||||
CloseWrite() error
|
|
||||||
}); ok {
|
|
||||||
if err := conn.CloseWrite(); err != nil {
|
|
||||||
log.Debugf("Couldn't send EOF: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Discard errors due to pipe interruption
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if stdout != nil || stderr != nil {
|
|
||||||
if err := <-receiveStdout; err != nil {
|
|
||||||
log.Debugf("Error receiveStdout: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cli.isTerminalIn {
|
|
||||||
if err := <-sendStdin; err != nil {
|
|
||||||
log.Debugf("Error sendStdin: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,296 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
gosignal "os/signal"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/dockerversion"
|
|
||||||
"github.com/docker/docker/engine"
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
|
||||||
"github.com/docker/docker/pkg/term"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (cli *DockerCli) HTTPClient() *http.Client {
|
|
||||||
return &http.Client{Transport: cli.transport}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
|
||||||
params := bytes.NewBuffer(nil)
|
|
||||||
if data != nil {
|
|
||||||
if env, ok := data.(engine.Env); ok {
|
|
||||||
if err := env.Encode(params); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buf, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := params.Write(buf); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return params, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
|
||||||
params, err := cli.encodeData(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
if passAuthInfo {
|
|
||||||
cli.LoadConfigFile()
|
|
||||||
// Resolve the Auth config relevant for this server
|
|
||||||
authConfig := cli.configFile.Configs[registry.IndexServerAddress()]
|
|
||||||
getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
|
|
||||||
buf, err := json.Marshal(authConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
registryAuthHeader := []string{
|
|
||||||
base64.URLEncoding.EncodeToString(buf),
|
|
||||||
}
|
|
||||||
return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
|
|
||||||
}
|
|
||||||
if headers, err := getHeaders(authConfig); err == nil && headers != nil {
|
|
||||||
for k, v := range headers {
|
|
||||||
req.Header[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
|
||||||
req.URL.Host = cli.addr
|
|
||||||
req.URL.Scheme = cli.scheme
|
|
||||||
if data != nil {
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
} else if method == "POST" {
|
|
||||||
req.Header.Set("Content-Type", "text/plain")
|
|
||||||
}
|
|
||||||
resp, err := cli.HTTPClient().Do(req)
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
|
||||||
return nil, -1, ErrConnectionRefused
|
|
||||||
}
|
|
||||||
|
|
||||||
if cli.tlsConfig == nil {
|
|
||||||
return nil, -1, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
|
||||||
}
|
|
||||||
return nil, -1, fmt.Errorf("An error occurred trying to connect: %v", err)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
if len(body) == 0 {
|
|
||||||
return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL)
|
|
||||||
}
|
|
||||||
return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.Body, resp.StatusCode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
|
|
||||||
return cli.streamHelper(method, path, true, in, out, nil, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error {
|
|
||||||
if (method == "POST" || method == "PUT") && in == nil {
|
|
||||||
in = bytes.NewReader([]byte{})
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
|
||||||
req.URL.Host = cli.addr
|
|
||||||
req.URL.Scheme = cli.scheme
|
|
||||||
if method == "POST" {
|
|
||||||
req.Header.Set("Content-Type", "text/plain")
|
|
||||||
}
|
|
||||||
|
|
||||||
if headers != nil {
|
|
||||||
for k, v := range headers {
|
|
||||||
req.Header[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp, err := cli.HTTPClient().Do(req)
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
|
||||||
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(body) == 0 {
|
|
||||||
return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
|
||||||
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut)
|
|
||||||
}
|
|
||||||
if stdout != nil || stderr != nil {
|
|
||||||
// When TTY is ON, use regular copy
|
|
||||||
if setRawTerminal {
|
|
||||||
_, err = io.Copy(stdout, resp.Body)
|
|
||||||
} else {
|
|
||||||
_, err = stdcopy.StdCopy(stdout, stderr, resp.Body)
|
|
||||||
}
|
|
||||||
log.Debugf("[stream] End of stdout")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
|
||||||
height, width := cli.getTtySize()
|
|
||||||
if height == 0 && width == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
v := url.Values{}
|
|
||||||
v.Set("h", strconv.Itoa(height))
|
|
||||||
v.Set("w", strconv.Itoa(width))
|
|
||||||
|
|
||||||
path := ""
|
|
||||||
if !isExec {
|
|
||||||
path = "/containers/" + id + "/resize?"
|
|
||||||
} else {
|
|
||||||
path = "/exec/" + id + "/resize?"
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil {
|
|
||||||
log.Debugf("Error resize: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
|
||||||
stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var out engine.Env
|
|
||||||
if err := out.Decode(stream); err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
return out.GetInt("StatusCode"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getExitCode perform an inspect on the container. It returns
|
|
||||||
// the running state and the exit code.
|
|
||||||
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
|
||||||
stream, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false)
|
|
||||||
if err != nil {
|
|
||||||
// If we can't connect, then the daemon probably died.
|
|
||||||
if err != ErrConnectionRefused {
|
|
||||||
return false, -1, err
|
|
||||||
}
|
|
||||||
return false, -1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var result engine.Env
|
|
||||||
if err := result.Decode(stream); err != nil {
|
|
||||||
return false, -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
state := result.GetSubEnv("State")
|
|
||||||
return state.GetBool("Running"), state.GetInt("ExitCode"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getExecExitCode perform an inspect on the exec command. It returns
|
|
||||||
// the running state and the exit code.
|
|
||||||
func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
|
|
||||||
stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false)
|
|
||||||
if err != nil {
|
|
||||||
// If we can't connect, then the daemon probably died.
|
|
||||||
if err != ErrConnectionRefused {
|
|
||||||
return false, -1, err
|
|
||||||
}
|
|
||||||
return false, -1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var result engine.Env
|
|
||||||
if err := result.Decode(stream); err != nil {
|
|
||||||
return false, -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.GetBool("Running"), result.GetInt("ExitCode"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
|
||||||
cli.resizeTty(id, isExec)
|
|
||||||
|
|
||||||
sigchan := make(chan os.Signal, 1)
|
|
||||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
|
||||||
go func() {
|
|
||||||
for _ = range sigchan {
|
|
||||||
cli.resizeTty(id, isExec)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) getTtySize() (int, int) {
|
|
||||||
if !cli.isTerminalOut {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
ws, err := term.GetWinsize(cli.outFd)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Error getting size: %s", err)
|
|
||||||
if ws == nil {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return int(ws.Height), int(ws.Width)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
|
|
||||||
if stream != nil {
|
|
||||||
defer stream.Close()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, statusCode, err
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(stream)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
return body, statusCode, nil
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"mime"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/engine"
|
|
||||||
"github.com/docker/docker/pkg/parsers"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
"github.com/docker/libtrust"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
APIVERSION version.Version = "1.17"
|
|
||||||
DEFAULTHTTPHOST = "127.0.0.1"
|
|
||||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
|
||||||
DefaultDockerfileName string = "Dockerfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ValidateHost(val string) (string, error) {
|
|
||||||
host, err := parsers.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val)
|
|
||||||
if err != nil {
|
|
||||||
return val, err
|
|
||||||
}
|
|
||||||
return host, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO remove, used on < 1.5 in getContainersJSON
|
|
||||||
func DisplayablePorts(ports *engine.Table) string {
|
|
||||||
result := []string{}
|
|
||||||
ports.SetKey("PublicPort")
|
|
||||||
ports.Sort()
|
|
||||||
for _, port := range ports.Data {
|
|
||||||
if port.Get("IP") == "" {
|
|
||||||
result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PrivatePort"), port.Get("Type")))
|
|
||||||
} else {
|
|
||||||
result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(result, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func MatchesContentType(contentType, expectedType string) bool {
|
|
||||||
mimetype, _, err := mime.ParseMediaType(contentType)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error parsing media type: %s error: %s", contentType, err.Error())
|
|
||||||
}
|
|
||||||
return err == nil && mimetype == expectedType
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
|
|
||||||
// otherwise generates a new one
|
|
||||||
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
|
|
||||||
err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
trustKey, err := libtrust.LoadKeyFile(trustKeyPath)
|
|
||||||
if err == libtrust.ErrKeyFileDoesNotExist {
|
|
||||||
trustKey, err = libtrust.GenerateECP256PrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error generating key: %s", err)
|
|
||||||
}
|
|
||||||
if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil {
|
|
||||||
return nil, fmt.Errorf("Error saving key file: %s", err)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error loading key file %s: %s", trustKeyPath, err)
|
|
||||||
}
|
|
||||||
return trustKey, nil
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
Victor Vieux <vieux@docker.com> (@vieux)
|
|
||||||
# Johan Euphrosine <proppy@google.com> (@proppy)
|
|
File diff suppressed because it is too large
Load Diff
553
Godeps/_workspace/src/github.com/docker/docker/api/server/server_unit_test.go
generated
vendored
553
Godeps/_workspace/src/github.com/docker/docker/api/server/server_unit_test.go
generated
vendored
|
@ -1,553 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/engine"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetBoolParam(t *testing.T) {
|
|
||||||
if ret, err := getBoolParam("true"); err != nil || !ret {
|
|
||||||
t.Fatalf("true -> true, nil | got %t %s", ret, err)
|
|
||||||
}
|
|
||||||
if ret, err := getBoolParam("True"); err != nil || !ret {
|
|
||||||
t.Fatalf("True -> true, nil | got %t %s", ret, err)
|
|
||||||
}
|
|
||||||
if ret, err := getBoolParam("1"); err != nil || !ret {
|
|
||||||
t.Fatalf("1 -> true, nil | got %t %s", ret, err)
|
|
||||||
}
|
|
||||||
if ret, err := getBoolParam(""); err != nil || ret {
|
|
||||||
t.Fatalf("\"\" -> false, nil | got %t %s", ret, err)
|
|
||||||
}
|
|
||||||
if ret, err := getBoolParam("false"); err != nil || ret {
|
|
||||||
t.Fatalf("false -> false, nil | got %t %s", ret, err)
|
|
||||||
}
|
|
||||||
if ret, err := getBoolParam("0"); err != nil || ret {
|
|
||||||
t.Fatalf("0 -> false, nil | got %t %s", ret, err)
|
|
||||||
}
|
|
||||||
if ret, err := getBoolParam("faux"); err == nil || ret {
|
|
||||||
t.Fatalf("faux -> false, err | got %t %s", ret, err)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TesthttpError(t *testing.T) {
|
|
||||||
r := httptest.NewRecorder()
|
|
||||||
|
|
||||||
httpError(r, fmt.Errorf("No such method"))
|
|
||||||
if r.Code != http.StatusNotFound {
|
|
||||||
t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpError(r, fmt.Errorf("This accound hasn't been activated"))
|
|
||||||
if r.Code != http.StatusForbidden {
|
|
||||||
t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpError(r, fmt.Errorf("Some error"))
|
|
||||||
if r.Code != http.StatusInternalServerError {
|
|
||||||
t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetVersion(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
var called bool
|
|
||||||
eng.Register("version", func(job *engine.Job) engine.Status {
|
|
||||||
called = true
|
|
||||||
v := &engine.Env{}
|
|
||||||
v.SetJson("Version", "42.1")
|
|
||||||
v.Set("ApiVersion", "1.1.1.1.1")
|
|
||||||
v.Set("GoVersion", "2.42")
|
|
||||||
v.Set("Os", "Linux")
|
|
||||||
v.Set("Arch", "x86_64")
|
|
||||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
|
||||||
return job.Error(err)
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequest("GET", "/version", nil, eng, t)
|
|
||||||
if !called {
|
|
||||||
t.Fatalf("handler was not called")
|
|
||||||
}
|
|
||||||
v := readEnv(r.Body, t)
|
|
||||||
if v.Get("Version") != "42.1" {
|
|
||||||
t.Fatalf("%#v\n", v)
|
|
||||||
}
|
|
||||||
if r.HeaderMap.Get("Content-Type") != "application/json" {
|
|
||||||
t.Fatalf("%#v\n", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetInfo(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
var called bool
|
|
||||||
eng.Register("info", func(job *engine.Job) engine.Status {
|
|
||||||
called = true
|
|
||||||
v := &engine.Env{}
|
|
||||||
v.SetInt("Containers", 1)
|
|
||||||
v.SetInt("Images", 42000)
|
|
||||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
|
||||||
return job.Error(err)
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequest("GET", "/info", nil, eng, t)
|
|
||||||
if !called {
|
|
||||||
t.Fatalf("handler was not called")
|
|
||||||
}
|
|
||||||
v := readEnv(r.Body, t)
|
|
||||||
if v.GetInt("Images") != 42000 {
|
|
||||||
t.Fatalf("%#v\n", v)
|
|
||||||
}
|
|
||||||
if v.GetInt("Containers") != 1 {
|
|
||||||
t.Fatalf("%#v\n", v)
|
|
||||||
}
|
|
||||||
assertContentType(r, "application/json", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetImagesJSON(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
var called bool
|
|
||||||
eng.Register("images", func(job *engine.Job) engine.Status {
|
|
||||||
called = true
|
|
||||||
v := createEnvFromGetImagesJSONStruct(sampleImage)
|
|
||||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
|
||||||
return job.Error(err)
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequest("GET", "/images/json", nil, eng, t)
|
|
||||||
if !called {
|
|
||||||
t.Fatal("handler was not called")
|
|
||||||
}
|
|
||||||
assertHttpNotError(r, t)
|
|
||||||
assertContentType(r, "application/json", t)
|
|
||||||
var observed getImagesJSONStruct
|
|
||||||
if err := json.Unmarshal(r.Body.Bytes(), &observed); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(observed, sampleImage) {
|
|
||||||
t.Errorf("Expected %#v but got %#v", sampleImage, observed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetImagesJSONFilter(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
filter := "nothing"
|
|
||||||
eng.Register("images", func(job *engine.Job) engine.Status {
|
|
||||||
filter = job.Getenv("filter")
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
serveRequest("GET", "/images/json?filter=aaaa", nil, eng, t)
|
|
||||||
if filter != "aaaa" {
|
|
||||||
t.Errorf("%#v", filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetImagesJSONFilters(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
filter := "nothing"
|
|
||||||
eng.Register("images", func(job *engine.Job) engine.Status {
|
|
||||||
filter = job.Getenv("filters")
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
serveRequest("GET", "/images/json?filters=nnnn", nil, eng, t)
|
|
||||||
if filter != "nnnn" {
|
|
||||||
t.Errorf("%#v", filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetImagesJSONAll(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
allFilter := "-1"
|
|
||||||
eng.Register("images", func(job *engine.Job) engine.Status {
|
|
||||||
allFilter = job.Getenv("all")
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
serveRequest("GET", "/images/json?all=1", nil, eng, t)
|
|
||||||
if allFilter != "1" {
|
|
||||||
t.Errorf("%#v", allFilter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetImagesJSONLegacyFormat(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
var called bool
|
|
||||||
eng.Register("images", func(job *engine.Job) engine.Status {
|
|
||||||
called = true
|
|
||||||
outsLegacy := engine.NewTable("Created", 0)
|
|
||||||
outsLegacy.Add(createEnvFromGetImagesJSONStruct(sampleImage))
|
|
||||||
if _, err := outsLegacy.WriteListTo(job.Stdout); err != nil {
|
|
||||||
return job.Error(err)
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequestUsingVersion("GET", "/images/json", "1.6", nil, eng, t)
|
|
||||||
if !called {
|
|
||||||
t.Fatal("handler was not called")
|
|
||||||
}
|
|
||||||
assertHttpNotError(r, t)
|
|
||||||
assertContentType(r, "application/json", t)
|
|
||||||
images := engine.NewTable("Created", 0)
|
|
||||||
if _, err := images.ReadListFrom(r.Body.Bytes()); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if images.Len() != 1 {
|
|
||||||
t.Fatalf("Expected 1 image, %d found", images.Len())
|
|
||||||
}
|
|
||||||
image := images.Data[0]
|
|
||||||
if image.Get("Tag") != "test-tag" {
|
|
||||||
t.Errorf("Expected tag 'test-tag', found '%s'", image.Get("Tag"))
|
|
||||||
}
|
|
||||||
if image.Get("Repository") != "test-name" {
|
|
||||||
t.Errorf("Expected repository 'test-name', found '%s'", image.Get("Repository"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetContainersByName(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
name := "container_name"
|
|
||||||
var called bool
|
|
||||||
eng.Register("container_inspect", func(job *engine.Job) engine.Status {
|
|
||||||
called = true
|
|
||||||
if job.Args[0] != name {
|
|
||||||
t.Errorf("name != '%s': %#v", name, job.Args[0])
|
|
||||||
}
|
|
||||||
if api.APIVERSION.LessThan("1.12") && !job.GetenvBool("dirty") {
|
|
||||||
t.Errorf("dirty env variable not set")
|
|
||||||
} else if api.APIVERSION.GreaterThanOrEqualTo("1.12") && job.GetenvBool("dirty") {
|
|
||||||
t.Errorf("dirty env variable set when it shouldn't")
|
|
||||||
}
|
|
||||||
v := &engine.Env{}
|
|
||||||
v.SetBool("dirty", true)
|
|
||||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
|
||||||
return job.Error(err)
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequest("GET", "/containers/"+name+"/json", nil, eng, t)
|
|
||||||
if !called {
|
|
||||||
t.Fatal("handler was not called")
|
|
||||||
}
|
|
||||||
assertContentType(r, "application/json", t)
|
|
||||||
var stdoutJson interface{}
|
|
||||||
if err := json.Unmarshal(r.Body.Bytes(), &stdoutJson); err != nil {
|
|
||||||
t.Fatalf("%#v", err)
|
|
||||||
}
|
|
||||||
if stdoutJson.(map[string]interface{})["dirty"].(float64) != 1 {
|
|
||||||
t.Fatalf("%#v", stdoutJson)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetEvents(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
var called bool
|
|
||||||
eng.Register("events", func(job *engine.Job) engine.Status {
|
|
||||||
called = true
|
|
||||||
since := job.Getenv("since")
|
|
||||||
if since != "1" {
|
|
||||||
t.Fatalf("'since' should be 1, found %#v instead", since)
|
|
||||||
}
|
|
||||||
until := job.Getenv("until")
|
|
||||||
if until != "0" {
|
|
||||||
t.Fatalf("'until' should be 0, found %#v instead", until)
|
|
||||||
}
|
|
||||||
v := &engine.Env{}
|
|
||||||
v.Set("since", since)
|
|
||||||
v.Set("until", until)
|
|
||||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
|
||||||
return job.Error(err)
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequest("GET", "/events?since=1&until=0", nil, eng, t)
|
|
||||||
if !called {
|
|
||||||
t.Fatal("handler was not called")
|
|
||||||
}
|
|
||||||
assertContentType(r, "application/json", t)
|
|
||||||
var stdout_json struct {
|
|
||||||
Since int
|
|
||||||
Until int
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(r.Body.Bytes(), &stdout_json); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if stdout_json.Since != 1 {
|
|
||||||
t.Errorf("since != 1: %#v", stdout_json.Since)
|
|
||||||
}
|
|
||||||
if stdout_json.Until != 0 {
|
|
||||||
t.Errorf("until != 0: %#v", stdout_json.Until)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogs(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
var inspect bool
|
|
||||||
var logs bool
|
|
||||||
eng.Register("container_inspect", func(job *engine.Job) engine.Status {
|
|
||||||
inspect = true
|
|
||||||
if len(job.Args) == 0 {
|
|
||||||
t.Fatal("Job arguments is empty")
|
|
||||||
}
|
|
||||||
if job.Args[0] != "test" {
|
|
||||||
t.Fatalf("Container name %s, must be test", job.Args[0])
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
expected := "logs"
|
|
||||||
eng.Register("logs", func(job *engine.Job) engine.Status {
|
|
||||||
logs = true
|
|
||||||
if len(job.Args) == 0 {
|
|
||||||
t.Fatal("Job arguments is empty")
|
|
||||||
}
|
|
||||||
if job.Args[0] != "test" {
|
|
||||||
t.Fatalf("Container name %s, must be test", job.Args[0])
|
|
||||||
}
|
|
||||||
follow := job.Getenv("follow")
|
|
||||||
if follow != "1" {
|
|
||||||
t.Fatalf("follow: %s, must be 1", follow)
|
|
||||||
}
|
|
||||||
stdout := job.Getenv("stdout")
|
|
||||||
if stdout != "1" {
|
|
||||||
t.Fatalf("stdout %s, must be 1", stdout)
|
|
||||||
}
|
|
||||||
stderr := job.Getenv("stderr")
|
|
||||||
if stderr != "" {
|
|
||||||
t.Fatalf("stderr %s, must be empty", stderr)
|
|
||||||
}
|
|
||||||
timestamps := job.Getenv("timestamps")
|
|
||||||
if timestamps != "1" {
|
|
||||||
t.Fatalf("timestamps %s, must be 1", timestamps)
|
|
||||||
}
|
|
||||||
job.Stdout.Write([]byte(expected))
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequest("GET", "/containers/test/logs?follow=1&stdout=1×tamps=1", nil, eng, t)
|
|
||||||
if r.Code != http.StatusOK {
|
|
||||||
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK)
|
|
||||||
}
|
|
||||||
if !inspect {
|
|
||||||
t.Fatal("container_inspect job was not called")
|
|
||||||
}
|
|
||||||
if !logs {
|
|
||||||
t.Fatal("logs job was not called")
|
|
||||||
}
|
|
||||||
res := r.Body.String()
|
|
||||||
if res != expected {
|
|
||||||
t.Fatalf("Output %s, expected %s", res, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogsNoStreams(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
var inspect bool
|
|
||||||
var logs bool
|
|
||||||
eng.Register("container_inspect", func(job *engine.Job) engine.Status {
|
|
||||||
inspect = true
|
|
||||||
if len(job.Args) == 0 {
|
|
||||||
t.Fatal("Job arguments is empty")
|
|
||||||
}
|
|
||||||
if job.Args[0] != "test" {
|
|
||||||
t.Fatalf("Container name %s, must be test", job.Args[0])
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
eng.Register("logs", func(job *engine.Job) engine.Status {
|
|
||||||
logs = true
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequest("GET", "/containers/test/logs", nil, eng, t)
|
|
||||||
if r.Code != http.StatusBadRequest {
|
|
||||||
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
if inspect {
|
|
||||||
t.Fatal("container_inspect job was called, but it shouldn't")
|
|
||||||
}
|
|
||||||
if logs {
|
|
||||||
t.Fatal("logs job was called, but it shouldn't")
|
|
||||||
}
|
|
||||||
res := strings.TrimSpace(r.Body.String())
|
|
||||||
expected := "Bad parameters: you must choose at least one stream"
|
|
||||||
if !strings.Contains(res, expected) {
|
|
||||||
t.Fatalf("Output %s, expected %s in it", res, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetImagesHistory(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
imageName := "docker-test-image"
|
|
||||||
var called bool
|
|
||||||
eng.Register("history", func(job *engine.Job) engine.Status {
|
|
||||||
called = true
|
|
||||||
if len(job.Args) == 0 {
|
|
||||||
t.Fatal("Job arguments is empty")
|
|
||||||
}
|
|
||||||
if job.Args[0] != imageName {
|
|
||||||
t.Fatalf("name != '%s': %#v", imageName, job.Args[0])
|
|
||||||
}
|
|
||||||
v := &engine.Env{}
|
|
||||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
|
||||||
return job.Error(err)
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequest("GET", "/images/"+imageName+"/history", nil, eng, t)
|
|
||||||
if !called {
|
|
||||||
t.Fatalf("handler was not called")
|
|
||||||
}
|
|
||||||
if r.Code != http.StatusOK {
|
|
||||||
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK)
|
|
||||||
}
|
|
||||||
if r.HeaderMap.Get("Content-Type") != "application/json" {
|
|
||||||
t.Fatalf("%#v\n", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetImagesByName(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
name := "image_name"
|
|
||||||
var called bool
|
|
||||||
eng.Register("image_inspect", func(job *engine.Job) engine.Status {
|
|
||||||
called = true
|
|
||||||
if job.Args[0] != name {
|
|
||||||
t.Fatalf("name != '%s': %#v", name, job.Args[0])
|
|
||||||
}
|
|
||||||
if api.APIVERSION.LessThan("1.12") && !job.GetenvBool("dirty") {
|
|
||||||
t.Fatal("dirty env variable not set")
|
|
||||||
} else if api.APIVERSION.GreaterThanOrEqualTo("1.12") && job.GetenvBool("dirty") {
|
|
||||||
t.Fatal("dirty env variable set when it shouldn't")
|
|
||||||
}
|
|
||||||
v := &engine.Env{}
|
|
||||||
v.SetBool("dirty", true)
|
|
||||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
|
||||||
return job.Error(err)
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequest("GET", "/images/"+name+"/json", nil, eng, t)
|
|
||||||
if !called {
|
|
||||||
t.Fatal("handler was not called")
|
|
||||||
}
|
|
||||||
if r.HeaderMap.Get("Content-Type") != "application/json" {
|
|
||||||
t.Fatalf("%#v\n", r)
|
|
||||||
}
|
|
||||||
var stdoutJson interface{}
|
|
||||||
if err := json.Unmarshal(r.Body.Bytes(), &stdoutJson); err != nil {
|
|
||||||
t.Fatalf("%#v", err)
|
|
||||||
}
|
|
||||||
if stdoutJson.(map[string]interface{})["dirty"].(float64) != 1 {
|
|
||||||
t.Fatalf("%#v", stdoutJson)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteContainers(t *testing.T) {
|
|
||||||
eng := engine.New()
|
|
||||||
name := "foo"
|
|
||||||
var called bool
|
|
||||||
eng.Register("rm", func(job *engine.Job) engine.Status {
|
|
||||||
called = true
|
|
||||||
if len(job.Args) == 0 {
|
|
||||||
t.Fatalf("Job arguments is empty")
|
|
||||||
}
|
|
||||||
if job.Args[0] != name {
|
|
||||||
t.Fatalf("name != '%s': %#v", name, job.Args[0])
|
|
||||||
}
|
|
||||||
return engine.StatusOK
|
|
||||||
})
|
|
||||||
r := serveRequest("DELETE", "/containers/"+name, nil, eng, t)
|
|
||||||
if !called {
|
|
||||||
t.Fatalf("handler was not called")
|
|
||||||
}
|
|
||||||
if r.Code != http.StatusNoContent {
|
|
||||||
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusNoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveRequest(method, target string, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder {
|
|
||||||
return serveRequestUsingVersion(method, target, api.APIVERSION, body, eng, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveRequestUsingVersion(method, target string, version version.Version, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder {
|
|
||||||
r := httptest.NewRecorder()
|
|
||||||
req, err := http.NewRequest(method, target, body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
ServeRequest(eng, version, r, req)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func readEnv(src io.Reader, t *testing.T) *engine.Env {
|
|
||||||
out := engine.NewOutput()
|
|
||||||
v, err := out.AddEnv()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(out, src); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
out.Close()
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func toJson(data interface{}, t *testing.T) io.Reader {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := json.NewEncoder(&buf).Encode(data); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return &buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertContentType(recorder *httptest.ResponseRecorder, content_type string, t *testing.T) {
|
|
||||||
if recorder.HeaderMap.Get("Content-Type") != content_type {
|
|
||||||
t.Fatalf("%#v\n", recorder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: Duplicated from integration/utils_test.go, but maybe that's OK as that
|
|
||||||
// should die as soon as we converted all integration tests?
|
|
||||||
// assertHttpNotError expect the given response to not have an error.
|
|
||||||
// Otherwise the it causes the test to fail.
|
|
||||||
func assertHttpNotError(r *httptest.ResponseRecorder, t *testing.T) {
|
|
||||||
// Non-error http status are [200, 400)
|
|
||||||
if r.Code < http.StatusOK || r.Code >= http.StatusBadRequest {
|
|
||||||
t.Fatal(fmt.Errorf("Unexpected http error: %v", r.Code))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createEnvFromGetImagesJSONStruct(data getImagesJSONStruct) *engine.Env {
|
|
||||||
v := &engine.Env{}
|
|
||||||
v.SetList("RepoTags", data.RepoTags)
|
|
||||||
v.Set("Id", data.Id)
|
|
||||||
v.SetInt64("Created", data.Created)
|
|
||||||
v.SetInt64("Size", data.Size)
|
|
||||||
v.SetInt64("VirtualSize", data.VirtualSize)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
type getImagesJSONStruct struct {
|
|
||||||
RepoTags []string
|
|
||||||
Id string
|
|
||||||
Created int64
|
|
||||||
Size int64
|
|
||||||
VirtualSize int64
|
|
||||||
}
|
|
||||||
|
|
||||||
var sampleImage getImagesJSONStruct = getImagesJSONStruct{
|
|
||||||
RepoTags: []string{"test-name:test-tag"},
|
|
||||||
Id: "ID",
|
|
||||||
Created: 999,
|
|
||||||
Size: 777,
|
|
||||||
VirtualSize: 666,
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
// This package is used for API stability in the types and response to the
|
|
||||||
// consumers of the API stats endpoint.
|
|
||||||
package stats
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type ThrottlingData struct {
|
|
||||||
// Number of periods with throttling active
|
|
||||||
Periods uint64 `json:"periods"`
|
|
||||||
// Number of periods when the container hit its throttling limit.
|
|
||||||
ThrottledPeriods uint64 `json:"throttled_periods"`
|
|
||||||
// Aggregate time the container was throttled for in nanoseconds.
|
|
||||||
ThrottledTime uint64 `json:"throttled_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// All CPU stats are aggregated since container inception.
|
|
||||||
type CpuUsage struct {
|
|
||||||
// Total CPU time consumed.
|
|
||||||
// Units: nanoseconds.
|
|
||||||
TotalUsage uint64 `json:"total_usage"`
|
|
||||||
// Total CPU time consumed per core.
|
|
||||||
// Units: nanoseconds.
|
|
||||||
PercpuUsage []uint64 `json:"percpu_usage"`
|
|
||||||
// Time spent by tasks of the cgroup in kernel mode.
|
|
||||||
// Units: nanoseconds.
|
|
||||||
UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
|
|
||||||
// Time spent by tasks of the cgroup in user mode.
|
|
||||||
// Units: nanoseconds.
|
|
||||||
UsageInUsermode uint64 `json:"usage_in_usermode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CpuStats struct {
|
|
||||||
CpuUsage CpuUsage `json:"cpu_usage"`
|
|
||||||
SystemUsage uint64 `json:"system_cpu_usage"`
|
|
||||||
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemoryStats struct {
|
|
||||||
// current res_counter usage for memory
|
|
||||||
Usage uint64 `json:"usage"`
|
|
||||||
// maximum usage ever recorded.
|
|
||||||
MaxUsage uint64 `json:"max_usage"`
|
|
||||||
// TODO(vishh): Export these as stronger types.
|
|
||||||
// all the stats exported via memory.stat.
|
|
||||||
Stats map[string]uint64 `json:"stats"`
|
|
||||||
// number of times memory usage hits limits.
|
|
||||||
Failcnt uint64 `json:"failcnt"`
|
|
||||||
Limit uint64 `json:"limit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BlkioStatEntry struct {
|
|
||||||
Major uint64 `json:"major"`
|
|
||||||
Minor uint64 `json:"minor"`
|
|
||||||
Op string `json:"op"`
|
|
||||||
Value uint64 `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BlkioStats struct {
|
|
||||||
// number of bytes tranferred to and from the block device
|
|
||||||
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"`
|
|
||||||
IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive"`
|
|
||||||
IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive"`
|
|
||||||
IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive"`
|
|
||||||
IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive"`
|
|
||||||
IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive"`
|
|
||||||
IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive"`
|
|
||||||
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Network struct {
|
|
||||||
RxBytes uint64 `json:"rx_bytes"`
|
|
||||||
RxPackets uint64 `json:"rx_packets"`
|
|
||||||
RxErrors uint64 `json:"rx_errors"`
|
|
||||||
RxDropped uint64 `json:"rx_dropped"`
|
|
||||||
TxBytes uint64 `json:"tx_bytes"`
|
|
||||||
TxPackets uint64 `json:"tx_packets"`
|
|
||||||
TxErrors uint64 `json:"tx_errors"`
|
|
||||||
TxDropped uint64 `json:"tx_dropped"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Stats struct {
|
|
||||||
Read time.Time `json:"read"`
|
|
||||||
Network Network `json:"network,omitempty"`
|
|
||||||
CpuStats CpuStats `json:"cpu_stats,omitempty"`
|
|
||||||
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
|
||||||
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
|
||||||
}
|
|
|
@ -3,9 +3,8 @@ package commands
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdActive(c *cli.Context) {
|
func cmdActive(c *cli.Context) {
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/skarademir/naturalsort"
|
"github.com/skarademir/naturalsort"
|
||||||
|
|
||||||
|
@ -31,6 +30,7 @@ import (
|
||||||
"github.com/docker/machine/libmachine"
|
"github.com/docker/machine/libmachine"
|
||||||
"github.com/docker/machine/libmachine/auth"
|
"github.com/docker/machine/libmachine/auth"
|
||||||
"github.com/docker/machine/libmachine/swarm"
|
"github.com/docker/machine/libmachine/swarm"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,9 +5,8 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdIp(c *cli.Context) {
|
func cmdIp(c *cli.Context) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdRegenerateCerts(c *cli.Context) {
|
func cmdRegenerateCerts(c *cli.Context) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdRm(c *cli.Context) {
|
func cmdRm(c *cli.Context) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package commands
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,10 +12,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
"github.com/docker/machine/drivers/amazonec2/amz"
|
"github.com/docker/machine/drivers/amazonec2/amz"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
|
|
@ -11,13 +11,13 @@ import (
|
||||||
azure "github.com/MSOpenTech/azure-sdk-for-go"
|
azure "github.com/MSOpenTech/azure-sdk-for-go"
|
||||||
"github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient"
|
"github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
|
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.google.com/p/goauth2/oauth"
|
"code.google.com/p/goauth2/oauth"
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/digitalocean/godo"
|
"github.com/digitalocean/godo"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.google.com/p/goauth2/oauth"
|
"code.google.com/p/goauth2/oauth"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
raw "google.golang.org/api/compute/v1"
|
raw "google.golang.org/api/compute/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
raw "google.golang.org/api/compute/v1"
|
raw "google.golang.org/api/compute/v1"
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var powershell string
|
var powershell string
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
@ -112,12 +111,8 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
||||||
if url == "" {
|
if url == "" {
|
||||||
return fmt.Errorf("--url option is required when no driver is selected")
|
return fmt.Errorf("--url option is required when no driver is selected")
|
||||||
}
|
}
|
||||||
validatedUrl, err := api.ValidateHost(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.URL = validatedUrl
|
d.URL = url
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/version"
|
"github.com/docker/machine/version"
|
||||||
"github.com/rackspace/gophercloud"
|
"github.com/rackspace/gophercloud"
|
||||||
"github.com/rackspace/gophercloud/openstack"
|
"github.com/rackspace/gophercloud/openstack"
|
||||||
|
|
|
@ -7,13 +7,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
|
|
|
@ -3,8 +3,8 @@ package rackspace
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/machine/drivers/openstack"
|
"github.com/docker/machine/drivers/openstack"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/version"
|
"github.com/docker/machine/version"
|
||||||
"github.com/rackspace/gophercloud"
|
"github.com/rackspace/gophercloud"
|
||||||
"github.com/rackspace/gophercloud/rackspace"
|
"github.com/rackspace/gophercloud/rackspace"
|
||||||
|
|
|
@ -3,10 +3,10 @@ package rackspace
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
"github.com/docker/machine/drivers/openstack"
|
"github.com/docker/machine/drivers/openstack"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Driver is a machine driver for Rackspace. It's a specialization of the generic OpenStack one.
|
// Driver is a machine driver for Rackspace. It's a specialization of the generic OpenStack one.
|
||||||
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -16,9 +16,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
|
|
@ -19,9 +19,9 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -12,13 +12,13 @@ import (
|
||||||
|
|
||||||
"github.com/vmware/govcloudair"
|
"github.com/vmware/govcloudair"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/provider"
|
"github.com/docker/machine/provider"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/machine/drivers/vmwarevsphere/errors"
|
"github.com/docker/machine/drivers/vmwarevsphere/errors"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VcConn struct {
|
type VcConn struct {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,13 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
"github.com/docker/machine/libmachine/auth"
|
"github.com/docker/machine/libmachine/auth"
|
||||||
"github.com/docker/machine/libmachine/engine"
|
"github.com/docker/machine/libmachine/engine"
|
||||||
"github.com/docker/machine/libmachine/provision"
|
"github.com/docker/machine/libmachine/provision"
|
||||||
"github.com/docker/machine/libmachine/provision/pkgaction"
|
"github.com/docker/machine/libmachine/provision/pkgaction"
|
||||||
"github.com/docker/machine/libmachine/swarm"
|
"github.com/docker/machine/libmachine/swarm"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
|
|
|
@ -7,12 +7,12 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
"github.com/docker/machine/libmachine/auth"
|
"github.com/docker/machine/libmachine/auth"
|
||||||
"github.com/docker/machine/libmachine/engine"
|
"github.com/docker/machine/libmachine/engine"
|
||||||
"github.com/docker/machine/libmachine/provision/pkgaction"
|
"github.com/docker/machine/libmachine/provision/pkgaction"
|
||||||
"github.com/docker/machine/libmachine/swarm"
|
"github.com/docker/machine/libmachine/swarm"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The /etc/os-release file contains operating system identification data
|
// The /etc/os-release file contains operating system identification data
|
||||||
|
|
|
@ -5,12 +5,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
"github.com/docker/machine/libmachine/auth"
|
"github.com/docker/machine/libmachine/auth"
|
||||||
"github.com/docker/machine/libmachine/engine"
|
"github.com/docker/machine/libmachine/engine"
|
||||||
"github.com/docker/machine/libmachine/provision/pkgaction"
|
"github.com/docker/machine/libmachine/provision/pkgaction"
|
||||||
"github.com/docker/machine/libmachine/swarm"
|
"github.com/docker/machine/libmachine/swarm"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/machine/libmachine/auth"
|
"github.com/docker/machine/libmachine/auth"
|
||||||
"github.com/docker/machine/libmachine/provision/pkgaction"
|
"github.com/docker/machine/libmachine/provision/pkgaction"
|
||||||
"github.com/docker/machine/libmachine/swarm"
|
"github.com/docker/machine/libmachine/swarm"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
12
log.go
12
log.go
|
@ -1,12 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initLogging(lvl log.Level) {
|
|
||||||
log.SetOutput(os.Stderr)
|
|
||||||
log.SetLevel(lvl)
|
|
||||||
}
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Why the interface? We may only want to print to STDOUT and STDERR for now,
|
||||||
|
// but it won't neccessarily be that way forever. This interface is intended
|
||||||
|
// to provide a "framework" for a variety of different logging types in the
|
||||||
|
// future (log to file, log to logstash, etc.) There could be a driver model
|
||||||
|
// similar to what is done with OS or machine providers.
|
||||||
|
type Logger interface {
|
||||||
|
Debug(...interface{})
|
||||||
|
Debugf(string, ...interface{})
|
||||||
|
|
||||||
|
Error(...interface{})
|
||||||
|
Errorf(string, ...interface{})
|
||||||
|
Errorln(...interface{})
|
||||||
|
|
||||||
|
Info(...interface{})
|
||||||
|
Infof(string, ...interface{})
|
||||||
|
Infoln(...interface{})
|
||||||
|
|
||||||
|
Fatal(...interface{})
|
||||||
|
Fatalf(string, ...interface{})
|
||||||
|
|
||||||
|
Print(...interface{})
|
||||||
|
Printf(string, ...interface{})
|
||||||
|
|
||||||
|
Warn(...interface{})
|
||||||
|
Warnf(string, ...interface{})
|
||||||
|
|
||||||
|
WithFields(Fields) Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
l = TerminalLogger{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: I think this is superflous and can be replaced by one check for if
|
||||||
|
// debug is on that sets a variable in this module.
|
||||||
|
func isDebug() bool {
|
||||||
|
debugEnv := os.Getenv("DEBUG")
|
||||||
|
if debugEnv != "" {
|
||||||
|
showDebug, err := strconv.ParseBool(debugEnv)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error parsing boolean value from DEBUG: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return showDebug
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fields map[string]interface{}
|
||||||
|
|
||||||
|
func Debug(args ...interface{}) {
|
||||||
|
l.Debug(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debugf(fmtString string, args ...interface{}) {
|
||||||
|
l.Debugf(fmtString, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(args ...interface{}) {
|
||||||
|
l.Error(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorf(fmtString string, args ...interface{}) {
|
||||||
|
l.Errorf(fmtString, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorln(args ...interface{}) {
|
||||||
|
l.Errorln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(args ...interface{}) {
|
||||||
|
l.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Infof(fmtString string, args ...interface{}) {
|
||||||
|
l.Infof(fmtString, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Infoln(args ...interface{}) {
|
||||||
|
l.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatal(args ...interface{}) {
|
||||||
|
l.Fatal(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatalf(fmtString string, args ...interface{}) {
|
||||||
|
l.Fatalf(fmtString, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Print(args ...interface{}) {
|
||||||
|
l.Print(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Printf(fmtString string, args ...interface{}) {
|
||||||
|
l.Printf(fmtString, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(args ...interface{}) {
|
||||||
|
l.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warnf(fmtString string, args ...interface{}) {
|
||||||
|
l.Warnf(fmtString, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithField(fieldName string, field interface{}) Logger {
|
||||||
|
return l.WithFields(Fields{
|
||||||
|
fieldName: field,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFields(fields Fields) Logger {
|
||||||
|
return l.WithFields(fields)
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestTerminalLoggerWithFields(t *testing.T) {
|
||||||
|
logger := TerminalLogger{}
|
||||||
|
withFieldsLogger := logger.WithFields(Fields{
|
||||||
|
"foo": "bar",
|
||||||
|
"spam": "eggs",
|
||||||
|
})
|
||||||
|
withFieldsTerminalLogger, ok := withFieldsLogger.(TerminalLogger)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Type assertion to TerminalLogger failed")
|
||||||
|
}
|
||||||
|
expectedOutFields := "\t\t foo=bar spam=eggs"
|
||||||
|
if withFieldsTerminalLogger.fieldOut != expectedOutFields {
|
||||||
|
t.Fatalf("Expected %q, got %q", expectedOutFields, withFieldsTerminalLogger.fieldOut)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TerminalLogger struct {
|
||||||
|
// fieldOut is used to do log.WithFields correctly
|
||||||
|
fieldOut string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) log(args ...interface{}) {
|
||||||
|
fmt.Print(args...)
|
||||||
|
fmt.Print(t.fieldOut, "\n")
|
||||||
|
t.fieldOut = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) logf(fmtString string, args ...interface{}) {
|
||||||
|
fmt.Printf(fmtString, args...)
|
||||||
|
fmt.Print(t.fieldOut, "\n")
|
||||||
|
t.fieldOut = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) err(args ...interface{}) {
|
||||||
|
fmt.Fprint(os.Stderr, args...)
|
||||||
|
fmt.Fprint(os.Stderr, t.fieldOut, "\n")
|
||||||
|
t.fieldOut = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) errf(fmtString string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(os.Stderr, fmtString, args...)
|
||||||
|
fmt.Print(t.fieldOut, "\n")
|
||||||
|
t.fieldOut = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Debug(args ...interface{}) {
|
||||||
|
if isDebug() {
|
||||||
|
t.log(args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Debugf(fmtString string, args ...interface{}) {
|
||||||
|
if isDebug() {
|
||||||
|
t.logf(fmtString, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Error(args ...interface{}) {
|
||||||
|
t.err(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Errorf(fmtString string, args ...interface{}) {
|
||||||
|
t.errf(fmtString, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Errorln(args ...interface{}) {
|
||||||
|
t.err(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Info(args ...interface{}) {
|
||||||
|
t.log(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Infof(fmtString string, args ...interface{}) {
|
||||||
|
t.logf(fmtString, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Infoln(args ...interface{}) {
|
||||||
|
t.log(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Fatal(args ...interface{}) {
|
||||||
|
t.err(args...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Fatalf(fmtString string, args ...interface{}) {
|
||||||
|
t.errf(fmtString, args...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Print(args ...interface{}) {
|
||||||
|
t.log(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Printf(fmtString string, args ...interface{}) {
|
||||||
|
t.logf(fmtString, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Warn(args ...interface{}) {
|
||||||
|
t.log(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) Warnf(fmtString string, args ...interface{}) {
|
||||||
|
t.logf(fmtString, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TerminalLogger) WithFields(fields Fields) Logger {
|
||||||
|
// When the user calls WithFields, we make a string which gets appended
|
||||||
|
// to the output of the final [Info|Warn|Error] call for the
|
||||||
|
// descriptive fields. Because WithFields returns the proper Logger
|
||||||
|
// (with the fieldOut string set correctly), the logrus syntax will
|
||||||
|
// still work.
|
||||||
|
kvpairs := []string{}
|
||||||
|
|
||||||
|
// Why the string slice song and dance? Because Go's map iteration
|
||||||
|
// order is random, we will get inconsistent results if we don't sort
|
||||||
|
// the fields (or their resulting string K/V pairs, like we have here).
|
||||||
|
// Otherwise, we couldn't test this reliably.
|
||||||
|
for k, v := range fields {
|
||||||
|
kvpairs = append(kvpairs, fmt.Sprintf("%s=%v", k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(kvpairs)
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// 1. Is this thread-safe?
|
||||||
|
// 2. Add more tabs?
|
||||||
|
t.fieldOut = "\t\t"
|
||||||
|
|
||||||
|
for _, s := range kvpairs {
|
||||||
|
// TODO: Is %v the correct format string here?
|
||||||
|
t.fieldOut = fmt.Sprintf("%s %s", t.fieldOut, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
3
main.go
3
main.go
|
@ -4,10 +4,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
|
||||||
"github.com/docker/machine/commands"
|
"github.com/docker/machine/commands"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
"github.com/docker/machine/version"
|
"github.com/docker/machine/version"
|
||||||
)
|
)
|
||||||
|
@ -49,7 +49,6 @@ func main() {
|
||||||
for _, f := range os.Args {
|
for _, f := range os.Args {
|
||||||
if f == "-D" || f == "--debug" || f == "-debug" {
|
if f == "-D" || f == "--debug" || f == "-debug" {
|
||||||
os.Setenv("DEBUG", "1")
|
os.Setenv("DEBUG", "1")
|
||||||
initLogging(log.DebugLevel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import "net"
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func WaitForTCP(addr string) error {
|
func WaitForTCP(addr string) error {
|
||||||
for {
|
for {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -8,9 +10,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetHomeDir() string {
|
func GetHomeDir() string {
|
||||||
|
@ -126,3 +129,32 @@ func DumpVal(vals ...interface{}) {
|
||||||
log.Debug(string(prettyJSON))
|
log.Debug(string(prettyJSON))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Following two functions are from github.com/docker/docker/utils module. It
|
||||||
|
// was way overkill to include the whole module, so we just have these bits
|
||||||
|
// that we're using here.
|
||||||
|
func TruncateID(id string) string {
|
||||||
|
shortLen := 12
|
||||||
|
if len(id) < shortLen {
|
||||||
|
shortLen = len(id)
|
||||||
|
}
|
||||||
|
return id[:shortLen]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRandomID returns an unique id
|
||||||
|
func GenerateRandomID() string {
|
||||||
|
for {
|
||||||
|
id := make([]byte, 32)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, id); err != nil {
|
||||||
|
panic(err) // This shouldn't happen
|
||||||
|
}
|
||||||
|
value := hex.EncodeToString(id)
|
||||||
|
// if we try to parse the truncated for as an int and we don't have
|
||||||
|
// an error then the value is all numberic and causes issues when
|
||||||
|
// used as a hostname. ref #3869
|
||||||
|
if _, err := strconv.ParseInt(TruncateID(value), 10, 64); err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue