Merge pull request #1569 from bradrydzewski/master

backport ginrus logging and godotenv from 0.5
This commit is contained in:
Brad Rydzewski 2016-04-11 16:12:36 -07:00
commit 89f4f500f3
32 changed files with 1202 additions and 243 deletions

View file

@ -1,10 +0,0 @@
# build environment used in .drone.yml
#
# docker build --rm=true -t drone/golang:1.5 -f Dockerfile.env .
FROM golang:1.5
ADD contrib/*.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/setup-sassc.sh && \
chmod +x /usr/local/bin/setup-sqlite.sh && \
/usr/local/bin/setup-sassc.sh && \
/usr/local/bin/setup-sqlite.sh

View file

@ -1,7 +1,8 @@
package main package main
import ( import (
"flag" "net/http"
"time"
"github.com/drone/drone/engine" "github.com/drone/drone/engine"
"github.com/drone/drone/remote" "github.com/drone/drone/remote"
@ -10,27 +11,34 @@ import (
"github.com/drone/drone/router/middleware/context" "github.com/drone/drone/router/middleware/context"
"github.com/drone/drone/router/middleware/header" "github.com/drone/drone/router/middleware/header"
"github.com/drone/drone/shared/envconfig" "github.com/drone/drone/shared/envconfig"
"github.com/drone/drone/shared/server"
"github.com/drone/drone/store/datastore" "github.com/drone/drone/store/datastore"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/gin-gonic/contrib/ginrus"
"github.com/ianschenck/envflag"
_ "github.com/joho/godotenv/autoload"
) )
var ( var (
dotenv = flag.String("config", ".env", "") addr = envflag.String("SERVER_ADDR", ":8000", "")
debug = flag.Bool("debug", false, "") cert = envflag.String("SERVER_CERT", "", "")
key = envflag.String("SERVER_KEY", "", "")
debug = envflag.Bool("DEBUG", false, "")
) )
func main() { func main() {
flag.Parse() envflag.Parse()
// debug level if requested by user // debug level if requested by user
if *debug { if *debug {
logrus.SetLevel(logrus.DebugLevel) logrus.SetLevel(logrus.DebugLevel)
} else {
logrus.SetLevel(logrus.WarnLevel)
} }
// Load the configuration from env file // Load the configuration from env file
env := envconfig.Load(*dotenv) env := envconfig.Load(".env")
// Setup the database driver // Setup the database driver
store_ := datastore.Load(env) store_ := datastore.Load(env)
@ -42,14 +50,22 @@ func main() {
engine_ := engine.Load(env, store_) engine_ := engine.Load(env, store_)
// setup the server and start the listener // setup the server and start the listener
server_ := server.Load(env) handler := router.Load(
server_.Run( ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true),
router.Load( header.Version,
header.Version, cache.Default(),
cache.Default(), context.SetStore(store_),
context.SetStore(store_), context.SetRemote(remote_),
context.SetRemote(remote_), context.SetEngine(engine_),
context.SetEngine(engine_),
),
) )
if *cert != "" {
logrus.Fatal(
http.ListenAndServeTLS(*addr, *cert, *key, handler),
)
} else {
logrus.Fatal(
http.ListenAndServe(*addr, handler),
)
}
} }

View file

@ -17,7 +17,9 @@ import (
) )
func Load(middleware ...gin.HandlerFunc) http.Handler { func Load(middleware ...gin.HandlerFunc) http.Handler {
e := gin.Default() e := gin.New()
e.Use(gin.Recovery())
e.SetHTMLTemplate(template.Load()) e.SetHTMLTemplate(template.Load())
e.StaticFS("/static", static.FileSystem()) e.StaticFS("/static", static.FileSystem())

View file

@ -1,36 +0,0 @@
package server
import (
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/shared/envconfig"
)
type Server struct {
Addr string
Cert string
Key string
}
func Load(env envconfig.Env) *Server {
return &Server{
Addr: env.String("SERVER_ADDR", ":8000"),
Cert: env.String("SERVER_CERT", ""),
Key: env.String("SERVER_KEY", ""),
}
}
func (s *Server) Run(handler http.Handler) {
log.Infof("starting server %s", s.Addr)
if len(s.Cert) != 0 {
log.Fatal(
http.ListenAndServeTLS(s.Addr, s.Cert, s.Key, handler),
)
} else {
log.Fatal(
http.ListenAndServe(s.Addr, handler),
)
}
}

66
vendor/github.com/Sirupsen/logrus/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,66 @@
# 0.10.0
* feature: Add a test hook (#180)
* feature: `ParseLevel` is now case-insensitive (#326)
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
* performance: avoid re-allocations on `WithFields` (#335)
# 0.9.0
* logrus/text_formatter: don't emit empty msg
* logrus/hooks/airbrake: move out of main repository
* logrus/hooks/sentry: move out of main repository
* logrus/hooks/papertrail: move out of main repository
* logrus/hooks/bugsnag: move out of main repository
* logrus/core: run tests with `-race`
* logrus/core: detect TTY based on `stderr`
* logrus/core: support `WithError` on logger
* logrus/core: Solaris support
# 0.8.7
* logrus/core: fix possible race (#216)
* logrus/doc: small typo fixes and doc improvements
# 0.8.6
* hooks/raven: allow passing an initialized client
# 0.8.5
* logrus/core: revert #208
# 0.8.4
* formatter/text: fix data race (#218)
# 0.8.3
* logrus/core: fix entry log level (#208)
* logrus/core: improve performance of text formatter by 40%
* logrus/core: expose `LevelHooks` type
* logrus/core: add support for DragonflyBSD and NetBSD
* formatter/text: print structs more verbosely
# 0.8.2
* logrus: fix more Fatal family functions
# 0.8.1
* logrus: fix not exiting on `Fatalf` and `Fatalln`
# 0.8.0
* logrus: defaults to stderr instead of stdout
* hooks/sentry: add special field for `*http.Request`
* formatter/text: ignore Windows for colors
# 0.7.3
* formatter/\*: allow configuration of timestamp layout
# 0.7.2
* formatter/text: Add configuration option for time format (#158)

View file

@ -1,4 +1,4 @@
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus)&nbsp;[![godoc reference](https://godoc.org/github.com/Sirupsen/logrus?status.png)][godoc] # Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus)&nbsp;[![GoDoc](https://godoc.org/github.com/Sirupsen/logrus?status.svg)](https://godoc.org/github.com/Sirupsen/logrus)
Logrus is a structured logger for Go (golang), completely API compatible with 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 the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
@ -12,7 +12,7 @@ plain text):
![Colored](http://i.imgur.com/PY7qMwd.png) ![Colored](http://i.imgur.com/PY7qMwd.png)
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
or Splunk: or Splunk:
```json ```json
@ -32,16 +32,18 @@ ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"} "time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
``` ```
With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
attached, the output is compatible with the attached, the output is compatible with the
[logfmt](http://godoc.org/github.com/kr/logfmt) format: [logfmt](http://godoc.org/github.com/kr/logfmt) format:
```text ```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="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122 time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10 time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9 time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100 time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
exit status 1
``` ```
#### Example #### Example
@ -73,17 +75,12 @@ package main
import ( import (
"os" "os"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake"
) )
func init() { func init() {
// Log as JSON instead of the default ASCII formatter. // Log as JSON instead of the default ASCII formatter.
log.SetFormatter(&log.JSONFormatter{}) 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. // Output to stderr instead of stdout, could also be a file.
log.SetOutput(os.Stderr) log.SetOutput(os.Stderr)
@ -106,6 +103,16 @@ func main() {
"omg": true, "omg": true,
"number": 100, "number": 100,
}).Fatal("The ice breaks!") }).Fatal("The ice breaks!")
// A common pattern is to re-use fields between logging statements by re-using
// the logrus.Entry returned from WithFields()
contextLogger := log.WithFields(log.Fields{
"common": "this is a common field",
"other": "I also should be logged always",
})
contextLogger.Info("I'll be logged with common and other field")
contextLogger.Info("Me too")
} }
``` ```
@ -164,54 +171,22 @@ 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 tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
multiple places simultaneously, e.g. syslog. multiple places simultaneously, e.g. syslog.
```go Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
// Not the real implementation of the Airbrake hook. Just a simple sample. `init`:
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 ```go
import ( import (
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake" "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
"github.com/Sirupsen/logrus/hooks/syslog" logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
"log/syslog" "log/syslog"
) )
func init() { func init() {
log.AddHook(new(logrus_airbrake.AirbrakeHook))
// 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(airbrake.NewHook(123, "xyz", "production"))
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil { if err != nil {
@ -221,26 +196,37 @@ func init() {
} }
} }
``` ```
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) | Hook | Description |
Send errors to an exception tracking service compatible with the Airbrake API. | ----- | ----------- |
Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
| [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. |
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
* [`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.
* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly)
Send logs to Loggly (https://www.loggly.com/)
* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus)
Hook for Slack chat.
#### Level logging #### Level logging
@ -296,10 +282,10 @@ init() {
// do something here to set environment depending on an environment variable // do something here to set environment depending on an environment variable
// or command-line flag // or command-line flag
if Environment == "production" { if Environment == "production" {
log.SetFormatter(logrus.JSONFormatter) log.SetFormatter(&log.JSONFormatter{})
} else { } else {
// The TextFormatter is default, you don't actually have to do this. // The TextFormatter is default, you don't actually have to do this.
log.SetFormatter(logrus.TextFormatter) log.SetFormatter(&log.TextFormatter{})
} }
} }
``` ```
@ -318,10 +304,16 @@ The built-in logging formatters are:
field to `true`. To force no colored output even if there is a TTY set the field to `true`. To force no colored output even if there is a TTY set the
`DisableColors` field to `true` `DisableColors` field to `true`
* `logrus.JSONFormatter`. Logs fields as JSON. * `logrus.JSONFormatter`. Logs fields as JSON.
* `logrus/formatters/logstash.LogstashFormatter`. Logs fields as [Logstash](http://logstash.net) Events.
```go
logrus.SetFormatter(&logstash.LogstashFormatter{Type: "application_name"})
```
Third party logging formatters: Third party logging formatters:
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦. * [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
* [`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, You can define your formatter by implementing the `Formatter` interface,
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
@ -334,7 +326,7 @@ type MyJSONFormatter struct {
log.SetFormatter(new(MyJSONFormatter)) log.SetFormatter(new(MyJSONFormatter))
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
// Note this doesn't include Time, Level and Message which are available on // 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 // the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers. // source of the official loggers.
@ -348,7 +340,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
#### Logger as an `io.Writer` #### Logger as an `io.Writer`
Logrus can be transormed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it. Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
```go ```go
w := logger.Writer() w := logger.Writer()
@ -370,5 +362,27 @@ Log rotation is not provided with Logrus. Log rotation should be done by an
external program (like `logrotate(8)`) that can compress and delete old log external program (like `logrotate(8)`) that can compress and delete old log
entries. It should not be a feature of the application-level logger. entries. It should not be a feature of the application-level logger.
#### Tools
[godoc]: https://godoc.org/github.com/Sirupsen/logrus | Tool | Description |
| ---- | ----------- |
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
#### Testing
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
```go
logger, hook := NewNullLogger()
logger.Error("Hello error")
assert.Equal(1, len(hook.Entries))
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
assert.Equal("Hello error", hook.LastEntry().Message)
hook.Reset()
assert.Nil(hook.LastEntry())
```

26
vendor/github.com/Sirupsen/logrus/doc.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
/*
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
The simplest way to use Logrus is simply the package-level exported logger:
package main
import (
log "github.com/Sirupsen/logrus"
)
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
"number": 1,
"size": 10,
}).Info("A walrus appears")
}
Output:
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
For a full guide visit https://github.com/Sirupsen/logrus
*/
package logrus

View file

@ -8,6 +8,9 @@ import (
"time" "time"
) )
// Defines the key when adding errors using WithError.
var ErrorKey = "error"
// An entry is the final or intermediate Logrus logging entry. It contains all // 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, // 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 // Warn, Error, Fatal or Panic is called on it. These objects can be reused and
@ -53,6 +56,11 @@ func (entry *Entry) String() (string, error) {
return reader.String(), err return reader.String(), err
} }
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
func (entry *Entry) WithError(err error) *Entry {
return entry.WithField(ErrorKey, err)
}
// Add a single field to the Entry. // Add a single field to the Entry.
func (entry *Entry) WithField(key string, value interface{}) *Entry { func (entry *Entry) WithField(key string, value interface{}) *Entry {
return entry.WithFields(Fields{key: value}) return entry.WithFields(Fields{key: value})
@ -60,7 +68,7 @@ func (entry *Entry) WithField(key string, value interface{}) *Entry {
// Add a map of fields to the Entry. // Add a map of fields to the Entry.
func (entry *Entry) WithFields(fields Fields) *Entry { func (entry *Entry) WithFields(fields Fields) *Entry {
data := Fields{} data := make(Fields, len(entry.Data)+len(fields))
for k, v := range entry.Data { for k, v := range entry.Data {
data[k] = v data[k] = v
} }
@ -70,12 +78,14 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
return &Entry{Logger: entry.Logger, Data: data} return &Entry{Logger: entry.Logger, Data: data}
} }
func (entry *Entry) log(level Level, msg string) { // This function is not declared with a pointer value because otherwise
// race conditions will occur when using multiple goroutines
func (entry Entry) log(level Level, msg string) {
entry.Time = time.Now() entry.Time = time.Now()
entry.Level = level entry.Level = level
entry.Message = msg entry.Message = msg
if err := entry.Logger.Hooks.Fire(level, entry); err != nil { if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
entry.Logger.mu.Lock() entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
entry.Logger.mu.Unlock() entry.Logger.mu.Unlock()
@ -100,7 +110,7 @@ func (entry *Entry) log(level Level, msg string) {
// panic() to use in Entry#Panic(), we avoid the allocation by checking // panic() to use in Entry#Panic(), we avoid the allocation by checking
// directly here. // directly here.
if level <= PanicLevel { if level <= PanicLevel {
panic(entry) panic(&entry)
} }
} }
@ -188,6 +198,7 @@ func (entry *Entry) Fatalf(format string, args ...interface{}) {
if entry.Logger.Level >= FatalLevel { if entry.Logger.Level >= FatalLevel {
entry.Fatal(fmt.Sprintf(format, args...)) entry.Fatal(fmt.Sprintf(format, args...))
} }
os.Exit(1)
} }
func (entry *Entry) Panicf(format string, args ...interface{}) { func (entry *Entry) Panicf(format string, args ...interface{}) {
@ -234,6 +245,7 @@ func (entry *Entry) Fatalln(args ...interface{}) {
if entry.Logger.Level >= FatalLevel { if entry.Logger.Level >= FatalLevel {
entry.Fatal(entry.sprintlnn(args...)) entry.Fatal(entry.sprintlnn(args...))
} }
os.Exit(1)
} }
func (entry *Entry) Panicln(args ...interface{}) { func (entry *Entry) Panicln(args ...interface{}) {

View file

@ -36,6 +36,8 @@ func SetLevel(level Level) {
// GetLevel returns the standard logger level. // GetLevel returns the standard logger level.
func GetLevel() Level { func GetLevel() Level {
std.mu.Lock()
defer std.mu.Unlock()
return std.Level return std.Level
} }
@ -46,6 +48,11 @@ func AddHook(hook Hook) {
std.Hooks.Add(hook) std.Hooks.Add(hook)
} }
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
func WithError(err error) *Entry {
return std.WithField(ErrorKey, err)
}
// WithField creates an entry from the standard logger and adds a field to // WithField creates an entry from the standard logger and adds a field to
// it. If you want multiple fields, use `WithFields`. // it. If you want multiple fields, use `WithFields`.
// //

View file

@ -1,5 +1,9 @@
package logrus package logrus
import "time"
const DefaultTimestampFormat = time.RFC3339
// The Formatter interface is used to implement a custom Formatter. It takes an // The Formatter interface is used to implement a custom Formatter. It takes an
// `Entry`. It exposes all the fields, including the default ones: // `Entry`. It exposes all the fields, including the default ones:
// //

View file

@ -11,11 +11,11 @@ type Hook interface {
} }
// Internal type for storing the hooks on a logger instance. // Internal type for storing the hooks on a logger instance.
type levelHooks map[Level][]Hook type LevelHooks map[Level][]Hook
// Add a hook to an instance of logger. This is called with // Add a hook to an instance of logger. This is called with
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. // `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
func (hooks levelHooks) Add(hook Hook) { func (hooks LevelHooks) Add(hook Hook) {
for _, level := range hook.Levels() { for _, level := range hook.Levels() {
hooks[level] = append(hooks[level], hook) hooks[level] = append(hooks[level], hook)
} }
@ -23,7 +23,7 @@ func (hooks levelHooks) Add(hook Hook) {
// Fire all the hooks for the passed level. Used by `entry.log` to fire // Fire all the hooks for the passed level. Used by `entry.log` to fire
// appropriate hooks for a log entry. // appropriate hooks for a log entry.
func (hooks levelHooks) Fire(level Level, entry *Entry) error { func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
for _, hook := range hooks[level] { for _, hook := range hooks[level] {
if err := hook.Fire(entry); err != nil { if err := hook.Fire(entry); err != nil {
return err return err

View file

@ -3,18 +3,33 @@ package logrus
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"time"
) )
type JSONFormatter struct{} type JSONFormatter struct {
// TimestampFormat sets the format used for marshaling timestamps.
TimestampFormat string
}
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
data := make(Fields, len(entry.Data)+3) data := make(Fields, len(entry.Data)+3)
for k, v := range entry.Data { for k, v := range entry.Data {
data[k] = v switch v := v.(type) {
case error:
// Otherwise errors are ignored by `encoding/json`
// https://github.com/Sirupsen/logrus/issues/137
data[k] = v.Error()
default:
data[k] = v
}
} }
prefixFieldClashes(data) prefixFieldClashes(data)
data["time"] = entry.Time.Format(time.RFC3339)
timestampFormat := f.TimestampFormat
if timestampFormat == "" {
timestampFormat = DefaultTimestampFormat
}
data["time"] = entry.Time.Format(timestampFormat)
data["msg"] = entry.Message data["msg"] = entry.Message
data["level"] = entry.Level.String() data["level"] = entry.Level.String()

View file

@ -8,13 +8,13 @@ import (
type Logger struct { type Logger struct {
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a // 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 // file, or leave it default which is `os.Stderr`. You can also set this to
// something more adventorous, such as logging to Kafka. // something more adventorous, such as logging to Kafka.
Out io.Writer Out io.Writer
// Hooks for the logger instance. These allow firing events based on logging // 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 // levels and log entries. For example, to send errors to an error tracking
// service, log to StatsD or dump the core on fatal errors. // service, log to StatsD or dump the core on fatal errors.
Hooks levelHooks Hooks LevelHooks
// All log entries pass through the formatter before logged to Out. The // All log entries pass through the formatter before logged to Out. The
// included formatters are `TextFormatter` and `JSONFormatter` for which // included formatters are `TextFormatter` and `JSONFormatter` for which
// TextFormatter is the default. In development (when a TTY is attached) it // TextFormatter is the default. In development (when a TTY is attached) it
@ -37,23 +37,23 @@ type Logger struct {
// var log = &Logger{ // var log = &Logger{
// Out: os.Stderr, // Out: os.Stderr,
// Formatter: new(JSONFormatter), // Formatter: new(JSONFormatter),
// Hooks: make(levelHooks), // Hooks: make(LevelHooks),
// Level: logrus.DebugLevel, // Level: logrus.DebugLevel,
// } // }
// //
// It's recommended to make this a global instance called `log`. // It's recommended to make this a global instance called `log`.
func New() *Logger { func New() *Logger {
return &Logger{ return &Logger{
Out: os.Stdout, Out: os.Stderr,
Formatter: new(TextFormatter), Formatter: new(TextFormatter),
Hooks: make(levelHooks), Hooks: make(LevelHooks),
Level: InfoLevel, Level: InfoLevel,
} }
} }
// Adds a field to the log entry, note that you it doesn't log until you call // 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. // Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
// Ff you want multiple fields, use `WithFields`. // If you want multiple fields, use `WithFields`.
func (logger *Logger) WithField(key string, value interface{}) *Entry { func (logger *Logger) WithField(key string, value interface{}) *Entry {
return NewEntry(logger).WithField(key, value) return NewEntry(logger).WithField(key, value)
} }
@ -64,12 +64,22 @@ func (logger *Logger) WithFields(fields Fields) *Entry {
return NewEntry(logger).WithFields(fields) return NewEntry(logger).WithFields(fields)
} }
// Add an error as single field to the log entry. All it does is call
// `WithError` for the given `error`.
func (logger *Logger) WithError(err error) *Entry {
return NewEntry(logger).WithError(err)
}
func (logger *Logger) Debugf(format string, args ...interface{}) { func (logger *Logger) Debugf(format string, args ...interface{}) {
NewEntry(logger).Debugf(format, args...) if logger.Level >= DebugLevel {
NewEntry(logger).Debugf(format, args...)
}
} }
func (logger *Logger) Infof(format string, args ...interface{}) { func (logger *Logger) Infof(format string, args ...interface{}) {
NewEntry(logger).Infof(format, args...) if logger.Level >= InfoLevel {
NewEntry(logger).Infof(format, args...)
}
} }
func (logger *Logger) Printf(format string, args ...interface{}) { func (logger *Logger) Printf(format string, args ...interface{}) {
@ -77,31 +87,46 @@ func (logger *Logger) Printf(format string, args ...interface{}) {
} }
func (logger *Logger) Warnf(format string, args ...interface{}) { func (logger *Logger) Warnf(format string, args ...interface{}) {
NewEntry(logger).Warnf(format, args...) if logger.Level >= WarnLevel {
NewEntry(logger).Warnf(format, args...)
}
} }
func (logger *Logger) Warningf(format string, args ...interface{}) { func (logger *Logger) Warningf(format string, args ...interface{}) {
NewEntry(logger).Warnf(format, args...) if logger.Level >= WarnLevel {
NewEntry(logger).Warnf(format, args...)
}
} }
func (logger *Logger) Errorf(format string, args ...interface{}) { func (logger *Logger) Errorf(format string, args ...interface{}) {
NewEntry(logger).Errorf(format, args...) if logger.Level >= ErrorLevel {
NewEntry(logger).Errorf(format, args...)
}
} }
func (logger *Logger) Fatalf(format string, args ...interface{}) { func (logger *Logger) Fatalf(format string, args ...interface{}) {
NewEntry(logger).Fatalf(format, args...) if logger.Level >= FatalLevel {
NewEntry(logger).Fatalf(format, args...)
}
os.Exit(1)
} }
func (logger *Logger) Panicf(format string, args ...interface{}) { func (logger *Logger) Panicf(format string, args ...interface{}) {
NewEntry(logger).Panicf(format, args...) if logger.Level >= PanicLevel {
NewEntry(logger).Panicf(format, args...)
}
} }
func (logger *Logger) Debug(args ...interface{}) { func (logger *Logger) Debug(args ...interface{}) {
NewEntry(logger).Debug(args...) if logger.Level >= DebugLevel {
NewEntry(logger).Debug(args...)
}
} }
func (logger *Logger) Info(args ...interface{}) { func (logger *Logger) Info(args ...interface{}) {
NewEntry(logger).Info(args...) if logger.Level >= InfoLevel {
NewEntry(logger).Info(args...)
}
} }
func (logger *Logger) Print(args ...interface{}) { func (logger *Logger) Print(args ...interface{}) {
@ -109,31 +134,46 @@ func (logger *Logger) Print(args ...interface{}) {
} }
func (logger *Logger) Warn(args ...interface{}) { func (logger *Logger) Warn(args ...interface{}) {
NewEntry(logger).Warn(args...) if logger.Level >= WarnLevel {
NewEntry(logger).Warn(args...)
}
} }
func (logger *Logger) Warning(args ...interface{}) { func (logger *Logger) Warning(args ...interface{}) {
NewEntry(logger).Warn(args...) if logger.Level >= WarnLevel {
NewEntry(logger).Warn(args...)
}
} }
func (logger *Logger) Error(args ...interface{}) { func (logger *Logger) Error(args ...interface{}) {
NewEntry(logger).Error(args...) if logger.Level >= ErrorLevel {
NewEntry(logger).Error(args...)
}
} }
func (logger *Logger) Fatal(args ...interface{}) { func (logger *Logger) Fatal(args ...interface{}) {
NewEntry(logger).Fatal(args...) if logger.Level >= FatalLevel {
NewEntry(logger).Fatal(args...)
}
os.Exit(1)
} }
func (logger *Logger) Panic(args ...interface{}) { func (logger *Logger) Panic(args ...interface{}) {
NewEntry(logger).Panic(args...) if logger.Level >= PanicLevel {
NewEntry(logger).Panic(args...)
}
} }
func (logger *Logger) Debugln(args ...interface{}) { func (logger *Logger) Debugln(args ...interface{}) {
NewEntry(logger).Debugln(args...) if logger.Level >= DebugLevel {
NewEntry(logger).Debugln(args...)
}
} }
func (logger *Logger) Infoln(args ...interface{}) { func (logger *Logger) Infoln(args ...interface{}) {
NewEntry(logger).Infoln(args...) if logger.Level >= InfoLevel {
NewEntry(logger).Infoln(args...)
}
} }
func (logger *Logger) Println(args ...interface{}) { func (logger *Logger) Println(args ...interface{}) {
@ -141,21 +181,32 @@ func (logger *Logger) Println(args ...interface{}) {
} }
func (logger *Logger) Warnln(args ...interface{}) { func (logger *Logger) Warnln(args ...interface{}) {
NewEntry(logger).Warnln(args...) if logger.Level >= WarnLevel {
NewEntry(logger).Warnln(args...)
}
} }
func (logger *Logger) Warningln(args ...interface{}) { func (logger *Logger) Warningln(args ...interface{}) {
NewEntry(logger).Warnln(args...) if logger.Level >= WarnLevel {
NewEntry(logger).Warnln(args...)
}
} }
func (logger *Logger) Errorln(args ...interface{}) { func (logger *Logger) Errorln(args ...interface{}) {
NewEntry(logger).Errorln(args...) if logger.Level >= ErrorLevel {
NewEntry(logger).Errorln(args...)
}
} }
func (logger *Logger) Fatalln(args ...interface{}) { func (logger *Logger) Fatalln(args ...interface{}) {
NewEntry(logger).Fatalln(args...) if logger.Level >= FatalLevel {
NewEntry(logger).Fatalln(args...)
}
os.Exit(1)
} }
func (logger *Logger) Panicln(args ...interface{}) { func (logger *Logger) Panicln(args ...interface{}) {
NewEntry(logger).Panicln(args...) if logger.Level >= PanicLevel {
NewEntry(logger).Panicln(args...)
}
} }

View file

@ -3,6 +3,7 @@ package logrus
import ( import (
"fmt" "fmt"
"log" "log"
"strings"
) )
// Fields type, used to pass to `WithFields`. // Fields type, used to pass to `WithFields`.
@ -33,7 +34,7 @@ func (level Level) String() string {
// ParseLevel takes a string level and returns the Logrus log level constant. // ParseLevel takes a string level and returns the Logrus log level constant.
func ParseLevel(lvl string) (Level, error) { func ParseLevel(lvl string) (Level, error) {
switch lvl { switch strings.ToLower(lvl) {
case "panic": case "panic":
return PanicLevel, nil return PanicLevel, nil
case "fatal": case "fatal":
@ -52,6 +53,16 @@ func ParseLevel(lvl string) (Level, error) {
return l, fmt.Errorf("not a valid logrus Level: %q", lvl) return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
} }
// A constant exposing all logging levels
var AllLevels = []Level{
PanicLevel,
FatalLevel,
ErrorLevel,
WarnLevel,
InfoLevel,
DebugLevel,
}
// These are the different logging levels. You can set the logging level to log // These are the different logging levels. You can set the logging level to log
// on your instance of logger, obtained with `logrus.New()`. // on your instance of logger, obtained with `logrus.New()`.
const ( const (
@ -74,7 +85,11 @@ const (
) )
// Won't compile if StdLogger can't be realized by a log.Logger // Won't compile if StdLogger can't be realized by a log.Logger
var _ StdLogger = &log.Logger{} var (
_ StdLogger = &log.Logger{}
_ StdLogger = &Entry{}
_ StdLogger = &Logger{}
)
// StdLogger is what your logrus-enabled library should take, that way // 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 // it'll accept a stdlib logger and a logrus logger. There's no standard
@ -92,3 +107,37 @@ type StdLogger interface {
Panicf(string, ...interface{}) Panicf(string, ...interface{})
Panicln(...interface{}) Panicln(...interface{})
} }
// The FieldLogger interface generalizes the Entry and Logger types
type FieldLogger interface {
WithField(key string, value interface{}) *Entry
WithFields(fields Fields) *Entry
WithError(err error) *Entry
Debugf(format string, args ...interface{})
Infof(format string, args ...interface{})
Printf(format string, args ...interface{})
Warnf(format string, args ...interface{})
Warningf(format string, args ...interface{})
Errorf(format string, args ...interface{})
Fatalf(format string, args ...interface{})
Panicf(format string, args ...interface{})
Debug(args ...interface{})
Info(args ...interface{})
Print(args ...interface{})
Warn(args ...interface{})
Warning(args ...interface{})
Error(args ...interface{})
Fatal(args ...interface{})
Panic(args ...interface{})
Debugln(args ...interface{})
Infoln(args ...interface{})
Println(args ...interface{})
Warnln(args ...interface{})
Warningln(args ...interface{})
Errorln(args ...interface{})
Fatalln(args ...interface{})
Panicln(args ...interface{})
}

View file

@ -1,3 +1,4 @@
// +build darwin freebsd openbsd netbsd dragonfly
package logrus package logrus

View file

@ -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

View file

@ -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
}

View file

@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build linux darwin freebsd openbsd // +build linux darwin freebsd openbsd netbsd dragonfly
package logrus package logrus
@ -12,9 +12,9 @@ import (
"unsafe" "unsafe"
) )
// IsTerminal returns true if the given file descriptor is a terminal. // IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal() bool { func IsTerminal() bool {
fd := syscall.Stdout fd := syscall.Stderr
var termios Termios var termios Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0 return err == 0

15
vendor/github.com/Sirupsen/logrus/terminal_solaris.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
// +build solaris
package logrus
import (
"os"
"golang.org/x/sys/unix"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
_, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA)
return err == nil
}

View file

@ -18,9 +18,9 @@ var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode") procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
) )
// IsTerminal returns true if the given file descriptor is a terminal. // IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal() bool { func IsTerminal() bool {
fd := syscall.Stdout fd := syscall.Stderr
var st uint32 var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0 return r != 0 && e == 0

View file

@ -3,7 +3,7 @@ package logrus
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"regexp" "runtime"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -15,12 +15,12 @@ const (
green = 32 green = 32
yellow = 33 yellow = 33
blue = 34 blue = 34
gray = 37
) )
var ( var (
baseTimestamp time.Time baseTimestamp time.Time
isTerminal bool isTerminal bool
noQuoteNeeded *regexp.Regexp
) )
func init() { func init() {
@ -34,35 +34,59 @@ func miniTS() int {
type TextFormatter struct { type TextFormatter struct {
// Set to true to bypass checking for a TTY before outputting colors. // Set to true to bypass checking for a TTY before outputting colors.
ForceColors bool ForceColors bool
// Force disabling colors.
DisableColors bool DisableColors bool
// Set to true to disable timestamp logging (useful when the output
// is redirected to a logging system already adding a timestamp) // Disable timestamp logging. useful when output is redirected to logging
// system that already adds timestamps.
DisableTimestamp bool DisableTimestamp bool
// Enable logging the full timestamp when a TTY is attached instead of just
// the time passed since beginning of execution.
FullTimestamp bool
// TimestampFormat to use for display when a full timestamp is printed
TimestampFormat string
// The fields are sorted by default for a consistent output. For applications
// that log extremely frequently and don't use the JSON formatter this may not
// be desired.
DisableSorting bool
} }
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
var keys []string = make([]string, 0, len(entry.Data)) var keys []string = make([]string, 0, len(entry.Data))
for k := range entry.Data { for k := range entry.Data {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys)
if !f.DisableSorting {
sort.Strings(keys)
}
b := &bytes.Buffer{} b := &bytes.Buffer{}
prefixFieldClashes(entry.Data) prefixFieldClashes(entry.Data)
isColored := (f.ForceColors || isTerminal) && !f.DisableColors isColorTerminal := isTerminal && (runtime.GOOS != "windows")
isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
timestampFormat := f.TimestampFormat
if timestampFormat == "" {
timestampFormat = DefaultTimestampFormat
}
if isColored { if isColored {
printColored(b, entry, keys) f.printColored(b, entry, keys, timestampFormat)
} else { } else {
if !f.DisableTimestamp { if !f.DisableTimestamp {
f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339)) f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
} }
f.appendKeyValue(b, "level", entry.Level.String()) f.appendKeyValue(b, "level", entry.Level.String())
f.appendKeyValue(b, "msg", entry.Message) if entry.Message != "" {
f.appendKeyValue(b, "msg", entry.Message)
}
for _, key := range keys { for _, key := range keys {
f.appendKeyValue(b, key, entry.Data[key]) f.appendKeyValue(b, key, entry.Data[key])
} }
@ -72,9 +96,11 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
return b.Bytes(), nil return b.Bytes(), nil
} }
func printColored(b *bytes.Buffer, entry *Entry, keys []string) { func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
var levelColor int var levelColor int
switch entry.Level { switch entry.Level {
case DebugLevel:
levelColor = gray
case WarnLevel: case WarnLevel:
levelColor = yellow levelColor = yellow
case ErrorLevel, FatalLevel, PanicLevel: case ErrorLevel, FatalLevel, PanicLevel:
@ -85,10 +111,14 @@ func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
levelText := strings.ToUpper(entry.Level.String())[0:4] levelText := strings.ToUpper(entry.Level.String())[0:4]
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) if !f.FullTimestamp {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
} else {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
}
for _, k := range keys { for _, k := range keys {
v := entry.Data[k] v := entry.Data[k]
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v) fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
} }
} }
@ -96,7 +126,7 @@ func needsQuoting(text string) bool {
for _, ch := range text { for _, ch := range text {
if !((ch >= 'a' && ch <= 'z') || if !((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') || (ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch < '9') || (ch >= '0' && ch <= '9') ||
ch == '-' || ch == '.') { ch == '-' || ch == '.') {
return false return false
} }
@ -104,21 +134,28 @@ func needsQuoting(text string) bool {
return true return true
} }
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) { func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
switch value.(type) {
b.WriteString(key)
b.WriteByte('=')
switch value := value.(type) {
case string: case string:
if needsQuoting(value.(string)) { if needsQuoting(value) {
fmt.Fprintf(b, "%v=%s ", key, value) b.WriteString(value)
} else { } else {
fmt.Fprintf(b, "%v=%q ", key, value) fmt.Fprintf(b, "%q", value)
} }
case error: case error:
if needsQuoting(value.(error).Error()) { errmsg := value.Error()
fmt.Fprintf(b, "%v=%s ", key, value) if needsQuoting(errmsg) {
b.WriteString(errmsg)
} else { } else {
fmt.Fprintf(b, "%v=%q ", key, value) fmt.Fprintf(b, "%q", value)
} }
default: default:
fmt.Fprintf(b, "%v=%v ", key, value) fmt.Fprint(b, value)
} }
b.WriteByte(' ')
} }

View file

@ -6,7 +6,7 @@ import (
"runtime" "runtime"
) )
func (logger *Logger) Writer() (*io.PipeWriter) { func (logger *Logger) Writer() *io.PipeWriter {
reader, writer := io.Pipe() reader, writer := io.Pipe()
go logger.writerScanner(reader) go logger.writerScanner(reader)

51
vendor/github.com/gin-gonic/contrib/ginrus/ginrus.go generated vendored Normal file
View file

@ -0,0 +1,51 @@
// Package ginrus provides log handling using logrus package.
//
// Based on github.com/stephenmuss/ginerus but adds more options.
package ginrus
import (
"time"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
)
// Ginrus returns a gin.HandlerFunc (middleware) that logs requests using logrus.
//
// Requests with errors are logged using logrus.Error().
// Requests without errors are logged using logrus.Info().
//
// It receives:
// 1. A time package format string (e.g. time.RFC3339).
// 2. A boolean stating whether to use UTC time zone or local.
func Ginrus(logger *logrus.Logger, timeFormat string, utc bool) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
// some evil middlewares modify this values
path := c.Request.URL.Path
c.Next()
end := time.Now()
latency := end.Sub(start)
if utc {
end = end.UTC()
}
entry := logger.WithFields(logrus.Fields{
"status": c.Writer.Status(),
"method": c.Request.Method,
"path": path,
"ip": c.ClientIP(),
"latency": latency,
"user-agent": c.Request.UserAgent(),
"time": end.Format(timeFormat),
})
if len(c.Errors) > 0 {
// Append error field if this is an erroneous request.
entry.Error(c.Errors.String())
} else {
entry.Info()
}
}
}

27
vendor/github.com/ianschenck/envflag/LICENSE generated vendored Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2013, Ian Schenck
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
Neither the name of Ian Schenck nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

37
vendor/github.com/ianschenck/envflag/README.md generated vendored Normal file
View file

@ -0,0 +1,37 @@
envflag
=======
Golang flags, but bolted onto the environment rather than the command-line.
Read the [godocs](http://godoc.org/github.com/ianschenck/envflag).
Motivation
==========
Some like the distinction that command-line flags control behavior
while environment variables configure. Also
[12-factor](http://12factor.net/) recommends the use of environment
variables for configuration. The interface of the golang flag package
is well designed and easy to use, and allows for other lists
(os.Environ() vs os.Args) to be parsed as flags. It makes sense then
to use the same interface, the same types, and the same parsing
(caveat: there is some ugly string hacking to make environment
variables look like flags) to the same ends.
Differences
===========
Calling `flag.Parse()` will not parse environment flags. Calling
`envflag.Parse()` will not parse command-line flags. There is no good
reason to combine these two when the net savings is a single line in a
`func main()`. Furthermore, doing so would require users to accept a
precedence order of my choosing.
The presence of an environment variable named `h` or `help` will
probably cause problems (print Usage and os.Exit(0)). Work around this
by defining those flags somewhere (and ignoring them).
Before calling `Flagset.Parse` on `EnvironmentFlags`, the environment
variables being passed to `Parse` are trimmed down using
`Lookup`. This behavior is different from `flag.Parse` in that extra
environment variables are ignored (and won't crash `envflag.Parse`).

192
vendor/github.com/ianschenck/envflag/envflag.go generated vendored Normal file
View file

@ -0,0 +1,192 @@
// Copyright 2013 Ian Schenck. Use of this source code is governed by
// a license that can be found in the LICENSE file.
/*
Package envflag adds environment variable flags to the flag package.
Usage:
Define flags using envflag.String(), Bool(), Int(), etc. This package
works nearly the same as the stdlib flag package. Parsing the
Environment flags is done by calling envflag.Parse()
It will *not* attempt to parse any normally-defined command-line
flags. Command-line flags are explicitly left alone and separate.
*/
package envflag
import (
"flag"
"fmt"
"os"
"strings"
"time"
)
// VisitAll visits the environment flags in lexicographical order,
// calling fn for each. It visits all flags, even those not set.
func VisitAll(fn func(*flag.Flag)) {
EnvironmentFlags.VisitAll(fn)
}
// Visit visits the environment flags in lexicographical order,
// calling fn for each. It visits only those flags that have been
// set.
func Visit(fn func(*flag.Flag)) {
EnvironmentFlags.Visit(fn)
}
// Lookup returns the Flag structure of the named environment flag,
// returning nil if none exists.
func Lookup(name string) *flag.Flag {
return EnvironmentFlags.Lookup(name)
}
// Set sets the value of the named environment flag.
func Set(name, value string) error {
return EnvironmentFlags.Set(name, value)
}
// BoolVar defines a bool flag with specified name, default value, and
// usage string. The argument p points to a bool variable in which to
// store the value of the flag.
func BoolVar(p *bool, name string, value bool, usage string) {
EnvironmentFlags.BoolVar(p, name, value, usage)
}
// Bool defines a bool flag with specified name, default value, and
// usage string. The return value is the address of a bool variable
// that stores the value of the flag.
func Bool(name string, value bool, usage string) *bool {
return EnvironmentFlags.Bool(name, value, usage)
}
// IntVar defines an int flag with specified name, default value, and
// usage string. The argument p points to an int variable in which to
// store the value of the flag.
func IntVar(p *int, name string, value int, usage string) {
EnvironmentFlags.IntVar(p, name, value, usage)
}
// Int defines an int flag with specified name, default value, and
// usage string. The return value is the address of an int variable
// that stores the value of the flag.
func Int(name string, value int, usage string) *int {
return EnvironmentFlags.Int(name, value, usage)
}
// Int64Var defines an int64 flag with specified name, default value,
// and usage string. The argument p points to an int64 variable in
// which to store the value of the flag.
func Int64Var(p *int64, name string, value int64, usage string) {
EnvironmentFlags.Int64Var(p, name, value, usage)
}
// Int64 defines an int64 flag with specified name, default value, and
// usage string. The return value is the address of an int64 variable
// that stores the value of the flag.
func Int64(name string, value int64, usage string) *int64 {
return EnvironmentFlags.Int64(name, value, usage)
}
// UintVar defines a uint flag with specified name, default value, and
// usage string. The argument p points to a uint variable in which to
// store the value of the flag.
func UintVar(p *uint, name string, value uint, usage string) {
EnvironmentFlags.UintVar(p, name, value, usage)
}
// Uint defines a uint flag with specified name, default value, and
// usage string. The return value is the address of a uint variable
// that stores the value of the flag.
func Uint(name string, value uint, usage string) *uint {
return EnvironmentFlags.Uint(name, value, usage)
}
// Uint64Var defines a uint64 flag with specified name, default value,
// and usage string. The argument p points to a uint64 variable in
// which to store the value of the flag.
func Uint64Var(p *uint64, name string, value uint64, usage string) {
EnvironmentFlags.Uint64Var(p, name, value, usage)
}
// Uint64 defines a uint64 flag with specified name, default value,
// and usage string. The return value is the address of a uint64
// variable that stores the value of the flag.
func Uint64(name string, value uint64, usage string) *uint64 {
return EnvironmentFlags.Uint64(name, value, usage)
}
// StringVar defines a string flag with specified name, default value,
// and usage string. The argument p points to a string variable in
// which to store the value of the flag.
func StringVar(p *string, name string, value string, usage string) {
EnvironmentFlags.StringVar(p, name, value, usage)
}
// String defines a string flag with specified name, default value,
// and usage string. The return value is the address of a string
// variable that stores the value of the flag.
func String(name string, value string, usage string) *string {
return EnvironmentFlags.String(name, value, usage)
}
// Float64Var defines a float64 flag with specified name, default
// value, and usage string. The argument p points to a float64
// variable in which to store the value of the flag.
func Float64Var(p *float64, name string, value float64, usage string) {
EnvironmentFlags.Float64Var(p, name, value, usage)
}
// Float64 defines a float64 flag with specified name, default value,
// and usage string. The return value is the address of a float64
// variable that stores the value of the flag.
func Float64(name string, value float64, usage string) *float64 {
return EnvironmentFlags.Float64(name, value, usage)
}
// DurationVar defines a time.Duration flag with specified name,
// default value, and usage string. The argument p points to a
// time.Duration variable in which to store the value of the flag.
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
EnvironmentFlags.DurationVar(p, name, value, usage)
}
// Duration defines a time.Duration flag with specified name, default
// value, and usage string. The return value is the address of a
// time.Duration variable that stores the value of the flag.
func Duration(name string, value time.Duration, usage string) *time.Duration {
return EnvironmentFlags.Duration(name, value, usage)
}
// PrintDefaults prints to standard error the default values of all
// defined environment flags.
func PrintDefaults() {
EnvironmentFlags.PrintDefaults()
}
// Parse parses the environment flags from os.Environ. Must be called
// after all flags are defined and before flags are accessed by the
// program.
func Parse() {
env := os.Environ()
// Clean up and "fake" some flag k/v pairs.
args := make([]string, 0, len(env))
for _, value := range env {
if Lookup(value[:strings.Index(value, "=")]) == nil {
continue
}
args = append(args, fmt.Sprintf("-%s", value))
}
EnvironmentFlags.Parse(args)
}
// Parsed returns true if the environment flags have been parsed.
func Parsed() bool {
return EnvironmentFlags.Parsed()
}
// EnvironmentFlags is the default set of environment flags, parsed
// from os.Environ(). The top-level functions such as BoolVar, Arg,
// and on are wrappers for the methods of EnvironmentFlags.
var EnvironmentFlags = flag.NewFlagSet("environment", flag.ExitOnError)

23
vendor/github.com/joho/godotenv/LICENCE generated vendored Normal file
View file

@ -0,0 +1,23 @@
Copyright (c) 2013 John Barton
MIT License
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.

127
vendor/github.com/joho/godotenv/README.md generated vendored Normal file
View file

@ -0,0 +1,127 @@
# GoDotEnv [![wercker status](https://app.wercker.com/status/507594c2ec7e60f19403a568dfea0f78 "wercker status")](https://app.wercker.com/project/bykey/507594c2ec7e60f19403a568dfea0f78)
A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file)
From the original Library:
> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environmentssuch as resource handles for databases or credentials for external servicesshould be extracted from the code into environment variables.
>
> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped.
It can be used as a library (for loading in env for your own daemons etc) or as a bin command.
There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows.
## Installation
As a library
```shell
go get github.com/joho/godotenv
```
or if you want to use it as a bin command
```shell
go get github.com/joho/godotenv/cmd/godotenv
```
## Usage
Add your application configuration to your `.env` file in the root of your project:
```shell
S3_BUCKET=YOURS3BUCKET
SECRET_KEY=YOURSECRETKEYGOESHERE
```
Then in your Go app you can do something like
```go
package main
import (
"github.com/joho/godotenv"
"log"
"os"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
s3Bucket := os.Getenv("S3_BUCKET")
secretKey := os.Getenv("SECRET_KEY")
// now do something with s3 or whatever
}
```
If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import
```go
import _ "github.com/joho/godotenv/autoload"
```
While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit
```go
_ = godotenv.Load("somerandomfile")
_ = godotenv.Load("filenumberone.env", "filenumbertwo.env")
```
If you want to be really fancy with your env file you can do comments and exports (below is a valid env file)
```shell
# I am a comment and that is OK
SOME_VAR=someval
FOO=BAR # comments at line end are OK too
export BAR=BAZ
```
Or finally you can do YAML(ish) style
```yaml
FOO: bar
BAR: baz
```
as a final aside, if you don't want godotenv munging your env you can just get a map back instead
```go
var myEnv map[string]string
myEnv, err := godotenv.Read()
s3Bucket := myEnv["S3_BUCKET"]
```
### Command Mode
Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH`
```
godotenv -f /some/path/to/.env some_command with some args
```
If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD`
## Contributing
Contributions are most welcome! The parser itself is pretty stupidly naive and I wouldn't be surprised if it breaks with edge cases.
*code changes without tests will not be accepted*
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Added some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
## CI
Linux: [![wercker status](https://app.wercker.com/status/507594c2ec7e60f19403a568dfea0f78/m "wercker status")](https://app.wercker.com/project/bykey/507594c2ec7e60f19403a568dfea0f78) Windows: [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4)](https://ci.appveyor.com/project/joho/godotenv)
## Who?
The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](http://whoisjohnbarton.com) based off the tests/fixtures in the original library.

15
vendor/github.com/joho/godotenv/autoload/autoload.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
package autoload
/*
You can just read the .env file on import just by doing
import _ "github.com/joho/godotenv/autoload"
And bob's your mother's brother
*/
import "github.com/joho/godotenv"
func init() {
godotenv.Load()
}

229
vendor/github.com/joho/godotenv/godotenv.go generated vendored Normal file
View file

@ -0,0 +1,229 @@
// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
//
// Examples/readme can be found on the github page at https://github.com/joho/godotenv
//
// The TL;DR is that you make a .env file that looks something like
//
// SOME_ENV_VAR=somevalue
//
// and then in your go code you can call
//
// godotenv.Load()
//
// and all the env vars declared in .env will be avaiable through os.Getenv("SOME_ENV_VAR")
package godotenv
import (
"bufio"
"errors"
"os"
"os/exec"
"strings"
)
// Load will read your env file(s) and load them into ENV for this process.
//
// Call this function as close as possible to the start of your program (ideally in main)
//
// If you call Load without any args it will default to loading .env in the current path
//
// You can otherwise tell it which files to load (there can be more than one) like
//
// godotenv.Load("fileone", "filetwo")
//
// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
func Load(filenames ...string) (err error) {
filenames = filenamesOrDefault(filenames)
for _, filename := range filenames {
err = loadFile(filename, false)
if err != nil {
return // return early on a spazout
}
}
return
}
// Overload will read your env file(s) and load them into ENV for this process.
//
// Call this function as close as possible to the start of your program (ideally in main)
//
// If you call Overload without any args it will default to loading .env in the current path
//
// You can otherwise tell it which files to load (there can be more than one) like
//
// godotenv.Overload("fileone", "filetwo")
//
// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
func Overload(filenames ...string) (err error) {
filenames = filenamesOrDefault(filenames)
for _, filename := range filenames {
err = loadFile(filename, true)
if err != nil {
return // return early on a spazout
}
}
return
}
// Read all env (with same file loading semantics as Load) but return values as
// a map rather than automatically writing values into env
func Read(filenames ...string) (envMap map[string]string, err error) {
filenames = filenamesOrDefault(filenames)
envMap = make(map[string]string)
for _, filename := range filenames {
individualEnvMap, individualErr := readFile(filename)
if individualErr != nil {
err = individualErr
return // return early on a spazout
}
for key, value := range individualEnvMap {
envMap[key] = value
}
}
return
}
// Exec loads env vars from the specified filenames (empty map falls back to default)
// then executes the cmd specified.
//
// Simply hooks up os.Stdin/err/out to the command and calls Run()
//
// If you want more fine grained control over your command it's recommended
// that you use `Load()` or `Read()` and the `os/exec` package yourself.
func Exec(filenames []string, cmd string, cmdArgs []string) error {
Load(filenames...)
command := exec.Command(cmd, cmdArgs...)
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
return command.Run()
}
func filenamesOrDefault(filenames []string) []string {
if len(filenames) == 0 {
return []string{".env"}
}
return filenames
}
func loadFile(filename string, overload bool) error {
envMap, err := readFile(filename)
if err != nil {
return err
}
for key, value := range envMap {
if os.Getenv(key) == "" || overload {
os.Setenv(key, value)
}
}
return nil
}
func readFile(filename string) (envMap map[string]string, err error) {
file, err := os.Open(filename)
if err != nil {
return
}
defer file.Close()
envMap = make(map[string]string)
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
for _, fullLine := range lines {
if !isIgnoredLine(fullLine) {
key, value, err := parseLine(fullLine)
if err == nil {
envMap[key] = value
}
}
}
return
}
func parseLine(line string) (key string, value string, err error) {
if len(line) == 0 {
err = errors.New("zero length string")
return
}
// ditch the comments (but keep quoted hashes)
if strings.Contains(line, "#") {
segmentsBetweenHashes := strings.Split(line, "#")
quotesAreOpen := false
var segmentsToKeep []string
for _, segment := range segmentsBetweenHashes {
if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 {
if quotesAreOpen {
quotesAreOpen = false
segmentsToKeep = append(segmentsToKeep, segment)
} else {
quotesAreOpen = true
}
}
if len(segmentsToKeep) == 0 || quotesAreOpen {
segmentsToKeep = append(segmentsToKeep, segment)
}
}
line = strings.Join(segmentsToKeep, "#")
}
// now split key from value
splitString := strings.SplitN(line, "=", 2)
if len(splitString) != 2 {
// try yaml mode!
splitString = strings.SplitN(line, ":", 2)
}
if len(splitString) != 2 {
err = errors.New("Can't separate key from value")
return
}
// Parse the key
key = splitString[0]
if strings.HasPrefix(key, "export") {
key = strings.TrimPrefix(key, "export")
}
key = strings.Trim(key, " ")
// Parse the value
value = splitString[1]
// trim
value = strings.Trim(value, " ")
// check if we've got quoted values
if strings.Count(value, "\"") == 2 || strings.Count(value, "'") == 2 {
// pull the quotes off the edges
value = strings.Trim(value, "\"'")
// expand quotes
value = strings.Replace(value, "\\\"", "\"", -1)
// expand newlines
value = strings.Replace(value, "\\n", "\n", -1)
}
return
}
func isIgnoredLine(line string) bool {
trimmedLine := strings.Trim(line, " \n\t")
return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#")
}

1
vendor/github.com/joho/godotenv/wercker.yml generated vendored Normal file
View file

@ -0,0 +1 @@
box: pjvds/golang

24
vendor/vendor.json vendored
View file

@ -9,8 +9,8 @@
}, },
{ {
"path": "github.com/Sirupsen/logrus", "path": "github.com/Sirupsen/logrus",
"revision": "273bd5984cd7deae8d4b71b0ba9bfc5767f7284b", "revision": "4b6ea7319e214d98c938f12692336f7ca9348d6b",
"revisionTime": "2015-02-17T12:42:44-05:00" "revisionTime": "2016-03-17T14:11:10Z"
}, },
{ {
"origin": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew", "origin": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew",
@ -53,6 +53,11 @@
"revision": "889391d730237f8aca06ce3e62975112983f96b4", "revision": "889391d730237f8aca06ce3e62975112983f96b4",
"revisionTime": "2016-01-23T18:11:54-03:00" "revisionTime": "2016-01-23T18:11:54-03:00"
}, },
{
"path": "github.com/gin-gonic/contrib/ginrus",
"revision": "14f66d54cdb96059bafca98665bcc6d9df4951f2",
"revisionTime": "2015-08-15T19:25:43+02:00"
},
{ {
"path": "github.com/gin-gonic/gin", "path": "github.com/gin-gonic/gin",
"revision": "3d002e382355cafc15d706b92899b1961d5b79e9", "revision": "3d002e382355cafc15d706b92899b1961d5b79e9",
@ -98,6 +103,21 @@
"revision": "1b0c7f6e9ab3d7f500fd7d50c7ad835ff428139b", "revision": "1b0c7f6e9ab3d7f500fd7d50c7ad835ff428139b",
"revisionTime": "2014-04-09T13:11:00+02:00" "revisionTime": "2014-04-09T13:11:00+02:00"
}, },
{
"path": "github.com/ianschenck/envflag",
"revision": "9111d830d133f952887a936367fb0211c3134f0d",
"revisionTime": "2014-07-20T15:03:42-06:00"
},
{
"path": "github.com/joho/godotenv",
"revision": "4ed13390c0acd2ff4e371e64d8b97c8954138243",
"revisionTime": "2015-09-07T11:02:28+10:00"
},
{
"path": "github.com/joho/godotenv/autoload",
"revision": "4ed13390c0acd2ff4e371e64d8b97c8954138243",
"revisionTime": "2015-09-07T11:02:28+10:00"
},
{ {
"path": "github.com/koding/cache", "path": "github.com/koding/cache",
"revision": "487fc0ca06f9aa1a02d796f5510784b47d5afae2", "revision": "487fc0ca06f9aa1a02d796f5510784b47d5afae2",