parent
464fb5de1c
commit
32a2b3a5c0
|
@ -7,12 +7,6 @@
|
||||||
"revision": "574d3147eee384229bf96a5d12c207fe7b5234f3",
|
"revision": "574d3147eee384229bf96a5d12c207fe7b5234f3",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"importpath": "github.com/Sirupsen/logrus",
|
|
||||||
"repository": "https://github.com/Sirupsen/logrus",
|
|
||||||
"revision": "61e43dc76f7ee59a82bdf3d71033dc12bea4c77d",
|
|
||||||
"branch": "master"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"importpath": "github.com/alecthomas/gometalinter",
|
"importpath": "github.com/alecthomas/gometalinter",
|
||||||
"repository": "https://github.com/alecthomas/gometalinter",
|
"repository": "https://github.com/alecthomas/gometalinter",
|
||||||
|
@ -129,7 +123,7 @@
|
||||||
{
|
{
|
||||||
"importpath": "github.com/matrix-org/dugong",
|
"importpath": "github.com/matrix-org/dugong",
|
||||||
"repository": "https://github.com/matrix-org/dugong",
|
"repository": "https://github.com/matrix-org/dugong",
|
||||||
"revision": "f04553160a2b197248e032f4fe23d57b9af20cde",
|
"revision": "a012152b8769963afc822a9af7a3eceac4d776e8",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -263,7 +257,7 @@
|
||||||
{
|
{
|
||||||
"importpath": "github.com/sirupsen/logrus",
|
"importpath": "github.com/sirupsen/logrus",
|
||||||
"repository": "https://github.com/sirupsen/logrus",
|
"repository": "https://github.com/sirupsen/logrus",
|
||||||
"revision": "61e43dc76f7ee59a82bdf3d71033dc12bea4c77d",
|
"revision": "89742aefa4b206dcf400792f3bd35b542998eb3b",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -332,24 +326,38 @@
|
||||||
{
|
{
|
||||||
"importpath": "golang.org/x/crypto/bcrypt",
|
"importpath": "golang.org/x/crypto/bcrypt",
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
"repository": "https://go.googlesource.com/crypto",
|
||||||
"revision": "0fe963104e9d1877082f8fb38f816fcd97eb1d10",
|
"revision": "9419663f5a44be8b34ca85f08abc5fe1be11f8a3",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"path": "/bcrypt"
|
"path": "/bcrypt"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"importpath": "golang.org/x/crypto/blowfish",
|
"importpath": "golang.org/x/crypto/blowfish",
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
"repository": "https://go.googlesource.com/crypto",
|
||||||
"revision": "0fe963104e9d1877082f8fb38f816fcd97eb1d10",
|
"revision": "9419663f5a44be8b34ca85f08abc5fe1be11f8a3",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"path": "/blowfish"
|
"path": "/blowfish"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"importpath": "golang.org/x/crypto/curve25519",
|
||||||
|
"repository": "https://go.googlesource.com/crypto",
|
||||||
|
"revision": "9419663f5a44be8b34ca85f08abc5fe1be11f8a3",
|
||||||
|
"branch": "master",
|
||||||
|
"path": "/curve25519"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"importpath": "golang.org/x/crypto/ed25519",
|
"importpath": "golang.org/x/crypto/ed25519",
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
"repository": "https://go.googlesource.com/crypto",
|
||||||
"revision": "77014cf7f9bde4925afeed52b7bf676d5f5b4285",
|
"revision": "9419663f5a44be8b34ca85f08abc5fe1be11f8a3",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"path": "/ed25519"
|
"path": "/ed25519"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"importpath": "golang.org/x/crypto/ssh",
|
||||||
|
"repository": "https://go.googlesource.com/crypto",
|
||||||
|
"revision": "9419663f5a44be8b34ca85f08abc5fe1be11f8a3",
|
||||||
|
"branch": "master",
|
||||||
|
"path": "/ssh"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"importpath": "golang.org/x/net/bpf",
|
"importpath": "golang.org/x/net/bpf",
|
||||||
"repository": "https://go.googlesource.com/net",
|
"repository": "https://go.googlesource.com/net",
|
||||||
|
@ -392,6 +400,13 @@
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"path": "/ipv6"
|
"path": "/ipv6"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"importpath": "golang.org/x/sys/unix",
|
||||||
|
"repository": "https://go.googlesource.com/sys",
|
||||||
|
"revision": "43eea11bc92608addb41b8a406b0407495c106f6",
|
||||||
|
"branch": "master",
|
||||||
|
"path": "/unix"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"importpath": "gopkg.in/Shopify/sarama.v1",
|
"importpath": "gopkg.in/Shopify/sarama.v1",
|
||||||
"repository": "https://gopkg.in/Shopify/sarama.v1",
|
"repository": "https://gopkg.in/Shopify/sarama.v1",
|
||||||
|
@ -413,7 +428,7 @@
|
||||||
{
|
{
|
||||||
"importpath": "gopkg.in/gemnasium/logrus-airbrake-hook.v2",
|
"importpath": "gopkg.in/gemnasium/logrus-airbrake-hook.v2",
|
||||||
"repository": "https://gopkg.in/gemnasium/logrus-airbrake-hook.v2",
|
"repository": "https://gopkg.in/gemnasium/logrus-airbrake-hook.v2",
|
||||||
"revision": "bfee1239d796830ca346767650cce5ba90d58c57",
|
"revision": "e928b033a891c0175fb643d5aa0779e86325eb12",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
# 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)
|
|
|
@ -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,433 +0,0 @@
|
||||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/Sirupsen/logrus?status.svg)](https://godoc.org/github.com/Sirupsen/logrus)
|
|
||||||
|
|
||||||
**Seeing weird case-sensitive problems?** See [this
|
|
||||||
issue](https://github.com/sirupsen/logrus/issues/451#issuecomment-264332021).
|
|
||||||
This change has been reverted. I apologize for causing this. I greatly
|
|
||||||
underestimated the impact this would have. Logrus strives for stability and
|
|
||||||
backwards compatibility and failed to provide that.
|
|
||||||
|
|
||||||
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). Logrus itself is completely stable and has been used in
|
|
||||||
many large deployments. The core API is unlikely to 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):
|
|
||||||
|
|
||||||
![Colored](http://i.imgur.com/PY7qMwd.png)
|
|
||||||
|
|
||||||
With `log.SetFormatter(&log.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.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
|
||||||
attached, the output is compatible with the
|
|
||||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
|
||||||
|
|
||||||
```text
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" 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="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
|
||||||
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
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Log as JSON instead of the default ASCII formatter.
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
|
|
||||||
// Output to stdout instead of the default stderr, could also be a file.
|
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
|
|
||||||
// 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!")
|
|
||||||
|
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
|
||||||
`init`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
|
|
||||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
|
||||||
"log/syslog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
// 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, "")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to connect to local syslog daemon")
|
|
||||||
} else {
|
|
||||||
log.AddHook(hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
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).
|
|
||||||
|
|
||||||
| Hook | Description |
|
|
||||||
| ----- | ----------- |
|
|
||||||
| [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 |
|
|
||||||
| [Influxus] (http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB] (http://influxdata.com/) |
|
|
||||||
| [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|
|
|
||||||
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
|
||||||
| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)|
|
|
||||||
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
|
||||||
| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
|
|
||||||
| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
|
|
||||||
| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) |
|
|
||||||
| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) |
|
|
||||||
| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
|
||||||
|
|
||||||
|
|
||||||
#### 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(&log.JSONFormatter{})
|
|
||||||
} else {
|
|
||||||
// The TextFormatter is default, you don't actually have to do this.
|
|
||||||
log.SetFormatter(&log.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:
|
|
||||||
|
|
||||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
|
||||||
* [`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,
|
|
||||||
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 *MyJSONFormatter) 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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Logger as an `io.Writer`
|
|
||||||
|
|
||||||
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
|
|
||||||
w := logger.Writer()
|
|
||||||
defer w.Close()
|
|
||||||
|
|
||||||
srv := http.Server{
|
|
||||||
// create a stdlib log.Logger that writes to
|
|
||||||
// logrus.Logger.
|
|
||||||
ErrorLog: log.New(w, "", 0),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Each line written to that writer will be printed the usual way, using formatters
|
|
||||||
and hooks. The level for those entries is `info`.
|
|
||||||
|
|
||||||
#### Rotation
|
|
||||||
|
|
||||||
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
|
|
||||||
entries. It should not be a feature of the application-level logger.
|
|
||||||
|
|
||||||
#### Tools
|
|
||||||
|
|
||||||
| 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.|
|
|
||||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper arround Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
|
||||||
|
|
||||||
#### 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())
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fatal handlers
|
|
||||||
|
|
||||||
Logrus can register one or more functions that will be called when any `fatal`
|
|
||||||
level message is logged. The registered handlers will be executed before
|
|
||||||
logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
|
|
||||||
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
|
||||||
|
|
||||||
```
|
|
||||||
...
|
|
||||||
handler := func() {
|
|
||||||
// gracefully shutdown something...
|
|
||||||
}
|
|
||||||
logrus.RegisterExitHandler(handler)
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Thread safety
|
|
||||||
|
|
||||||
By default Logger is protected by mutex for concurrent writes, this mutex is invoked when calling hooks and writing logs.
|
|
||||||
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
|
||||||
|
|
||||||
Situation when locking is not needed includes:
|
|
||||||
|
|
||||||
* You have no hooks registered, or hooks calling is already thread-safe.
|
|
||||||
|
|
||||||
* Writing to logger.Out is already thread-safe, for example:
|
|
||||||
|
|
||||||
1) logger.Out is protected by locks.
|
|
||||||
|
|
||||||
2) logger.Out is a os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allow multi-thread/multi-process writing)
|
|
||||||
|
|
||||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
|
|
@ -1,64 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
// The following code was sourced and modified from the
|
|
||||||
// https://bitbucket.org/tebeka/atexit package governed by the following license:
|
|
||||||
//
|
|
||||||
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var handlers = []func(){}
|
|
||||||
|
|
||||||
func runHandler(handler func()) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
handler()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHandlers() {
|
|
||||||
for _, handler := range handlers {
|
|
||||||
runHandler(handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
|
||||||
func Exit(code int) {
|
|
||||||
runHandlers()
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
|
|
||||||
// all handlers. The handlers will also be invoked when any Fatal log entry is
|
|
||||||
// made.
|
|
||||||
//
|
|
||||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
|
||||||
// message but also needs to gracefully shutdown. An example usecase could be
|
|
||||||
// closing database connections, or sending a alert that the application is
|
|
||||||
// closing.
|
|
||||||
func RegisterExitHandler(handler func()) {
|
|
||||||
handlers = append(handlers, handler)
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os/exec"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRegister(t *testing.T) {
|
|
||||||
current := len(handlers)
|
|
||||||
RegisterExitHandler(func() {})
|
|
||||||
if len(handlers) != current+1 {
|
|
||||||
t.Fatalf("can't add handler")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandler(t *testing.T) {
|
|
||||||
gofile := "/tmp/testprog.go"
|
|
||||||
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
|
|
||||||
t.Fatalf("can't create go file")
|
|
||||||
}
|
|
||||||
|
|
||||||
outfile := "/tmp/testprog.out"
|
|
||||||
arg := time.Now().UTC().String()
|
|
||||||
err := exec.Command("go", "run", gofile, outfile, arg).Run()
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("completed normally, should have failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(outfile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("can't read output file %s", outfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(data) != arg {
|
|
||||||
t.Fatalf("bad data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var testprog = []byte(`
|
|
||||||
// Test program for atexit, gets output file and data as arguments and writes
|
|
||||||
// data to output file in atexit handler.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
var outfile = ""
|
|
||||||
var data = ""
|
|
||||||
|
|
||||||
func handler() {
|
|
||||||
ioutil.WriteFile(outfile, []byte(data), 0666)
|
|
||||||
}
|
|
||||||
|
|
||||||
func badHandler() {
|
|
||||||
n := 0
|
|
||||||
fmt.Println(1/n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
outfile = flag.Arg(0)
|
|
||||||
data = flag.Arg(1)
|
|
||||||
|
|
||||||
logrus.RegisterExitHandler(handler)
|
|
||||||
logrus.RegisterExitHandler(badHandler)
|
|
||||||
logrus.Fatal("Bye bye")
|
|
||||||
}
|
|
||||||
`)
|
|
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
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
|
|
|
@ -1,275 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var bufferPool *sync.Pool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
bufferPool = &sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return new(bytes.Buffer)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the key when adding errors using WithError.
|
|
||||||
var ErrorKey = "error"
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// When formatter is called in entry.log(), an Buffer may be set to entry
|
|
||||||
Buffer *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEntry(logger *Logger) *Entry {
|
|
||||||
return &Entry{
|
|
||||||
Logger: logger,
|
|
||||||
// Default is three fields, give a little extra room
|
|
||||||
Data: make(Fields, 5),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the string representation from the reader and ultimately the
|
|
||||||
// formatter.
|
|
||||||
func (entry *Entry) String() (string, error) {
|
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
str := string(serialized)
|
|
||||||
return str, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
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 := make(Fields, len(entry.Data)+len(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}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
var buffer *bytes.Buffer
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
buffer = bufferPool.Get().(*bytes.Buffer)
|
|
||||||
buffer.Reset()
|
|
||||||
defer bufferPool.Put(buffer)
|
|
||||||
entry.Buffer = buffer
|
|
||||||
serialized, err := entry.Logger.Formatter.Format(&entry)
|
|
||||||
entry.Buffer = nil
|
|
||||||
if err != nil {
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
|
||||||
entry.Logger.mu.Unlock()
|
|
||||||
} else {
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
_, err = entry.Logger.Out.Write(serialized)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
|
||||||
}
|
|
||||||
entry.Logger.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) Warning(args ...interface{}) {
|
|
||||||
entry.Warn(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...))
|
|
||||||
}
|
|
||||||
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...))
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
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...))
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,77 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEntryWithError(t *testing.T) {
|
|
||||||
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
ErrorKey = "error"
|
|
||||||
}()
|
|
||||||
|
|
||||||
err := fmt.Errorf("kaboom at layer %d", 4711)
|
|
||||||
|
|
||||||
assert.Equal(err, WithError(err).Data["error"])
|
|
||||||
|
|
||||||
logger := New()
|
|
||||||
logger.Out = &bytes.Buffer{}
|
|
||||||
entry := NewEntry(logger)
|
|
||||||
|
|
||||||
assert.Equal(err, entry.WithError(err).Data["error"])
|
|
||||||
|
|
||||||
ErrorKey = "err"
|
|
||||||
|
|
||||||
assert.Equal(err, entry.WithError(err).Data["err"])
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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,50 +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
|
|
||||||
log.Level = logrus.DebugLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
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",
|
|
||||||
"number": 8,
|
|
||||||
}).Debug("Started observing beach")
|
|
||||||
|
|
||||||
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{
|
|
||||||
"temperature": -4,
|
|
||||||
}).Debug("Temperature changes")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "orca",
|
|
||||||
"size": 9009,
|
|
||||||
}).Panic("It's over 9000!")
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logrus.New()
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.Formatter = new(logrus.TextFormatter) // default
|
|
||||||
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
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,193 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// std is the name of the standard logger in stdlib `log`
|
|
||||||
std = New()
|
|
||||||
)
|
|
||||||
|
|
||||||
func StandardLogger() *Logger {
|
|
||||||
return std
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
std.mu.Lock()
|
|
||||||
defer std.mu.Unlock()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// 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,45 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
const DefaultTimestampFormat = time.RFC3339
|
|
||||||
|
|
||||||
// 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(data Fields) {
|
|
||||||
if t, ok := data["time"]; ok {
|
|
||||||
data["fields.time"] = t
|
|
||||||
}
|
|
||||||
|
|
||||||
if m, ok := data["msg"]; ok {
|
|
||||||
data["fields.msg"] = m
|
|
||||||
}
|
|
||||||
|
|
||||||
if l, ok := data["level"]; ok {
|
|
||||||
data["fields.level"] = l
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"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",
|
|
||||||
}
|
|
||||||
|
|
||||||
var errorFields = Fields{
|
|
||||||
"foo": fmt.Errorf("bar"),
|
|
||||||
"baz": fmt.Errorf("qux"),
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkErrorTextFormatter(b *testing.B) {
|
|
||||||
doBenchmark(b, &TextFormatter{DisableColors: true}, errorFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,39 +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"
|
|
||||||
logrus_syslog "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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following.
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"log/syslog"
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log := logrus.New()
|
|
||||||
hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
log.Hooks.Add(hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,54 +0,0 @@
|
||||||
// +build !windows,!nacl,!plan9
|
|
||||||
|
|
||||||
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.AllLevels
|
|
||||||
}
|
|
|
@ -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,67 +0,0 @@
|
||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// test.Hook is a hook designed for dealing with logs in test scenarios.
|
|
||||||
type Hook struct {
|
|
||||||
Entries []*logrus.Entry
|
|
||||||
}
|
|
||||||
|
|
||||||
// Installs a test hook for the global logger.
|
|
||||||
func NewGlobal() *Hook {
|
|
||||||
|
|
||||||
hook := new(Hook)
|
|
||||||
logrus.AddHook(hook)
|
|
||||||
|
|
||||||
return hook
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Installs a test hook for a given local logger.
|
|
||||||
func NewLocal(logger *logrus.Logger) *Hook {
|
|
||||||
|
|
||||||
hook := new(Hook)
|
|
||||||
logger.Hooks.Add(hook)
|
|
||||||
|
|
||||||
return hook
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a discarding logger and installs the test hook.
|
|
||||||
func NewNullLogger() (*logrus.Logger, *Hook) {
|
|
||||||
|
|
||||||
logger := logrus.New()
|
|
||||||
logger.Out = ioutil.Discard
|
|
||||||
|
|
||||||
return logger, NewLocal(logger)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Hook) Fire(e *logrus.Entry) error {
|
|
||||||
t.Entries = append(t.Entries, e)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Hook) Levels() []logrus.Level {
|
|
||||||
return logrus.AllLevels
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastEntry returns the last entry that was logged or nil.
|
|
||||||
func (t *Hook) LastEntry() (l *logrus.Entry) {
|
|
||||||
|
|
||||||
if i := len(t.Entries) - 1; i < 0 {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return t.Entries[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset removes all Entries from this test hook.
|
|
||||||
func (t *Hook) Reset() {
|
|
||||||
t.Entries = make([]*logrus.Entry, 0)
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAllHooks(t *testing.T) {
|
|
||||||
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
logger, hook := NewNullLogger()
|
|
||||||
assert.Nil(hook.LastEntry())
|
|
||||||
assert.Equal(0, len(hook.Entries))
|
|
||||||
|
|
||||||
logger.Error("Hello error")
|
|
||||||
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
|
||||||
assert.Equal("Hello error", hook.LastEntry().Message)
|
|
||||||
assert.Equal(1, len(hook.Entries))
|
|
||||||
|
|
||||||
logger.Warn("Hello warning")
|
|
||||||
assert.Equal(logrus.WarnLevel, hook.LastEntry().Level)
|
|
||||||
assert.Equal("Hello warning", hook.LastEntry().Message)
|
|
||||||
assert.Equal(2, len(hook.Entries))
|
|
||||||
|
|
||||||
hook.Reset()
|
|
||||||
assert.Nil(hook.LastEntry())
|
|
||||||
assert.Equal(0, len(hook.Entries))
|
|
||||||
|
|
||||||
hook = NewGlobal()
|
|
||||||
|
|
||||||
logrus.Error("Hello error")
|
|
||||||
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
|
||||||
assert.Equal("Hello error", hook.LastEntry().Message)
|
|
||||||
assert.Equal(1, len(hook.Entries))
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fieldKey string
|
|
||||||
type FieldMap map[fieldKey]string
|
|
||||||
|
|
||||||
const (
|
|
||||||
FieldKeyMsg = "msg"
|
|
||||||
FieldKeyLevel = "level"
|
|
||||||
FieldKeyTime = "time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f FieldMap) resolve(key fieldKey) string {
|
|
||||||
if k, ok := f[key]; ok {
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
type JSONFormatter struct {
|
|
||||||
// TimestampFormat sets the format used for marshaling timestamps.
|
|
||||||
TimestampFormat string
|
|
||||||
|
|
||||||
// DisableTimestamp allows disabling automatic timestamps in output
|
|
||||||
DisableTimestamp bool
|
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for various fields.
|
|
||||||
// As an example:
|
|
||||||
// formatter := &JSONFormatter{
|
|
||||||
// FieldMap: FieldMap{
|
|
||||||
// FieldKeyTime: "@timestamp",
|
|
||||||
// FieldKeyLevel: "@level",
|
|
||||||
// FieldKeyLevel: "@message",
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
FieldMap FieldMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
data := make(Fields, len(entry.Data)+3)
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
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)
|
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
|
||||||
if timestampFormat == "" {
|
|
||||||
timestampFormat = DefaultTimestampFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.DisableTimestamp {
|
|
||||||
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
|
||||||
}
|
|
||||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
|
||||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
|
||||||
|
|
||||||
serialized, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
|
||||||
}
|
|
||||||
return append(serialized, '\n'), nil
|
|
||||||
}
|
|
|
@ -1,199 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestErrorNotLost(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{}
|
|
||||||
|
|
||||||
b, err := formatter.Format(WithField("error", errors.New("wild walrus")))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := make(map[string]interface{})
|
|
||||||
err = json.Unmarshal(b, &entry)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry["error"] != "wild walrus" {
|
|
||||||
t.Fatal("Error field not set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorNotLostOnFieldNotNamedError(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{}
|
|
||||||
|
|
||||||
b, err := formatter.Format(WithField("omg", errors.New("wild walrus")))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := make(map[string]interface{})
|
|
||||||
err = json.Unmarshal(b, &entry)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry["omg"] != "wild walrus" {
|
|
||||||
t.Fatal("Error field not set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFieldClashWithTime(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{}
|
|
||||||
|
|
||||||
b, err := formatter.Format(WithField("time", "right now!"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := make(map[string]interface{})
|
|
||||||
err = json.Unmarshal(b, &entry)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry["fields.time"] != "right now!" {
|
|
||||||
t.Fatal("fields.time not set to original time field")
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry["time"] != "0001-01-01T00:00:00Z" {
|
|
||||||
t.Fatal("time field not set to current time, was: ", entry["time"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFieldClashWithMsg(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{}
|
|
||||||
|
|
||||||
b, err := formatter.Format(WithField("msg", "something"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := make(map[string]interface{})
|
|
||||||
err = json.Unmarshal(b, &entry)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry["fields.msg"] != "something" {
|
|
||||||
t.Fatal("fields.msg not set to original msg field")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFieldClashWithLevel(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{}
|
|
||||||
|
|
||||||
b, err := formatter.Format(WithField("level", "something"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := make(map[string]interface{})
|
|
||||||
err = json.Unmarshal(b, &entry)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry["fields.level"] != "something" {
|
|
||||||
t.Fatal("fields.level not set to original level field")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJSONEntryEndsWithNewline(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{}
|
|
||||||
|
|
||||||
b, err := formatter.Format(WithField("level", "something"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b[len(b)-1] != '\n' {
|
|
||||||
t.Fatal("Expected JSON log entry to end with a newline")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJSONMessageKey(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{
|
|
||||||
FieldMap: FieldMap{
|
|
||||||
FieldKeyMsg: "message",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := formatter.Format(&Entry{Message: "oh hai"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
s := string(b)
|
|
||||||
if !(strings.Contains(s, "message") && strings.Contains(s, "oh hai")) {
|
|
||||||
t.Fatal("Expected JSON to format message key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJSONLevelKey(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{
|
|
||||||
FieldMap: FieldMap{
|
|
||||||
FieldKeyLevel: "somelevel",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := formatter.Format(WithField("level", "something"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
s := string(b)
|
|
||||||
if !strings.Contains(s, "somelevel") {
|
|
||||||
t.Fatal("Expected JSON to format level key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJSONTimeKey(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{
|
|
||||||
FieldMap: FieldMap{
|
|
||||||
FieldKeyTime: "timeywimey",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := formatter.Format(WithField("level", "something"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
s := string(b)
|
|
||||||
if !strings.Contains(s, "timeywimey") {
|
|
||||||
t.Fatal("Expected JSON to format time key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJSONDisableTimestamp(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{
|
|
||||||
DisableTimestamp: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := formatter.Format(WithField("level", "something"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
s := string(b)
|
|
||||||
if strings.Contains(s, FieldKeyTime) {
|
|
||||||
t.Error("Did not prevent timestamp", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJSONEnableTimestamp(t *testing.T) {
|
|
||||||
formatter := &JSONFormatter{}
|
|
||||||
|
|
||||||
b, err := formatter.Format(WithField("level", "something"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to format entry: ", err)
|
|
||||||
}
|
|
||||||
s := string(b)
|
|
||||||
if !strings.Contains(s, FieldKeyTime) {
|
|
||||||
t.Error("Timestamp not present", s)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,308 +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.Stderr`. 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. Locking is enabled by Default
|
|
||||||
mu MutexWrap
|
|
||||||
// Reusable empty entry
|
|
||||||
entryPool sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
type MutexWrap struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
disabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Lock() {
|
|
||||||
if !mw.disabled {
|
|
||||||
mw.lock.Lock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Unlock() {
|
|
||||||
if !mw.disabled {
|
|
||||||
mw.lock.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Disable() {
|
|
||||||
mw.disabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.DebugLevel,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// It's recommended to make this a global instance called `log`.
|
|
||||||
func New() *Logger {
|
|
||||||
return &Logger{
|
|
||||||
Out: os.Stderr,
|
|
||||||
Formatter: new(TextFormatter),
|
|
||||||
Hooks: make(LevelHooks),
|
|
||||||
Level: InfoLevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) newEntry() *Entry {
|
|
||||||
entry, ok := logger.entryPool.Get().(*Entry)
|
|
||||||
if ok {
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
return NewEntry(logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) releaseEntry(entry *Entry) {
|
|
||||||
logger.entryPool.Put(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a field to the log entry, note that it doesn't log until you call
|
|
||||||
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
|
|
||||||
// If you want multiple fields, use `WithFields`.
|
|
||||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.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 {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.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 {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= DebugLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Debugf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= InfoLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Infof(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Printf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warnf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warnf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= ErrorLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Errorf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= FatalLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Fatalf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= PanicLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Panicf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debug(args ...interface{}) {
|
|
||||||
if logger.Level >= DebugLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Debug(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Info(args ...interface{}) {
|
|
||||||
if logger.Level >= InfoLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Info(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Print(args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Info(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warn(args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warn(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warning(args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warn(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Error(args ...interface{}) {
|
|
||||||
if logger.Level >= ErrorLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Error(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatal(args ...interface{}) {
|
|
||||||
if logger.Level >= FatalLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Fatal(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panic(args ...interface{}) {
|
|
||||||
if logger.Level >= PanicLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Panic(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugln(args ...interface{}) {
|
|
||||||
if logger.Level >= DebugLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Debugln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infoln(args ...interface{}) {
|
|
||||||
if logger.Level >= InfoLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Infoln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Println(args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Println(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnln(args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warnln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningln(args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warnln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorln(args ...interface{}) {
|
|
||||||
if logger.Level >= ErrorLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Errorln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
|
||||||
if logger.Level >= FatalLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Fatalln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicln(args ...interface{}) {
|
|
||||||
if logger.Level >= PanicLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Panicln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//When file is opened with appending mode, it's safe to
|
|
||||||
//write concurrently to a file (within 4k message on Linux).
|
|
||||||
//In these cases user can choose to disable the lock.
|
|
||||||
func (logger *Logger) SetNoLock() {
|
|
||||||
logger.mu.Disable()
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// smallFields is a small size data set for benchmarking
|
|
||||||
var loggerFields = Fields{
|
|
||||||
"foo": "bar",
|
|
||||||
"baz": "qux",
|
|
||||||
"one": "two",
|
|
||||||
"three": "four",
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDummyLogger(b *testing.B) {
|
|
||||||
nullf, err := os.OpenFile("/dev/null", os.O_WRONLY, 0666)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
defer nullf.Close()
|
|
||||||
doLoggerBenchmark(b, nullf, &TextFormatter{DisableColors: true}, smallFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDummyLoggerNoLock(b *testing.B) {
|
|
||||||
nullf, err := os.OpenFile("/dev/null", os.O_WRONLY|os.O_APPEND, 0666)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
defer nullf.Close()
|
|
||||||
doLoggerBenchmarkNoLock(b, nullf, &TextFormatter{DisableColors: true}, smallFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doLoggerBenchmark(b *testing.B, out *os.File, formatter Formatter, fields Fields) {
|
|
||||||
logger := Logger{
|
|
||||||
Out: out,
|
|
||||||
Level: InfoLevel,
|
|
||||||
Formatter: formatter,
|
|
||||||
}
|
|
||||||
entry := logger.WithFields(fields)
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
|
||||||
for pb.Next() {
|
|
||||||
entry.Info("aaa")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doLoggerBenchmarkNoLock(b *testing.B, out *os.File, formatter Formatter, fields Fields) {
|
|
||||||
logger := Logger{
|
|
||||||
Out: out,
|
|
||||||
Level: InfoLevel,
|
|
||||||
Formatter: formatter,
|
|
||||||
}
|
|
||||||
logger.SetNoLock()
|
|
||||||
entry := logger.WithFields(fields)
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
|
||||||
for pb.Next() {
|
|
||||||
entry.Info("aaa")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 strings.ToLower(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// 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 = &Entry{}
|
|
||||||
_ StdLogger = &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{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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{})
|
|
||||||
}
|
|
|
@ -1,361 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"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 := kvArr[1]
|
|
||||||
if kvArr[1][0] == '"' {
|
|
||||||
var err error
|
|
||||||
val, err = strconv.Unquote(val)
|
|
||||||
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.0) // JSON has floats only
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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 TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
var fields Fields
|
|
||||||
|
|
||||||
logger := New()
|
|
||||||
logger.Out = &buffer
|
|
||||||
logger.Formatter = new(JSONFormatter)
|
|
||||||
|
|
||||||
llog := logger.WithField("context", "eating raw fish")
|
|
||||||
|
|
||||||
llog.Info("looks delicious")
|
|
||||||
|
|
||||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
|
||||||
assert.NoError(t, err, "should have decoded first message")
|
|
||||||
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
|
|
||||||
assert.Equal(t, fields["msg"], "looks delicious")
|
|
||||||
assert.Equal(t, fields["context"], "eating raw fish")
|
|
||||||
|
|
||||||
buffer.Reset()
|
|
||||||
|
|
||||||
llog.Warn("omg it is!")
|
|
||||||
|
|
||||||
err = json.Unmarshal(buffer.Bytes(), &fields)
|
|
||||||
assert.NoError(t, err, "should have decoded second message")
|
|
||||||
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
|
|
||||||
assert.Equal(t, fields["msg"], "omg it is!")
|
|
||||||
assert.Equal(t, fields["context"], "eating raw fish")
|
|
||||||
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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("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("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("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("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("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("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("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())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetSetLevelRace(t *testing.T) {
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int) {
|
|
||||||
defer wg.Done()
|
|
||||||
if i%2 == 0 {
|
|
||||||
SetLevel(InfoLevel)
|
|
||||||
} else {
|
|
||||||
GetLevel()
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoggingRace(t *testing.T) {
|
|
||||||
logger := New()
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(100)
|
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
go func() {
|
|
||||||
logger.Info("info")
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile test
|
|
||||||
func TestLogrusInterface(t *testing.T) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
fn := func(l FieldLogger) {
|
|
||||||
b := l.WithField("key", "value")
|
|
||||||
b.Debug("Test")
|
|
||||||
}
|
|
||||||
// test logger
|
|
||||||
logger := New()
|
|
||||||
logger.Out = &buffer
|
|
||||||
fn(logger)
|
|
||||||
|
|
||||||
// test Entry
|
|
||||||
e := logger.WithField("another", "value")
|
|
||||||
fn(e)
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// +build appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
// +build darwin freebsd openbsd netbsd dragonfly
|
|
||||||
// +build !appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA
|
|
||||||
|
|
||||||
type Termios syscall.Termios
|
|
|
@ -1,22 +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 darwin freebsd openbsd netbsd dragonfly
|
|
||||||
// +build !appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
fd := syscall.Stderr
|
|
||||||
var termios Termios
|
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
|
||||||
return err == 0
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
// +build solaris,!appengine
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -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,!appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
|
|
||||||
var (
|
|
||||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
fd := syscall.Stderr
|
|
||||||
var st uint32
|
|
||||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
|
||||||
return r != 0 && e == 0
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
nocolor = 0
|
|
||||||
red = 31
|
|
||||||
green = 32
|
|
||||||
yellow = 33
|
|
||||||
blue = 34
|
|
||||||
gray = 37
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
baseTimestamp time.Time
|
|
||||||
isTerminal bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
baseTimestamp = time.Now()
|
|
||||||
isTerminal = IsTerminal()
|
|
||||||
}
|
|
||||||
|
|
||||||
type TextFormatter struct {
|
|
||||||
// Set to true to bypass checking for a TTY before outputting colors.
|
|
||||||
ForceColors bool
|
|
||||||
|
|
||||||
// Force disabling colors.
|
|
||||||
DisableColors bool
|
|
||||||
|
|
||||||
// Disable timestamp logging. useful when output is redirected to logging
|
|
||||||
// system that already adds timestamps.
|
|
||||||
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) {
|
|
||||||
var b *bytes.Buffer
|
|
||||||
keys := make([]string, 0, len(entry.Data))
|
|
||||||
for k := range entry.Data {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.DisableSorting {
|
|
||||||
sort.Strings(keys)
|
|
||||||
}
|
|
||||||
if entry.Buffer != nil {
|
|
||||||
b = entry.Buffer
|
|
||||||
} else {
|
|
||||||
b = &bytes.Buffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
prefixFieldClashes(entry.Data)
|
|
||||||
|
|
||||||
isColorTerminal := isTerminal && (runtime.GOOS != "windows")
|
|
||||||
isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
|
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
|
||||||
if timestampFormat == "" {
|
|
||||||
timestampFormat = DefaultTimestampFormat
|
|
||||||
}
|
|
||||||
if isColored {
|
|
||||||
f.printColored(b, entry, keys, timestampFormat)
|
|
||||||
} else {
|
|
||||||
if !f.DisableTimestamp {
|
|
||||||
f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
|
|
||||||
}
|
|
||||||
f.appendKeyValue(b, "level", entry.Level.String())
|
|
||||||
if entry.Message != "" {
|
|
||||||
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 (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
|
|
||||||
var levelColor int
|
|
||||||
switch entry.Level {
|
|
||||||
case DebugLevel:
|
|
||||||
levelColor = gray
|
|
||||||
case WarnLevel:
|
|
||||||
levelColor = yellow
|
|
||||||
case ErrorLevel, FatalLevel, PanicLevel:
|
|
||||||
levelColor = red
|
|
||||||
default:
|
|
||||||
levelColor = blue
|
|
||||||
}
|
|
||||||
|
|
||||||
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
|
||||||
|
|
||||||
if f.DisableTimestamp {
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
|
|
||||||
} else if !f.FullTimestamp {
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), 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 {
|
|
||||||
v := entry.Data[k]
|
|
||||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
|
||||||
f.appendValue(b, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func needsQuoting(text string) bool {
|
|
||||||
for _, ch := range text {
|
|
||||||
if !((ch >= 'a' && ch <= 'z') ||
|
|
||||||
(ch >= 'A' && ch <= 'Z') ||
|
|
||||||
(ch >= '0' && ch <= '9') ||
|
|
||||||
ch == '-' || ch == '.') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
|
||||||
|
|
||||||
b.WriteString(key)
|
|
||||||
b.WriteByte('=')
|
|
||||||
f.appendValue(b, value)
|
|
||||||
b.WriteByte(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case string:
|
|
||||||
if !needsQuoting(value) {
|
|
||||||
b.WriteString(value)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(b, "%q", value)
|
|
||||||
}
|
|
||||||
case error:
|
|
||||||
errmsg := value.Error()
|
|
||||||
if !needsQuoting(errmsg) {
|
|
||||||
b.WriteString(errmsg)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(b, "%q", errmsg)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fmt.Fprint(b, value)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestQuoting(t *testing.T) {
|
|
||||||
tf := &TextFormatter{DisableColors: true}
|
|
||||||
|
|
||||||
checkQuoting := func(q bool, value interface{}) {
|
|
||||||
b, _ := tf.Format(WithField("test", value))
|
|
||||||
idx := bytes.Index(b, ([]byte)("test="))
|
|
||||||
cont := bytes.Contains(b[idx+5:], []byte{'"'})
|
|
||||||
if cont != q {
|
|
||||||
if q {
|
|
||||||
t.Errorf("quoting expected for: %#v", value)
|
|
||||||
} else {
|
|
||||||
t.Errorf("quoting not expected for: %#v", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkQuoting(false, "abcd")
|
|
||||||
checkQuoting(false, "v1.0")
|
|
||||||
checkQuoting(false, "1234567890")
|
|
||||||
checkQuoting(true, "/foobar")
|
|
||||||
checkQuoting(true, "x y")
|
|
||||||
checkQuoting(true, "x,y")
|
|
||||||
checkQuoting(false, errors.New("invalid"))
|
|
||||||
checkQuoting(true, errors.New("invalid argument"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimestampFormat(t *testing.T) {
|
|
||||||
checkTimeStr := func(format string) {
|
|
||||||
customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format}
|
|
||||||
customStr, _ := customFormatter.Format(WithField("test", "test"))
|
|
||||||
timeStart := bytes.Index(customStr, ([]byte)("time="))
|
|
||||||
timeEnd := bytes.Index(customStr, ([]byte)("level="))
|
|
||||||
timeStr := customStr[timeStart+5 : timeEnd-1]
|
|
||||||
if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' {
|
|
||||||
timeStr = timeStr[1 : len(timeStr)-1]
|
|
||||||
}
|
|
||||||
if format == "" {
|
|
||||||
format = time.RFC3339
|
|
||||||
}
|
|
||||||
_, e := time.Parse(format, (string)(timeStr))
|
|
||||||
if e != nil {
|
|
||||||
t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkTimeStr("2006-01-02T15:04:05.000000000Z07:00")
|
|
||||||
checkTimeStr("Mon Jan _2 15:04:05 2006")
|
|
||||||
checkTimeStr("")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDisableTimestampWithColoredOutput(t *testing.T) {
|
|
||||||
tf := &TextFormatter{DisableTimestamp: true, ForceColors: true}
|
|
||||||
|
|
||||||
b, _ := tf.Format(WithField("test", "test"))
|
|
||||||
if strings.Contains(string(b), "[0000]") {
|
|
||||||
t.Error("timestamp not expected when DisableTimestamp is true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO add tests for sorting etc., this requires a parser for the text
|
|
||||||
// formatter output.
|
|
|
@ -1,53 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (logger *Logger) Writer() *io.PipeWriter {
|
|
||||||
return logger.WriterLevel(InfoLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
|
||||||
reader, writer := io.Pipe()
|
|
||||||
|
|
||||||
var printFunc func(args ...interface{})
|
|
||||||
switch level {
|
|
||||||
case DebugLevel:
|
|
||||||
printFunc = logger.Debug
|
|
||||||
case InfoLevel:
|
|
||||||
printFunc = logger.Info
|
|
||||||
case WarnLevel:
|
|
||||||
printFunc = logger.Warn
|
|
||||||
case ErrorLevel:
|
|
||||||
printFunc = logger.Error
|
|
||||||
case FatalLevel:
|
|
||||||
printFunc = logger.Fatal
|
|
||||||
case PanicLevel:
|
|
||||||
printFunc = logger.Panic
|
|
||||||
default:
|
|
||||||
printFunc = logger.Print
|
|
||||||
}
|
|
||||||
|
|
||||||
go logger.writerScanner(reader, printFunc)
|
|
||||||
runtime.SetFinalizer(writer, writerFinalizer)
|
|
||||||
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
|
||||||
scanner := bufio.NewScanner(reader)
|
|
||||||
for scanner.Scan() {
|
|
||||||
printFunc(scanner.Text())
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
logger.Errorf("Error while reading from Writer: %s", err)
|
|
||||||
}
|
|
||||||
reader.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func writerFinalizer(writer *io.PipeWriter) {
|
|
||||||
writer.Close()
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
# dugong
|
# dugong
|
||||||
Logging utilities for [logrus](https://github.com/Sirupsen/logrus).
|
Logging utilities for [logrus](https://github.com/sirupsen/logrus).
|
||||||
|
|
||||||
To develop on this library, you need logrus on your GOPATH:
|
To develop on this library, you need logrus on your GOPATH:
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,50 @@
|
||||||
|
# 1.0.3
|
||||||
|
|
||||||
|
* Replace example files with testable examples
|
||||||
|
|
||||||
|
# 1.0.2
|
||||||
|
|
||||||
|
* bug: quote non-string values in text formatter (#583)
|
||||||
|
* Make (*Logger) SetLevel a public method
|
||||||
|
|
||||||
|
# 1.0.1
|
||||||
|
|
||||||
|
* bug: fix escaping in text formatter (#575)
|
||||||
|
|
||||||
|
# 1.0.0
|
||||||
|
|
||||||
|
* Officially changed name to lower-case
|
||||||
|
* bug: colors on Windows 10 (#541)
|
||||||
|
* bug: fix race in accessing level (#512)
|
||||||
|
|
||||||
|
# 0.11.5
|
||||||
|
|
||||||
|
* feature: add writer and writerlevel to entry (#372)
|
||||||
|
|
||||||
|
# 0.11.4
|
||||||
|
|
||||||
|
* bug: fix undefined variable on solaris (#493)
|
||||||
|
|
||||||
|
# 0.11.3
|
||||||
|
|
||||||
|
* formatter: configure quoting of empty values (#484)
|
||||||
|
* formatter: configure quoting character (default is `"`) (#484)
|
||||||
|
* bug: fix not importing io correctly in non-linux environments (#481)
|
||||||
|
|
||||||
|
# 0.11.2
|
||||||
|
|
||||||
|
* bug: fix windows terminal detection (#476)
|
||||||
|
|
||||||
|
# 0.11.1
|
||||||
|
|
||||||
|
* bug: fix tty detection with custom out (#471)
|
||||||
|
|
||||||
|
# 0.11.0
|
||||||
|
|
||||||
|
* performance: Use bufferpool to allocate (#370)
|
||||||
|
* terminal: terminal detection for app-engine (#343)
|
||||||
|
* feature: exit handler (#375)
|
||||||
|
|
||||||
# 0.10.0
|
# 0.10.0
|
||||||
|
|
||||||
* feature: Add a test hook (#180)
|
* feature: Add a test hook (#180)
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/Sirupsen/logrus?status.svg)](https://godoc.org/github.com/Sirupsen/logrus)
|
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
|
||||||
|
|
||||||
**Seeing weird case-sensitive problems?** See [this
|
|
||||||
issue](https://github.com/sirupsen/logrus/issues/451#issuecomment-264332021).
|
|
||||||
This change has been reverted. I apologize for causing this. I greatly
|
|
||||||
underestimated the impact this would have. Logrus strives for stability and
|
|
||||||
backwards compatibility and failed to provide that.
|
|
||||||
|
|
||||||
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.
|
||||||
yet stable (pre 1.0). Logrus itself is completely stable and has been used in
|
|
||||||
many large deployments. The core API is unlikely to change much but please
|
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
||||||
version control your Logrus to make sure you aren't fetching latest `master` on
|
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
||||||
every build.**
|
this caused issues in the community and we needed a standard. Some environments
|
||||||
|
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||||
|
Everything using `logrus` will need to use the lower-case:
|
||||||
|
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
||||||
|
|
||||||
|
To fix Glide, see [these
|
||||||
|
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
||||||
|
For an in-depth explanation of the casing issue, see [this
|
||||||
|
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
||||||
|
|
||||||
|
**Are you interested in assisting in maintaining Logrus?** Currently I have a
|
||||||
|
lot of obligations, and I am unable to provide Logrus with the maintainership it
|
||||||
|
needs. If you'd like to help, please reach out to me at `simon at author's
|
||||||
|
username dot com`.
|
||||||
|
|
||||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||||
plain text):
|
plain text):
|
||||||
|
@ -52,6 +59,12 @@ time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x20822
|
||||||
exit status 1
|
exit status 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Case-sensitivity
|
||||||
|
|
||||||
|
The organization's name was changed to lower-case--and this will not be changed
|
||||||
|
back. If you are getting import conflicts due to case sensitivity, please use
|
||||||
|
the lower-case import: `github.com/sirupsen/logrus`.
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
The simplest way to use Logrus is simply the package-level exported logger:
|
The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
|
@ -60,7 +73,7 @@ The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -71,7 +84,7 @@ func main() {
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
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"`
|
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
|
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||||
want:
|
want:
|
||||||
|
|
||||||
|
@ -80,14 +93,15 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
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{})
|
||||||
|
|
||||||
// Output to stdout instead of the default stderr, could also be a file.
|
// Output to stdout instead of the default stderr
|
||||||
|
// Can be any io.Writer, see below for File example
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
|
|
||||||
// Only log the warning severity or above.
|
// Only log the warning severity or above.
|
||||||
|
@ -129,7 +143,8 @@ application, you can also create an instance of the `logrus` Logger:
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Sirupsen/logrus"
|
"os"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create a new instance of the logger. You can have any number of instances.
|
// Create a new instance of the logger. You can have any number of instances.
|
||||||
|
@ -138,7 +153,15 @@ var log = logrus.New()
|
||||||
func main() {
|
func main() {
|
||||||
// The API for setting attributes is a little different than the package level
|
// The API for setting attributes is a little different than the package level
|
||||||
// exported logger. See Godoc.
|
// exported logger. See Godoc.
|
||||||
log.Out = os.Stderr
|
log.Out = os.Stdout
|
||||||
|
|
||||||
|
// You could set this to any `io.Writer` such as a file
|
||||||
|
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||||
|
// if err == nil {
|
||||||
|
// log.Out = file
|
||||||
|
// } else {
|
||||||
|
// log.Info("Failed to log to file, using default stderr")
|
||||||
|
// }
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"animal": "walrus",
|
"animal": "walrus",
|
||||||
|
@ -149,7 +172,7 @@ func main() {
|
||||||
|
|
||||||
#### Fields
|
#### Fields
|
||||||
|
|
||||||
Logrus encourages careful, structured logging though logging fields instead of
|
Logrus encourages careful, structured logging through logging fields instead of
|
||||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
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
|
to send event %s to topic %s with key %d")`, you should log the much more
|
||||||
discoverable:
|
discoverable:
|
||||||
|
@ -171,6 +194,20 @@ 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
|
seen as a hint you should add a field, however, you can still use the
|
||||||
`printf`-family functions with Logrus.
|
`printf`-family functions with Logrus.
|
||||||
|
|
||||||
|
#### Default Fields
|
||||||
|
|
||||||
|
Often it's helpful to have fields _always_ attached to log statements in an
|
||||||
|
application or parts of one. For example, you may want to always log the
|
||||||
|
`request_id` and `user_ip` in the context of a request. Instead of writing
|
||||||
|
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
||||||
|
every line, you can create a `logrus.Entry` to pass around instead:
|
||||||
|
|
||||||
|
```go
|
||||||
|
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
||||||
|
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
||||||
|
requestLogger.Warn("something not great happened")
|
||||||
|
```
|
||||||
|
|
||||||
#### Hooks
|
#### Hooks
|
||||||
|
|
||||||
You can add hooks for logging levels. For example to send errors to an exception
|
You can add hooks for logging levels. For example to send errors to an exception
|
||||||
|
@ -182,9 +219,9 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
|
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
|
||||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -206,42 +243,54 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
|
||||||
|
|
||||||
| Hook | Description |
|
| Hook | Description |
|
||||||
| ----- | ----------- |
|
| ----- | ----------- |
|
||||||
| [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. |
|
| [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. |
|
| [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. |
|
||||||
| [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. |
|
| [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) |
|
||||||
| [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 |
|
|
||||||
| [Influxus] (http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB] (http://influxdata.com/) |
|
|
||||||
| [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) |
|
| [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 |
|
| [AzureTableHook](https://github.com/kpfaulkner/azuretablehook/) | Hook for logging to Azure Table Storage|
|
||||||
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||||
|
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
|
||||||
|
| [Discordrus](https://github.com/kz/discordrus) | Hook for logging to [Discord](https://discordapp.com/) |
|
||||||
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
|
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
|
||||||
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
| [Firehose](https://github.com/beaubrewer/logrus_firehose) | Hook for logging to [Amazon Firehose](https://aws.amazon.com/kinesis/firehose/)
|
||||||
| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)|
|
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
|
||||||
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
| [Go-Slack](https://github.com/multiplay/go-slack) | Hook for logging to [Slack](https://slack.com) |
|
||||||
| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
|
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
|
||||||
| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
|
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
||||||
| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) |
|
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
|
||||||
| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) |
|
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
|
||||||
|
| [Influxus](http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB](http://influxdata.com/) |
|
||||||
|
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||||
|
| [KafkaLogrus](https://github.com/tracer0tong/kafkalogrus) | Hook for logging to Kafka |
|
||||||
|
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
|
||||||
|
| [Logentries](https://github.com/jcftang/logentriesrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||||
| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||||
|
| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
|
||||||
|
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||||
|
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
||||||
|
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
|
||||||
|
| [Mattermost](https://github.com/shuLhan/mattermost-integration/tree/master/hooks/logrus) | Hook for logging to [Mattermost](https://mattermost.com/) |
|
||||||
|
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
|
||||||
|
| [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) |
|
||||||
|
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
|
||||||
|
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
|
||||||
|
| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) |
|
||||||
|
| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) |
|
||||||
|
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
|
||||||
|
| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
|
||||||
|
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
|
||||||
|
| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)|
|
||||||
|
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
|
||||||
|
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||||
|
| [Stackdriver](https://github.com/knq/sdhook) | Hook for logging to [Google Stackdriver](https://cloud.google.com/logging/) |
|
||||||
|
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
||||||
|
| [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. |
|
||||||
|
| [Syslog TLS](https://github.com/shinji62/logrus-syslog-ng) | Send errors to remote syslog server with TLS support. |
|
||||||
|
| [Telegram](https://github.com/rossmcdonald/telegram_hook) | Hook for logging errors to [Telegram](https://telegram.org/) |
|
||||||
|
| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) |
|
||||||
|
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
||||||
|
| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
|
||||||
|
| [SQS-Hook](https://github.com/tsarpaul/logrus_sqs) | Hook for logging to [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/) |
|
||||||
|
|
||||||
#### Level logging
|
#### Level logging
|
||||||
|
|
||||||
|
@ -290,7 +339,7 @@ could do:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
@ -317,11 +366,15 @@ The built-in logging formatters are:
|
||||||
without colors.
|
without colors.
|
||||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
* *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
|
field to `true`. To force no colored output even if there is a TTY set the
|
||||||
`DisableColors` field to `true`
|
`DisableColors` field to `true`. For Windows, see
|
||||||
|
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
||||||
|
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
||||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||||
|
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
||||||
|
|
||||||
Third party logging formatters:
|
Third party logging formatters:
|
||||||
|
|
||||||
|
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can by parsed by Kubernetes and Google Container Engine.
|
||||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
* [`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̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||||
|
@ -367,6 +420,18 @@ srv := http.Server{
|
||||||
Each line written to that writer will be printed the usual way, using formatters
|
Each line written to that writer will be printed the usual way, using formatters
|
||||||
and hooks. The level for those entries is `info`.
|
and hooks. The level for those entries is `info`.
|
||||||
|
|
||||||
|
This means that we can override the standard library logger easily:
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger := logrus.New()
|
||||||
|
logger.Formatter = &logrus.JSONFormatter{}
|
||||||
|
|
||||||
|
// Use logrus for standard log output
|
||||||
|
// Note that `log` here references stdlib's log
|
||||||
|
// Not logrus imported under the name `log`.
|
||||||
|
log.SetOutput(logger.Writer())
|
||||||
|
```
|
||||||
|
|
||||||
#### Rotation
|
#### Rotation
|
||||||
|
|
||||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||||
|
@ -378,7 +443,7 @@ entries. It should not be a feature of the application-level logger.
|
||||||
| Tool | Description |
|
| 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.|
|
|[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.|
|
||||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper arround Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
||||||
|
|
||||||
#### Testing
|
#### Testing
|
||||||
|
|
||||||
|
@ -388,15 +453,24 @@ Logrus has a built in facility for asserting the presence of log messages. This
|
||||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||||
|
|
||||||
```go
|
```go
|
||||||
logger, hook := NewNullLogger()
|
import(
|
||||||
logger.Error("Hello error")
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
assert.Equal(1, len(hook.Entries))
|
func TestSomething(t*testing.T){
|
||||||
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
logger, hook := test.NewNullLogger()
|
||||||
assert.Equal("Hello error", hook.LastEntry().Message)
|
logger.Error("Helloerror")
|
||||||
|
|
||||||
hook.Reset()
|
assert.Equal(t, 1, len(hook.Entries))
|
||||||
assert.Nil(hook.LastEntry())
|
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
||||||
|
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
||||||
|
|
||||||
|
hook.Reset()
|
||||||
|
assert.Nil(t, hook.LastEntry())
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Fatal handlers
|
#### Fatal handlers
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
// The following code was sourced and modified from the
|
// The following code was sourced and modified from the
|
||||||
// https://bitbucket.org/tebeka/atexit package governed by the following license:
|
// https://github.com/tebeka/atexit package governed by the following license:
|
||||||
//
|
//
|
||||||
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
||||||
//
|
//
|
||||||
|
|
|
@ -2,7 +2,10 @@ package logrus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -11,30 +14,36 @@ func TestRegister(t *testing.T) {
|
||||||
current := len(handlers)
|
current := len(handlers)
|
||||||
RegisterExitHandler(func() {})
|
RegisterExitHandler(func() {})
|
||||||
if len(handlers) != current+1 {
|
if len(handlers) != current+1 {
|
||||||
t.Fatalf("can't add handler")
|
t.Fatalf("expected %d handlers, got %d", current+1, len(handlers))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandler(t *testing.T) {
|
func TestHandler(t *testing.T) {
|
||||||
gofile := "/tmp/testprog.go"
|
tempDir, err := ioutil.TempDir("", "test_handler")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("can't create temp dir. %q", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
gofile := filepath.Join(tempDir, "gofile.go")
|
||||||
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
|
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
|
||||||
t.Fatalf("can't create go file")
|
t.Fatalf("can't create go file. %q", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
outfile := "/tmp/testprog.out"
|
outfile := filepath.Join(tempDir, "outfile.out")
|
||||||
arg := time.Now().UTC().String()
|
arg := time.Now().UTC().String()
|
||||||
err := exec.Command("go", "run", gofile, outfile, arg).Run()
|
err = exec.Command("go", "run", gofile, outfile, arg).Run()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("completed normally, should have failed")
|
t.Fatalf("completed normally, should have failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(outfile)
|
data, err := ioutil.ReadFile(outfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't read output file %s", outfile)
|
t.Fatalf("can't read output file %s. %q", outfile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(data) != arg {
|
if string(data) != arg {
|
||||||
t.Fatalf("bad data")
|
t.Fatalf("bad data. Expected %q, got %q", data, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +53,7 @@ var testprog = []byte(`
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
version: "{build}"
|
||||||
|
platform: x64
|
||||||
|
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
||||||
|
environment:
|
||||||
|
GOPATH: c:\gopath
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
install:
|
||||||
|
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||||
|
- go version
|
||||||
|
build_script:
|
||||||
|
- go get -t
|
||||||
|
- go test
|
|
@ -7,7 +7,7 @@ The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -21,6 +21,6 @@ The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
Output:
|
Output:
|
||||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
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
|
For a full guide visit https://github.com/sirupsen/logrus
|
||||||
*/
|
*/
|
||||||
package logrus
|
package logrus
|
||||||
|
|
|
@ -35,6 +35,7 @@ type Entry struct {
|
||||||
Time time.Time
|
Time time.Time
|
||||||
|
|
||||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
||||||
Level Level
|
Level Level
|
||||||
|
|
||||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
@ -93,7 +94,10 @@ func (entry Entry) log(level Level, msg string) {
|
||||||
entry.Level = level
|
entry.Level = level
|
||||||
entry.Message = msg
|
entry.Message = msg
|
||||||
|
|
||||||
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
|
entry.Logger.mu.Lock()
|
||||||
|
err := entry.Logger.Hooks.Fire(level, &entry)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
if 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()
|
||||||
|
@ -126,7 +130,7 @@ func (entry Entry) log(level Level, msg string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Debug(args ...interface{}) {
|
func (entry *Entry) Debug(args ...interface{}) {
|
||||||
if entry.Logger.Level >= DebugLevel {
|
if entry.Logger.level() >= DebugLevel {
|
||||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,13 +140,13 @@ func (entry *Entry) Print(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Info(args ...interface{}) {
|
func (entry *Entry) Info(args ...interface{}) {
|
||||||
if entry.Logger.Level >= InfoLevel {
|
if entry.Logger.level() >= InfoLevel {
|
||||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warn(args ...interface{}) {
|
func (entry *Entry) Warn(args ...interface{}) {
|
||||||
if entry.Logger.Level >= WarnLevel {
|
if entry.Logger.level() >= WarnLevel {
|
||||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,20 +156,20 @@ func (entry *Entry) Warning(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Error(args ...interface{}) {
|
func (entry *Entry) Error(args ...interface{}) {
|
||||||
if entry.Logger.Level >= ErrorLevel {
|
if entry.Logger.level() >= ErrorLevel {
|
||||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatal(args ...interface{}) {
|
func (entry *Entry) Fatal(args ...interface{}) {
|
||||||
if entry.Logger.Level >= FatalLevel {
|
if entry.Logger.level() >= FatalLevel {
|
||||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
Exit(1)
|
Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panic(args ...interface{}) {
|
func (entry *Entry) Panic(args ...interface{}) {
|
||||||
if entry.Logger.Level >= PanicLevel {
|
if entry.Logger.level() >= PanicLevel {
|
||||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
panic(fmt.Sprint(args...))
|
panic(fmt.Sprint(args...))
|
||||||
|
@ -174,13 +178,13 @@ func (entry *Entry) Panic(args ...interface{}) {
|
||||||
// Entry Printf family functions
|
// Entry Printf family functions
|
||||||
|
|
||||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= DebugLevel {
|
if entry.Logger.level() >= DebugLevel {
|
||||||
entry.Debug(fmt.Sprintf(format, args...))
|
entry.Debug(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= InfoLevel {
|
if entry.Logger.level() >= InfoLevel {
|
||||||
entry.Info(fmt.Sprintf(format, args...))
|
entry.Info(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +194,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= WarnLevel {
|
if entry.Logger.level() >= WarnLevel {
|
||||||
entry.Warn(fmt.Sprintf(format, args...))
|
entry.Warn(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,20 +204,20 @@ func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= ErrorLevel {
|
if entry.Logger.level() >= ErrorLevel {
|
||||||
entry.Error(fmt.Sprintf(format, args...))
|
entry.Error(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
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...))
|
||||||
}
|
}
|
||||||
Exit(1)
|
Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
if entry.Logger.Level >= PanicLevel {
|
if entry.Logger.level() >= PanicLevel {
|
||||||
entry.Panic(fmt.Sprintf(format, args...))
|
entry.Panic(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,13 +225,13 @@ func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
// Entry Println family functions
|
// Entry Println family functions
|
||||||
|
|
||||||
func (entry *Entry) Debugln(args ...interface{}) {
|
func (entry *Entry) Debugln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= DebugLevel {
|
if entry.Logger.level() >= DebugLevel {
|
||||||
entry.Debug(entry.sprintlnn(args...))
|
entry.Debug(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Infoln(args ...interface{}) {
|
func (entry *Entry) Infoln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= InfoLevel {
|
if entry.Logger.level() >= InfoLevel {
|
||||||
entry.Info(entry.sprintlnn(args...))
|
entry.Info(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,7 +241,7 @@ func (entry *Entry) Println(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warnln(args ...interface{}) {
|
func (entry *Entry) Warnln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= WarnLevel {
|
if entry.Logger.level() >= WarnLevel {
|
||||||
entry.Warn(entry.sprintlnn(args...))
|
entry.Warn(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,20 +251,20 @@ func (entry *Entry) Warningln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Errorln(args ...interface{}) {
|
func (entry *Entry) Errorln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= ErrorLevel {
|
if entry.Logger.level() >= ErrorLevel {
|
||||||
entry.Error(entry.sprintlnn(args...))
|
entry.Error(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
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...))
|
||||||
}
|
}
|
||||||
Exit(1)
|
Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panicln(args ...interface{}) {
|
func (entry *Entry) Panicln(args ...interface{}) {
|
||||||
if entry.Logger.Level >= PanicLevel {
|
if entry.Logger.level() >= PanicLevel {
|
||||||
entry.Panic(entry.sprintlnn(args...))
|
entry.Panic(entry.sprintlnn(args...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package logrus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_basic() {
|
||||||
|
var log = logrus.New()
|
||||||
|
log.Formatter = new(logrus.JSONFormatter)
|
||||||
|
log.Formatter = new(logrus.TextFormatter) //default
|
||||||
|
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
|
||||||
|
log.Level = logrus.DebugLevel
|
||||||
|
log.Out = os.Stdout
|
||||||
|
|
||||||
|
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||||
|
// if err == nil {
|
||||||
|
// log.Out = file
|
||||||
|
// } else {
|
||||||
|
// log.Info("Failed to log to file, using default stderr")
|
||||||
|
// }
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
entry := err.(*logrus.Entry)
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"err_animal": entry.Data["animal"],
|
||||||
|
"err_size": entry.Data["size"],
|
||||||
|
"err_level": entry.Level,
|
||||||
|
"err_message": entry.Message,
|
||||||
|
"number": 100,
|
||||||
|
}).Error("The ice breaks!") // or use Fatal() to force the process to exit with a nonzero code
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"number": 8,
|
||||||
|
}).Debug("Started observing beach")
|
||||||
|
|
||||||
|
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{
|
||||||
|
"temperature": -4,
|
||||||
|
}).Debug("Temperature changes")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "orca",
|
||||||
|
"size": 9009,
|
||||||
|
}).Panic("It's over 9000!")
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// level=debug msg="Started observing beach" animal=walrus number=8
|
||||||
|
// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||||
|
// level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||||
|
// level=debug msg="Temperature changes" temperature=-4
|
||||||
|
// level=panic msg="It's over 9000!" animal=orca size=9009
|
||||||
|
// level=error msg="The ice breaks!" err_animal=orca err_level=panic err_message="It's over 9000!" err_size=9009 number=100 omg=true
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package logrus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_hook() {
|
||||||
|
var log = logrus.New()
|
||||||
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
|
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
|
||||||
|
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
|
||||||
|
log.Out = os.Stdout
|
||||||
|
|
||||||
|
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,
|
||||||
|
}).Error("The ice breaks!")
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||||
|
// level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||||
|
// level=error msg="The ice breaks!" number=100 omg=true
|
||||||
|
}
|
|
@ -1,50 +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
|
|
||||||
log.Level = logrus.DebugLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
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",
|
|
||||||
"number": 8,
|
|
||||||
}).Debug("Started observing beach")
|
|
||||||
|
|
||||||
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{
|
|
||||||
"temperature": -4,
|
|
||||||
}).Debug("Temperature changes")
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "orca",
|
|
||||||
"size": 9009,
|
|
||||||
}).Panic("It's over 9000!")
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logrus.New()
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.Formatter = new(logrus.TextFormatter) // default
|
|
||||||
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
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!")
|
|
||||||
}
|
|
|
@ -31,14 +31,14 @@ func SetFormatter(formatter Formatter) {
|
||||||
func SetLevel(level Level) {
|
func SetLevel(level Level) {
|
||||||
std.mu.Lock()
|
std.mu.Lock()
|
||||||
defer std.mu.Unlock()
|
defer std.mu.Unlock()
|
||||||
std.Level = level
|
std.SetLevel(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLevel returns the standard logger level.
|
// GetLevel returns the standard logger level.
|
||||||
func GetLevel() Level {
|
func GetLevel() Level {
|
||||||
std.mu.Lock()
|
std.mu.Lock()
|
||||||
defer std.mu.Unlock()
|
defer std.mu.Unlock()
|
||||||
return std.Level
|
return std.level()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddHook adds a hook to the standard logger hooks.
|
// AddHook adds a hook to the standard logger hooks.
|
||||||
|
|
|
@ -2,7 +2,7 @@ package logrus
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
const DefaultTimestampFormat = time.RFC3339
|
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:
|
||||||
|
|
|
@ -80,11 +80,14 @@ func BenchmarkLargeJSONFormatter(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
|
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
|
||||||
|
logger := New()
|
||||||
|
|
||||||
entry := &Entry{
|
entry := &Entry{
|
||||||
Time: time.Time{},
|
Time: time.Time{},
|
||||||
Level: InfoLevel,
|
Level: InfoLevel,
|
||||||
Message: "message",
|
Message: "message",
|
||||||
Data: fields,
|
Data: fields,
|
||||||
|
Logger: logger,
|
||||||
}
|
}
|
||||||
var d []byte
|
var d []byte
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -120,3 +121,24 @@ func TestErrorHookShouldFireOnError(t *testing.T) {
|
||||||
assert.Equal(t, hook.Fired, true)
|
assert.Equal(t, hook.Fired, true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddHookRace(t *testing.T) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
hook := new(ErrorHook)
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
log.AddHook(hook)
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
log.Error("test")
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
}, func(fields Fields) {
|
||||||
|
// the line may have been logged
|
||||||
|
// before the hook was added, so we can't
|
||||||
|
// actually assert on the hook
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log := logrus.New()
|
log := logrus.New()
|
||||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
hook, err := lSyslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Hooks.Add(hook)
|
log.Hooks.Add(hook)
|
||||||
|
@ -24,13 +24,13 @@ If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log := logrus.New()
|
log := logrus.New()
|
||||||
hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
|
hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Hooks.Add(hook)
|
log.Hooks.Add(hook)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
// +build !windows,!nacl,!plan9
|
// +build !windows,!nacl,!plan9
|
||||||
|
|
||||||
package logrus_syslog
|
package syslog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SyslogHook to send logs via syslog.
|
// SyslogHook to send logs via syslog.
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package logrus_syslog
|
package syslog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLocalhostAddAndPrint(t *testing.T) {
|
func TestLocalhostAddAndPrint(t *testing.T) {
|
||||||
|
|
|
@ -1,17 +1,25 @@
|
||||||
|
// The Test package is used for testing logrus. It is here for backwards
|
||||||
|
// compatibility from when logrus' organization was upper-case. Please use
|
||||||
|
// lower-case logrus and the `null` package instead of this one.
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// test.Hook is a hook designed for dealing with logs in test scenarios.
|
// Hook is a hook designed for dealing with logs in test scenarios.
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
|
// Entries is an array of all entries that have been received by this hook.
|
||||||
|
// For safe access, use the AllEntries() method, rather than reading this
|
||||||
|
// value directly.
|
||||||
Entries []*logrus.Entry
|
Entries []*logrus.Entry
|
||||||
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Installs a test hook for the global logger.
|
// NewGlobal installs a test hook for the global logger.
|
||||||
func NewGlobal() *Hook {
|
func NewGlobal() *Hook {
|
||||||
|
|
||||||
hook := new(Hook)
|
hook := new(Hook)
|
||||||
|
@ -21,7 +29,7 @@ func NewGlobal() *Hook {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Installs a test hook for a given local logger.
|
// NewLocal installs a test hook for a given local logger.
|
||||||
func NewLocal(logger *logrus.Logger) *Hook {
|
func NewLocal(logger *logrus.Logger) *Hook {
|
||||||
|
|
||||||
hook := new(Hook)
|
hook := new(Hook)
|
||||||
|
@ -31,7 +39,7 @@ func NewLocal(logger *logrus.Logger) *Hook {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a discarding logger and installs the test hook.
|
// NewNullLogger creates a discarding logger and installs the test hook.
|
||||||
func NewNullLogger() (*logrus.Logger, *Hook) {
|
func NewNullLogger() (*logrus.Logger, *Hook) {
|
||||||
|
|
||||||
logger := logrus.New()
|
logger := logrus.New()
|
||||||
|
@ -42,6 +50,8 @@ func NewNullLogger() (*logrus.Logger, *Hook) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Hook) Fire(e *logrus.Entry) error {
|
func (t *Hook) Fire(e *logrus.Entry) error {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
t.Entries = append(t.Entries, e)
|
t.Entries = append(t.Entries, e)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -51,17 +61,35 @@ func (t *Hook) Levels() []logrus.Level {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastEntry returns the last entry that was logged or nil.
|
// LastEntry returns the last entry that was logged or nil.
|
||||||
func (t *Hook) LastEntry() (l *logrus.Entry) {
|
func (t *Hook) LastEntry() *logrus.Entry {
|
||||||
|
t.mu.RLock()
|
||||||
if i := len(t.Entries) - 1; i < 0 {
|
defer t.mu.RUnlock()
|
||||||
|
i := len(t.Entries) - 1
|
||||||
|
if i < 0 {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
|
||||||
return t.Entries[i]
|
|
||||||
}
|
}
|
||||||
|
// Make a copy, for safety
|
||||||
|
e := *t.Entries[i]
|
||||||
|
return &e
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllEntries returns all entries that were logged.
|
||||||
|
func (t *Hook) AllEntries() []*logrus.Entry {
|
||||||
|
t.mu.RLock()
|
||||||
|
defer t.mu.RUnlock()
|
||||||
|
// Make a copy so the returned value won't race with future log requests
|
||||||
|
entries := make([]*logrus.Entry, len(t.Entries))
|
||||||
|
for i, entry := range t.Entries {
|
||||||
|
// Make a copy, for safety
|
||||||
|
e := *entry
|
||||||
|
entries[i] = &e
|
||||||
|
}
|
||||||
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset removes all Entries from this test hook.
|
// Reset removes all Entries from this test hook.
|
||||||
func (t *Hook) Reset() {
|
func (t *Hook) Reset() {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
t.Entries = make([]*logrus.Entry, 0)
|
t.Entries = make([]*logrus.Entry, 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type fieldKey string
|
type fieldKey string
|
||||||
|
|
||||||
|
// FieldMap allows customization of the key names for default fields.
|
||||||
type FieldMap map[fieldKey]string
|
type FieldMap map[fieldKey]string
|
||||||
|
|
||||||
|
// Default key names for the default fields
|
||||||
const (
|
const (
|
||||||
FieldKeyMsg = "msg"
|
FieldKeyMsg = "msg"
|
||||||
FieldKeyLevel = "level"
|
FieldKeyLevel = "level"
|
||||||
|
@ -22,6 +25,7 @@ func (f FieldMap) resolve(key fieldKey) string {
|
||||||
return string(key)
|
return string(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSONFormatter formats logs into parsable json
|
||||||
type JSONFormatter struct {
|
type JSONFormatter struct {
|
||||||
// TimestampFormat sets the format used for marshaling timestamps.
|
// TimestampFormat sets the format used for marshaling timestamps.
|
||||||
TimestampFormat string
|
TimestampFormat string
|
||||||
|
@ -29,25 +33,26 @@ type JSONFormatter struct {
|
||||||
// DisableTimestamp allows disabling automatic timestamps in output
|
// DisableTimestamp allows disabling automatic timestamps in output
|
||||||
DisableTimestamp bool
|
DisableTimestamp bool
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for various fields.
|
// FieldMap allows users to customize the names of keys for default fields.
|
||||||
// As an example:
|
// As an example:
|
||||||
// formatter := &JSONFormatter{
|
// formatter := &JSONFormatter{
|
||||||
// FieldMap: FieldMap{
|
// FieldMap: FieldMap{
|
||||||
// FieldKeyTime: "@timestamp",
|
// FieldKeyTime: "@timestamp",
|
||||||
// FieldKeyLevel: "@level",
|
// FieldKeyLevel: "@level",
|
||||||
// FieldKeyLevel: "@message",
|
// FieldKeyMsg: "@message",
|
||||||
// },
|
// },
|
||||||
// }
|
// }
|
||||||
FieldMap FieldMap
|
FieldMap FieldMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format renders a single log entry
|
||||||
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 {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case error:
|
case error:
|
||||||
// Otherwise errors are ignored by `encoding/json`
|
// Otherwise errors are ignored by `encoding/json`
|
||||||
// https://github.com/Sirupsen/logrus/issues/137
|
// https://github.com/sirupsen/logrus/issues/137
|
||||||
data[k] = v.Error()
|
data[k] = v.Error()
|
||||||
default:
|
default:
|
||||||
data[k] = v
|
data[k] = v
|
||||||
|
@ -57,7 +62,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
timestampFormat := f.TimestampFormat
|
||||||
if timestampFormat == "" {
|
if timestampFormat == "" {
|
||||||
timestampFormat = DefaultTimestampFormat
|
timestampFormat = defaultTimestampFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
if !f.DisableTimestamp {
|
if !f.DisableTimestamp {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
|
@ -24,7 +25,7 @@ type Logger struct {
|
||||||
Formatter Formatter
|
Formatter Formatter
|
||||||
// The logging level the logger should log at. This is typically (and defaults
|
// 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
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
// logged. `logrus.Debug` is useful in
|
// logged.
|
||||||
Level Level
|
Level Level
|
||||||
// Used to sync writing to the log. Locking is enabled by Default
|
// Used to sync writing to the log. Locking is enabled by Default
|
||||||
mu MutexWrap
|
mu MutexWrap
|
||||||
|
@ -112,7 +113,7 @@ func (logger *Logger) WithError(err error) *Entry {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
if logger.Level >= DebugLevel {
|
if logger.level() >= DebugLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Debugf(format, args...)
|
entry.Debugf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -120,7 +121,7 @@ func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||||
if logger.Level >= InfoLevel {
|
if logger.level() >= InfoLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Infof(format, args...)
|
entry.Infof(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -134,7 +135,7 @@ func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnf(format, args...)
|
entry.Warnf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -142,7 +143,7 @@ func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnf(format, args...)
|
entry.Warnf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -150,7 +151,7 @@ func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
if logger.Level >= ErrorLevel {
|
if logger.level() >= ErrorLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Errorf(format, args...)
|
entry.Errorf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -158,7 +159,7 @@ func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
if logger.Level >= FatalLevel {
|
if logger.level() >= FatalLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Fatalf(format, args...)
|
entry.Fatalf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -167,7 +168,7 @@ func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
if logger.Level >= PanicLevel {
|
if logger.level() >= PanicLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Panicf(format, args...)
|
entry.Panicf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -175,7 +176,7 @@ func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debug(args ...interface{}) {
|
func (logger *Logger) Debug(args ...interface{}) {
|
||||||
if logger.Level >= DebugLevel {
|
if logger.level() >= DebugLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Debug(args...)
|
entry.Debug(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -183,7 +184,7 @@ func (logger *Logger) Debug(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Info(args ...interface{}) {
|
func (logger *Logger) Info(args ...interface{}) {
|
||||||
if logger.Level >= InfoLevel {
|
if logger.level() >= InfoLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Info(args...)
|
entry.Info(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -197,7 +198,7 @@ func (logger *Logger) Print(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warn(args ...interface{}) {
|
func (logger *Logger) Warn(args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warn(args...)
|
entry.Warn(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -205,7 +206,7 @@ func (logger *Logger) Warn(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warning(args ...interface{}) {
|
func (logger *Logger) Warning(args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warn(args...)
|
entry.Warn(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -213,7 +214,7 @@ func (logger *Logger) Warning(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Error(args ...interface{}) {
|
func (logger *Logger) Error(args ...interface{}) {
|
||||||
if logger.Level >= ErrorLevel {
|
if logger.level() >= ErrorLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Error(args...)
|
entry.Error(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -221,7 +222,7 @@ func (logger *Logger) Error(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatal(args ...interface{}) {
|
func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
if logger.Level >= FatalLevel {
|
if logger.level() >= FatalLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Fatal(args...)
|
entry.Fatal(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -230,7 +231,7 @@ func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panic(args ...interface{}) {
|
func (logger *Logger) Panic(args ...interface{}) {
|
||||||
if logger.Level >= PanicLevel {
|
if logger.level() >= PanicLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Panic(args...)
|
entry.Panic(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -238,7 +239,7 @@ func (logger *Logger) Panic(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debugln(args ...interface{}) {
|
func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
if logger.Level >= DebugLevel {
|
if logger.level() >= DebugLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Debugln(args...)
|
entry.Debugln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -246,7 +247,7 @@ func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Infoln(args ...interface{}) {
|
func (logger *Logger) Infoln(args ...interface{}) {
|
||||||
if logger.Level >= InfoLevel {
|
if logger.level() >= InfoLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Infoln(args...)
|
entry.Infoln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -260,7 +261,7 @@ func (logger *Logger) Println(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warnln(args ...interface{}) {
|
func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnln(args...)
|
entry.Warnln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -268,7 +269,7 @@ func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warningln(args ...interface{}) {
|
func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
if logger.Level >= WarnLevel {
|
if logger.level() >= WarnLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Warnln(args...)
|
entry.Warnln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -276,7 +277,7 @@ func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Errorln(args ...interface{}) {
|
func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
if logger.Level >= ErrorLevel {
|
if logger.level() >= ErrorLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Errorln(args...)
|
entry.Errorln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -284,7 +285,7 @@ func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
if logger.Level >= FatalLevel {
|
if logger.level() >= FatalLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Fatalln(args...)
|
entry.Fatalln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -293,7 +294,7 @@ func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panicln(args ...interface{}) {
|
func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
if logger.Level >= PanicLevel {
|
if logger.level() >= PanicLevel {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Panicln(args...)
|
entry.Panicln(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
|
@ -306,3 +307,17 @@ func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
func (logger *Logger) SetNoLock() {
|
func (logger *Logger) SetNoLock() {
|
||||||
logger.mu.Disable()
|
logger.mu.Disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) level() Level {
|
||||||
|
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) SetLevel(level Level) {
|
||||||
|
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) AddHook(hook Hook) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
type Fields map[string]interface{}
|
type Fields map[string]interface{}
|
||||||
|
|
||||||
// Level type
|
// Level type
|
||||||
type Level uint8
|
type Level uint32
|
||||||
|
|
||||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||||
func (level Level) String() string {
|
func (level Level) String() string {
|
||||||
|
|
|
@ -359,3 +359,28 @@ func TestLogrusInterface(t *testing.T) {
|
||||||
e := logger.WithField("another", "value")
|
e := logger.WithField("another", "value")
|
||||||
fn(e)
|
fn(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements io.Writer using channels for synchronization, so we can wait on
|
||||||
|
// the Entry.Writer goroutine to write in a non-racey way. This does assume that
|
||||||
|
// there is a single call to Logger.Out for each message.
|
||||||
|
type channelWriter chan []byte
|
||||||
|
|
||||||
|
func (cw channelWriter) Write(p []byte) (int, error) {
|
||||||
|
cw <- p
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEntryWriter(t *testing.T) {
|
||||||
|
cw := channelWriter(make(chan []byte, 1))
|
||||||
|
log := New()
|
||||||
|
log.Out = cw
|
||||||
|
log.Formatter = new(JSONFormatter)
|
||||||
|
log.WithField("foo", "bar").WriterLevel(WarnLevel).Write([]byte("hello\n"))
|
||||||
|
|
||||||
|
bs := <-cw
|
||||||
|
var fields Fields
|
||||||
|
err := json.Unmarshal(bs, &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, fields["foo"], "bar")
|
||||||
|
assert.Equal(t, fields["level"], "warning")
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
// +build appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import "syscall"
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA
|
const ioctlReadTermios = unix.TIOCGETA
|
||||||
|
|
||||||
type Termios syscall.Termios
|
type Termios unix.Termios
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import "syscall"
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TCGETS
|
const ioctlReadTermios = unix.TCGETS
|
||||||
|
|
||||||
type Termios syscall.Termios
|
type Termios unix.Termios
|
||||||
|
|
|
@ -1,22 +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 darwin freebsd openbsd netbsd dragonfly
|
|
||||||
// +build !appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
fd := syscall.Stderr
|
|
||||||
var termios Termios
|
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
|
||||||
return err == 0
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
// +build solaris,!appengine
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -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,!appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
|
|
||||||
var (
|
|
||||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
fd := syscall.Stderr
|
|
||||||
var st uint32
|
|
||||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
|
||||||
return r != 0 && e == 0
|
|
||||||
}
|
|
|
@ -3,10 +3,14 @@ package logrus
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"io"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -14,20 +18,19 @@ const (
|
||||||
red = 31
|
red = 31
|
||||||
green = 32
|
green = 32
|
||||||
yellow = 33
|
yellow = 33
|
||||||
blue = 34
|
blue = 36
|
||||||
gray = 37
|
gray = 37
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
baseTimestamp time.Time
|
baseTimestamp time.Time
|
||||||
isTerminal bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
baseTimestamp = time.Now()
|
baseTimestamp = time.Now()
|
||||||
isTerminal = IsTerminal()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TextFormatter formats logs into text
|
||||||
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
|
||||||
|
@ -50,8 +53,32 @@ type TextFormatter struct {
|
||||||
// that log extremely frequently and don't use the JSON formatter this may not
|
// that log extremely frequently and don't use the JSON formatter this may not
|
||||||
// be desired.
|
// be desired.
|
||||||
DisableSorting bool
|
DisableSorting bool
|
||||||
|
|
||||||
|
// QuoteEmptyFields will wrap empty fields in quotes if true
|
||||||
|
QuoteEmptyFields bool
|
||||||
|
|
||||||
|
// Whether the logger's out is to a terminal
|
||||||
|
isTerminal bool
|
||||||
|
|
||||||
|
sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) init(entry *Entry) {
|
||||||
|
if entry.Logger != nil {
|
||||||
|
f.isTerminal = f.checkIfTerminal(entry.Logger.Out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) checkIfTerminal(w io.Writer) bool {
|
||||||
|
switch v := w.(type) {
|
||||||
|
case *os.File:
|
||||||
|
return terminal.IsTerminal(int(v.Fd()))
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format renders a single log entry
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
var b *bytes.Buffer
|
var b *bytes.Buffer
|
||||||
keys := make([]string, 0, len(entry.Data))
|
keys := make([]string, 0, len(entry.Data))
|
||||||
|
@ -70,12 +97,13 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
|
||||||
prefixFieldClashes(entry.Data)
|
prefixFieldClashes(entry.Data)
|
||||||
|
|
||||||
isColorTerminal := isTerminal && (runtime.GOOS != "windows")
|
f.Do(func() { f.init(entry) })
|
||||||
isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
|
|
||||||
|
isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
timestampFormat := f.TimestampFormat
|
||||||
if timestampFormat == "" {
|
if timestampFormat == "" {
|
||||||
timestampFormat = DefaultTimestampFormat
|
timestampFormat = defaultTimestampFormat
|
||||||
}
|
}
|
||||||
if isColored {
|
if isColored {
|
||||||
f.printColored(b, entry, keys, timestampFormat)
|
f.printColored(b, entry, keys, timestampFormat)
|
||||||
|
@ -125,12 +153,15 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func needsQuoting(text string) bool {
|
func (f *TextFormatter) needsQuoting(text string) bool {
|
||||||
|
if f.QuoteEmptyFields && len(text) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
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 == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,29 +169,23 @@ func needsQuoting(text string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||||
|
if b.Len() > 0 {
|
||||||
|
b.WriteByte(' ')
|
||||||
|
}
|
||||||
b.WriteString(key)
|
b.WriteString(key)
|
||||||
b.WriteByte('=')
|
b.WriteByte('=')
|
||||||
f.appendValue(b, value)
|
f.appendValue(b, value)
|
||||||
b.WriteByte(' ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
||||||
switch value := value.(type) {
|
stringVal, ok := value.(string)
|
||||||
case string:
|
if !ok {
|
||||||
if !needsQuoting(value) {
|
stringVal = fmt.Sprint(value)
|
||||||
b.WriteString(value)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(b, "%q", value)
|
|
||||||
}
|
}
|
||||||
case error:
|
|
||||||
errmsg := value.Error()
|
if !f.needsQuoting(stringVal) {
|
||||||
if !needsQuoting(errmsg) {
|
b.WriteString(stringVal)
|
||||||
b.WriteString(errmsg)
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(b, "%q", errmsg)
|
b.WriteString(fmt.Sprintf("%q", stringVal))
|
||||||
}
|
|
||||||
default:
|
|
||||||
fmt.Fprint(b, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,38 @@ package logrus
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestFormatting(t *testing.T) {
|
||||||
|
tf := &TextFormatter{DisableColors: true}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
value string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{`foo`, "time=\"0001-01-01T00:00:00Z\" level=panic test=foo\n"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
b, _ := tf.Format(WithField("test", tc.value))
|
||||||
|
|
||||||
|
if string(b) != tc.expected {
|
||||||
|
t.Errorf("formatting expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestQuoting(t *testing.T) {
|
func TestQuoting(t *testing.T) {
|
||||||
tf := &TextFormatter{DisableColors: true}
|
tf := &TextFormatter{DisableColors: true}
|
||||||
|
|
||||||
checkQuoting := func(q bool, value interface{}) {
|
checkQuoting := func(q bool, value interface{}) {
|
||||||
b, _ := tf.Format(WithField("test", value))
|
b, _ := tf.Format(WithField("test", value))
|
||||||
idx := bytes.Index(b, ([]byte)("test="))
|
idx := bytes.Index(b, ([]byte)("test="))
|
||||||
cont := bytes.Contains(b[idx+5:], []byte{'"'})
|
cont := bytes.Contains(b[idx+5:], []byte("\""))
|
||||||
if cont != q {
|
if cont != q {
|
||||||
if q {
|
if q {
|
||||||
t.Errorf("quoting expected for: %#v", value)
|
t.Errorf("quoting expected for: %#v", value)
|
||||||
|
@ -24,14 +44,67 @@ func TestQuoting(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkQuoting(false, "")
|
||||||
checkQuoting(false, "abcd")
|
checkQuoting(false, "abcd")
|
||||||
checkQuoting(false, "v1.0")
|
checkQuoting(false, "v1.0")
|
||||||
checkQuoting(false, "1234567890")
|
checkQuoting(false, "1234567890")
|
||||||
checkQuoting(true, "/foobar")
|
checkQuoting(false, "/foobar")
|
||||||
|
checkQuoting(false, "foo_bar")
|
||||||
|
checkQuoting(false, "foo@bar")
|
||||||
|
checkQuoting(false, "foobar^")
|
||||||
|
checkQuoting(false, "+/-_^@f.oobar")
|
||||||
|
checkQuoting(true, "foobar$")
|
||||||
|
checkQuoting(true, "&foobar")
|
||||||
checkQuoting(true, "x y")
|
checkQuoting(true, "x y")
|
||||||
checkQuoting(true, "x,y")
|
checkQuoting(true, "x,y")
|
||||||
checkQuoting(false, errors.New("invalid"))
|
checkQuoting(false, errors.New("invalid"))
|
||||||
checkQuoting(true, errors.New("invalid argument"))
|
checkQuoting(true, errors.New("invalid argument"))
|
||||||
|
|
||||||
|
// Test for quoting empty fields.
|
||||||
|
tf.QuoteEmptyFields = true
|
||||||
|
checkQuoting(true, "")
|
||||||
|
checkQuoting(false, "abcd")
|
||||||
|
checkQuoting(true, errors.New("invalid argument"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscaping(t *testing.T) {
|
||||||
|
tf := &TextFormatter{DisableColors: true}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
value string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{`ba"r`, `ba\"r`},
|
||||||
|
{`ba'r`, `ba'r`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
b, _ := tf.Format(WithField("test", tc.value))
|
||||||
|
if !bytes.Contains(b, []byte(tc.expected)) {
|
||||||
|
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscaping_Interface(t *testing.T) {
|
||||||
|
tf := &TextFormatter{DisableColors: true}
|
||||||
|
|
||||||
|
ts := time.Now()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
value interface{}
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{ts, fmt.Sprintf("\"%s\"", ts.String())},
|
||||||
|
{errors.New("error: something went wrong"), "\"error: something went wrong\""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
b, _ := tf.Format(WithField("test", tc.value))
|
||||||
|
if !bytes.Contains(b, []byte(tc.expected)) {
|
||||||
|
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimestampFormat(t *testing.T) {
|
func TestTimestampFormat(t *testing.T) {
|
||||||
|
@ -40,10 +113,7 @@ func TestTimestampFormat(t *testing.T) {
|
||||||
customStr, _ := customFormatter.Format(WithField("test", "test"))
|
customStr, _ := customFormatter.Format(WithField("test", "test"))
|
||||||
timeStart := bytes.Index(customStr, ([]byte)("time="))
|
timeStart := bytes.Index(customStr, ([]byte)("time="))
|
||||||
timeEnd := bytes.Index(customStr, ([]byte)("level="))
|
timeEnd := bytes.Index(customStr, ([]byte)("level="))
|
||||||
timeStr := customStr[timeStart+5 : timeEnd-1]
|
timeStr := customStr[timeStart+5+len("\"") : timeEnd-1-len("\"")]
|
||||||
if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' {
|
|
||||||
timeStr = timeStr[1 : len(timeStr)-1]
|
|
||||||
}
|
|
||||||
if format == "" {
|
if format == "" {
|
||||||
format = time.RFC3339
|
format = time.RFC3339
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,39 +11,48 @@ func (logger *Logger) Writer() *io.PipeWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
||||||
|
return NewEntry(logger).WriterLevel(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Writer() *io.PipeWriter {
|
||||||
|
return entry.WriterLevel(InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
||||||
reader, writer := io.Pipe()
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
var printFunc func(args ...interface{})
|
var printFunc func(args ...interface{})
|
||||||
|
|
||||||
switch level {
|
switch level {
|
||||||
case DebugLevel:
|
case DebugLevel:
|
||||||
printFunc = logger.Debug
|
printFunc = entry.Debug
|
||||||
case InfoLevel:
|
case InfoLevel:
|
||||||
printFunc = logger.Info
|
printFunc = entry.Info
|
||||||
case WarnLevel:
|
case WarnLevel:
|
||||||
printFunc = logger.Warn
|
printFunc = entry.Warn
|
||||||
case ErrorLevel:
|
case ErrorLevel:
|
||||||
printFunc = logger.Error
|
printFunc = entry.Error
|
||||||
case FatalLevel:
|
case FatalLevel:
|
||||||
printFunc = logger.Fatal
|
printFunc = entry.Fatal
|
||||||
case PanicLevel:
|
case PanicLevel:
|
||||||
printFunc = logger.Panic
|
printFunc = entry.Panic
|
||||||
default:
|
default:
|
||||||
printFunc = logger.Print
|
printFunc = entry.Print
|
||||||
}
|
}
|
||||||
|
|
||||||
go logger.writerScanner(reader, printFunc)
|
go entry.writerScanner(reader, printFunc)
|
||||||
runtime.SetFinalizer(writer, writerFinalizer)
|
runtime.SetFinalizer(writer, writerFinalizer)
|
||||||
|
|
||||||
return writer
|
return writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
printFunc(scanner.Text())
|
printFunc(scanner.Text())
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
logger.Errorf("Error while reading from Writer: %s", err)
|
entry.Errorf("Error while reading from Writer: %s", err)
|
||||||
}
|
}
|
||||||
reader.Close()
|
reader.Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,10 @@ import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/crypto/blowfish"
|
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/blowfish"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -205,7 +206,6 @@ func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
|
func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
|
||||||
|
|
||||||
csalt, err := base64Decode(salt)
|
csalt, err := base64Decode(salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -213,7 +213,8 @@ func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cip
|
||||||
|
|
||||||
// Bug compatibility with C bcrypt implementations. They use the trailing
|
// Bug compatibility with C bcrypt implementations. They use the trailing
|
||||||
// NULL in the key string during expansion.
|
// NULL in the key string during expansion.
|
||||||
ckey := append(key, 0)
|
// We copy the key to prevent changing the underlying array.
|
||||||
|
ckey := append(key[:len(key):len(key)], 0)
|
||||||
|
|
||||||
c, err := blowfish.NewSaltedCipher(ckey, csalt)
|
c, err := blowfish.NewSaltedCipher(ckey, csalt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -224,3 +224,20 @@ func BenchmarkGeneration(b *testing.B) {
|
||||||
GenerateFromPassword(passwd, 10)
|
GenerateFromPassword(passwd, 10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See Issue https://github.com/golang/go/issues/20425.
|
||||||
|
func TestNoSideEffectsFromCompare(t *testing.T) {
|
||||||
|
source := []byte("passw0rd123456")
|
||||||
|
password := source[:len(source)-6]
|
||||||
|
token := source[len(source)-6:]
|
||||||
|
want := make([]byte, len(source))
|
||||||
|
copy(want, source)
|
||||||
|
|
||||||
|
wantHash := []byte("$2a$10$LK9XRuhNxHHCvjX3tdkRKei1QiCDUKrJRhZv7WWZPuQGRUM92rOUa")
|
||||||
|
_ = CompareHashAndPassword(wantHash, password)
|
||||||
|
|
||||||
|
got := bytes.Join([][]byte{password, token}, []byte(""))
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("got=%q want=%q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ type CryptTest struct {
|
||||||
out []byte
|
out []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test vector values are from http://www.schneier.com/code/vectors.txt.
|
// Test vector values are from https://www.schneier.com/code/vectors.txt.
|
||||||
var encryptTests = []CryptTest{
|
var encryptTests = []CryptTest{
|
||||||
{
|
{
|
||||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
package blowfish // import "golang.org/x/crypto/blowfish"
|
package blowfish // import "golang.org/x/crypto/blowfish"
|
||||||
|
|
||||||
// The code is a port of Bruce Schneier's C implementation.
|
// The code is a port of Bruce Schneier's C implementation.
|
||||||
// See http://www.schneier.com/blowfish.html.
|
// See https://www.schneier.com/blowfish.html.
|
||||||
|
|
||||||
import "strconv"
|
import "strconv"
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
// The startup permutation array and substitution boxes.
|
// The startup permutation array and substitution boxes.
|
||||||
// They are the hexadecimal digits of PI; see:
|
// They are the hexadecimal digits of PI; see:
|
||||||
// http://www.schneier.com/code/constants.txt.
|
// https://www.schneier.com/code/constants.txt.
|
||||||
|
|
||||||
package blowfish
|
package blowfish
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// This code was translated into a form compatible with 6a from the public
|
||||||
|
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
|
||||||
|
|
||||||
|
#define REDMASK51 0x0007FFFFFFFFFFFF
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// This code was translated into a form compatible with 6a from the public
|
||||||
|
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
|
||||||
|
|
||||||
|
// +build amd64,!gccgo,!appengine
|
||||||
|
|
||||||
|
// These constants cannot be encoded in non-MOVQ immediates.
|
||||||
|
// We access them directly from memory instead.
|
||||||
|
|
||||||
|
DATA ·_121666_213(SB)/8, $996687872
|
||||||
|
GLOBL ·_121666_213(SB), 8, $8
|
||||||
|
|
||||||
|
DATA ·_2P0(SB)/8, $0xFFFFFFFFFFFDA
|
||||||
|
GLOBL ·_2P0(SB), 8, $8
|
||||||
|
|
||||||
|
DATA ·_2P1234(SB)/8, $0xFFFFFFFFFFFFE
|
||||||
|
GLOBL ·_2P1234(SB), 8, $8
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2012 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 amd64,!gccgo,!appengine
|
||||||
|
|
||||||
|
// func cswap(inout *[4][5]uint64, v uint64)
|
||||||
|
TEXT ·cswap(SB),7,$0
|
||||||
|
MOVQ inout+0(FP),DI
|
||||||
|
MOVQ v+8(FP),SI
|
||||||
|
|
||||||
|
SUBQ $1, SI
|
||||||
|
NOTQ SI
|
||||||
|
MOVQ SI, X15
|
||||||
|
PSHUFD $0x44, X15, X15
|
||||||
|
|
||||||
|
MOVOU 0(DI), X0
|
||||||
|
MOVOU 16(DI), X2
|
||||||
|
MOVOU 32(DI), X4
|
||||||
|
MOVOU 48(DI), X6
|
||||||
|
MOVOU 64(DI), X8
|
||||||
|
MOVOU 80(DI), X1
|
||||||
|
MOVOU 96(DI), X3
|
||||||
|
MOVOU 112(DI), X5
|
||||||
|
MOVOU 128(DI), X7
|
||||||
|
MOVOU 144(DI), X9
|
||||||
|
|
||||||
|
MOVO X1, X10
|
||||||
|
MOVO X3, X11
|
||||||
|
MOVO X5, X12
|
||||||
|
MOVO X7, X13
|
||||||
|
MOVO X9, X14
|
||||||
|
|
||||||
|
PXOR X0, X10
|
||||||
|
PXOR X2, X11
|
||||||
|
PXOR X4, X12
|
||||||
|
PXOR X6, X13
|
||||||
|
PXOR X8, X14
|
||||||
|
PAND X15, X10
|
||||||
|
PAND X15, X11
|
||||||
|
PAND X15, X12
|
||||||
|
PAND X15, X13
|
||||||
|
PAND X15, X14
|
||||||
|
PXOR X10, X0
|
||||||
|
PXOR X10, X1
|
||||||
|
PXOR X11, X2
|
||||||
|
PXOR X11, X3
|
||||||
|
PXOR X12, X4
|
||||||
|
PXOR X12, X5
|
||||||
|
PXOR X13, X6
|
||||||
|
PXOR X13, X7
|
||||||
|
PXOR X14, X8
|
||||||
|
PXOR X14, X9
|
||||||
|
|
||||||
|
MOVOU X0, 0(DI)
|
||||||
|
MOVOU X2, 16(DI)
|
||||||
|
MOVOU X4, 32(DI)
|
||||||
|
MOVOU X6, 48(DI)
|
||||||
|
MOVOU X8, 64(DI)
|
||||||
|
MOVOU X1, 80(DI)
|
||||||
|
MOVOU X3, 96(DI)
|
||||||
|
MOVOU X5, 112(DI)
|
||||||
|
MOVOU X7, 128(DI)
|
||||||
|
MOVOU X9, 144(DI)
|
||||||
|
RET
|
|
@ -0,0 +1,834 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// We have an implementation in amd64 assembly so this code is only run on
|
||||||
|
// non-amd64 platforms. The amd64 assembly does not support gccgo.
|
||||||
|
// +build !amd64 gccgo appengine
|
||||||
|
|
||||||
|
package curve25519
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This code is a port of the public domain, "ref10" implementation of
|
||||||
|
// curve25519 from SUPERCOP 20130419 by D. J. Bernstein.
|
||||||
|
|
||||||
|
// fieldElement represents an element of the field GF(2^255 - 19). An element
|
||||||
|
// t, entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77
|
||||||
|
// t[3]+2^102 t[4]+...+2^230 t[9]. Bounds on each t[i] vary depending on
|
||||||
|
// context.
|
||||||
|
type fieldElement [10]int32
|
||||||
|
|
||||||
|
func feZero(fe *fieldElement) {
|
||||||
|
for i := range fe {
|
||||||
|
fe[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func feOne(fe *fieldElement) {
|
||||||
|
feZero(fe)
|
||||||
|
fe[0] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func feAdd(dst, a, b *fieldElement) {
|
||||||
|
for i := range dst {
|
||||||
|
dst[i] = a[i] + b[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func feSub(dst, a, b *fieldElement) {
|
||||||
|
for i := range dst {
|
||||||
|
dst[i] = a[i] - b[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func feCopy(dst, src *fieldElement) {
|
||||||
|
for i := range dst {
|
||||||
|
dst[i] = src[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// feCSwap replaces (f,g) with (g,f) if b == 1; replaces (f,g) with (f,g) if b == 0.
|
||||||
|
//
|
||||||
|
// Preconditions: b in {0,1}.
|
||||||
|
func feCSwap(f, g *fieldElement, b int32) {
|
||||||
|
b = -b
|
||||||
|
for i := range f {
|
||||||
|
t := b & (f[i] ^ g[i])
|
||||||
|
f[i] ^= t
|
||||||
|
g[i] ^= t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load3 reads a 24-bit, little-endian value from in.
|
||||||
|
func load3(in []byte) int64 {
|
||||||
|
var r int64
|
||||||
|
r = int64(in[0])
|
||||||
|
r |= int64(in[1]) << 8
|
||||||
|
r |= int64(in[2]) << 16
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// load4 reads a 32-bit, little-endian value from in.
|
||||||
|
func load4(in []byte) int64 {
|
||||||
|
return int64(binary.LittleEndian.Uint32(in))
|
||||||
|
}
|
||||||
|
|
||||||
|
func feFromBytes(dst *fieldElement, src *[32]byte) {
|
||||||
|
h0 := load4(src[:])
|
||||||
|
h1 := load3(src[4:]) << 6
|
||||||
|
h2 := load3(src[7:]) << 5
|
||||||
|
h3 := load3(src[10:]) << 3
|
||||||
|
h4 := load3(src[13:]) << 2
|
||||||
|
h5 := load4(src[16:])
|
||||||
|
h6 := load3(src[20:]) << 7
|
||||||
|
h7 := load3(src[23:]) << 5
|
||||||
|
h8 := load3(src[26:]) << 4
|
||||||
|
h9 := load3(src[29:]) << 2
|
||||||
|
|
||||||
|
var carry [10]int64
|
||||||
|
carry[9] = (h9 + 1<<24) >> 25
|
||||||
|
h0 += carry[9] * 19
|
||||||
|
h9 -= carry[9] << 25
|
||||||
|
carry[1] = (h1 + 1<<24) >> 25
|
||||||
|
h2 += carry[1]
|
||||||
|
h1 -= carry[1] << 25
|
||||||
|
carry[3] = (h3 + 1<<24) >> 25
|
||||||
|
h4 += carry[3]
|
||||||
|
h3 -= carry[3] << 25
|
||||||
|
carry[5] = (h5 + 1<<24) >> 25
|
||||||
|
h6 += carry[5]
|
||||||
|
h5 -= carry[5] << 25
|
||||||
|
carry[7] = (h7 + 1<<24) >> 25
|
||||||
|
h8 += carry[7]
|
||||||
|
h7 -= carry[7] << 25
|
||||||
|
|
||||||
|
carry[0] = (h0 + 1<<25) >> 26
|
||||||
|
h1 += carry[0]
|
||||||
|
h0 -= carry[0] << 26
|
||||||
|
carry[2] = (h2 + 1<<25) >> 26
|
||||||
|
h3 += carry[2]
|
||||||
|
h2 -= carry[2] << 26
|
||||||
|
carry[4] = (h4 + 1<<25) >> 26
|
||||||
|
h5 += carry[4]
|
||||||
|
h4 -= carry[4] << 26
|
||||||
|
carry[6] = (h6 + 1<<25) >> 26
|
||||||
|
h7 += carry[6]
|
||||||
|
h6 -= carry[6] << 26
|
||||||
|
carry[8] = (h8 + 1<<25) >> 26
|
||||||
|
h9 += carry[8]
|
||||||
|
h8 -= carry[8] << 26
|
||||||
|
|
||||||
|
dst[0] = int32(h0)
|
||||||
|
dst[1] = int32(h1)
|
||||||
|
dst[2] = int32(h2)
|
||||||
|
dst[3] = int32(h3)
|
||||||
|
dst[4] = int32(h4)
|
||||||
|
dst[5] = int32(h5)
|
||||||
|
dst[6] = int32(h6)
|
||||||
|
dst[7] = int32(h7)
|
||||||
|
dst[8] = int32(h8)
|
||||||
|
dst[9] = int32(h9)
|
||||||
|
}
|
||||||
|
|
||||||
|
// feToBytes marshals h to s.
|
||||||
|
// Preconditions:
|
||||||
|
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||||
|
//
|
||||||
|
// Write p=2^255-19; q=floor(h/p).
|
||||||
|
// Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))).
|
||||||
|
//
|
||||||
|
// Proof:
|
||||||
|
// Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4.
|
||||||
|
// Also have |h-2^230 h9|<2^230 so |19 2^(-255)(h-2^230 h9)|<1/4.
|
||||||
|
//
|
||||||
|
// Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9).
|
||||||
|
// Then 0<y<1.
|
||||||
|
//
|
||||||
|
// Write r=h-pq.
|
||||||
|
// Have 0<=r<=p-1=2^255-20.
|
||||||
|
// Thus 0<=r+19(2^-255)r<r+19(2^-255)2^255<=2^255-1.
|
||||||
|
//
|
||||||
|
// Write x=r+19(2^-255)r+y.
|
||||||
|
// Then 0<x<2^255 so floor(2^(-255)x) = 0 so floor(q+2^(-255)x) = q.
|
||||||
|
//
|
||||||
|
// Have q+2^(-255)x = 2^(-255)(h + 19 2^(-25) h9 + 2^(-1))
|
||||||
|
// so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q.
|
||||||
|
func feToBytes(s *[32]byte, h *fieldElement) {
|
||||||
|
var carry [10]int32
|
||||||
|
|
||||||
|
q := (19*h[9] + (1 << 24)) >> 25
|
||||||
|
q = (h[0] + q) >> 26
|
||||||
|
q = (h[1] + q) >> 25
|
||||||
|
q = (h[2] + q) >> 26
|
||||||
|
q = (h[3] + q) >> 25
|
||||||
|
q = (h[4] + q) >> 26
|
||||||
|
q = (h[5] + q) >> 25
|
||||||
|
q = (h[6] + q) >> 26
|
||||||
|
q = (h[7] + q) >> 25
|
||||||
|
q = (h[8] + q) >> 26
|
||||||
|
q = (h[9] + q) >> 25
|
||||||
|
|
||||||
|
// Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20.
|
||||||
|
h[0] += 19 * q
|
||||||
|
// Goal: Output h-2^255 q, which is between 0 and 2^255-20.
|
||||||
|
|
||||||
|
carry[0] = h[0] >> 26
|
||||||
|
h[1] += carry[0]
|
||||||
|
h[0] -= carry[0] << 26
|
||||||
|
carry[1] = h[1] >> 25
|
||||||
|
h[2] += carry[1]
|
||||||
|
h[1] -= carry[1] << 25
|
||||||
|
carry[2] = h[2] >> 26
|
||||||
|
h[3] += carry[2]
|
||||||
|
h[2] -= carry[2] << 26
|
||||||
|
carry[3] = h[3] >> 25
|
||||||
|
h[4] += carry[3]
|
||||||
|
h[3] -= carry[3] << 25
|
||||||
|
carry[4] = h[4] >> 26
|
||||||
|
h[5] += carry[4]
|
||||||
|
h[4] -= carry[4] << 26
|
||||||
|
carry[5] = h[5] >> 25
|
||||||
|
h[6] += carry[5]
|
||||||
|
h[5] -= carry[5] << 25
|
||||||
|
carry[6] = h[6] >> 26
|
||||||
|
h[7] += carry[6]
|
||||||
|
h[6] -= carry[6] << 26
|
||||||
|
carry[7] = h[7] >> 25
|
||||||
|
h[8] += carry[7]
|
||||||
|
h[7] -= carry[7] << 25
|
||||||
|
carry[8] = h[8] >> 26
|
||||||
|
h[9] += carry[8]
|
||||||
|
h[8] -= carry[8] << 26
|
||||||
|
carry[9] = h[9] >> 25
|
||||||
|
h[9] -= carry[9] << 25
|
||||||
|
// h10 = carry9
|
||||||
|
|
||||||
|
// Goal: Output h[0]+...+2^255 h10-2^255 q, which is between 0 and 2^255-20.
|
||||||
|
// Have h[0]+...+2^230 h[9] between 0 and 2^255-1;
|
||||||
|
// evidently 2^255 h10-2^255 q = 0.
|
||||||
|
// Goal: Output h[0]+...+2^230 h[9].
|
||||||
|
|
||||||
|
s[0] = byte(h[0] >> 0)
|
||||||
|
s[1] = byte(h[0] >> 8)
|
||||||
|
s[2] = byte(h[0] >> 16)
|
||||||
|
s[3] = byte((h[0] >> 24) | (h[1] << 2))
|
||||||
|
s[4] = byte(h[1] >> 6)
|
||||||
|
s[5] = byte(h[1] >> 14)
|
||||||
|
s[6] = byte((h[1] >> 22) | (h[2] << 3))
|
||||||
|
s[7] = byte(h[2] >> 5)
|
||||||
|
s[8] = byte(h[2] >> 13)
|
||||||
|
s[9] = byte((h[2] >> 21) | (h[3] << 5))
|
||||||
|
s[10] = byte(h[3] >> 3)
|
||||||
|
s[11] = byte(h[3] >> 11)
|
||||||
|
s[12] = byte((h[3] >> 19) | (h[4] << 6))
|
||||||
|
s[13] = byte(h[4] >> 2)
|
||||||
|
s[14] = byte(h[4] >> 10)
|
||||||
|
s[15] = byte(h[4] >> 18)
|
||||||
|
s[16] = byte(h[5] >> 0)
|
||||||
|
s[17] = byte(h[5] >> 8)
|
||||||
|
s[18] = byte(h[5] >> 16)
|
||||||
|
s[19] = byte((h[5] >> 24) | (h[6] << 1))
|
||||||
|
s[20] = byte(h[6] >> 7)
|
||||||
|
s[21] = byte(h[6] >> 15)
|
||||||
|
s[22] = byte((h[6] >> 23) | (h[7] << 3))
|
||||||
|
s[23] = byte(h[7] >> 5)
|
||||||
|
s[24] = byte(h[7] >> 13)
|
||||||
|
s[25] = byte((h[7] >> 21) | (h[8] << 4))
|
||||||
|
s[26] = byte(h[8] >> 4)
|
||||||
|
s[27] = byte(h[8] >> 12)
|
||||||
|
s[28] = byte((h[8] >> 20) | (h[9] << 6))
|
||||||
|
s[29] = byte(h[9] >> 2)
|
||||||
|
s[30] = byte(h[9] >> 10)
|
||||||
|
s[31] = byte(h[9] >> 18)
|
||||||
|
}
|
||||||
|
|
||||||
|
// feMul calculates h = f * g
|
||||||
|
// Can overlap h with f or g.
|
||||||
|
//
|
||||||
|
// Preconditions:
|
||||||
|
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||||
|
// |g| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||||
|
//
|
||||||
|
// Postconditions:
|
||||||
|
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||||
|
//
|
||||||
|
// Notes on implementation strategy:
|
||||||
|
//
|
||||||
|
// Using schoolbook multiplication.
|
||||||
|
// Karatsuba would save a little in some cost models.
|
||||||
|
//
|
||||||
|
// Most multiplications by 2 and 19 are 32-bit precomputations;
|
||||||
|
// cheaper than 64-bit postcomputations.
|
||||||
|
//
|
||||||
|
// There is one remaining multiplication by 19 in the carry chain;
|
||||||
|
// one *19 precomputation can be merged into this,
|
||||||
|
// but the resulting data flow is considerably less clean.
|
||||||
|
//
|
||||||
|
// There are 12 carries below.
|
||||||
|
// 10 of them are 2-way parallelizable and vectorizable.
|
||||||
|
// Can get away with 11 carries, but then data flow is much deeper.
|
||||||
|
//
|
||||||
|
// With tighter constraints on inputs can squeeze carries into int32.
|
||||||
|
func feMul(h, f, g *fieldElement) {
|
||||||
|
f0 := f[0]
|
||||||
|
f1 := f[1]
|
||||||
|
f2 := f[2]
|
||||||
|
f3 := f[3]
|
||||||
|
f4 := f[4]
|
||||||
|
f5 := f[5]
|
||||||
|
f6 := f[6]
|
||||||
|
f7 := f[7]
|
||||||
|
f8 := f[8]
|
||||||
|
f9 := f[9]
|
||||||
|
g0 := g[0]
|
||||||
|
g1 := g[1]
|
||||||
|
g2 := g[2]
|
||||||
|
g3 := g[3]
|
||||||
|
g4 := g[4]
|
||||||
|
g5 := g[5]
|
||||||
|
g6 := g[6]
|
||||||
|
g7 := g[7]
|
||||||
|
g8 := g[8]
|
||||||
|
g9 := g[9]
|
||||||
|
g1_19 := 19 * g1 // 1.4*2^29
|
||||||
|
g2_19 := 19 * g2 // 1.4*2^30; still ok
|
||||||
|
g3_19 := 19 * g3
|
||||||
|
g4_19 := 19 * g4
|
||||||
|
g5_19 := 19 * g5
|
||||||
|
g6_19 := 19 * g6
|
||||||
|
g7_19 := 19 * g7
|
||||||
|
g8_19 := 19 * g8
|
||||||
|
g9_19 := 19 * g9
|
||||||
|
f1_2 := 2 * f1
|
||||||
|
f3_2 := 2 * f3
|
||||||
|
f5_2 := 2 * f5
|
||||||
|
f7_2 := 2 * f7
|
||||||
|
f9_2 := 2 * f9
|
||||||
|
f0g0 := int64(f0) * int64(g0)
|
||||||
|
f0g1 := int64(f0) * int64(g1)
|
||||||
|
f0g2 := int64(f0) * int64(g2)
|
||||||
|
f0g3 := int64(f0) * int64(g3)
|
||||||
|
f0g4 := int64(f0) * int64(g4)
|
||||||
|
f0g5 := int64(f0) * int64(g5)
|
||||||
|
f0g6 := int64(f0) * int64(g6)
|
||||||
|
f0g7 := int64(f0) * int64(g7)
|
||||||
|
f0g8 := int64(f0) * int64(g8)
|
||||||
|
f0g9 := int64(f0) * int64(g9)
|
||||||
|
f1g0 := int64(f1) * int64(g0)
|
||||||
|
f1g1_2 := int64(f1_2) * int64(g1)
|
||||||
|
f1g2 := int64(f1) * int64(g2)
|
||||||
|
f1g3_2 := int64(f1_2) * int64(g3)
|
||||||
|
f1g4 := int64(f1) * int64(g4)
|
||||||
|
f1g5_2 := int64(f1_2) * int64(g5)
|
||||||
|
f1g6 := int64(f1) * int64(g6)
|
||||||
|
f1g7_2 := int64(f1_2) * int64(g7)
|
||||||
|
f1g8 := int64(f1) * int64(g8)
|
||||||
|
f1g9_38 := int64(f1_2) * int64(g9_19)
|
||||||
|
f2g0 := int64(f2) * int64(g0)
|
||||||
|
f2g1 := int64(f2) * int64(g1)
|
||||||
|
f2g2 := int64(f2) * int64(g2)
|
||||||
|
f2g3 := int64(f2) * int64(g3)
|
||||||
|
f2g4 := int64(f2) * int64(g4)
|
||||||
|
f2g5 := int64(f2) * int64(g5)
|
||||||
|
f2g6 := int64(f2) * int64(g6)
|
||||||
|
f2g7 := int64(f2) * int64(g7)
|
||||||
|
f2g8_19 := int64(f2) * int64(g8_19)
|
||||||
|
f2g9_19 := int64(f2) * int64(g9_19)
|
||||||
|
f3g0 := int64(f3) * int64(g0)
|
||||||
|
f3g1_2 := int64(f3_2) * int64(g1)
|
||||||
|
f3g2 := int64(f3) * int64(g2)
|
||||||
|
f3g3_2 := int64(f3_2) * int64(g3)
|
||||||
|
f3g4 := int64(f3) * int64(g4)
|
||||||
|
f3g5_2 := int64(f3_2) * int64(g5)
|
||||||
|
f3g6 := int64(f3) * int64(g6)
|
||||||
|
f3g7_38 := int64(f3_2) * int64(g7_19)
|
||||||
|
f3g8_19 := int64(f3) * int64(g8_19)
|
||||||
|
f3g9_38 := int64(f3_2) * int64(g9_19)
|
||||||
|
f4g0 := int64(f4) * int64(g0)
|
||||||
|
f4g1 := int64(f4) * int64(g1)
|
||||||
|
f4g2 := int64(f4) * int64(g2)
|
||||||
|
f4g3 := int64(f4) * int64(g3)
|
||||||
|
f4g4 := int64(f4) * int64(g4)
|
||||||
|
f4g5 := int64(f4) * int64(g5)
|
||||||
|
f4g6_19 := int64(f4) * int64(g6_19)
|
||||||
|
f4g7_19 := int64(f4) * int64(g7_19)
|
||||||
|
f4g8_19 := int64(f4) * int64(g8_19)
|
||||||
|
f4g9_19 := int64(f4) * int64(g9_19)
|
||||||
|
f5g0 := int64(f5) * int64(g0)
|
||||||
|
f5g1_2 := int64(f5_2) * int64(g1)
|
||||||
|
f5g2 := int64(f5) * int64(g2)
|
||||||
|
f5g3_2 := int64(f5_2) * int64(g3)
|
||||||
|
f5g4 := int64(f5) * int64(g4)
|
||||||
|
f5g5_38 := int64(f5_2) * int64(g5_19)
|
||||||
|
f5g6_19 := int64(f5) * int64(g6_19)
|
||||||
|
f5g7_38 := int64(f5_2) * int64(g7_19)
|
||||||
|
f5g8_19 := int64(f5) * int64(g8_19)
|
||||||
|
f5g9_38 := int64(f5_2) * int64(g9_19)
|
||||||
|
f6g0 := int64(f6) * int64(g0)
|
||||||
|
f6g1 := int64(f6) * int64(g1)
|
||||||
|
f6g2 := int64(f6) * int64(g2)
|
||||||
|
f6g3 := int64(f6) * int64(g3)
|
||||||
|
f6g4_19 := int64(f6) * int64(g4_19)
|
||||||
|
f6g5_19 := int64(f6) * int64(g5_19)
|
||||||
|
f6g6_19 := int64(f6) * int64(g6_19)
|
||||||
|
f6g7_19 := int64(f6) * int64(g7_19)
|
||||||
|
f6g8_19 := int64(f6) * int64(g8_19)
|
||||||
|
f6g9_19 := int64(f6) * int64(g9_19)
|
||||||
|
f7g0 := int64(f7) * int64(g0)
|
||||||
|
f7g1_2 := int64(f7_2) * int64(g1)
|
||||||
|
f7g2 := int64(f7) * int64(g2)
|
||||||
|
f7g3_38 := int64(f7_2) * int64(g3_19)
|
||||||
|
f7g4_19 := int64(f7) * int64(g4_19)
|
||||||
|
f7g5_38 := int64(f7_2) * int64(g5_19)
|
||||||
|
f7g6_19 := int64(f7) * int64(g6_19)
|
||||||
|
f7g7_38 := int64(f7_2) * int64(g7_19)
|
||||||
|
f7g8_19 := int64(f7) * int64(g8_19)
|
||||||
|
f7g9_38 := int64(f7_2) * int64(g9_19)
|
||||||
|
f8g0 := int64(f8) * int64(g0)
|
||||||
|
f8g1 := int64(f8) * int64(g1)
|
||||||
|
f8g2_19 := int64(f8) * int64(g2_19)
|
||||||
|
f8g3_19 := int64(f8) * int64(g3_19)
|
||||||
|
f8g4_19 := int64(f8) * int64(g4_19)
|
||||||
|
f8g5_19 := int64(f8) * int64(g5_19)
|
||||||
|
f8g6_19 := int64(f8) * int64(g6_19)
|
||||||
|
f8g7_19 := int64(f8) * int64(g7_19)
|
||||||
|
f8g8_19 := int64(f8) * int64(g8_19)
|
||||||
|
f8g9_19 := int64(f8) * int64(g9_19)
|
||||||
|
f9g0 := int64(f9) * int64(g0)
|
||||||
|
f9g1_38 := int64(f9_2) * int64(g1_19)
|
||||||
|
f9g2_19 := int64(f9) * int64(g2_19)
|
||||||
|
f9g3_38 := int64(f9_2) * int64(g3_19)
|
||||||
|
f9g4_19 := int64(f9) * int64(g4_19)
|
||||||
|
f9g5_38 := int64(f9_2) * int64(g5_19)
|
||||||
|
f9g6_19 := int64(f9) * int64(g6_19)
|
||||||
|
f9g7_38 := int64(f9_2) * int64(g7_19)
|
||||||
|
f9g8_19 := int64(f9) * int64(g8_19)
|
||||||
|
f9g9_38 := int64(f9_2) * int64(g9_19)
|
||||||
|
h0 := f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38
|
||||||
|
h1 := f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19
|
||||||
|
h2 := f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38
|
||||||
|
h3 := f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19
|
||||||
|
h4 := f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38
|
||||||
|
h5 := f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19
|
||||||
|
h6 := f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38
|
||||||
|
h7 := f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19
|
||||||
|
h8 := f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38
|
||||||
|
h9 := f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0
|
||||||
|
var carry [10]int64
|
||||||
|
|
||||||
|
// |h0| <= (1.1*1.1*2^52*(1+19+19+19+19)+1.1*1.1*2^50*(38+38+38+38+38))
|
||||||
|
// i.e. |h0| <= 1.2*2^59; narrower ranges for h2, h4, h6, h8
|
||||||
|
// |h1| <= (1.1*1.1*2^51*(1+1+19+19+19+19+19+19+19+19))
|
||||||
|
// i.e. |h1| <= 1.5*2^58; narrower ranges for h3, h5, h7, h9
|
||||||
|
|
||||||
|
carry[0] = (h0 + (1 << 25)) >> 26
|
||||||
|
h1 += carry[0]
|
||||||
|
h0 -= carry[0] << 26
|
||||||
|
carry[4] = (h4 + (1 << 25)) >> 26
|
||||||
|
h5 += carry[4]
|
||||||
|
h4 -= carry[4] << 26
|
||||||
|
// |h0| <= 2^25
|
||||||
|
// |h4| <= 2^25
|
||||||
|
// |h1| <= 1.51*2^58
|
||||||
|
// |h5| <= 1.51*2^58
|
||||||
|
|
||||||
|
carry[1] = (h1 + (1 << 24)) >> 25
|
||||||
|
h2 += carry[1]
|
||||||
|
h1 -= carry[1] << 25
|
||||||
|
carry[5] = (h5 + (1 << 24)) >> 25
|
||||||
|
h6 += carry[5]
|
||||||
|
h5 -= carry[5] << 25
|
||||||
|
// |h1| <= 2^24; from now on fits into int32
|
||||||
|
// |h5| <= 2^24; from now on fits into int32
|
||||||
|
// |h2| <= 1.21*2^59
|
||||||
|
// |h6| <= 1.21*2^59
|
||||||
|
|
||||||
|
carry[2] = (h2 + (1 << 25)) >> 26
|
||||||
|
h3 += carry[2]
|
||||||
|
h2 -= carry[2] << 26
|
||||||
|
carry[6] = (h6 + (1 << 25)) >> 26
|
||||||
|
h7 += carry[6]
|
||||||
|
h6 -= carry[6] << 26
|
||||||
|
// |h2| <= 2^25; from now on fits into int32 unchanged
|
||||||
|
// |h6| <= 2^25; from now on fits into int32 unchanged
|
||||||
|
// |h3| <= 1.51*2^58
|
||||||
|
// |h7| <= 1.51*2^58
|
||||||
|
|
||||||
|
carry[3] = (h3 + (1 << 24)) >> 25
|
||||||
|
h4 += carry[3]
|
||||||
|
h3 -= carry[3] << 25
|
||||||
|
carry[7] = (h7 + (1 << 24)) >> 25
|
||||||
|
h8 += carry[7]
|
||||||
|
h7 -= carry[7] << 25
|
||||||
|
// |h3| <= 2^24; from now on fits into int32 unchanged
|
||||||
|
// |h7| <= 2^24; from now on fits into int32 unchanged
|
||||||
|
// |h4| <= 1.52*2^33
|
||||||
|
// |h8| <= 1.52*2^33
|
||||||
|
|
||||||
|
carry[4] = (h4 + (1 << 25)) >> 26
|
||||||
|
h5 += carry[4]
|
||||||
|
h4 -= carry[4] << 26
|
||||||
|
carry[8] = (h8 + (1 << 25)) >> 26
|
||||||
|
h9 += carry[8]
|
||||||
|
h8 -= carry[8] << 26
|
||||||
|
// |h4| <= 2^25; from now on fits into int32 unchanged
|
||||||
|
// |h8| <= 2^25; from now on fits into int32 unchanged
|
||||||
|
// |h5| <= 1.01*2^24
|
||||||
|
// |h9| <= 1.51*2^58
|
||||||
|
|
||||||
|
carry[9] = (h9 + (1 << 24)) >> 25
|
||||||
|
h0 += carry[9] * 19
|
||||||
|
h9 -= carry[9] << 25
|
||||||
|
// |h9| <= 2^24; from now on fits into int32 unchanged
|
||||||
|
// |h0| <= 1.8*2^37
|
||||||
|
|
||||||
|
carry[0] = (h0 + (1 << 25)) >> 26
|
||||||
|
h1 += carry[0]
|
||||||
|
h0 -= carry[0] << 26
|
||||||
|
// |h0| <= 2^25; from now on fits into int32 unchanged
|
||||||
|
// |h1| <= 1.01*2^24
|
||||||
|
|
||||||
|
h[0] = int32(h0)
|
||||||
|
h[1] = int32(h1)
|
||||||
|
h[2] = int32(h2)
|
||||||
|
h[3] = int32(h3)
|
||||||
|
h[4] = int32(h4)
|
||||||
|
h[5] = int32(h5)
|
||||||
|
h[6] = int32(h6)
|
||||||
|
h[7] = int32(h7)
|
||||||
|
h[8] = int32(h8)
|
||||||
|
h[9] = int32(h9)
|
||||||
|
}
|
||||||
|
|
||||||
|
// feSquare calculates h = f*f. Can overlap h with f.
|
||||||
|
//
|
||||||
|
// Preconditions:
|
||||||
|
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||||
|
//
|
||||||
|
// Postconditions:
|
||||||
|
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||||
|
func feSquare(h, f *fieldElement) {
|
||||||
|
f0 := f[0]
|
||||||
|
f1 := f[1]
|
||||||
|
f2 := f[2]
|
||||||
|
f3 := f[3]
|
||||||
|
f4 := f[4]
|
||||||
|
f5 := f[5]
|
||||||
|
f6 := f[6]
|
||||||
|
f7 := f[7]
|
||||||
|
f8 := f[8]
|
||||||
|
f9 := f[9]
|
||||||
|
f0_2 := 2 * f0
|
||||||
|
f1_2 := 2 * f1
|
||||||
|
f2_2 := 2 * f2
|
||||||
|
f3_2 := 2 * f3
|
||||||
|
f4_2 := 2 * f4
|
||||||
|
f5_2 := 2 * f5
|
||||||
|
f6_2 := 2 * f6
|
||||||
|
f7_2 := 2 * f7
|
||||||
|
f5_38 := 38 * f5 // 1.31*2^30
|
||||||
|
f6_19 := 19 * f6 // 1.31*2^30
|
||||||
|
f7_38 := 38 * f7 // 1.31*2^30
|
||||||
|
f8_19 := 19 * f8 // 1.31*2^30
|
||||||
|
f9_38 := 38 * f9 // 1.31*2^30
|
||||||
|
f0f0 := int64(f0) * int64(f0)
|
||||||
|
f0f1_2 := int64(f0_2) * int64(f1)
|
||||||
|
f0f2_2 := int64(f0_2) * int64(f2)
|
||||||
|
f0f3_2 := int64(f0_2) * int64(f3)
|
||||||
|
f0f4_2 := int64(f0_2) * int64(f4)
|
||||||
|
f0f5_2 := int64(f0_2) * int64(f5)
|
||||||
|
f0f6_2 := int64(f0_2) * int64(f6)
|
||||||
|
f0f7_2 := int64(f0_2) * int64(f7)
|
||||||
|
f0f8_2 := int64(f0_2) * int64(f8)
|
||||||
|
f0f9_2 := int64(f0_2) * int64(f9)
|
||||||
|
f1f1_2 := int64(f1_2) * int64(f1)
|
||||||
|
f1f2_2 := int64(f1_2) * int64(f2)
|
||||||
|
f1f3_4 := int64(f1_2) * int64(f3_2)
|
||||||
|
f1f4_2 := int64(f1_2) * int64(f4)
|
||||||
|
f1f5_4 := int64(f1_2) * int64(f5_2)
|
||||||
|
f1f6_2 := int64(f1_2) * int64(f6)
|
||||||
|
f1f7_4 := int64(f1_2) * int64(f7_2)
|
||||||
|
f1f8_2 := int64(f1_2) * int64(f8)
|
||||||
|
f1f9_76 := int64(f1_2) * int64(f9_38)
|
||||||
|
f2f2 := int64(f2) * int64(f2)
|
||||||
|
f2f3_2 := int64(f2_2) * int64(f3)
|
||||||
|
f2f4_2 := int64(f2_2) * int64(f4)
|
||||||
|
f2f5_2 := int64(f2_2) * int64(f5)
|
||||||
|
f2f6_2 := int64(f2_2) * int64(f6)
|
||||||
|
f2f7_2 := int64(f2_2) * int64(f7)
|
||||||
|
f2f8_38 := int64(f2_2) * int64(f8_19)
|
||||||
|
f2f9_38 := int64(f2) * int64(f9_38)
|
||||||
|
f3f3_2 := int64(f3_2) * int64(f3)
|
||||||
|
f3f4_2 := int64(f3_2) * int64(f4)
|
||||||
|
f3f5_4 := int64(f3_2) * int64(f5_2)
|
||||||
|
f3f6_2 := int64(f3_2) * int64(f6)
|
||||||
|
f3f7_76 := int64(f3_2) * int64(f7_38)
|
||||||
|
f3f8_38 := int64(f3_2) * int64(f8_19)
|
||||||
|
f3f9_76 := int64(f3_2) * int64(f9_38)
|
||||||
|
f4f4 := int64(f4) * int64(f4)
|
||||||
|
f4f5_2 := int64(f4_2) * int64(f5)
|
||||||
|
f4f6_38 := int64(f4_2) * int64(f6_19)
|
||||||
|
f4f7_38 := int64(f4) * int64(f7_38)
|
||||||
|
f4f8_38 := int64(f4_2) * int64(f8_19)
|
||||||
|
f4f9_38 := int64(f4) * int64(f9_38)
|
||||||
|
f5f5_38 := int64(f5) * int64(f5_38)
|
||||||
|
f5f6_38 := int64(f5_2) * int64(f6_19)
|
||||||
|
f5f7_76 := int64(f5_2) * int64(f7_38)
|
||||||
|
f5f8_38 := int64(f5_2) * int64(f8_19)
|
||||||
|
f5f9_76 := int64(f5_2) * int64(f9_38)
|
||||||
|
f6f6_19 := int64(f6) * int64(f6_19)
|
||||||
|
f6f7_38 := int64(f6) * int64(f7_38)
|
||||||
|
f6f8_38 := int64(f6_2) * int64(f8_19)
|
||||||
|
f6f9_38 := int64(f6) * int64(f9_38)
|
||||||
|
f7f7_38 := int64(f7) * int64(f7_38)
|
||||||
|
f7f8_38 := int64(f7_2) * int64(f8_19)
|
||||||
|
f7f9_76 := int64(f7_2) * int64(f9_38)
|
||||||
|
f8f8_19 := int64(f8) * int64(f8_19)
|
||||||
|
f8f9_38 := int64(f8) * int64(f9_38)
|
||||||
|
f9f9_38 := int64(f9) * int64(f9_38)
|
||||||
|
h0 := f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38
|
||||||
|
h1 := f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38
|
||||||
|
h2 := f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19
|
||||||
|
h3 := f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38
|
||||||
|
h4 := f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38
|
||||||
|
h5 := f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38
|
||||||
|
h6 := f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19
|
||||||
|
h7 := f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38
|
||||||
|
h8 := f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38
|
||||||
|
h9 := f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2
|
||||||
|
var carry [10]int64
|
||||||
|
|
||||||
|
carry[0] = (h0 + (1 << 25)) >> 26
|
||||||
|
h1 += carry[0]
|
||||||
|
h0 -= carry[0] << 26
|
||||||
|
carry[4] = (h4 + (1 << 25)) >> 26
|
||||||
|
h5 += carry[4]
|
||||||
|
h4 -= carry[4] << 26
|
||||||
|
|
||||||
|
carry[1] = (h1 + (1 << 24)) >> 25
|
||||||
|
h2 += carry[1]
|
||||||
|
h1 -= carry[1] << 25
|
||||||
|
carry[5] = (h5 + (1 << 24)) >> 25
|
||||||
|
h6 += carry[5]
|
||||||
|
h5 -= carry[5] << 25
|
||||||
|
|
||||||
|
carry[2] = (h2 + (1 << 25)) >> 26
|
||||||
|
h3 += carry[2]
|
||||||
|
h2 -= carry[2] << 26
|
||||||
|
carry[6] = (h6 + (1 << 25)) >> 26
|
||||||
|
h7 += carry[6]
|
||||||
|
h6 -= carry[6] << 26
|
||||||
|
|
||||||
|
carry[3] = (h3 + (1 << 24)) >> 25
|
||||||
|
h4 += carry[3]
|
||||||
|
h3 -= carry[3] << 25
|
||||||
|
carry[7] = (h7 + (1 << 24)) >> 25
|
||||||
|
h8 += carry[7]
|
||||||
|
h7 -= carry[7] << 25
|
||||||
|
|
||||||
|
carry[4] = (h4 + (1 << 25)) >> 26
|
||||||
|
h5 += carry[4]
|
||||||
|
h4 -= carry[4] << 26
|
||||||
|
carry[8] = (h8 + (1 << 25)) >> 26
|
||||||
|
h9 += carry[8]
|
||||||
|
h8 -= carry[8] << 26
|
||||||
|
|
||||||
|
carry[9] = (h9 + (1 << 24)) >> 25
|
||||||
|
h0 += carry[9] * 19
|
||||||
|
h9 -= carry[9] << 25
|
||||||
|
|
||||||
|
carry[0] = (h0 + (1 << 25)) >> 26
|
||||||
|
h1 += carry[0]
|
||||||
|
h0 -= carry[0] << 26
|
||||||
|
|
||||||
|
h[0] = int32(h0)
|
||||||
|
h[1] = int32(h1)
|
||||||
|
h[2] = int32(h2)
|
||||||
|
h[3] = int32(h3)
|
||||||
|
h[4] = int32(h4)
|
||||||
|
h[5] = int32(h5)
|
||||||
|
h[6] = int32(h6)
|
||||||
|
h[7] = int32(h7)
|
||||||
|
h[8] = int32(h8)
|
||||||
|
h[9] = int32(h9)
|
||||||
|
}
|
||||||
|
|
||||||
|
// feMul121666 calculates h = f * 121666. Can overlap h with f.
|
||||||
|
//
|
||||||
|
// Preconditions:
|
||||||
|
// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
|
||||||
|
//
|
||||||
|
// Postconditions:
|
||||||
|
// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
|
||||||
|
func feMul121666(h, f *fieldElement) {
|
||||||
|
h0 := int64(f[0]) * 121666
|
||||||
|
h1 := int64(f[1]) * 121666
|
||||||
|
h2 := int64(f[2]) * 121666
|
||||||
|
h3 := int64(f[3]) * 121666
|
||||||
|
h4 := int64(f[4]) * 121666
|
||||||
|
h5 := int64(f[5]) * 121666
|
||||||
|
h6 := int64(f[6]) * 121666
|
||||||
|
h7 := int64(f[7]) * 121666
|
||||||
|
h8 := int64(f[8]) * 121666
|
||||||
|
h9 := int64(f[9]) * 121666
|
||||||
|
var carry [10]int64
|
||||||
|
|
||||||
|
carry[9] = (h9 + (1 << 24)) >> 25
|
||||||
|
h0 += carry[9] * 19
|
||||||
|
h9 -= carry[9] << 25
|
||||||
|
carry[1] = (h1 + (1 << 24)) >> 25
|
||||||
|
h2 += carry[1]
|
||||||
|
h1 -= carry[1] << 25
|
||||||
|
carry[3] = (h3 + (1 << 24)) >> 25
|
||||||
|
h4 += carry[3]
|
||||||
|
h3 -= carry[3] << 25
|
||||||
|
carry[5] = (h5 + (1 << 24)) >> 25
|
||||||
|
h6 += carry[5]
|
||||||
|
h5 -= carry[5] << 25
|
||||||
|
carry[7] = (h7 + (1 << 24)) >> 25
|
||||||
|
h8 += carry[7]
|
||||||
|
h7 -= carry[7] << 25
|
||||||
|
|
||||||
|
carry[0] = (h0 + (1 << 25)) >> 26
|
||||||
|
h1 += carry[0]
|
||||||
|
h0 -= carry[0] << 26
|
||||||
|
carry[2] = (h2 + (1 << 25)) >> 26
|
||||||
|
h3 += carry[2]
|
||||||
|
h2 -= carry[2] << 26
|
||||||
|
carry[4] = (h4 + (1 << 25)) >> 26
|
||||||
|
h5 += carry[4]
|
||||||
|
h4 -= carry[4] << 26
|
||||||
|
carry[6] = (h6 + (1 << 25)) >> 26
|
||||||
|
h7 += carry[6]
|
||||||
|
h6 -= carry[6] << 26
|
||||||
|
carry[8] = (h8 + (1 << 25)) >> 26
|
||||||
|
h9 += carry[8]
|
||||||
|
h8 -= carry[8] << 26
|
||||||
|
|
||||||
|
h[0] = int32(h0)
|
||||||
|
h[1] = int32(h1)
|
||||||
|
h[2] = int32(h2)
|
||||||
|
h[3] = int32(h3)
|
||||||
|
h[4] = int32(h4)
|
||||||
|
h[5] = int32(h5)
|
||||||
|
h[6] = int32(h6)
|
||||||
|
h[7] = int32(h7)
|
||||||
|
h[8] = int32(h8)
|
||||||
|
h[9] = int32(h9)
|
||||||
|
}
|
||||||
|
|
||||||
|
// feInvert sets out = z^-1.
|
||||||
|
func feInvert(out, z *fieldElement) {
|
||||||
|
var t0, t1, t2, t3 fieldElement
|
||||||
|
var i int
|
||||||
|
|
||||||
|
feSquare(&t0, z)
|
||||||
|
for i = 1; i < 1; i++ {
|
||||||
|
feSquare(&t0, &t0)
|
||||||
|
}
|
||||||
|
feSquare(&t1, &t0)
|
||||||
|
for i = 1; i < 2; i++ {
|
||||||
|
feSquare(&t1, &t1)
|
||||||
|
}
|
||||||
|
feMul(&t1, z, &t1)
|
||||||
|
feMul(&t0, &t0, &t1)
|
||||||
|
feSquare(&t2, &t0)
|
||||||
|
for i = 1; i < 1; i++ {
|
||||||
|
feSquare(&t2, &t2)
|
||||||
|
}
|
||||||
|
feMul(&t1, &t1, &t2)
|
||||||
|
feSquare(&t2, &t1)
|
||||||
|
for i = 1; i < 5; i++ {
|
||||||
|
feSquare(&t2, &t2)
|
||||||
|
}
|
||||||
|
feMul(&t1, &t2, &t1)
|
||||||
|
feSquare(&t2, &t1)
|
||||||
|
for i = 1; i < 10; i++ {
|
||||||
|
feSquare(&t2, &t2)
|
||||||
|
}
|
||||||
|
feMul(&t2, &t2, &t1)
|
||||||
|
feSquare(&t3, &t2)
|
||||||
|
for i = 1; i < 20; i++ {
|
||||||
|
feSquare(&t3, &t3)
|
||||||
|
}
|
||||||
|
feMul(&t2, &t3, &t2)
|
||||||
|
feSquare(&t2, &t2)
|
||||||
|
for i = 1; i < 10; i++ {
|
||||||
|
feSquare(&t2, &t2)
|
||||||
|
}
|
||||||
|
feMul(&t1, &t2, &t1)
|
||||||
|
feSquare(&t2, &t1)
|
||||||
|
for i = 1; i < 50; i++ {
|
||||||
|
feSquare(&t2, &t2)
|
||||||
|
}
|
||||||
|
feMul(&t2, &t2, &t1)
|
||||||
|
feSquare(&t3, &t2)
|
||||||
|
for i = 1; i < 100; i++ {
|
||||||
|
feSquare(&t3, &t3)
|
||||||
|
}
|
||||||
|
feMul(&t2, &t3, &t2)
|
||||||
|
feSquare(&t2, &t2)
|
||||||
|
for i = 1; i < 50; i++ {
|
||||||
|
feSquare(&t2, &t2)
|
||||||
|
}
|
||||||
|
feMul(&t1, &t2, &t1)
|
||||||
|
feSquare(&t1, &t1)
|
||||||
|
for i = 1; i < 5; i++ {
|
||||||
|
feSquare(&t1, &t1)
|
||||||
|
}
|
||||||
|
feMul(out, &t1, &t0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scalarMult(out, in, base *[32]byte) {
|
||||||
|
var e [32]byte
|
||||||
|
|
||||||
|
copy(e[:], in[:])
|
||||||
|
e[0] &= 248
|
||||||
|
e[31] &= 127
|
||||||
|
e[31] |= 64
|
||||||
|
|
||||||
|
var x1, x2, z2, x3, z3, tmp0, tmp1 fieldElement
|
||||||
|
feFromBytes(&x1, base)
|
||||||
|
feOne(&x2)
|
||||||
|
feCopy(&x3, &x1)
|
||||||
|
feOne(&z3)
|
||||||
|
|
||||||
|
swap := int32(0)
|
||||||
|
for pos := 254; pos >= 0; pos-- {
|
||||||
|
b := e[pos/8] >> uint(pos&7)
|
||||||
|
b &= 1
|
||||||
|
swap ^= int32(b)
|
||||||
|
feCSwap(&x2, &x3, swap)
|
||||||
|
feCSwap(&z2, &z3, swap)
|
||||||
|
swap = int32(b)
|
||||||
|
|
||||||
|
feSub(&tmp0, &x3, &z3)
|
||||||
|
feSub(&tmp1, &x2, &z2)
|
||||||
|
feAdd(&x2, &x2, &z2)
|
||||||
|
feAdd(&z2, &x3, &z3)
|
||||||
|
feMul(&z3, &tmp0, &x2)
|
||||||
|
feMul(&z2, &z2, &tmp1)
|
||||||
|
feSquare(&tmp0, &tmp1)
|
||||||
|
feSquare(&tmp1, &x2)
|
||||||
|
feAdd(&x3, &z3, &z2)
|
||||||
|
feSub(&z2, &z3, &z2)
|
||||||
|
feMul(&x2, &tmp1, &tmp0)
|
||||||
|
feSub(&tmp1, &tmp1, &tmp0)
|
||||||
|
feSquare(&z2, &z2)
|
||||||
|
feMul121666(&z3, &tmp1)
|
||||||
|
feSquare(&x3, &x3)
|
||||||
|
feAdd(&tmp0, &tmp0, &z3)
|
||||||
|
feMul(&z3, &x1, &z2)
|
||||||
|
feMul(&z2, &tmp1, &tmp0)
|
||||||
|
}
|
||||||
|
|
||||||
|
feCSwap(&x2, &x3, swap)
|
||||||
|
feCSwap(&z2, &z3, swap)
|
||||||
|
|
||||||
|
feInvert(&z2, &z2)
|
||||||
|
feMul(&x2, &x2, &z2)
|
||||||
|
feToBytes(out, &x2)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2012 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 curve25519
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const expectedHex = "89161fde887b2b53de549af483940106ecc114d6982daa98256de23bdf77661a"
|
||||||
|
|
||||||
|
func TestBaseScalarMult(t *testing.T) {
|
||||||
|
var a, b [32]byte
|
||||||
|
in := &a
|
||||||
|
out := &b
|
||||||
|
a[0] = 1
|
||||||
|
|
||||||
|
for i := 0; i < 200; i++ {
|
||||||
|
ScalarBaseMult(out, in)
|
||||||
|
in, out = out, in
|
||||||
|
}
|
||||||
|
|
||||||
|
result := fmt.Sprintf("%x", in[:])
|
||||||
|
if result != expectedHex {
|
||||||
|
t.Errorf("incorrect result: got %s, want %s", result, expectedHex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkScalarBaseMult(b *testing.B) {
|
||||||
|
var in, out [32]byte
|
||||||
|
in[0] = 1
|
||||||
|
|
||||||
|
b.SetBytes(32)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ScalarBaseMult(&out, &in)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright 2012 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 curve25519 provides an implementation of scalar multiplication on
|
||||||
|
// the elliptic curve known as curve25519. See https://cr.yp.to/ecdh.html
|
||||||
|
package curve25519 // import "golang.org/x/crypto/curve25519"
|
||||||
|
|
||||||
|
// basePoint is the x coordinate of the generator of the curve.
|
||||||
|
var basePoint = [32]byte{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
|
|
||||||
|
// ScalarMult sets dst to the product in*base where dst and base are the x
|
||||||
|
// coordinates of group points and all values are in little-endian form.
|
||||||
|
func ScalarMult(dst, in, base *[32]byte) {
|
||||||
|
scalarMult(dst, in, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScalarBaseMult sets dst to the product in*base where dst and base are the x
|
||||||
|
// coordinates of group points, base is the standard generator and all values
|
||||||
|
// are in little-endian form.
|
||||||
|
func ScalarBaseMult(dst, in *[32]byte) {
|
||||||
|
ScalarMult(dst, in, &basePoint)
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// This code was translated into a form compatible with 6a from the public
|
||||||
|
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
|
||||||
|
|
||||||
|
// +build amd64,!gccgo,!appengine
|
||||||
|
|
||||||
|
#include "const_amd64.h"
|
||||||
|
|
||||||
|
// func freeze(inout *[5]uint64)
|
||||||
|
TEXT ·freeze(SB),7,$0-8
|
||||||
|
MOVQ inout+0(FP), DI
|
||||||
|
|
||||||
|
MOVQ 0(DI),SI
|
||||||
|
MOVQ 8(DI),DX
|
||||||
|
MOVQ 16(DI),CX
|
||||||
|
MOVQ 24(DI),R8
|
||||||
|
MOVQ 32(DI),R9
|
||||||
|
MOVQ $REDMASK51,AX
|
||||||
|
MOVQ AX,R10
|
||||||
|
SUBQ $18,R10
|
||||||
|
MOVQ $3,R11
|
||||||
|
REDUCELOOP:
|
||||||
|
MOVQ SI,R12
|
||||||
|
SHRQ $51,R12
|
||||||
|
ANDQ AX,SI
|
||||||
|
ADDQ R12,DX
|
||||||
|
MOVQ DX,R12
|
||||||
|
SHRQ $51,R12
|
||||||
|
ANDQ AX,DX
|
||||||
|
ADDQ R12,CX
|
||||||
|
MOVQ CX,R12
|
||||||
|
SHRQ $51,R12
|
||||||
|
ANDQ AX,CX
|
||||||
|
ADDQ R12,R8
|
||||||
|
MOVQ R8,R12
|
||||||
|
SHRQ $51,R12
|
||||||
|
ANDQ AX,R8
|
||||||
|
ADDQ R12,R9
|
||||||
|
MOVQ R9,R12
|
||||||
|
SHRQ $51,R12
|
||||||
|
ANDQ AX,R9
|
||||||
|
IMUL3Q $19,R12,R12
|
||||||
|
ADDQ R12,SI
|
||||||
|
SUBQ $1,R11
|
||||||
|
JA REDUCELOOP
|
||||||
|
MOVQ $1,R12
|
||||||
|
CMPQ R10,SI
|
||||||
|
CMOVQLT R11,R12
|
||||||
|
CMPQ AX,DX
|
||||||
|
CMOVQNE R11,R12
|
||||||
|
CMPQ AX,CX
|
||||||
|
CMOVQNE R11,R12
|
||||||
|
CMPQ AX,R8
|
||||||
|
CMOVQNE R11,R12
|
||||||
|
CMPQ AX,R9
|
||||||
|
CMOVQNE R11,R12
|
||||||
|
NEGQ R12
|
||||||
|
ANDQ R12,AX
|
||||||
|
ANDQ R12,R10
|
||||||
|
SUBQ R10,SI
|
||||||
|
SUBQ AX,DX
|
||||||
|
SUBQ AX,CX
|
||||||
|
SUBQ AX,R8
|
||||||
|
SUBQ AX,R9
|
||||||
|
MOVQ SI,0(DI)
|
||||||
|
MOVQ DX,8(DI)
|
||||||
|
MOVQ CX,16(DI)
|
||||||
|
MOVQ R8,24(DI)
|
||||||
|
MOVQ R9,32(DI)
|
||||||
|
RET
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,240 @@
|
||||||
|
// Copyright 2012 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 amd64,!gccgo,!appengine
|
||||||
|
|
||||||
|
package curve25519
|
||||||
|
|
||||||
|
// These functions are implemented in the .s files. The names of the functions
|
||||||
|
// in the rest of the file are also taken from the SUPERCOP sources to help
|
||||||
|
// people following along.
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
|
||||||
|
func cswap(inout *[5]uint64, v uint64)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
|
||||||
|
func ladderstep(inout *[5][5]uint64)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
|
||||||
|
func freeze(inout *[5]uint64)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
|
||||||
|
func mul(dest, a, b *[5]uint64)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
|
||||||
|
func square(out, in *[5]uint64)
|
||||||
|
|
||||||
|
// mladder uses a Montgomery ladder to calculate (xr/zr) *= s.
|
||||||
|
func mladder(xr, zr *[5]uint64, s *[32]byte) {
|
||||||
|
var work [5][5]uint64
|
||||||
|
|
||||||
|
work[0] = *xr
|
||||||
|
setint(&work[1], 1)
|
||||||
|
setint(&work[2], 0)
|
||||||
|
work[3] = *xr
|
||||||
|
setint(&work[4], 1)
|
||||||
|
|
||||||
|
j := uint(6)
|
||||||
|
var prevbit byte
|
||||||
|
|
||||||
|
for i := 31; i >= 0; i-- {
|
||||||
|
for j < 8 {
|
||||||
|
bit := ((*s)[i] >> j) & 1
|
||||||
|
swap := bit ^ prevbit
|
||||||
|
prevbit = bit
|
||||||
|
cswap(&work[1], uint64(swap))
|
||||||
|
ladderstep(&work)
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
j = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
*xr = work[1]
|
||||||
|
*zr = work[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
func scalarMult(out, in, base *[32]byte) {
|
||||||
|
var e [32]byte
|
||||||
|
copy(e[:], (*in)[:])
|
||||||
|
e[0] &= 248
|
||||||
|
e[31] &= 127
|
||||||
|
e[31] |= 64
|
||||||
|
|
||||||
|
var t, z [5]uint64
|
||||||
|
unpack(&t, base)
|
||||||
|
mladder(&t, &z, &e)
|
||||||
|
invert(&z, &z)
|
||||||
|
mul(&t, &t, &z)
|
||||||
|
pack(out, &t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setint(r *[5]uint64, v uint64) {
|
||||||
|
r[0] = v
|
||||||
|
r[1] = 0
|
||||||
|
r[2] = 0
|
||||||
|
r[3] = 0
|
||||||
|
r[4] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpack sets r = x where r consists of 5, 51-bit limbs in little-endian
|
||||||
|
// order.
|
||||||
|
func unpack(r *[5]uint64, x *[32]byte) {
|
||||||
|
r[0] = uint64(x[0]) |
|
||||||
|
uint64(x[1])<<8 |
|
||||||
|
uint64(x[2])<<16 |
|
||||||
|
uint64(x[3])<<24 |
|
||||||
|
uint64(x[4])<<32 |
|
||||||
|
uint64(x[5])<<40 |
|
||||||
|
uint64(x[6]&7)<<48
|
||||||
|
|
||||||
|
r[1] = uint64(x[6])>>3 |
|
||||||
|
uint64(x[7])<<5 |
|
||||||
|
uint64(x[8])<<13 |
|
||||||
|
uint64(x[9])<<21 |
|
||||||
|
uint64(x[10])<<29 |
|
||||||
|
uint64(x[11])<<37 |
|
||||||
|
uint64(x[12]&63)<<45
|
||||||
|
|
||||||
|
r[2] = uint64(x[12])>>6 |
|
||||||
|
uint64(x[13])<<2 |
|
||||||
|
uint64(x[14])<<10 |
|
||||||
|
uint64(x[15])<<18 |
|
||||||
|
uint64(x[16])<<26 |
|
||||||
|
uint64(x[17])<<34 |
|
||||||
|
uint64(x[18])<<42 |
|
||||||
|
uint64(x[19]&1)<<50
|
||||||
|
|
||||||
|
r[3] = uint64(x[19])>>1 |
|
||||||
|
uint64(x[20])<<7 |
|
||||||
|
uint64(x[21])<<15 |
|
||||||
|
uint64(x[22])<<23 |
|
||||||
|
uint64(x[23])<<31 |
|
||||||
|
uint64(x[24])<<39 |
|
||||||
|
uint64(x[25]&15)<<47
|
||||||
|
|
||||||
|
r[4] = uint64(x[25])>>4 |
|
||||||
|
uint64(x[26])<<4 |
|
||||||
|
uint64(x[27])<<12 |
|
||||||
|
uint64(x[28])<<20 |
|
||||||
|
uint64(x[29])<<28 |
|
||||||
|
uint64(x[30])<<36 |
|
||||||
|
uint64(x[31]&127)<<44
|
||||||
|
}
|
||||||
|
|
||||||
|
// pack sets out = x where out is the usual, little-endian form of the 5,
|
||||||
|
// 51-bit limbs in x.
|
||||||
|
func pack(out *[32]byte, x *[5]uint64) {
|
||||||
|
t := *x
|
||||||
|
freeze(&t)
|
||||||
|
|
||||||
|
out[0] = byte(t[0])
|
||||||
|
out[1] = byte(t[0] >> 8)
|
||||||
|
out[2] = byte(t[0] >> 16)
|
||||||
|
out[3] = byte(t[0] >> 24)
|
||||||
|
out[4] = byte(t[0] >> 32)
|
||||||
|
out[5] = byte(t[0] >> 40)
|
||||||
|
out[6] = byte(t[0] >> 48)
|
||||||
|
|
||||||
|
out[6] ^= byte(t[1]<<3) & 0xf8
|
||||||
|
out[7] = byte(t[1] >> 5)
|
||||||
|
out[8] = byte(t[1] >> 13)
|
||||||
|
out[9] = byte(t[1] >> 21)
|
||||||
|
out[10] = byte(t[1] >> 29)
|
||||||
|
out[11] = byte(t[1] >> 37)
|
||||||
|
out[12] = byte(t[1] >> 45)
|
||||||
|
|
||||||
|
out[12] ^= byte(t[2]<<6) & 0xc0
|
||||||
|
out[13] = byte(t[2] >> 2)
|
||||||
|
out[14] = byte(t[2] >> 10)
|
||||||
|
out[15] = byte(t[2] >> 18)
|
||||||
|
out[16] = byte(t[2] >> 26)
|
||||||
|
out[17] = byte(t[2] >> 34)
|
||||||
|
out[18] = byte(t[2] >> 42)
|
||||||
|
out[19] = byte(t[2] >> 50)
|
||||||
|
|
||||||
|
out[19] ^= byte(t[3]<<1) & 0xfe
|
||||||
|
out[20] = byte(t[3] >> 7)
|
||||||
|
out[21] = byte(t[3] >> 15)
|
||||||
|
out[22] = byte(t[3] >> 23)
|
||||||
|
out[23] = byte(t[3] >> 31)
|
||||||
|
out[24] = byte(t[3] >> 39)
|
||||||
|
out[25] = byte(t[3] >> 47)
|
||||||
|
|
||||||
|
out[25] ^= byte(t[4]<<4) & 0xf0
|
||||||
|
out[26] = byte(t[4] >> 4)
|
||||||
|
out[27] = byte(t[4] >> 12)
|
||||||
|
out[28] = byte(t[4] >> 20)
|
||||||
|
out[29] = byte(t[4] >> 28)
|
||||||
|
out[30] = byte(t[4] >> 36)
|
||||||
|
out[31] = byte(t[4] >> 44)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invert calculates r = x^-1 mod p using Fermat's little theorem.
|
||||||
|
func invert(r *[5]uint64, x *[5]uint64) {
|
||||||
|
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t [5]uint64
|
||||||
|
|
||||||
|
square(&z2, x) /* 2 */
|
||||||
|
square(&t, &z2) /* 4 */
|
||||||
|
square(&t, &t) /* 8 */
|
||||||
|
mul(&z9, &t, x) /* 9 */
|
||||||
|
mul(&z11, &z9, &z2) /* 11 */
|
||||||
|
square(&t, &z11) /* 22 */
|
||||||
|
mul(&z2_5_0, &t, &z9) /* 2^5 - 2^0 = 31 */
|
||||||
|
|
||||||
|
square(&t, &z2_5_0) /* 2^6 - 2^1 */
|
||||||
|
for i := 1; i < 5; i++ { /* 2^20 - 2^10 */
|
||||||
|
square(&t, &t)
|
||||||
|
}
|
||||||
|
mul(&z2_10_0, &t, &z2_5_0) /* 2^10 - 2^0 */
|
||||||
|
|
||||||
|
square(&t, &z2_10_0) /* 2^11 - 2^1 */
|
||||||
|
for i := 1; i < 10; i++ { /* 2^20 - 2^10 */
|
||||||
|
square(&t, &t)
|
||||||
|
}
|
||||||
|
mul(&z2_20_0, &t, &z2_10_0) /* 2^20 - 2^0 */
|
||||||
|
|
||||||
|
square(&t, &z2_20_0) /* 2^21 - 2^1 */
|
||||||
|
for i := 1; i < 20; i++ { /* 2^40 - 2^20 */
|
||||||
|
square(&t, &t)
|
||||||
|
}
|
||||||
|
mul(&t, &t, &z2_20_0) /* 2^40 - 2^0 */
|
||||||
|
|
||||||
|
square(&t, &t) /* 2^41 - 2^1 */
|
||||||
|
for i := 1; i < 10; i++ { /* 2^50 - 2^10 */
|
||||||
|
square(&t, &t)
|
||||||
|
}
|
||||||
|
mul(&z2_50_0, &t, &z2_10_0) /* 2^50 - 2^0 */
|
||||||
|
|
||||||
|
square(&t, &z2_50_0) /* 2^51 - 2^1 */
|
||||||
|
for i := 1; i < 50; i++ { /* 2^100 - 2^50 */
|
||||||
|
square(&t, &t)
|
||||||
|
}
|
||||||
|
mul(&z2_100_0, &t, &z2_50_0) /* 2^100 - 2^0 */
|
||||||
|
|
||||||
|
square(&t, &z2_100_0) /* 2^101 - 2^1 */
|
||||||
|
for i := 1; i < 100; i++ { /* 2^200 - 2^100 */
|
||||||
|
square(&t, &t)
|
||||||
|
}
|
||||||
|
mul(&t, &t, &z2_100_0) /* 2^200 - 2^0 */
|
||||||
|
|
||||||
|
square(&t, &t) /* 2^201 - 2^1 */
|
||||||
|
for i := 1; i < 50; i++ { /* 2^250 - 2^50 */
|
||||||
|
square(&t, &t)
|
||||||
|
}
|
||||||
|
mul(&t, &t, &z2_50_0) /* 2^250 - 2^0 */
|
||||||
|
|
||||||
|
square(&t, &t) /* 2^251 - 2^1 */
|
||||||
|
square(&t, &t) /* 2^252 - 2^2 */
|
||||||
|
square(&t, &t) /* 2^253 - 2^3 */
|
||||||
|
|
||||||
|
square(&t, &t) /* 2^254 - 2^4 */
|
||||||
|
|
||||||
|
square(&t, &t) /* 2^255 - 2^5 */
|
||||||
|
mul(r, &t, &z11) /* 2^255 - 21 */
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// This code was translated into a form compatible with 6a from the public
|
||||||
|
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
|
||||||
|
|
||||||
|
// +build amd64,!gccgo,!appengine
|
||||||
|
|
||||||
|
#include "const_amd64.h"
|
||||||
|
|
||||||
|
// func mul(dest, a, b *[5]uint64)
|
||||||
|
TEXT ·mul(SB),0,$16-24
|
||||||
|
MOVQ dest+0(FP), DI
|
||||||
|
MOVQ a+8(FP), SI
|
||||||
|
MOVQ b+16(FP), DX
|
||||||
|
|
||||||
|
MOVQ DX,CX
|
||||||
|
MOVQ 24(SI),DX
|
||||||
|
IMUL3Q $19,DX,AX
|
||||||
|
MOVQ AX,0(SP)
|
||||||
|
MULQ 16(CX)
|
||||||
|
MOVQ AX,R8
|
||||||
|
MOVQ DX,R9
|
||||||
|
MOVQ 32(SI),DX
|
||||||
|
IMUL3Q $19,DX,AX
|
||||||
|
MOVQ AX,8(SP)
|
||||||
|
MULQ 8(CX)
|
||||||
|
ADDQ AX,R8
|
||||||
|
ADCQ DX,R9
|
||||||
|
MOVQ 0(SI),AX
|
||||||
|
MULQ 0(CX)
|
||||||
|
ADDQ AX,R8
|
||||||
|
ADCQ DX,R9
|
||||||
|
MOVQ 0(SI),AX
|
||||||
|
MULQ 8(CX)
|
||||||
|
MOVQ AX,R10
|
||||||
|
MOVQ DX,R11
|
||||||
|
MOVQ 0(SI),AX
|
||||||
|
MULQ 16(CX)
|
||||||
|
MOVQ AX,R12
|
||||||
|
MOVQ DX,R13
|
||||||
|
MOVQ 0(SI),AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
MOVQ AX,R14
|
||||||
|
MOVQ DX,R15
|
||||||
|
MOVQ 0(SI),AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
MOVQ AX,BX
|
||||||
|
MOVQ DX,BP
|
||||||
|
MOVQ 8(SI),AX
|
||||||
|
MULQ 0(CX)
|
||||||
|
ADDQ AX,R10
|
||||||
|
ADCQ DX,R11
|
||||||
|
MOVQ 8(SI),AX
|
||||||
|
MULQ 8(CX)
|
||||||
|
ADDQ AX,R12
|
||||||
|
ADCQ DX,R13
|
||||||
|
MOVQ 8(SI),AX
|
||||||
|
MULQ 16(CX)
|
||||||
|
ADDQ AX,R14
|
||||||
|
ADCQ DX,R15
|
||||||
|
MOVQ 8(SI),AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
ADDQ AX,BX
|
||||||
|
ADCQ DX,BP
|
||||||
|
MOVQ 8(SI),DX
|
||||||
|
IMUL3Q $19,DX,AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX,R8
|
||||||
|
ADCQ DX,R9
|
||||||
|
MOVQ 16(SI),AX
|
||||||
|
MULQ 0(CX)
|
||||||
|
ADDQ AX,R12
|
||||||
|
ADCQ DX,R13
|
||||||
|
MOVQ 16(SI),AX
|
||||||
|
MULQ 8(CX)
|
||||||
|
ADDQ AX,R14
|
||||||
|
ADCQ DX,R15
|
||||||
|
MOVQ 16(SI),AX
|
||||||
|
MULQ 16(CX)
|
||||||
|
ADDQ AX,BX
|
||||||
|
ADCQ DX,BP
|
||||||
|
MOVQ 16(SI),DX
|
||||||
|
IMUL3Q $19,DX,AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
ADDQ AX,R8
|
||||||
|
ADCQ DX,R9
|
||||||
|
MOVQ 16(SI),DX
|
||||||
|
IMUL3Q $19,DX,AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX,R10
|
||||||
|
ADCQ DX,R11
|
||||||
|
MOVQ 24(SI),AX
|
||||||
|
MULQ 0(CX)
|
||||||
|
ADDQ AX,R14
|
||||||
|
ADCQ DX,R15
|
||||||
|
MOVQ 24(SI),AX
|
||||||
|
MULQ 8(CX)
|
||||||
|
ADDQ AX,BX
|
||||||
|
ADCQ DX,BP
|
||||||
|
MOVQ 0(SP),AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
ADDQ AX,R10
|
||||||
|
ADCQ DX,R11
|
||||||
|
MOVQ 0(SP),AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX,R12
|
||||||
|
ADCQ DX,R13
|
||||||
|
MOVQ 32(SI),AX
|
||||||
|
MULQ 0(CX)
|
||||||
|
ADDQ AX,BX
|
||||||
|
ADCQ DX,BP
|
||||||
|
MOVQ 8(SP),AX
|
||||||
|
MULQ 16(CX)
|
||||||
|
ADDQ AX,R10
|
||||||
|
ADCQ DX,R11
|
||||||
|
MOVQ 8(SP),AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
ADDQ AX,R12
|
||||||
|
ADCQ DX,R13
|
||||||
|
MOVQ 8(SP),AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX,R14
|
||||||
|
ADCQ DX,R15
|
||||||
|
MOVQ $REDMASK51,SI
|
||||||
|
SHLQ $13,R9:R8
|
||||||
|
ANDQ SI,R8
|
||||||
|
SHLQ $13,R11:R10
|
||||||
|
ANDQ SI,R10
|
||||||
|
ADDQ R9,R10
|
||||||
|
SHLQ $13,R13:R12
|
||||||
|
ANDQ SI,R12
|
||||||
|
ADDQ R11,R12
|
||||||
|
SHLQ $13,R15:R14
|
||||||
|
ANDQ SI,R14
|
||||||
|
ADDQ R13,R14
|
||||||
|
SHLQ $13,BP:BX
|
||||||
|
ANDQ SI,BX
|
||||||
|
ADDQ R15,BX
|
||||||
|
IMUL3Q $19,BP,DX
|
||||||
|
ADDQ DX,R8
|
||||||
|
MOVQ R8,DX
|
||||||
|
SHRQ $51,DX
|
||||||
|
ADDQ R10,DX
|
||||||
|
MOVQ DX,CX
|
||||||
|
SHRQ $51,DX
|
||||||
|
ANDQ SI,R8
|
||||||
|
ADDQ R12,DX
|
||||||
|
MOVQ DX,R9
|
||||||
|
SHRQ $51,DX
|
||||||
|
ANDQ SI,CX
|
||||||
|
ADDQ R14,DX
|
||||||
|
MOVQ DX,AX
|
||||||
|
SHRQ $51,DX
|
||||||
|
ANDQ SI,R9
|
||||||
|
ADDQ BX,DX
|
||||||
|
MOVQ DX,R10
|
||||||
|
SHRQ $51,DX
|
||||||
|
ANDQ SI,AX
|
||||||
|
IMUL3Q $19,DX,DX
|
||||||
|
ADDQ DX,R8
|
||||||
|
ANDQ SI,R10
|
||||||
|
MOVQ R8,0(DI)
|
||||||
|
MOVQ CX,8(DI)
|
||||||
|
MOVQ R9,16(DI)
|
||||||
|
MOVQ AX,24(DI)
|
||||||
|
MOVQ R10,32(DI)
|
||||||
|
RET
|
|
@ -0,0 +1,132 @@
|
||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// This code was translated into a form compatible with 6a from the public
|
||||||
|
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
|
||||||
|
|
||||||
|
// +build amd64,!gccgo,!appengine
|
||||||
|
|
||||||
|
#include "const_amd64.h"
|
||||||
|
|
||||||
|
// func square(out, in *[5]uint64)
|
||||||
|
TEXT ·square(SB),7,$0-16
|
||||||
|
MOVQ out+0(FP), DI
|
||||||
|
MOVQ in+8(FP), SI
|
||||||
|
|
||||||
|
MOVQ 0(SI),AX
|
||||||
|
MULQ 0(SI)
|
||||||
|
MOVQ AX,CX
|
||||||
|
MOVQ DX,R8
|
||||||
|
MOVQ 0(SI),AX
|
||||||
|
SHLQ $1,AX
|
||||||
|
MULQ 8(SI)
|
||||||
|
MOVQ AX,R9
|
||||||
|
MOVQ DX,R10
|
||||||
|
MOVQ 0(SI),AX
|
||||||
|
SHLQ $1,AX
|
||||||
|
MULQ 16(SI)
|
||||||
|
MOVQ AX,R11
|
||||||
|
MOVQ DX,R12
|
||||||
|
MOVQ 0(SI),AX
|
||||||
|
SHLQ $1,AX
|
||||||
|
MULQ 24(SI)
|
||||||
|
MOVQ AX,R13
|
||||||
|
MOVQ DX,R14
|
||||||
|
MOVQ 0(SI),AX
|
||||||
|
SHLQ $1,AX
|
||||||
|
MULQ 32(SI)
|
||||||
|
MOVQ AX,R15
|
||||||
|
MOVQ DX,BX
|
||||||
|
MOVQ 8(SI),AX
|
||||||
|
MULQ 8(SI)
|
||||||
|
ADDQ AX,R11
|
||||||
|
ADCQ DX,R12
|
||||||
|
MOVQ 8(SI),AX
|
||||||
|
SHLQ $1,AX
|
||||||
|
MULQ 16(SI)
|
||||||
|
ADDQ AX,R13
|
||||||
|
ADCQ DX,R14
|
||||||
|
MOVQ 8(SI),AX
|
||||||
|
SHLQ $1,AX
|
||||||
|
MULQ 24(SI)
|
||||||
|
ADDQ AX,R15
|
||||||
|
ADCQ DX,BX
|
||||||
|
MOVQ 8(SI),DX
|
||||||
|
IMUL3Q $38,DX,AX
|
||||||
|
MULQ 32(SI)
|
||||||
|
ADDQ AX,CX
|
||||||
|
ADCQ DX,R8
|
||||||
|
MOVQ 16(SI),AX
|
||||||
|
MULQ 16(SI)
|
||||||
|
ADDQ AX,R15
|
||||||
|
ADCQ DX,BX
|
||||||
|
MOVQ 16(SI),DX
|
||||||
|
IMUL3Q $38,DX,AX
|
||||||
|
MULQ 24(SI)
|
||||||
|
ADDQ AX,CX
|
||||||
|
ADCQ DX,R8
|
||||||
|
MOVQ 16(SI),DX
|
||||||
|
IMUL3Q $38,DX,AX
|
||||||
|
MULQ 32(SI)
|
||||||
|
ADDQ AX,R9
|
||||||
|
ADCQ DX,R10
|
||||||
|
MOVQ 24(SI),DX
|
||||||
|
IMUL3Q $19,DX,AX
|
||||||
|
MULQ 24(SI)
|
||||||
|
ADDQ AX,R9
|
||||||
|
ADCQ DX,R10
|
||||||
|
MOVQ 24(SI),DX
|
||||||
|
IMUL3Q $38,DX,AX
|
||||||
|
MULQ 32(SI)
|
||||||
|
ADDQ AX,R11
|
||||||
|
ADCQ DX,R12
|
||||||
|
MOVQ 32(SI),DX
|
||||||
|
IMUL3Q $19,DX,AX
|
||||||
|
MULQ 32(SI)
|
||||||
|
ADDQ AX,R13
|
||||||
|
ADCQ DX,R14
|
||||||
|
MOVQ $REDMASK51,SI
|
||||||
|
SHLQ $13,R8:CX
|
||||||
|
ANDQ SI,CX
|
||||||
|
SHLQ $13,R10:R9
|
||||||
|
ANDQ SI,R9
|
||||||
|
ADDQ R8,R9
|
||||||
|
SHLQ $13,R12:R11
|
||||||
|
ANDQ SI,R11
|
||||||
|
ADDQ R10,R11
|
||||||
|
SHLQ $13,R14:R13
|
||||||
|
ANDQ SI,R13
|
||||||
|
ADDQ R12,R13
|
||||||
|
SHLQ $13,BX:R15
|
||||||
|
ANDQ SI,R15
|
||||||
|
ADDQ R14,R15
|
||||||
|
IMUL3Q $19,BX,DX
|
||||||
|
ADDQ DX,CX
|
||||||
|
MOVQ CX,DX
|
||||||
|
SHRQ $51,DX
|
||||||
|
ADDQ R9,DX
|
||||||
|
ANDQ SI,CX
|
||||||
|
MOVQ DX,R8
|
||||||
|
SHRQ $51,DX
|
||||||
|
ADDQ R11,DX
|
||||||
|
ANDQ SI,R8
|
||||||
|
MOVQ DX,R9
|
||||||
|
SHRQ $51,DX
|
||||||
|
ADDQ R13,DX
|
||||||
|
ANDQ SI,R9
|
||||||
|
MOVQ DX,AX
|
||||||
|
SHRQ $51,DX
|
||||||
|
ADDQ R15,DX
|
||||||
|
ANDQ SI,AX
|
||||||
|
MOVQ DX,R10
|
||||||
|
SHRQ $51,DX
|
||||||
|
IMUL3Q $19,DX,DX
|
||||||
|
ADDQ DX,CX
|
||||||
|
ANDQ SI,R10
|
||||||
|
MOVQ CX,0(DI)
|
||||||
|
MOVQ R8,8(DI)
|
||||||
|
MOVQ R9,16(DI)
|
||||||
|
MOVQ AX,24(DI)
|
||||||
|
MOVQ R10,32(DI)
|
||||||
|
RET
|
|
@ -3,7 +3,7 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Package ed25519 implements the Ed25519 signature algorithm. See
|
// Package ed25519 implements the Ed25519 signature algorithm. See
|
||||||
// http://ed25519.cr.yp.to/.
|
// https://ed25519.cr.yp.to/.
|
||||||
//
|
//
|
||||||
// These functions are also compatible with the “Ed25519” function defined in
|
// These functions are also compatible with the “Ed25519” function defined in
|
||||||
// https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05.
|
// https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05.
|
||||||
|
@ -13,10 +13,10 @@ package ed25519
|
||||||
// from SUPERCOP.
|
// from SUPERCOP.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
cryptorand "crypto/rand"
|
cryptorand "crypto/rand"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"crypto/subtle"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -177,5 +177,5 @@ func Verify(publicKey PublicKey, message, sig []byte) bool {
|
||||||
|
|
||||||
var checkR [32]byte
|
var checkR [32]byte
|
||||||
R.ToBytes(&checkR)
|
R.ToBytes(&checkR)
|
||||||
return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1
|
return bytes.Equal(sig[:32], checkR[:])
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ func TestCryptoSigner(t *testing.T) {
|
||||||
|
|
||||||
func TestGolden(t *testing.T) {
|
func TestGolden(t *testing.T) {
|
||||||
// sign.input.gz is a selection of test cases from
|
// sign.input.gz is a selection of test cases from
|
||||||
// http://ed25519.cr.yp.to/python/sign.input
|
// https://ed25519.cr.yp.to/python/sign.input
|
||||||
testDataZ, err := os.Open("testdata/sign.input.gz")
|
testDataZ, err := os.Open("testdata/sign.input.gz")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -0,0 +1,683 @@
|
||||||
|
// Copyright 2012 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 agent implements the ssh-agent protocol, and provides both
|
||||||
|
// a client and a server. The client can talk to a standard ssh-agent
|
||||||
|
// that uses UNIX sockets, and one could implement an alternative
|
||||||
|
// ssh-agent process using the sample server.
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
// [PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD
|
||||||
|
package agent // import "golang.org/x/crypto/ssh/agent"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Agent represents the capabilities of an ssh-agent.
|
||||||
|
type Agent interface {
|
||||||
|
// List returns the identities known to the agent.
|
||||||
|
List() ([]*Key, error)
|
||||||
|
|
||||||
|
// Sign has the agent sign the data using a protocol 2 key as defined
|
||||||
|
// in [PROTOCOL.agent] section 2.6.2.
|
||||||
|
Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error)
|
||||||
|
|
||||||
|
// Add adds a private key to the agent.
|
||||||
|
Add(key AddedKey) error
|
||||||
|
|
||||||
|
// Remove removes all identities with the given public key.
|
||||||
|
Remove(key ssh.PublicKey) error
|
||||||
|
|
||||||
|
// RemoveAll removes all identities.
|
||||||
|
RemoveAll() error
|
||||||
|
|
||||||
|
// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
|
||||||
|
Lock(passphrase []byte) error
|
||||||
|
|
||||||
|
// Unlock undoes the effect of Lock
|
||||||
|
Unlock(passphrase []byte) error
|
||||||
|
|
||||||
|
// Signers returns signers for all the known keys.
|
||||||
|
Signers() ([]ssh.Signer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstraintExtension describes an optional constraint defined by users.
|
||||||
|
type ConstraintExtension struct {
|
||||||
|
// ExtensionName consist of a UTF-8 string suffixed by the
|
||||||
|
// implementation domain following the naming scheme defined
|
||||||
|
// in Section 4.2 of [RFC4251], e.g. "foo@example.com".
|
||||||
|
ExtensionName string
|
||||||
|
// ExtensionDetails contains the actual content of the extended
|
||||||
|
// constraint.
|
||||||
|
ExtensionDetails []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddedKey describes an SSH key to be added to an Agent.
|
||||||
|
type AddedKey struct {
|
||||||
|
// PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey or
|
||||||
|
// *ecdsa.PrivateKey, which will be inserted into the agent.
|
||||||
|
PrivateKey interface{}
|
||||||
|
// Certificate, if not nil, is communicated to the agent and will be
|
||||||
|
// stored with the key.
|
||||||
|
Certificate *ssh.Certificate
|
||||||
|
// Comment is an optional, free-form string.
|
||||||
|
Comment string
|
||||||
|
// LifetimeSecs, if not zero, is the number of seconds that the
|
||||||
|
// agent will store the key for.
|
||||||
|
LifetimeSecs uint32
|
||||||
|
// ConfirmBeforeUse, if true, requests that the agent confirm with the
|
||||||
|
// user before each use of this key.
|
||||||
|
ConfirmBeforeUse bool
|
||||||
|
// ConstraintExtensions are the experimental or private-use constraints
|
||||||
|
// defined by users.
|
||||||
|
ConstraintExtensions []ConstraintExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
// See [PROTOCOL.agent], section 3.
|
||||||
|
const (
|
||||||
|
agentRequestV1Identities = 1
|
||||||
|
agentRemoveAllV1Identities = 9
|
||||||
|
|
||||||
|
// 3.2 Requests from client to agent for protocol 2 key operations
|
||||||
|
agentAddIdentity = 17
|
||||||
|
agentRemoveIdentity = 18
|
||||||
|
agentRemoveAllIdentities = 19
|
||||||
|
agentAddIdConstrained = 25
|
||||||
|
|
||||||
|
// 3.3 Key-type independent requests from client to agent
|
||||||
|
agentAddSmartcardKey = 20
|
||||||
|
agentRemoveSmartcardKey = 21
|
||||||
|
agentLock = 22
|
||||||
|
agentUnlock = 23
|
||||||
|
agentAddSmartcardKeyConstrained = 26
|
||||||
|
|
||||||
|
// 3.7 Key constraint identifiers
|
||||||
|
agentConstrainLifetime = 1
|
||||||
|
agentConstrainConfirm = 2
|
||||||
|
agentConstrainExtension = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxAgentResponseBytes is the maximum agent reply size that is accepted. This
|
||||||
|
// is a sanity check, not a limit in the spec.
|
||||||
|
const maxAgentResponseBytes = 16 << 20
|
||||||
|
|
||||||
|
// Agent messages:
|
||||||
|
// These structures mirror the wire format of the corresponding ssh agent
|
||||||
|
// messages found in [PROTOCOL.agent].
|
||||||
|
|
||||||
|
// 3.4 Generic replies from agent to client
|
||||||
|
const agentFailure = 5
|
||||||
|
|
||||||
|
type failureAgentMsg struct{}
|
||||||
|
|
||||||
|
const agentSuccess = 6
|
||||||
|
|
||||||
|
type successAgentMsg struct{}
|
||||||
|
|
||||||
|
// See [PROTOCOL.agent], section 2.5.2.
|
||||||
|
const agentRequestIdentities = 11
|
||||||
|
|
||||||
|
type requestIdentitiesAgentMsg struct{}
|
||||||
|
|
||||||
|
// See [PROTOCOL.agent], section 2.5.2.
|
||||||
|
const agentIdentitiesAnswer = 12
|
||||||
|
|
||||||
|
type identitiesAnswerAgentMsg struct {
|
||||||
|
NumKeys uint32 `sshtype:"12"`
|
||||||
|
Keys []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// See [PROTOCOL.agent], section 2.6.2.
|
||||||
|
const agentSignRequest = 13
|
||||||
|
|
||||||
|
type signRequestAgentMsg struct {
|
||||||
|
KeyBlob []byte `sshtype:"13"`
|
||||||
|
Data []byte
|
||||||
|
Flags uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// See [PROTOCOL.agent], section 2.6.2.
|
||||||
|
|
||||||
|
// 3.6 Replies from agent to client for protocol 2 key operations
|
||||||
|
const agentSignResponse = 14
|
||||||
|
|
||||||
|
type signResponseAgentMsg struct {
|
||||||
|
SigBlob []byte `sshtype:"14"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type publicKey struct {
|
||||||
|
Format string
|
||||||
|
Rest []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.7 Key constraint identifiers
|
||||||
|
type constrainLifetimeAgentMsg struct {
|
||||||
|
LifetimeSecs uint32 `sshtype:"1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type constrainExtensionAgentMsg struct {
|
||||||
|
ExtensionName string `sshtype:"3"`
|
||||||
|
ExtensionDetails []byte
|
||||||
|
|
||||||
|
// Rest is a field used for parsing, not part of message
|
||||||
|
Rest []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key represents a protocol 2 public key as defined in
|
||||||
|
// [PROTOCOL.agent], section 2.5.2.
|
||||||
|
type Key struct {
|
||||||
|
Format string
|
||||||
|
Blob []byte
|
||||||
|
Comment string
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientErr(err error) error {
|
||||||
|
return fmt.Errorf("agent: client error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the storage form of an agent key with the format, base64
|
||||||
|
// encoded serialized key, and the comment if it is not empty.
|
||||||
|
func (k *Key) String() string {
|
||||||
|
s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob)
|
||||||
|
|
||||||
|
if k.Comment != "" {
|
||||||
|
s += " " + k.Comment
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the public key type.
|
||||||
|
func (k *Key) Type() string {
|
||||||
|
return k.Format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal returns key blob to satisfy the ssh.PublicKey interface.
|
||||||
|
func (k *Key) Marshal() []byte {
|
||||||
|
return k.Blob
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify satisfies the ssh.PublicKey interface.
|
||||||
|
func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
|
||||||
|
pubKey, err := ssh.ParsePublicKey(k.Blob)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("agent: bad public key: %v", err)
|
||||||
|
}
|
||||||
|
return pubKey.Verify(data, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
type wireKey struct {
|
||||||
|
Format string
|
||||||
|
Rest []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseKey(in []byte) (out *Key, rest []byte, err error) {
|
||||||
|
var record struct {
|
||||||
|
Blob []byte
|
||||||
|
Comment string
|
||||||
|
Rest []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ssh.Unmarshal(in, &record); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var wk wireKey
|
||||||
|
if err := ssh.Unmarshal(record.Blob, &wk); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Key{
|
||||||
|
Format: wk.Format,
|
||||||
|
Blob: record.Blob,
|
||||||
|
Comment: record.Comment,
|
||||||
|
}, record.Rest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// client is a client for an ssh-agent process.
|
||||||
|
type client struct {
|
||||||
|
// conn is typically a *net.UnixConn
|
||||||
|
conn io.ReadWriter
|
||||||
|
// mu is used to prevent concurrent access to the agent
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns an Agent that talks to an ssh-agent process over
|
||||||
|
// the given connection.
|
||||||
|
func NewClient(rw io.ReadWriter) Agent {
|
||||||
|
return &client{conn: rw}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call sends an RPC to the agent. On success, the reply is
|
||||||
|
// unmarshaled into reply and replyType is set to the first byte of
|
||||||
|
// the reply, which contains the type of the message.
|
||||||
|
func (c *client) call(req []byte) (reply interface{}, err error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
msg := make([]byte, 4+len(req))
|
||||||
|
binary.BigEndian.PutUint32(msg, uint32(len(req)))
|
||||||
|
copy(msg[4:], req)
|
||||||
|
if _, err = c.conn.Write(msg); err != nil {
|
||||||
|
return nil, clientErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var respSizeBuf [4]byte
|
||||||
|
if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil {
|
||||||
|
return nil, clientErr(err)
|
||||||
|
}
|
||||||
|
respSize := binary.BigEndian.Uint32(respSizeBuf[:])
|
||||||
|
if respSize > maxAgentResponseBytes {
|
||||||
|
return nil, clientErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, respSize)
|
||||||
|
if _, err = io.ReadFull(c.conn, buf); err != nil {
|
||||||
|
return nil, clientErr(err)
|
||||||
|
}
|
||||||
|
reply, err = unmarshal(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, clientErr(err)
|
||||||
|
}
|
||||||
|
return reply, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) simpleCall(req []byte) error {
|
||||||
|
resp, err := c.call(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, ok := resp.(*successAgentMsg); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("agent: failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) RemoveAll() error {
|
||||||
|
return c.simpleCall([]byte{agentRemoveAllIdentities})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Remove(key ssh.PublicKey) error {
|
||||||
|
req := ssh.Marshal(&agentRemoveIdentityMsg{
|
||||||
|
KeyBlob: key.Marshal(),
|
||||||
|
})
|
||||||
|
return c.simpleCall(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Lock(passphrase []byte) error {
|
||||||
|
req := ssh.Marshal(&agentLockMsg{
|
||||||
|
Passphrase: passphrase,
|
||||||
|
})
|
||||||
|
return c.simpleCall(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Unlock(passphrase []byte) error {
|
||||||
|
req := ssh.Marshal(&agentUnlockMsg{
|
||||||
|
Passphrase: passphrase,
|
||||||
|
})
|
||||||
|
return c.simpleCall(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns the identities known to the agent.
|
||||||
|
func (c *client) List() ([]*Key, error) {
|
||||||
|
// see [PROTOCOL.agent] section 2.5.2.
|
||||||
|
req := []byte{agentRequestIdentities}
|
||||||
|
|
||||||
|
msg, err := c.call(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *identitiesAnswerAgentMsg:
|
||||||
|
if msg.NumKeys > maxAgentResponseBytes/8 {
|
||||||
|
return nil, errors.New("agent: too many keys in agent reply")
|
||||||
|
}
|
||||||
|
keys := make([]*Key, msg.NumKeys)
|
||||||
|
data := msg.Keys
|
||||||
|
for i := uint32(0); i < msg.NumKeys; i++ {
|
||||||
|
var key *Key
|
||||||
|
var err error
|
||||||
|
if key, data, err = parseKey(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keys[i] = key
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
case *failureAgentMsg:
|
||||||
|
return nil, errors.New("agent: failed to list keys")
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign has the agent sign the data using a protocol 2 key as defined
|
||||||
|
// in [PROTOCOL.agent] section 2.6.2.
|
||||||
|
func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
||||||
|
req := ssh.Marshal(signRequestAgentMsg{
|
||||||
|
KeyBlob: key.Marshal(),
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
|
||||||
|
msg, err := c.call(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *signResponseAgentMsg:
|
||||||
|
var sig ssh.Signature
|
||||||
|
if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sig, nil
|
||||||
|
case *failureAgentMsg:
|
||||||
|
return nil, errors.New("agent: failed to sign challenge")
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshal parses an agent message in packet, returning the parsed
|
||||||
|
// form and the message type of packet.
|
||||||
|
func unmarshal(packet []byte) (interface{}, error) {
|
||||||
|
if len(packet) < 1 {
|
||||||
|
return nil, errors.New("agent: empty packet")
|
||||||
|
}
|
||||||
|
var msg interface{}
|
||||||
|
switch packet[0] {
|
||||||
|
case agentFailure:
|
||||||
|
return new(failureAgentMsg), nil
|
||||||
|
case agentSuccess:
|
||||||
|
return new(successAgentMsg), nil
|
||||||
|
case agentIdentitiesAnswer:
|
||||||
|
msg = new(identitiesAnswerAgentMsg)
|
||||||
|
case agentSignResponse:
|
||||||
|
msg = new(signResponseAgentMsg)
|
||||||
|
case agentV1IdentitiesAnswer:
|
||||||
|
msg = new(agentV1IdentityMsg)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("agent: unknown type tag %d", packet[0])
|
||||||
|
}
|
||||||
|
if err := ssh.Unmarshal(packet, msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rsaKeyMsg struct {
|
||||||
|
Type string `sshtype:"17|25"`
|
||||||
|
N *big.Int
|
||||||
|
E *big.Int
|
||||||
|
D *big.Int
|
||||||
|
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
||||||
|
P *big.Int
|
||||||
|
Q *big.Int
|
||||||
|
Comments string
|
||||||
|
Constraints []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dsaKeyMsg struct {
|
||||||
|
Type string `sshtype:"17|25"`
|
||||||
|
P *big.Int
|
||||||
|
Q *big.Int
|
||||||
|
G *big.Int
|
||||||
|
Y *big.Int
|
||||||
|
X *big.Int
|
||||||
|
Comments string
|
||||||
|
Constraints []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ecdsaKeyMsg struct {
|
||||||
|
Type string `sshtype:"17|25"`
|
||||||
|
Curve string
|
||||||
|
KeyBytes []byte
|
||||||
|
D *big.Int
|
||||||
|
Comments string
|
||||||
|
Constraints []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ed25519KeyMsg struct {
|
||||||
|
Type string `sshtype:"17|25"`
|
||||||
|
Pub []byte
|
||||||
|
Priv []byte
|
||||||
|
Comments string
|
||||||
|
Constraints []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert adds a private key to the agent.
|
||||||
|
func (c *client) insertKey(s interface{}, comment string, constraints []byte) error {
|
||||||
|
var req []byte
|
||||||
|
switch k := s.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
if len(k.Primes) != 2 {
|
||||||
|
return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes))
|
||||||
|
}
|
||||||
|
k.Precompute()
|
||||||
|
req = ssh.Marshal(rsaKeyMsg{
|
||||||
|
Type: ssh.KeyAlgoRSA,
|
||||||
|
N: k.N,
|
||||||
|
E: big.NewInt(int64(k.E)),
|
||||||
|
D: k.D,
|
||||||
|
Iqmp: k.Precomputed.Qinv,
|
||||||
|
P: k.Primes[0],
|
||||||
|
Q: k.Primes[1],
|
||||||
|
Comments: comment,
|
||||||
|
Constraints: constraints,
|
||||||
|
})
|
||||||
|
case *dsa.PrivateKey:
|
||||||
|
req = ssh.Marshal(dsaKeyMsg{
|
||||||
|
Type: ssh.KeyAlgoDSA,
|
||||||
|
P: k.P,
|
||||||
|
Q: k.Q,
|
||||||
|
G: k.G,
|
||||||
|
Y: k.Y,
|
||||||
|
X: k.X,
|
||||||
|
Comments: comment,
|
||||||
|
Constraints: constraints,
|
||||||
|
})
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
nistID := fmt.Sprintf("nistp%d", k.Params().BitSize)
|
||||||
|
req = ssh.Marshal(ecdsaKeyMsg{
|
||||||
|
Type: "ecdsa-sha2-" + nistID,
|
||||||
|
Curve: nistID,
|
||||||
|
KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y),
|
||||||
|
D: k.D,
|
||||||
|
Comments: comment,
|
||||||
|
Constraints: constraints,
|
||||||
|
})
|
||||||
|
case *ed25519.PrivateKey:
|
||||||
|
req = ssh.Marshal(ed25519KeyMsg{
|
||||||
|
Type: ssh.KeyAlgoED25519,
|
||||||
|
Pub: []byte(*k)[32:],
|
||||||
|
Priv: []byte(*k),
|
||||||
|
Comments: comment,
|
||||||
|
Constraints: constraints,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("agent: unsupported key type %T", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if constraints are present then the message type needs to be changed.
|
||||||
|
if len(constraints) != 0 {
|
||||||
|
req[0] = agentAddIdConstrained
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.call(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, ok := resp.(*successAgentMsg); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("agent: failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
type rsaCertMsg struct {
|
||||||
|
Type string `sshtype:"17|25"`
|
||||||
|
CertBytes []byte
|
||||||
|
D *big.Int
|
||||||
|
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
||||||
|
P *big.Int
|
||||||
|
Q *big.Int
|
||||||
|
Comments string
|
||||||
|
Constraints []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dsaCertMsg struct {
|
||||||
|
Type string `sshtype:"17|25"`
|
||||||
|
CertBytes []byte
|
||||||
|
X *big.Int
|
||||||
|
Comments string
|
||||||
|
Constraints []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ecdsaCertMsg struct {
|
||||||
|
Type string `sshtype:"17|25"`
|
||||||
|
CertBytes []byte
|
||||||
|
D *big.Int
|
||||||
|
Comments string
|
||||||
|
Constraints []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ed25519CertMsg struct {
|
||||||
|
Type string `sshtype:"17|25"`
|
||||||
|
CertBytes []byte
|
||||||
|
Pub []byte
|
||||||
|
Priv []byte
|
||||||
|
Comments string
|
||||||
|
Constraints []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a private key to the agent. If a certificate is given,
|
||||||
|
// that certificate is added instead as public key.
|
||||||
|
func (c *client) Add(key AddedKey) error {
|
||||||
|
var constraints []byte
|
||||||
|
|
||||||
|
if secs := key.LifetimeSecs; secs != 0 {
|
||||||
|
constraints = append(constraints, ssh.Marshal(constrainLifetimeAgentMsg{secs})...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.ConfirmBeforeUse {
|
||||||
|
constraints = append(constraints, agentConstrainConfirm)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert := key.Certificate; cert == nil {
|
||||||
|
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
||||||
|
} else {
|
||||||
|
return c.insertCert(key.PrivateKey, cert, key.Comment, constraints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error {
|
||||||
|
var req []byte
|
||||||
|
switch k := s.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
if len(k.Primes) != 2 {
|
||||||
|
return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes))
|
||||||
|
}
|
||||||
|
k.Precompute()
|
||||||
|
req = ssh.Marshal(rsaCertMsg{
|
||||||
|
Type: cert.Type(),
|
||||||
|
CertBytes: cert.Marshal(),
|
||||||
|
D: k.D,
|
||||||
|
Iqmp: k.Precomputed.Qinv,
|
||||||
|
P: k.Primes[0],
|
||||||
|
Q: k.Primes[1],
|
||||||
|
Comments: comment,
|
||||||
|
Constraints: constraints,
|
||||||
|
})
|
||||||
|
case *dsa.PrivateKey:
|
||||||
|
req = ssh.Marshal(dsaCertMsg{
|
||||||
|
Type: cert.Type(),
|
||||||
|
CertBytes: cert.Marshal(),
|
||||||
|
X: k.X,
|
||||||
|
Comments: comment,
|
||||||
|
Constraints: constraints,
|
||||||
|
})
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
req = ssh.Marshal(ecdsaCertMsg{
|
||||||
|
Type: cert.Type(),
|
||||||
|
CertBytes: cert.Marshal(),
|
||||||
|
D: k.D,
|
||||||
|
Comments: comment,
|
||||||
|
Constraints: constraints,
|
||||||
|
})
|
||||||
|
case *ed25519.PrivateKey:
|
||||||
|
req = ssh.Marshal(ed25519CertMsg{
|
||||||
|
Type: cert.Type(),
|
||||||
|
CertBytes: cert.Marshal(),
|
||||||
|
Pub: []byte(*k)[32:],
|
||||||
|
Priv: []byte(*k),
|
||||||
|
Comments: comment,
|
||||||
|
Constraints: constraints,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("agent: unsupported key type %T", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if constraints are present then the message type needs to be changed.
|
||||||
|
if len(constraints) != 0 {
|
||||||
|
req[0] = agentAddIdConstrained
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := ssh.NewSignerFromKey(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
|
||||||
|
return errors.New("agent: signer and cert have different public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.call(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, ok := resp.(*successAgentMsg); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("agent: failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signers provides a callback for client authentication.
|
||||||
|
func (c *client) Signers() ([]ssh.Signer, error) {
|
||||||
|
keys, err := c.List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []ssh.Signer
|
||||||
|
for _, k := range keys {
|
||||||
|
result = append(result, &agentKeyringSigner{c, k})
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type agentKeyringSigner struct {
|
||||||
|
agent *client
|
||||||
|
pub ssh.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *agentKeyringSigner) PublicKey() ssh.PublicKey {
|
||||||
|
return s.pub
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
|
||||||
|
// The agent has its own entropy source, so the rand argument is ignored.
|
||||||
|
return s.agent.Sign(s.pub, data)
|
||||||
|
}
|
|
@ -0,0 +1,379 @@
|
||||||
|
// Copyright 2012 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 agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// startOpenSSHAgent executes ssh-agent, and returns an Agent interface to it.
|
||||||
|
func startOpenSSHAgent(t *testing.T) (client Agent, socket string, cleanup func()) {
|
||||||
|
if testing.Short() {
|
||||||
|
// ssh-agent is not always available, and the key
|
||||||
|
// types supported vary by platform.
|
||||||
|
t.Skip("skipping test due to -short")
|
||||||
|
}
|
||||||
|
|
||||||
|
bin, err := exec.LookPath("ssh-agent")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("could not find ssh-agent")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(bin, "-s")
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cmd.Output: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Output looks like:
|
||||||
|
|
||||||
|
SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK;
|
||||||
|
SSH_AGENT_PID=15542; export SSH_AGENT_PID;
|
||||||
|
echo Agent pid 15542;
|
||||||
|
*/
|
||||||
|
fields := bytes.Split(out, []byte(";"))
|
||||||
|
line := bytes.SplitN(fields[0], []byte("="), 2)
|
||||||
|
line[0] = bytes.TrimLeft(line[0], "\n")
|
||||||
|
if string(line[0]) != "SSH_AUTH_SOCK" {
|
||||||
|
t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0])
|
||||||
|
}
|
||||||
|
socket = string(line[1])
|
||||||
|
|
||||||
|
line = bytes.SplitN(fields[2], []byte("="), 2)
|
||||||
|
line[0] = bytes.TrimLeft(line[0], "\n")
|
||||||
|
if string(line[0]) != "SSH_AGENT_PID" {
|
||||||
|
t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2])
|
||||||
|
}
|
||||||
|
pidStr := line[1]
|
||||||
|
pid, err := strconv.Atoi(string(pidStr))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Atoi(%q): %v", pidStr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial("unix", string(socket))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("net.Dial: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := NewClient(conn)
|
||||||
|
return ac, socket, func() {
|
||||||
|
proc, _ := os.FindProcess(pid)
|
||||||
|
if proc != nil {
|
||||||
|
proc.Kill()
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
os.RemoveAll(filepath.Dir(socket))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startKeyringAgent uses Keyring to simulate a ssh-agent Server and returns a client.
|
||||||
|
func startKeyringAgent(t *testing.T) (client Agent, cleanup func()) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
go ServeAgent(NewKeyring(), c2)
|
||||||
|
|
||||||
|
return NewClient(c1), func() {
|
||||||
|
c1.Close()
|
||||||
|
c2.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOpenSSHAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||||
|
agent, _, cleanup := startOpenSSHAgent(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
testAgentInterface(t, agent, key, cert, lifetimeSecs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testKeyringAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||||
|
agent, cleanup := startKeyringAgent(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
testAgentInterface(t, agent, key, cert, lifetimeSecs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||||
|
signer, err := ssh.NewSignerFromKey(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewSignerFromKey(%T): %v", key, err)
|
||||||
|
}
|
||||||
|
// The agent should start up empty.
|
||||||
|
if keys, err := agent.List(); err != nil {
|
||||||
|
t.Fatalf("RequestIdentities: %v", err)
|
||||||
|
} else if len(keys) > 0 {
|
||||||
|
t.Fatalf("got %d keys, want 0: %v", len(keys), keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to insert the key, with certificate if specified.
|
||||||
|
var pubKey ssh.PublicKey
|
||||||
|
if cert != nil {
|
||||||
|
err = agent.Add(AddedKey{
|
||||||
|
PrivateKey: key,
|
||||||
|
Certificate: cert,
|
||||||
|
Comment: "comment",
|
||||||
|
LifetimeSecs: lifetimeSecs,
|
||||||
|
})
|
||||||
|
pubKey = cert
|
||||||
|
} else {
|
||||||
|
err = agent.Add(AddedKey{PrivateKey: key, Comment: "comment", LifetimeSecs: lifetimeSecs})
|
||||||
|
pubKey = signer.PublicKey()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("insert(%T): %v", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did the key get inserted successfully?
|
||||||
|
if keys, err := agent.List(); err != nil {
|
||||||
|
t.Fatalf("List: %v", err)
|
||||||
|
} else if len(keys) != 1 {
|
||||||
|
t.Fatalf("got %v, want 1 key", keys)
|
||||||
|
} else if keys[0].Comment != "comment" {
|
||||||
|
t.Fatalf("key comment: got %v, want %v", keys[0].Comment, "comment")
|
||||||
|
} else if !bytes.Equal(keys[0].Blob, pubKey.Marshal()) {
|
||||||
|
t.Fatalf("key mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can the agent make a valid signature?
|
||||||
|
data := []byte("hello")
|
||||||
|
sig, err := agent.Sign(pubKey, data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Sign(%s): %v", pubKey.Type(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pubKey.Verify(data, sig); err != nil {
|
||||||
|
t.Fatalf("Verify(%s): %v", pubKey.Type(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the key has a lifetime, is it removed when it should be?
|
||||||
|
if lifetimeSecs > 0 {
|
||||||
|
time.Sleep(time.Second*time.Duration(lifetimeSecs) + 100*time.Millisecond)
|
||||||
|
keys, err := agent.List()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("List: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) > 0 {
|
||||||
|
t.Fatalf("key not expired")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgent(t *testing.T) {
|
||||||
|
for _, keyType := range []string{"rsa", "dsa", "ecdsa", "ed25519"} {
|
||||||
|
testOpenSSHAgent(t, testPrivateKeys[keyType], nil, 0)
|
||||||
|
testKeyringAgent(t, testPrivateKeys[keyType], nil, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCert(t *testing.T) {
|
||||||
|
cert := &ssh.Certificate{
|
||||||
|
Key: testPublicKeys["rsa"],
|
||||||
|
ValidBefore: ssh.CertTimeInfinity,
|
||||||
|
CertType: ssh.UserCert,
|
||||||
|
}
|
||||||
|
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||||
|
|
||||||
|
testOpenSSHAgent(t, testPrivateKeys["rsa"], cert, 0)
|
||||||
|
testKeyringAgent(t, testPrivateKeys["rsa"], cert, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
|
||||||
|
// therefore is buffered (net.Pipe deadlocks if both sides start with
|
||||||
|
// a write.)
|
||||||
|
func netPipe() (net.Conn, net.Conn, error) {
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
listener, err = net.Listen("tcp", "[::1]:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
c1, err := net.Dial("tcp", listener.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c2, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
c1.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c1, c2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuth(t *testing.T) {
|
||||||
|
agent, _, cleanup := startOpenSSHAgent(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
a, b, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer a.Close()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment"}); err != nil {
|
||||||
|
t.Errorf("Add: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConf := ssh.ServerConfig{}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
serverConf.PublicKeyCallback = func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||||
|
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("pubkey rejected")
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Server: %v", err)
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
conf := ssh.ClientConfig{
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
conf.Auth = append(conf.Auth, ssh.PublicKeysCallback(agent.Signers))
|
||||||
|
conn, _, _, err := ssh.NewClientConn(b, "", &conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewClientConn: %v", err)
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockOpenSSHAgent(t *testing.T) {
|
||||||
|
agent, _, cleanup := startOpenSSHAgent(t)
|
||||||
|
defer cleanup()
|
||||||
|
testLockAgent(agent, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockKeyringAgent(t *testing.T) {
|
||||||
|
agent, cleanup := startKeyringAgent(t)
|
||||||
|
defer cleanup()
|
||||||
|
testLockAgent(agent, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLockAgent(agent Agent, t *testing.T) {
|
||||||
|
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment 1"}); err != nil {
|
||||||
|
t.Errorf("Add: %v", err)
|
||||||
|
}
|
||||||
|
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["dsa"], Comment: "comment dsa"}); err != nil {
|
||||||
|
t.Errorf("Add: %v", err)
|
||||||
|
}
|
||||||
|
if keys, err := agent.List(); err != nil {
|
||||||
|
t.Errorf("List: %v", err)
|
||||||
|
} else if len(keys) != 2 {
|
||||||
|
t.Errorf("Want 2 keys, got %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
passphrase := []byte("secret")
|
||||||
|
if err := agent.Lock(passphrase); err != nil {
|
||||||
|
t.Errorf("Lock: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if keys, err := agent.List(); err != nil {
|
||||||
|
t.Errorf("List: %v", err)
|
||||||
|
} else if len(keys) != 0 {
|
||||||
|
t.Errorf("Want 0 keys, got %v", keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, _ := ssh.NewSignerFromKey(testPrivateKeys["rsa"])
|
||||||
|
if _, err := agent.Sign(signer.PublicKey(), []byte("hello")); err == nil {
|
||||||
|
t.Fatalf("Sign did not fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := agent.Remove(signer.PublicKey()); err == nil {
|
||||||
|
t.Fatalf("Remove did not fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := agent.RemoveAll(); err == nil {
|
||||||
|
t.Fatalf("RemoveAll did not fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := agent.Unlock(nil); err == nil {
|
||||||
|
t.Errorf("Unlock with wrong passphrase succeeded")
|
||||||
|
}
|
||||||
|
if err := agent.Unlock(passphrase); err != nil {
|
||||||
|
t.Errorf("Unlock: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := agent.Remove(signer.PublicKey()); err != nil {
|
||||||
|
t.Fatalf("Remove: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if keys, err := agent.List(); err != nil {
|
||||||
|
t.Errorf("List: %v", err)
|
||||||
|
} else if len(keys) != 1 {
|
||||||
|
t.Errorf("Want 1 keys, got %v", keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOpenSSHAgentLifetime(t *testing.T) {
|
||||||
|
agent, _, cleanup := startOpenSSHAgent(t)
|
||||||
|
defer cleanup()
|
||||||
|
testAgentLifetime(t, agent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testKeyringAgentLifetime(t *testing.T) {
|
||||||
|
agent, cleanup := startKeyringAgent(t)
|
||||||
|
defer cleanup()
|
||||||
|
testAgentLifetime(t, agent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAgentLifetime(t *testing.T, agent Agent) {
|
||||||
|
for _, keyType := range []string{"rsa", "dsa", "ecdsa"} {
|
||||||
|
// Add private keys to the agent.
|
||||||
|
err := agent.Add(AddedKey{
|
||||||
|
PrivateKey: testPrivateKeys[keyType],
|
||||||
|
Comment: "comment",
|
||||||
|
LifetimeSecs: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("add: %v", err)
|
||||||
|
}
|
||||||
|
// Add certs to the agent.
|
||||||
|
cert := &ssh.Certificate{
|
||||||
|
Key: testPublicKeys[keyType],
|
||||||
|
ValidBefore: ssh.CertTimeInfinity,
|
||||||
|
CertType: ssh.UserCert,
|
||||||
|
}
|
||||||
|
cert.SignCert(rand.Reader, testSigners[keyType])
|
||||||
|
err = agent.Add(AddedKey{
|
||||||
|
PrivateKey: testPrivateKeys[keyType],
|
||||||
|
Certificate: cert,
|
||||||
|
Comment: "comment",
|
||||||
|
LifetimeSecs: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("add: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(1100 * time.Millisecond)
|
||||||
|
if keys, err := agent.List(); err != nil {
|
||||||
|
t.Errorf("List: %v", err)
|
||||||
|
} else if len(keys) != 0 {
|
||||||
|
t.Errorf("Want 0 keys, got %v", len(keys))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2016 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 agent_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleClientAgent() {
|
||||||
|
// ssh-agent has a UNIX socket under $SSH_AUTH_SOCK
|
||||||
|
socket := os.Getenv("SSH_AUTH_SOCK")
|
||||||
|
conn, err := net.Dial("unix", socket)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("net.Dial: %v", err)
|
||||||
|
}
|
||||||
|
agentClient := agent.NewClient(conn)
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: "username",
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
// Use a callback rather than PublicKeys
|
||||||
|
// so we only consult the agent once the remote server
|
||||||
|
// wants it.
|
||||||
|
ssh.PublicKeysCallback(agentClient.Signers),
|
||||||
|
},
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
sshc, err := ssh.Dial("tcp", "localhost:22", config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
// .. use sshc
|
||||||
|
sshc.Close()
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2014 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 agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestAgentForwarding sets up agent forwarding for the session.
|
||||||
|
// ForwardToAgent or ForwardToRemote should be called to route
|
||||||
|
// the authentication requests.
|
||||||
|
func RequestAgentForwarding(session *ssh.Session) error {
|
||||||
|
ok, err := session.SendRequest("auth-agent-req@openssh.com", true, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return errors.New("forwarding request denied")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForwardToAgent routes authentication requests to the given keyring.
|
||||||
|
func ForwardToAgent(client *ssh.Client, keyring Agent) error {
|
||||||
|
channels := client.HandleChannelOpen(channelType)
|
||||||
|
if channels == nil {
|
||||||
|
return errors.New("agent: already have handler for " + channelType)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for ch := range channels {
|
||||||
|
channel, reqs, err := ch.Accept()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go ssh.DiscardRequests(reqs)
|
||||||
|
go func() {
|
||||||
|
ServeAgent(keyring, channel)
|
||||||
|
channel.Close()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelType = "auth-agent@openssh.com"
|
||||||
|
|
||||||
|
// ForwardToRemote routes authentication requests to the ssh-agent
|
||||||
|
// process serving on the given unix socket.
|
||||||
|
func ForwardToRemote(client *ssh.Client, addr string) error {
|
||||||
|
channels := client.HandleChannelOpen(channelType)
|
||||||
|
if channels == nil {
|
||||||
|
return errors.New("agent: already have handler for " + channelType)
|
||||||
|
}
|
||||||
|
conn, err := net.Dial("unix", addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for ch := range channels {
|
||||||
|
channel, reqs, err := ch.Accept()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go ssh.DiscardRequests(reqs)
|
||||||
|
go forwardUnixSocket(channel, addr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func forwardUnixSocket(channel ssh.Channel, addr string) {
|
||||||
|
conn, err := net.Dial("unix", addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
io.Copy(conn, channel)
|
||||||
|
conn.(*net.UnixConn).CloseWrite()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
io.Copy(channel, conn)
|
||||||
|
channel.CloseWrite()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
conn.Close()
|
||||||
|
channel.Close()
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
// Copyright 2014 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 agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/subtle"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type privKey struct {
|
||||||
|
signer ssh.Signer
|
||||||
|
comment string
|
||||||
|
expire *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyring struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
keys []privKey
|
||||||
|
|
||||||
|
locked bool
|
||||||
|
passphrase []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var errLocked = errors.New("agent: locked")
|
||||||
|
|
||||||
|
// NewKeyring returns an Agent that holds keys in memory. It is safe
|
||||||
|
// for concurrent use by multiple goroutines.
|
||||||
|
func NewKeyring() Agent {
|
||||||
|
return &keyring{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll removes all identities.
|
||||||
|
func (r *keyring) RemoveAll() error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if r.locked {
|
||||||
|
return errLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
r.keys = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeLocked does the actual key removal. The caller must already be holding the
|
||||||
|
// keyring mutex.
|
||||||
|
func (r *keyring) removeLocked(want []byte) error {
|
||||||
|
found := false
|
||||||
|
for i := 0; i < len(r.keys); {
|
||||||
|
if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) {
|
||||||
|
found = true
|
||||||
|
r.keys[i] = r.keys[len(r.keys)-1]
|
||||||
|
r.keys = r.keys[:len(r.keys)-1]
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return errors.New("agent: key not found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes all identities with the given public key.
|
||||||
|
func (r *keyring) Remove(key ssh.PublicKey) error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if r.locked {
|
||||||
|
return errLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.removeLocked(key.Marshal())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock locks the agent. Sign and Remove will fail, and List will return an empty list.
|
||||||
|
func (r *keyring) Lock(passphrase []byte) error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if r.locked {
|
||||||
|
return errLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
r.locked = true
|
||||||
|
r.passphrase = passphrase
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock undoes the effect of Lock
|
||||||
|
func (r *keyring) Unlock(passphrase []byte) error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if !r.locked {
|
||||||
|
return errors.New("agent: not locked")
|
||||||
|
}
|
||||||
|
if len(passphrase) != len(r.passphrase) || 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) {
|
||||||
|
return fmt.Errorf("agent: incorrect passphrase")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.locked = false
|
||||||
|
r.passphrase = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// expireKeysLocked removes expired keys from the keyring. If a key was added
|
||||||
|
// with a lifetimesecs contraint and seconds >= lifetimesecs seconds have
|
||||||
|
// ellapsed, it is removed. The caller *must* be holding the keyring mutex.
|
||||||
|
func (r *keyring) expireKeysLocked() {
|
||||||
|
for _, k := range r.keys {
|
||||||
|
if k.expire != nil && time.Now().After(*k.expire) {
|
||||||
|
r.removeLocked(k.signer.PublicKey().Marshal())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns the identities known to the agent.
|
||||||
|
func (r *keyring) List() ([]*Key, error) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if r.locked {
|
||||||
|
// section 2.7: locked agents return empty.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.expireKeysLocked()
|
||||||
|
var ids []*Key
|
||||||
|
for _, k := range r.keys {
|
||||||
|
pub := k.signer.PublicKey()
|
||||||
|
ids = append(ids, &Key{
|
||||||
|
Format: pub.Type(),
|
||||||
|
Blob: pub.Marshal(),
|
||||||
|
Comment: k.comment})
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert adds a private key to the keyring. If a certificate
|
||||||
|
// is given, that certificate is added as public key. Note that
|
||||||
|
// any constraints given are ignored.
|
||||||
|
func (r *keyring) Add(key AddedKey) error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if r.locked {
|
||||||
|
return errLocked
|
||||||
|
}
|
||||||
|
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert := key.Certificate; cert != nil {
|
||||||
|
signer, err = ssh.NewCertSigner(cert, signer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := privKey{
|
||||||
|
signer: signer,
|
||||||
|
comment: key.Comment,
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.LifetimeSecs > 0 {
|
||||||
|
t := time.Now().Add(time.Duration(key.LifetimeSecs) * time.Second)
|
||||||
|
p.expire = &t
|
||||||
|
}
|
||||||
|
|
||||||
|
r.keys = append(r.keys, p)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign returns a signature for the data.
|
||||||
|
func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if r.locked {
|
||||||
|
return nil, errLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
r.expireKeysLocked()
|
||||||
|
wanted := key.Marshal()
|
||||||
|
for _, k := range r.keys {
|
||||||
|
if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) {
|
||||||
|
return k.signer.Sign(rand.Reader, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signers returns signers for all the known keys.
|
||||||
|
func (r *keyring) Signers() ([]ssh.Signer, error) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if r.locked {
|
||||||
|
return nil, errLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
r.expireKeysLocked()
|
||||||
|
s := make([]ssh.Signer, 0, len(r.keys))
|
||||||
|
for _, k := range r.keys {
|
||||||
|
s = append(s, k.signer)
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright 2015 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 agent
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func addTestKey(t *testing.T, a Agent, keyName string) {
|
||||||
|
err := a.Add(AddedKey{
|
||||||
|
PrivateKey: testPrivateKeys[keyName],
|
||||||
|
Comment: keyName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to add key %q: %v", keyName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTestKey(t *testing.T, a Agent, keyName string) {
|
||||||
|
err := a.Remove(testPublicKeys[keyName])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to remove key %q: %v", keyName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateListedKeys(t *testing.T, a Agent, expectedKeys []string) {
|
||||||
|
listedKeys, err := a.List()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to list keys: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
actualKeys := make(map[string]bool)
|
||||||
|
for _, key := range listedKeys {
|
||||||
|
actualKeys[key.Comment] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedKeys := make(map[string]bool)
|
||||||
|
for _, expectedKey := range expectedKeys {
|
||||||
|
if !actualKeys[expectedKey] {
|
||||||
|
t.Fatalf("expected key %q, but was not found", expectedKey)
|
||||||
|
} else {
|
||||||
|
matchedKeys[expectedKey] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for actualKey := range actualKeys {
|
||||||
|
if !matchedKeys[actualKey] {
|
||||||
|
t.Fatalf("key %q was found, but was not expected", actualKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyringAddingAndRemoving(t *testing.T) {
|
||||||
|
keyNames := []string{"dsa", "ecdsa", "rsa", "user"}
|
||||||
|
|
||||||
|
// add all test private keys
|
||||||
|
k := NewKeyring()
|
||||||
|
for _, keyName := range keyNames {
|
||||||
|
addTestKey(t, k, keyName)
|
||||||
|
}
|
||||||
|
validateListedKeys(t, k, keyNames)
|
||||||
|
|
||||||
|
// remove a key in the middle
|
||||||
|
keyToRemove := keyNames[1]
|
||||||
|
keyNames = append(keyNames[:1], keyNames[2:]...)
|
||||||
|
|
||||||
|
removeTestKey(t, k, keyToRemove)
|
||||||
|
validateListedKeys(t, k, keyNames)
|
||||||
|
|
||||||
|
// remove all keys
|
||||||
|
err := k.RemoveAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to remove all keys: %v", err)
|
||||||
|
}
|
||||||
|
validateListedKeys(t, k, []string{})
|
||||||
|
}
|
|
@ -0,0 +1,523 @@
|
||||||
|
// Copyright 2012 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 agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server wraps an Agent and uses it to implement the agent side of
|
||||||
|
// the SSH-agent, wire protocol.
|
||||||
|
type server struct {
|
||||||
|
agent Agent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) processRequestBytes(reqData []byte) []byte {
|
||||||
|
rep, err := s.processRequest(reqData)
|
||||||
|
if err != nil {
|
||||||
|
if err != errLocked {
|
||||||
|
// TODO(hanwen): provide better logging interface?
|
||||||
|
log.Printf("agent %d: %v", reqData[0], err)
|
||||||
|
}
|
||||||
|
return []byte{agentFailure}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && rep == nil {
|
||||||
|
return []byte{agentSuccess}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssh.Marshal(rep)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalKey(k *Key) []byte {
|
||||||
|
var record struct {
|
||||||
|
Blob []byte
|
||||||
|
Comment string
|
||||||
|
}
|
||||||
|
record.Blob = k.Marshal()
|
||||||
|
record.Comment = k.Comment
|
||||||
|
|
||||||
|
return ssh.Marshal(&record)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See [PROTOCOL.agent], section 2.5.1.
|
||||||
|
const agentV1IdentitiesAnswer = 2
|
||||||
|
|
||||||
|
type agentV1IdentityMsg struct {
|
||||||
|
Numkeys uint32 `sshtype:"2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type agentRemoveIdentityMsg struct {
|
||||||
|
KeyBlob []byte `sshtype:"18"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type agentLockMsg struct {
|
||||||
|
Passphrase []byte `sshtype:"22"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type agentUnlockMsg struct {
|
||||||
|
Passphrase []byte `sshtype:"23"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) processRequest(data []byte) (interface{}, error) {
|
||||||
|
switch data[0] {
|
||||||
|
case agentRequestV1Identities:
|
||||||
|
return &agentV1IdentityMsg{0}, nil
|
||||||
|
|
||||||
|
case agentRemoveAllV1Identities:
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
case agentRemoveIdentity:
|
||||||
|
var req agentRemoveIdentityMsg
|
||||||
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var wk wireKey
|
||||||
|
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob})
|
||||||
|
|
||||||
|
case agentRemoveAllIdentities:
|
||||||
|
return nil, s.agent.RemoveAll()
|
||||||
|
|
||||||
|
case agentLock:
|
||||||
|
var req agentLockMsg
|
||||||
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, s.agent.Lock(req.Passphrase)
|
||||||
|
|
||||||
|
case agentUnlock:
|
||||||
|
var req agentUnlockMsg
|
||||||
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, s.agent.Unlock(req.Passphrase)
|
||||||
|
|
||||||
|
case agentSignRequest:
|
||||||
|
var req signRequestAgentMsg
|
||||||
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var wk wireKey
|
||||||
|
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
k := &Key{
|
||||||
|
Format: wk.Format,
|
||||||
|
Blob: req.KeyBlob,
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := s.agent.Sign(k, req.Data) // TODO(hanwen): flags.
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil
|
||||||
|
|
||||||
|
case agentRequestIdentities:
|
||||||
|
keys, err := s.agent.List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rep := identitiesAnswerAgentMsg{
|
||||||
|
NumKeys: uint32(len(keys)),
|
||||||
|
}
|
||||||
|
for _, k := range keys {
|
||||||
|
rep.Keys = append(rep.Keys, marshalKey(k)...)
|
||||||
|
}
|
||||||
|
return rep, nil
|
||||||
|
|
||||||
|
case agentAddIdConstrained, agentAddIdentity:
|
||||||
|
return nil, s.insertIdentity(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unknown opcode %d", data[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse bool, extensions []ConstraintExtension, err error) {
|
||||||
|
for len(constraints) != 0 {
|
||||||
|
switch constraints[0] {
|
||||||
|
case agentConstrainLifetime:
|
||||||
|
lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5])
|
||||||
|
constraints = constraints[5:]
|
||||||
|
case agentConstrainConfirm:
|
||||||
|
confirmBeforeUse = true
|
||||||
|
constraints = constraints[1:]
|
||||||
|
case agentConstrainExtension:
|
||||||
|
var msg constrainExtensionAgentMsg
|
||||||
|
if err = ssh.Unmarshal(constraints, &msg); err != nil {
|
||||||
|
return 0, false, nil, err
|
||||||
|
}
|
||||||
|
extensions = append(extensions, ConstraintExtension{
|
||||||
|
ExtensionName: msg.ExtensionName,
|
||||||
|
ExtensionDetails: msg.ExtensionDetails,
|
||||||
|
})
|
||||||
|
constraints = msg.Rest
|
||||||
|
default:
|
||||||
|
return 0, false, nil, fmt.Errorf("unknown constraint type: %d", constraints[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setConstraints(key *AddedKey, constraintBytes []byte) error {
|
||||||
|
lifetimeSecs, confirmBeforeUse, constraintExtensions, err := parseConstraints(constraintBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
key.LifetimeSecs = lifetimeSecs
|
||||||
|
key.ConfirmBeforeUse = confirmBeforeUse
|
||||||
|
key.ConstraintExtensions = constraintExtensions
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRSAKey(req []byte) (*AddedKey, error) {
|
||||||
|
var k rsaKeyMsg
|
||||||
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if k.E.BitLen() > 30 {
|
||||||
|
return nil, errors.New("agent: RSA public exponent too large")
|
||||||
|
}
|
||||||
|
priv := &rsa.PrivateKey{
|
||||||
|
PublicKey: rsa.PublicKey{
|
||||||
|
E: int(k.E.Int64()),
|
||||||
|
N: k.N,
|
||||||
|
},
|
||||||
|
D: k.D,
|
||||||
|
Primes: []*big.Int{k.P, k.Q},
|
||||||
|
}
|
||||||
|
priv.Precompute()
|
||||||
|
|
||||||
|
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
||||||
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEd25519Key(req []byte) (*AddedKey, error) {
|
||||||
|
var k ed25519KeyMsg
|
||||||
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
priv := ed25519.PrivateKey(k.Priv)
|
||||||
|
|
||||||
|
addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments}
|
||||||
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDSAKey(req []byte) (*AddedKey, error) {
|
||||||
|
var k dsaKeyMsg
|
||||||
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
priv := &dsa.PrivateKey{
|
||||||
|
PublicKey: dsa.PublicKey{
|
||||||
|
Parameters: dsa.Parameters{
|
||||||
|
P: k.P,
|
||||||
|
Q: k.Q,
|
||||||
|
G: k.G,
|
||||||
|
},
|
||||||
|
Y: k.Y,
|
||||||
|
},
|
||||||
|
X: k.X,
|
||||||
|
}
|
||||||
|
|
||||||
|
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
||||||
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalECDSA(curveName string, keyBytes []byte, privScalar *big.Int) (priv *ecdsa.PrivateKey, err error) {
|
||||||
|
priv = &ecdsa.PrivateKey{
|
||||||
|
D: privScalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch curveName {
|
||||||
|
case "nistp256":
|
||||||
|
priv.Curve = elliptic.P256()
|
||||||
|
case "nistp384":
|
||||||
|
priv.Curve = elliptic.P384()
|
||||||
|
case "nistp521":
|
||||||
|
priv.Curve = elliptic.P521()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("agent: unknown curve %q", curveName)
|
||||||
|
}
|
||||||
|
|
||||||
|
priv.X, priv.Y = elliptic.Unmarshal(priv.Curve, keyBytes)
|
||||||
|
if priv.X == nil || priv.Y == nil {
|
||||||
|
return nil, errors.New("agent: point not on curve")
|
||||||
|
}
|
||||||
|
|
||||||
|
return priv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEd25519Cert(req []byte) (*AddedKey, error) {
|
||||||
|
var k ed25519CertMsg
|
||||||
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
priv := ed25519.PrivateKey(k.Priv)
|
||||||
|
cert, ok := pubKey.(*ssh.Certificate)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("agent: bad ED25519 certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}
|
||||||
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseECDSAKey(req []byte) (*AddedKey, error) {
|
||||||
|
var k ecdsaKeyMsg
|
||||||
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := unmarshalECDSA(k.Curve, k.KeyBytes, k.D)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
||||||
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRSACert(req []byte) (*AddedKey, error) {
|
||||||
|
var k rsaCertMsg
|
||||||
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, ok := pubKey.(*ssh.Certificate)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("agent: bad RSA certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// An RSA publickey as marshaled by rsaPublicKey.Marshal() in keys.go
|
||||||
|
var rsaPub struct {
|
||||||
|
Name string
|
||||||
|
E *big.Int
|
||||||
|
N *big.Int
|
||||||
|
}
|
||||||
|
if err := ssh.Unmarshal(cert.Key.Marshal(), &rsaPub); err != nil {
|
||||||
|
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rsaPub.E.BitLen() > 30 {
|
||||||
|
return nil, errors.New("agent: RSA public exponent too large")
|
||||||
|
}
|
||||||
|
|
||||||
|
priv := rsa.PrivateKey{
|
||||||
|
PublicKey: rsa.PublicKey{
|
||||||
|
E: int(rsaPub.E.Int64()),
|
||||||
|
N: rsaPub.N,
|
||||||
|
},
|
||||||
|
D: k.D,
|
||||||
|
Primes: []*big.Int{k.Q, k.P},
|
||||||
|
}
|
||||||
|
priv.Precompute()
|
||||||
|
|
||||||
|
addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}
|
||||||
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDSACert(req []byte) (*AddedKey, error) {
|
||||||
|
var k dsaCertMsg
|
||||||
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cert, ok := pubKey.(*ssh.Certificate)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("agent: bad DSA certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DSA publickey as marshaled by dsaPublicKey.Marshal() in keys.go
|
||||||
|
var w struct {
|
||||||
|
Name string
|
||||||
|
P, Q, G, Y *big.Int
|
||||||
|
}
|
||||||
|
if err := ssh.Unmarshal(cert.Key.Marshal(), &w); err != nil {
|
||||||
|
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
priv := &dsa.PrivateKey{
|
||||||
|
PublicKey: dsa.PublicKey{
|
||||||
|
Parameters: dsa.Parameters{
|
||||||
|
P: w.P,
|
||||||
|
Q: w.Q,
|
||||||
|
G: w.G,
|
||||||
|
},
|
||||||
|
Y: w.Y,
|
||||||
|
},
|
||||||
|
X: k.X,
|
||||||
|
}
|
||||||
|
|
||||||
|
addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}
|
||||||
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseECDSACert(req []byte) (*AddedKey, error) {
|
||||||
|
var k ecdsaCertMsg
|
||||||
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cert, ok := pubKey.(*ssh.Certificate)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("agent: bad ECDSA certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// An ECDSA publickey as marshaled by ecdsaPublicKey.Marshal() in keys.go
|
||||||
|
var ecdsaPub struct {
|
||||||
|
Name string
|
||||||
|
ID string
|
||||||
|
Key []byte
|
||||||
|
}
|
||||||
|
if err := ssh.Unmarshal(cert.Key.Marshal(), &ecdsaPub); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := unmarshalECDSA(ecdsaPub.ID, ecdsaPub.Key, k.D)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}
|
||||||
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) insertIdentity(req []byte) error {
|
||||||
|
var record struct {
|
||||||
|
Type string `sshtype:"17|25"`
|
||||||
|
Rest []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ssh.Unmarshal(req, &record); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var addedKey *AddedKey
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch record.Type {
|
||||||
|
case ssh.KeyAlgoRSA:
|
||||||
|
addedKey, err = parseRSAKey(req)
|
||||||
|
case ssh.KeyAlgoDSA:
|
||||||
|
addedKey, err = parseDSAKey(req)
|
||||||
|
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
|
||||||
|
addedKey, err = parseECDSAKey(req)
|
||||||
|
case ssh.KeyAlgoED25519:
|
||||||
|
addedKey, err = parseEd25519Key(req)
|
||||||
|
case ssh.CertAlgoRSAv01:
|
||||||
|
addedKey, err = parseRSACert(req)
|
||||||
|
case ssh.CertAlgoDSAv01:
|
||||||
|
addedKey, err = parseDSACert(req)
|
||||||
|
case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01:
|
||||||
|
addedKey, err = parseECDSACert(req)
|
||||||
|
case ssh.CertAlgoED25519v01:
|
||||||
|
addedKey, err = parseEd25519Cert(req)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("agent: not implemented: %q", record.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.agent.Add(*addedKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeAgent serves the agent protocol on the given connection. It
|
||||||
|
// returns when an I/O error occurs.
|
||||||
|
func ServeAgent(agent Agent, c io.ReadWriter) error {
|
||||||
|
s := &server{agent}
|
||||||
|
|
||||||
|
var length [4]byte
|
||||||
|
for {
|
||||||
|
if _, err := io.ReadFull(c, length[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l := binary.BigEndian.Uint32(length[:])
|
||||||
|
if l > maxAgentResponseBytes {
|
||||||
|
// We also cap requests.
|
||||||
|
return fmt.Errorf("agent: request too large: %d", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := make([]byte, l)
|
||||||
|
if _, err := io.ReadFull(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repData := s.processRequestBytes(req)
|
||||||
|
if len(repData) > maxAgentResponseBytes {
|
||||||
|
return fmt.Errorf("agent: reply too large: %d bytes", len(repData))
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(length[:], uint32(len(repData)))
|
||||||
|
if _, err := c.Write(length[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := c.Write(repData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,259 @@
|
||||||
|
// Copyright 2012 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 agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
pseudorand "math/rand"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
client := NewClient(c1)
|
||||||
|
|
||||||
|
go ServeAgent(NewKeyring(), c2)
|
||||||
|
|
||||||
|
testAgentInterface(t, client, testPrivateKeys["rsa"], nil, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockServer(t *testing.T) {
|
||||||
|
testLockAgent(NewKeyring(), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupForwardAgent(t *testing.T) {
|
||||||
|
a, b, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer a.Close()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
_, socket, cleanup := startOpenSSHAgent(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
serverConf := ssh.ServerConfig{
|
||||||
|
NoClientAuth: true,
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["rsa"])
|
||||||
|
incoming := make(chan *ssh.ServerConn, 1)
|
||||||
|
go func() {
|
||||||
|
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Server: %v", err)
|
||||||
|
}
|
||||||
|
incoming <- conn
|
||||||
|
}()
|
||||||
|
|
||||||
|
conf := ssh.ClientConfig{
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewClientConn: %v", err)
|
||||||
|
}
|
||||||
|
client := ssh.NewClient(conn, chans, reqs)
|
||||||
|
|
||||||
|
if err := ForwardToRemote(client, socket); err != nil {
|
||||||
|
t.Fatalf("SetupForwardAgent: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := <-incoming
|
||||||
|
ch, reqs, err := server.OpenChannel(channelType, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OpenChannel(%q): %v", channelType, err)
|
||||||
|
}
|
||||||
|
go ssh.DiscardRequests(reqs)
|
||||||
|
|
||||||
|
agentClient := NewClient(ch)
|
||||||
|
testAgentInterface(t, agentClient, testPrivateKeys["rsa"], nil, 0)
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1ProtocolMessages(t *testing.T) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("netPipe: %v", err)
|
||||||
|
}
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
c := NewClient(c1)
|
||||||
|
|
||||||
|
go ServeAgent(NewKeyring(), c2)
|
||||||
|
|
||||||
|
testV1ProtocolMessages(t, c.(*client))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testV1ProtocolMessages(t *testing.T, c *client) {
|
||||||
|
reply, err := c.call([]byte{agentRequestV1Identities})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("v1 request all failed: %v", err)
|
||||||
|
}
|
||||||
|
if msg, ok := reply.(*agentV1IdentityMsg); !ok || msg.Numkeys != 0 {
|
||||||
|
t.Fatalf("invalid request all response: %#v", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err = c.call([]byte{agentRemoveAllV1Identities})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("v1 remove all failed: %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := reply.(*successAgentMsg); !ok {
|
||||||
|
t.Fatalf("invalid remove all response: %#v", reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyKey(sshAgent Agent) error {
|
||||||
|
keys, err := sshAgent.List()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listing keys: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keys) != 1 {
|
||||||
|
return fmt.Errorf("bad number of keys found. expected 1, got %d", len(keys))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 128)
|
||||||
|
if _, err := rand.Read(buf); err != nil {
|
||||||
|
return fmt.Errorf("rand: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := sshAgent.Sign(keys[0], buf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sign: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := keys[0].Verify(buf, sig); err != nil {
|
||||||
|
return fmt.Errorf("verify: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addKeyToAgent(key crypto.PrivateKey) error {
|
||||||
|
sshAgent := NewKeyring()
|
||||||
|
if err := sshAgent.Add(AddedKey{PrivateKey: key}); err != nil {
|
||||||
|
return fmt.Errorf("add: %v", err)
|
||||||
|
}
|
||||||
|
return verifyKey(sshAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyTypes(t *testing.T) {
|
||||||
|
for k, v := range testPrivateKeys {
|
||||||
|
if err := addKeyToAgent(v); err != nil {
|
||||||
|
t.Errorf("error adding key type %s, %v", k, err)
|
||||||
|
}
|
||||||
|
if err := addCertToAgentSock(v, nil); err != nil {
|
||||||
|
t.Errorf("error adding key type %s, %v", k, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addCertToAgentSock(key crypto.PrivateKey, cert *ssh.Certificate) error {
|
||||||
|
a, b, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
agentServer := NewKeyring()
|
||||||
|
go ServeAgent(agentServer, a)
|
||||||
|
|
||||||
|
agentClient := NewClient(b)
|
||||||
|
if err := agentClient.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil {
|
||||||
|
return fmt.Errorf("add: %v", err)
|
||||||
|
}
|
||||||
|
return verifyKey(agentClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addCertToAgent(key crypto.PrivateKey, cert *ssh.Certificate) error {
|
||||||
|
sshAgent := NewKeyring()
|
||||||
|
if err := sshAgent.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil {
|
||||||
|
return fmt.Errorf("add: %v", err)
|
||||||
|
}
|
||||||
|
return verifyKey(sshAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCertTypes(t *testing.T) {
|
||||||
|
for keyType, key := range testPublicKeys {
|
||||||
|
cert := &ssh.Certificate{
|
||||||
|
ValidPrincipals: []string{"gopher1"},
|
||||||
|
ValidAfter: 0,
|
||||||
|
ValidBefore: ssh.CertTimeInfinity,
|
||||||
|
Key: key,
|
||||||
|
Serial: 1,
|
||||||
|
CertType: ssh.UserCert,
|
||||||
|
SignatureKey: testPublicKeys["rsa"],
|
||||||
|
Permissions: ssh.Permissions{
|
||||||
|
CriticalOptions: map[string]string{},
|
||||||
|
Extensions: map[string]string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := cert.SignCert(rand.Reader, testSigners["rsa"]); err != nil {
|
||||||
|
t.Fatalf("signcert: %v", err)
|
||||||
|
}
|
||||||
|
if err := addCertToAgent(testPrivateKeys[keyType], cert); err != nil {
|
||||||
|
t.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
if err := addCertToAgentSock(testPrivateKeys[keyType], cert); err != nil {
|
||||||
|
t.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseConstraints(t *testing.T) {
|
||||||
|
// Test LifetimeSecs
|
||||||
|
var msg = constrainLifetimeAgentMsg{pseudorand.Uint32()}
|
||||||
|
lifetimeSecs, _, _, err := parseConstraints(ssh.Marshal(msg))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseConstraints: %v", err)
|
||||||
|
}
|
||||||
|
if lifetimeSecs != msg.LifetimeSecs {
|
||||||
|
t.Errorf("got lifetime %v, want %v", lifetimeSecs, msg.LifetimeSecs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test ConfirmBeforeUse
|
||||||
|
_, confirmBeforeUse, _, err := parseConstraints([]byte{agentConstrainConfirm})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
if !confirmBeforeUse {
|
||||||
|
t.Error("got comfirmBeforeUse == false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test ConstraintExtensions
|
||||||
|
var data []byte
|
||||||
|
var expect []ConstraintExtension
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
var ext = ConstraintExtension{
|
||||||
|
ExtensionName: fmt.Sprintf("name%d", i),
|
||||||
|
ExtensionDetails: []byte(fmt.Sprintf("details: %d", i)),
|
||||||
|
}
|
||||||
|
expect = append(expect, ext)
|
||||||
|
data = append(data, agentConstrainExtension)
|
||||||
|
data = append(data, ssh.Marshal(ext)...)
|
||||||
|
}
|
||||||
|
_, _, extensions, err := parseConstraints(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expect, extensions) {
|
||||||
|
t.Errorf("got extension %v, want %v", extensions, expect)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Unknown Constraint
|
||||||
|
_, _, _, err = parseConstraints([]byte{128})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "unknown constraint") {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places:
|
||||||
|
// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
|
||||||
|
// instances.
|
||||||
|
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testPrivateKeys map[string]interface{}
|
||||||
|
testSigners map[string]ssh.Signer
|
||||||
|
testPublicKeys map[string]ssh.PublicKey
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
n := len(testdata.PEMBytes)
|
||||||
|
testPrivateKeys = make(map[string]interface{}, n)
|
||||||
|
testSigners = make(map[string]ssh.Signer, n)
|
||||||
|
testPublicKeys = make(map[string]ssh.PublicKey, n)
|
||||||
|
for t, k := range testdata.PEMBytes {
|
||||||
|
testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
|
||||||
|
}
|
||||||
|
testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t])
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
|
||||||
|
}
|
||||||
|
testPublicKeys[t] = testSigners[t].PublicKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a cert and sign it for use in tests.
|
||||||
|
testCert := &ssh.Certificate{
|
||||||
|
Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||||
|
ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
|
||||||
|
ValidAfter: 0, // unix epoch
|
||||||
|
ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time.
|
||||||
|
Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||||
|
Key: testPublicKeys["ecdsa"],
|
||||||
|
SignatureKey: testPublicKeys["rsa"],
|
||||||
|
Permissions: ssh.Permissions{
|
||||||
|
CriticalOptions: map[string]string{},
|
||||||
|
Extensions: map[string]string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testCert.SignCert(rand.Reader, testSigners["rsa"])
|
||||||
|
testPrivateKeys["cert"] = testPrivateKeys["ecdsa"]
|
||||||
|
testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"])
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Unable to create certificate signer: %v", err))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
// 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 ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type server struct {
|
||||||
|
*ServerConn
|
||||||
|
chans <-chan NewChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func newServer(c net.Conn, conf *ServerConfig) (*server, error) {
|
||||||
|
sconn, chans, reqs, err := NewServerConn(c, conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
go DiscardRequests(reqs)
|
||||||
|
return &server{sconn, chans}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) Accept() (NewChannel, error) {
|
||||||
|
n, ok := <-s.chans
|
||||||
|
if !ok {
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sshPipe() (Conn, *server, error) {
|
||||||
|
c1, c2, err := netPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientConf := ClientConfig{
|
||||||
|
User: "user",
|
||||||
|
}
|
||||||
|
serverConf := ServerConfig{
|
||||||
|
NoClientAuth: true,
|
||||||
|
}
|
||||||
|
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||||
|
done := make(chan *server, 1)
|
||||||
|
go func() {
|
||||||
|
server, err := newServer(c2, &serverConf)
|
||||||
|
if err != nil {
|
||||||
|
done <- nil
|
||||||
|
}
|
||||||
|
done <- server
|
||||||
|
}()
|
||||||
|
|
||||||
|
client, _, reqs, err := NewClientConn(c1, "", &clientConf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
server := <-done
|
||||||
|
if server == nil {
|
||||||
|
return nil, nil, errors.New("server handshake failed.")
|
||||||
|
}
|
||||||
|
go DiscardRequests(reqs)
|
||||||
|
|
||||||
|
return client, server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEndToEnd(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
|
||||||
|
client, server, err := sshPipe()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("sshPipe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer client.Close()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
size := (1 << 20)
|
||||||
|
input := make([]byte, size)
|
||||||
|
output := make([]byte, size)
|
||||||
|
b.SetBytes(int64(size))
|
||||||
|
done := make(chan int, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
newCh, err := server.Accept()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("Client: %v", err)
|
||||||
|
}
|
||||||
|
ch, incoming, err := newCh.Accept()
|
||||||
|
go DiscardRequests(incoming)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if _, err := io.ReadFull(ch, output); err != nil {
|
||||||
|
b.Fatalf("ReadFull: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch.Close()
|
||||||
|
done <- 1
|
||||||
|
}()
|
||||||
|
|
||||||
|
ch, in, err := client.OpenChannel("speed", nil)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("OpenChannel: %v", err)
|
||||||
|
}
|
||||||
|
go DiscardRequests(in)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if _, err := ch.Write(input); err != nil {
|
||||||
|
b.Fatalf("WriteFull: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch.Close()
|
||||||
|
b.StopTimer()
|
||||||
|
|
||||||
|
<-done
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
// Copyright 2012 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 ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// buffer provides a linked list buffer for data exchange
|
||||||
|
// between producer and consumer. Theoretically the buffer is
|
||||||
|
// of unlimited capacity as it does no allocation of its own.
|
||||||
|
type buffer struct {
|
||||||
|
// protects concurrent access to head, tail and closed
|
||||||
|
*sync.Cond
|
||||||
|
|
||||||
|
head *element // the buffer that will be read first
|
||||||
|
tail *element // the buffer that will be read last
|
||||||
|
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// An element represents a single link in a linked list.
|
||||||
|
type element struct {
|
||||||
|
buf []byte
|
||||||
|
next *element
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBuffer returns an empty buffer that is not closed.
|
||||||
|
func newBuffer() *buffer {
|
||||||
|
e := new(element)
|
||||||
|
b := &buffer{
|
||||||
|
Cond: newCond(),
|
||||||
|
head: e,
|
||||||
|
tail: e,
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// write makes buf available for Read to receive.
|
||||||
|
// buf must not be modified after the call to write.
|
||||||
|
func (b *buffer) write(buf []byte) {
|
||||||
|
b.Cond.L.Lock()
|
||||||
|
e := &element{buf: buf}
|
||||||
|
b.tail.next = e
|
||||||
|
b.tail = e
|
||||||
|
b.Cond.Signal()
|
||||||
|
b.Cond.L.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// eof closes the buffer. Reads from the buffer once all
|
||||||
|
// the data has been consumed will receive io.EOF.
|
||||||
|
func (b *buffer) eof() {
|
||||||
|
b.Cond.L.Lock()
|
||||||
|
b.closed = true
|
||||||
|
b.Cond.Signal()
|
||||||
|
b.Cond.L.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads data from the internal buffer in buf. Reads will block
|
||||||
|
// if no data is available, or until the buffer is closed.
|
||||||
|
func (b *buffer) Read(buf []byte) (n int, err error) {
|
||||||
|
b.Cond.L.Lock()
|
||||||
|
defer b.Cond.L.Unlock()
|
||||||
|
|
||||||
|
for len(buf) > 0 {
|
||||||
|
// if there is data in b.head, copy it
|
||||||
|
if len(b.head.buf) > 0 {
|
||||||
|
r := copy(buf, b.head.buf)
|
||||||
|
buf, b.head.buf = buf[r:], b.head.buf[r:]
|
||||||
|
n += r
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if there is a next buffer, make it the head
|
||||||
|
if len(b.head.buf) == 0 && b.head != b.tail {
|
||||||
|
b.head = b.head.next
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if at least one byte has been copied, return
|
||||||
|
if n > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// if nothing was read, and there is nothing outstanding
|
||||||
|
// check to see if the buffer is closed.
|
||||||
|
if b.closed {
|
||||||
|
err = io.EOF
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// out of buffers, wait for producer
|
||||||
|
b.Cond.Wait()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var alphabet = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||||
|
|
||||||
|
func TestBufferReadwrite(t *testing.T) {
|
||||||
|
b := newBuffer()
|
||||||
|
b.write(alphabet[:10])
|
||||||
|
r, _ := b.Read(make([]byte, 10))
|
||||||
|
if r != 10 {
|
||||||
|
t.Fatalf("Expected written == read == 10, written: 10, read %d", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
b = newBuffer()
|
||||||
|
b.write(alphabet[:5])
|
||||||
|
r, _ = b.Read(make([]byte, 10))
|
||||||
|
if r != 5 {
|
||||||
|
t.Fatalf("Expected written == read == 5, written: 5, read %d", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
b = newBuffer()
|
||||||
|
b.write(alphabet[:10])
|
||||||
|
r, _ = b.Read(make([]byte, 5))
|
||||||
|
if r != 5 {
|
||||||
|
t.Fatalf("Expected written == 10, read == 5, written: 10, read %d", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
b = newBuffer()
|
||||||
|
b.write(alphabet[:5])
|
||||||
|
b.write(alphabet[5:15])
|
||||||
|
r, _ = b.Read(make([]byte, 10))
|
||||||
|
r2, _ := b.Read(make([]byte, 10))
|
||||||
|
if r != 10 || r2 != 5 || 15 != r+r2 {
|
||||||
|
t.Fatal("Expected written == read == 15")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferClose(t *testing.T) {
|
||||||
|
b := newBuffer()
|
||||||
|
b.write(alphabet[:10])
|
||||||
|
b.eof()
|
||||||
|
_, err := b.Read(make([]byte, 5))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("expected read of 5 to not return EOF")
|
||||||
|
}
|
||||||
|
b = newBuffer()
|
||||||
|
b.write(alphabet[:10])
|
||||||
|
b.eof()
|
||||||
|
r, err := b.Read(make([]byte, 5))
|
||||||
|
r2, err2 := b.Read(make([]byte, 10))
|
||||||
|
if r != 5 || r2 != 5 || err != nil || err2 != nil {
|
||||||
|
t.Fatal("expected reads of 5 and 5")
|
||||||
|
}
|
||||||
|
|
||||||
|
b = newBuffer()
|
||||||
|
b.write(alphabet[:10])
|
||||||
|
b.eof()
|
||||||
|
r, err = b.Read(make([]byte, 5))
|
||||||
|
r2, err2 = b.Read(make([]byte, 10))
|
||||||
|
r3, err3 := b.Read(make([]byte, 10))
|
||||||
|
if r != 5 || r2 != 5 || r3 != 0 || err != nil || err2 != nil || err3 != io.EOF {
|
||||||
|
t.Fatal("expected reads of 5 and 5 and 0, with EOF")
|
||||||
|
}
|
||||||
|
|
||||||
|
b = newBuffer()
|
||||||
|
b.write(make([]byte, 5))
|
||||||
|
b.write(make([]byte, 10))
|
||||||
|
b.eof()
|
||||||
|
r, err = b.Read(make([]byte, 9))
|
||||||
|
r2, err2 = b.Read(make([]byte, 3))
|
||||||
|
r3, err3 = b.Read(make([]byte, 3))
|
||||||
|
r4, err4 := b.Read(make([]byte, 10))
|
||||||
|
if err != nil || err2 != nil || err3 != nil || err4 != io.EOF {
|
||||||
|
t.Fatalf("Expected EOF on forth read only, err=%v, err2=%v, err3=%v, err4=%v", err, err2, err3, err4)
|
||||||
|
}
|
||||||
|
if r != 9 || r2 != 3 || r3 != 3 || r4 != 0 {
|
||||||
|
t.Fatal("Expected written == read == 15", r, r2, r3, r4)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,519 @@
|
||||||
|
// Copyright 2012 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 ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These constants from [PROTOCOL.certkeys] represent the algorithm names
|
||||||
|
// for certificate types supported by this package.
|
||||||
|
const (
|
||||||
|
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
|
||||||
|
CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
|
||||||
|
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
|
||||||
|
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
|
||||||
|
CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
|
||||||
|
CertAlgoED25519v01 = "ssh-ed25519-cert-v01@openssh.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Certificate types distinguish between host and user
|
||||||
|
// certificates. The values can be set in the CertType field of
|
||||||
|
// Certificate.
|
||||||
|
const (
|
||||||
|
UserCert = 1
|
||||||
|
HostCert = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Signature represents a cryptographic signature.
|
||||||
|
type Signature struct {
|
||||||
|
Format string
|
||||||
|
Blob []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertTimeInfinity can be used for OpenSSHCertV01.ValidBefore to indicate that
|
||||||
|
// a certificate does not expire.
|
||||||
|
const CertTimeInfinity = 1<<64 - 1
|
||||||
|
|
||||||
|
// An Certificate represents an OpenSSH certificate as defined in
|
||||||
|
// [PROTOCOL.certkeys]?rev=1.8.
|
||||||
|
type Certificate struct {
|
||||||
|
Nonce []byte
|
||||||
|
Key PublicKey
|
||||||
|
Serial uint64
|
||||||
|
CertType uint32
|
||||||
|
KeyId string
|
||||||
|
ValidPrincipals []string
|
||||||
|
ValidAfter uint64
|
||||||
|
ValidBefore uint64
|
||||||
|
Permissions
|
||||||
|
Reserved []byte
|
||||||
|
SignatureKey PublicKey
|
||||||
|
Signature *Signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// genericCertData holds the key-independent part of the certificate data.
|
||||||
|
// Overall, certificates contain an nonce, public key fields and
|
||||||
|
// key-independent fields.
|
||||||
|
type genericCertData struct {
|
||||||
|
Serial uint64
|
||||||
|
CertType uint32
|
||||||
|
KeyId string
|
||||||
|
ValidPrincipals []byte
|
||||||
|
ValidAfter uint64
|
||||||
|
ValidBefore uint64
|
||||||
|
CriticalOptions []byte
|
||||||
|
Extensions []byte
|
||||||
|
Reserved []byte
|
||||||
|
SignatureKey []byte
|
||||||
|
Signature []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalStringList(namelist []string) []byte {
|
||||||
|
var to []byte
|
||||||
|
for _, name := range namelist {
|
||||||
|
s := struct{ N string }{name}
|
||||||
|
to = append(to, Marshal(&s)...)
|
||||||
|
}
|
||||||
|
return to
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionsTuple struct {
|
||||||
|
Key string
|
||||||
|
Value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionsTupleValue struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize a map of critical options or extensions
|
||||||
|
// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation,
|
||||||
|
// we need two length prefixes for a non-empty string value
|
||||||
|
func marshalTuples(tups map[string]string) []byte {
|
||||||
|
keys := make([]string, 0, len(tups))
|
||||||
|
for key := range tups {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
var ret []byte
|
||||||
|
for _, key := range keys {
|
||||||
|
s := optionsTuple{Key: key}
|
||||||
|
if value := tups[key]; len(value) > 0 {
|
||||||
|
s.Value = Marshal(&optionsTupleValue{value})
|
||||||
|
}
|
||||||
|
ret = append(ret, Marshal(&s)...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation,
|
||||||
|
// we need two length prefixes for a non-empty option value
|
||||||
|
func parseTuples(in []byte) (map[string]string, error) {
|
||||||
|
tups := map[string]string{}
|
||||||
|
var lastKey string
|
||||||
|
var haveLastKey bool
|
||||||
|
|
||||||
|
for len(in) > 0 {
|
||||||
|
var key, val, extra []byte
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if key, in, ok = parseString(in); !ok {
|
||||||
|
return nil, errShortRead
|
||||||
|
}
|
||||||
|
keyStr := string(key)
|
||||||
|
// according to [PROTOCOL.certkeys], the names must be in
|
||||||
|
// lexical order.
|
||||||
|
if haveLastKey && keyStr <= lastKey {
|
||||||
|
return nil, fmt.Errorf("ssh: certificate options are not in lexical order")
|
||||||
|
}
|
||||||
|
lastKey, haveLastKey = keyStr, true
|
||||||
|
// the next field is a data field, which if non-empty has a string embedded
|
||||||
|
if val, in, ok = parseString(in); !ok {
|
||||||
|
return nil, errShortRead
|
||||||
|
}
|
||||||
|
if len(val) > 0 {
|
||||||
|
val, extra, ok = parseString(val)
|
||||||
|
if !ok {
|
||||||
|
return nil, errShortRead
|
||||||
|
}
|
||||||
|
if len(extra) > 0 {
|
||||||
|
return nil, fmt.Errorf("ssh: unexpected trailing data after certificate option value")
|
||||||
|
}
|
||||||
|
tups[keyStr] = string(val)
|
||||||
|
} else {
|
||||||
|
tups[keyStr] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCert(in []byte, privAlgo string) (*Certificate, error) {
|
||||||
|
nonce, rest, ok := parseString(in)
|
||||||
|
if !ok {
|
||||||
|
return nil, errShortRead
|
||||||
|
}
|
||||||
|
|
||||||
|
key, rest, err := parsePubKey(rest, privAlgo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var g genericCertData
|
||||||
|
if err := Unmarshal(rest, &g); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Certificate{
|
||||||
|
Nonce: nonce,
|
||||||
|
Key: key,
|
||||||
|
Serial: g.Serial,
|
||||||
|
CertType: g.CertType,
|
||||||
|
KeyId: g.KeyId,
|
||||||
|
ValidAfter: g.ValidAfter,
|
||||||
|
ValidBefore: g.ValidBefore,
|
||||||
|
}
|
||||||
|
|
||||||
|
for principals := g.ValidPrincipals; len(principals) > 0; {
|
||||||
|
principal, rest, ok := parseString(principals)
|
||||||
|
if !ok {
|
||||||
|
return nil, errShortRead
|
||||||
|
}
|
||||||
|
c.ValidPrincipals = append(c.ValidPrincipals, string(principal))
|
||||||
|
principals = rest
|
||||||
|
}
|
||||||
|
|
||||||
|
c.CriticalOptions, err = parseTuples(g.CriticalOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Extensions, err = parseTuples(g.Extensions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Reserved = g.Reserved
|
||||||
|
k, err := ParsePublicKey(g.SignatureKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SignatureKey = k
|
||||||
|
c.Signature, rest, ok = parseSignatureBody(g.Signature)
|
||||||
|
if !ok || len(rest) > 0 {
|
||||||
|
return nil, errors.New("ssh: signature parse error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type openSSHCertSigner struct {
|
||||||
|
pub *Certificate
|
||||||
|
signer Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCertSigner returns a Signer that signs with the given Certificate, whose
|
||||||
|
// private key is held by signer. It returns an error if the public key in cert
|
||||||
|
// doesn't match the key used by signer.
|
||||||
|
func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) {
|
||||||
|
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
|
||||||
|
return nil, errors.New("ssh: signer and cert have different public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &openSSHCertSigner{cert, signer}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *openSSHCertSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||||
|
return s.signer.Sign(rand, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *openSSHCertSigner) PublicKey() PublicKey {
|
||||||
|
return s.pub
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceAddressCriticalOption = "source-address"
|
||||||
|
|
||||||
|
// CertChecker does the work of verifying a certificate. Its methods
|
||||||
|
// can be plugged into ClientConfig.HostKeyCallback and
|
||||||
|
// ServerConfig.PublicKeyCallback. For the CertChecker to work,
|
||||||
|
// minimally, the IsAuthority callback should be set.
|
||||||
|
type CertChecker struct {
|
||||||
|
// SupportedCriticalOptions lists the CriticalOptions that the
|
||||||
|
// server application layer understands. These are only used
|
||||||
|
// for user certificates.
|
||||||
|
SupportedCriticalOptions []string
|
||||||
|
|
||||||
|
// IsUserAuthority should return true if the key is recognized as an
|
||||||
|
// authority for the given user certificate. This allows for
|
||||||
|
// certificates to be signed by other certificates. This must be set
|
||||||
|
// if this CertChecker will be checking user certificates.
|
||||||
|
IsUserAuthority func(auth PublicKey) bool
|
||||||
|
|
||||||
|
// IsHostAuthority should report whether the key is recognized as
|
||||||
|
// an authority for this host. This allows for certificates to be
|
||||||
|
// signed by other keys, and for those other keys to only be valid
|
||||||
|
// signers for particular hostnames. This must be set if this
|
||||||
|
// CertChecker will be checking host certificates.
|
||||||
|
IsHostAuthority func(auth PublicKey, address string) bool
|
||||||
|
|
||||||
|
// Clock is used for verifying time stamps. If nil, time.Now
|
||||||
|
// is used.
|
||||||
|
Clock func() time.Time
|
||||||
|
|
||||||
|
// UserKeyFallback is called when CertChecker.Authenticate encounters a
|
||||||
|
// public key that is not a certificate. It must implement validation
|
||||||
|
// of user keys or else, if nil, all such keys are rejected.
|
||||||
|
UserKeyFallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
||||||
|
|
||||||
|
// HostKeyFallback is called when CertChecker.CheckHostKey encounters a
|
||||||
|
// public key that is not a certificate. It must implement host key
|
||||||
|
// validation or else, if nil, all such keys are rejected.
|
||||||
|
HostKeyFallback HostKeyCallback
|
||||||
|
|
||||||
|
// IsRevoked is called for each certificate so that revocation checking
|
||||||
|
// can be implemented. It should return true if the given certificate
|
||||||
|
// is revoked and false otherwise. If nil, no certificates are
|
||||||
|
// considered to have been revoked.
|
||||||
|
IsRevoked func(cert *Certificate) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckHostKey checks a host key certificate. This method can be
|
||||||
|
// plugged into ClientConfig.HostKeyCallback.
|
||||||
|
func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey) error {
|
||||||
|
cert, ok := key.(*Certificate)
|
||||||
|
if !ok {
|
||||||
|
if c.HostKeyFallback != nil {
|
||||||
|
return c.HostKeyFallback(addr, remote, key)
|
||||||
|
}
|
||||||
|
return errors.New("ssh: non-certificate host key")
|
||||||
|
}
|
||||||
|
if cert.CertType != HostCert {
|
||||||
|
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
||||||
|
}
|
||||||
|
if !c.IsHostAuthority(cert.SignatureKey, addr) {
|
||||||
|
return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass hostname only as principal for host certificates (consistent with OpenSSH)
|
||||||
|
return c.CheckCert(hostname, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate checks a user certificate. Authenticate can be used as
|
||||||
|
// a value for ServerConfig.PublicKeyCallback.
|
||||||
|
func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) {
|
||||||
|
cert, ok := pubKey.(*Certificate)
|
||||||
|
if !ok {
|
||||||
|
if c.UserKeyFallback != nil {
|
||||||
|
return c.UserKeyFallback(conn, pubKey)
|
||||||
|
}
|
||||||
|
return nil, errors.New("ssh: normal key pairs not accepted")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert.CertType != UserCert {
|
||||||
|
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
||||||
|
}
|
||||||
|
if !c.IsUserAuthority(cert.SignatureKey) {
|
||||||
|
return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.CheckCert(conn.User(), cert); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cert.Permissions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and
|
||||||
|
// the signature of the certificate.
|
||||||
|
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
||||||
|
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
||||||
|
return fmt.Errorf("ssh: certicate serial %d revoked", cert.Serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
for opt, _ := range cert.CriticalOptions {
|
||||||
|
// sourceAddressCriticalOption will be enforced by
|
||||||
|
// serverAuthenticate
|
||||||
|
if opt == sourceAddressCriticalOption {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, supp := range c.SupportedCriticalOptions {
|
||||||
|
if supp == opt {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.ValidPrincipals) > 0 {
|
||||||
|
// By default, certs are valid for all users/hosts.
|
||||||
|
found := false
|
||||||
|
for _, p := range cert.ValidPrincipals {
|
||||||
|
if p == principal {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("ssh: principal %q not in the set of valid principals for given certificate: %q", principal, cert.ValidPrincipals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clock := c.Clock
|
||||||
|
if clock == nil {
|
||||||
|
clock = time.Now
|
||||||
|
}
|
||||||
|
|
||||||
|
unixNow := clock().Unix()
|
||||||
|
if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) {
|
||||||
|
return fmt.Errorf("ssh: cert is not yet valid")
|
||||||
|
}
|
||||||
|
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
||||||
|
return fmt.Errorf("ssh: cert has expired")
|
||||||
|
}
|
||||||
|
if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
||||||
|
return fmt.Errorf("ssh: certificate signature does not verify")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignCert sets c.SignatureKey to the authority's public key and stores a
|
||||||
|
// Signature, by authority, in the certificate.
|
||||||
|
func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
|
||||||
|
c.Nonce = make([]byte, 32)
|
||||||
|
if _, err := io.ReadFull(rand, c.Nonce); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.SignatureKey = authority.PublicKey()
|
||||||
|
|
||||||
|
sig, err := authority.Sign(rand, c.bytesForSigning())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Signature = sig
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var certAlgoNames = map[string]string{
|
||||||
|
KeyAlgoRSA: CertAlgoRSAv01,
|
||||||
|
KeyAlgoDSA: CertAlgoDSAv01,
|
||||||
|
KeyAlgoECDSA256: CertAlgoECDSA256v01,
|
||||||
|
KeyAlgoECDSA384: CertAlgoECDSA384v01,
|
||||||
|
KeyAlgoECDSA521: CertAlgoECDSA521v01,
|
||||||
|
KeyAlgoED25519: CertAlgoED25519v01,
|
||||||
|
}
|
||||||
|
|
||||||
|
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
|
||||||
|
// Panics if a non-certificate algorithm is passed.
|
||||||
|
func certToPrivAlgo(algo string) string {
|
||||||
|
for privAlgo, pubAlgo := range certAlgoNames {
|
||||||
|
if pubAlgo == algo {
|
||||||
|
return privAlgo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("unknown cert algorithm")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cert *Certificate) bytesForSigning() []byte {
|
||||||
|
c2 := *cert
|
||||||
|
c2.Signature = nil
|
||||||
|
out := c2.Marshal()
|
||||||
|
// Drop trailing signature length.
|
||||||
|
return out[:len(out)-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal serializes c into OpenSSH's wire format. It is part of the
|
||||||
|
// PublicKey interface.
|
||||||
|
func (c *Certificate) Marshal() []byte {
|
||||||
|
generic := genericCertData{
|
||||||
|
Serial: c.Serial,
|
||||||
|
CertType: c.CertType,
|
||||||
|
KeyId: c.KeyId,
|
||||||
|
ValidPrincipals: marshalStringList(c.ValidPrincipals),
|
||||||
|
ValidAfter: uint64(c.ValidAfter),
|
||||||
|
ValidBefore: uint64(c.ValidBefore),
|
||||||
|
CriticalOptions: marshalTuples(c.CriticalOptions),
|
||||||
|
Extensions: marshalTuples(c.Extensions),
|
||||||
|
Reserved: c.Reserved,
|
||||||
|
SignatureKey: c.SignatureKey.Marshal(),
|
||||||
|
}
|
||||||
|
if c.Signature != nil {
|
||||||
|
generic.Signature = Marshal(c.Signature)
|
||||||
|
}
|
||||||
|
genericBytes := Marshal(&generic)
|
||||||
|
keyBytes := c.Key.Marshal()
|
||||||
|
_, keyBytes, _ = parseString(keyBytes)
|
||||||
|
prefix := Marshal(&struct {
|
||||||
|
Name string
|
||||||
|
Nonce []byte
|
||||||
|
Key []byte `ssh:"rest"`
|
||||||
|
}{c.Type(), c.Nonce, keyBytes})
|
||||||
|
|
||||||
|
result := make([]byte, 0, len(prefix)+len(genericBytes))
|
||||||
|
result = append(result, prefix...)
|
||||||
|
result = append(result, genericBytes...)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the key name. It is part of the PublicKey interface.
|
||||||
|
func (c *Certificate) Type() string {
|
||||||
|
algo, ok := certAlgoNames[c.Key.Type()]
|
||||||
|
if !ok {
|
||||||
|
panic("unknown cert key type " + c.Key.Type())
|
||||||
|
}
|
||||||
|
return algo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify verifies a signature against the certificate's public
|
||||||
|
// key. It is part of the PublicKey interface.
|
||||||
|
func (c *Certificate) Verify(data []byte, sig *Signature) error {
|
||||||
|
return c.Key.Verify(data, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) {
|
||||||
|
format, in, ok := parseString(in)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out = &Signature{
|
||||||
|
Format: string(format),
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Blob, in, ok = parseString(in); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, in, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSignature(in []byte) (out *Signature, rest []byte, ok bool) {
|
||||||
|
sigBytes, rest, ok := parseString(in)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out, trailing, ok := parseSignatureBody(sigBytes)
|
||||||
|
if !ok || len(trailing) > 0 {
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue