Use Godep to vendor distribution dependencies
As we get closer to release, we need to ensure that builds are repeatable. Godep provides a workable solution to managing dependencies in Go to support this requirement. This commit should be bolstered by updates to documentation and build configuration. Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
parent
e9e26bd362
commit
fc2a840e8f
95
Godeps/Godeps.json
generated
Normal file
95
Godeps/Godeps.json
generated
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/distribution",
|
||||||
|
"GoVersion": "go1.4",
|
||||||
|
"Packages": [
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "code.google.com/p/go-uuid/uuid",
|
||||||
|
"Comment": "null-12",
|
||||||
|
"Rev": "7dda39b2e7d5e265014674c5af696ba4186679e9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Sirupsen/logrus",
|
||||||
|
"Comment": "v0.6.1-8-gcc09837",
|
||||||
|
"Rev": "cc09837bcd512ffe6bb2e3f635bed138c4cd6bc8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/bugsnag/bugsnag-go",
|
||||||
|
"Comment": "v1.0.2-5-gb1d1530",
|
||||||
|
"Rev": "b1d153021fcd90ca3f080db36bec96dc690fb274"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/bugsnag/osext",
|
||||||
|
"Rev": "0dd3f918b21bec95ace9dc86c7e70266cfc5c702"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/bugsnag/panicwrap",
|
||||||
|
"Rev": "e5f9854865b9778a45169fc249e99e338d4d6f27"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/crowdmob/goamz/aws",
|
||||||
|
"Rev": "cd22d9897beff6f3de22cec4bdb7d46b9e2dee67"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/crowdmob/goamz/cloudfront",
|
||||||
|
"Rev": "cd22d9897beff6f3de22cec4bdb7d46b9e2dee67"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/crowdmob/goamz/s3",
|
||||||
|
"Rev": "cd22d9897beff6f3de22cec4bdb7d46b9e2dee67"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/pkg/tarsum",
|
||||||
|
"Comment": "v1.4.1-330-g3fbf723",
|
||||||
|
"Rev": "3fbf723e81fa2696daa95847ccdcacddba6484da"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar",
|
||||||
|
"Comment": "v1.4.1-330-g3fbf723",
|
||||||
|
"Rev": "3fbf723e81fa2696daa95847ccdcacddba6484da"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/libtrust",
|
||||||
|
"Rev": "a9625ce37e2dc5fed2e51eec2d39c39e4ac4c1df"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/context",
|
||||||
|
"Rev": "14f550f51af52180c2eefed15e5fd18d63c0a64a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/handlers",
|
||||||
|
"Rev": "0e84b7d810c16aed432217e330206be156bafae0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/mux",
|
||||||
|
"Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/yvasiyarov/go-metrics",
|
||||||
|
"Rev": "57bccd1ccd43f94bb17fdd8bf3007059b802f85e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/yvasiyarov/gorelic",
|
||||||
|
"Comment": "v0.0.6-8-ga9bba5b",
|
||||||
|
"Rev": "a9bba5b9ab508a086f9a12b8c51fab68478e2128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/yvasiyarov/newrelic_platform_go",
|
||||||
|
"Rev": "b21fdbd4370f3717f3bbd2bf41c223bc273068e6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/BrianBland/yaml.v2",
|
||||||
|
"Rev": "3e92d6a11b92fa4612d66712704844bdc0c48aed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/check.v1",
|
||||||
|
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/yaml.v2",
|
||||||
|
"Rev": "d466437aa4adc35830964cffc5b5f262c63ddcb4"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/pkg
|
||||||
|
/bin
|
27
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/LICENSE
generated
vendored
Normal file
27
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
84
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/dce.go
generated
vendored
Normal file
84
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/dce.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Domain represents a Version 2 domain
|
||||||
|
type Domain byte
|
||||||
|
|
||||||
|
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||||
|
const (
|
||||||
|
Person = Domain(0)
|
||||||
|
Group = Domain(1)
|
||||||
|
Org = Domain(2)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||||
|
//
|
||||||
|
// The domain should be one of Person, Group or Org.
|
||||||
|
// On a POSIX system the id should be the users UID for the Person
|
||||||
|
// domain and the users GID for the Group. The meaning of id for
|
||||||
|
// the domain Org or on non-POSIX systems is site defined.
|
||||||
|
//
|
||||||
|
// For a given domain/id pair the same token may be returned for up to
|
||||||
|
// 7 minutes and 10 seconds.
|
||||||
|
func NewDCESecurity(domain Domain, id uint32) UUID {
|
||||||
|
uuid := NewUUID()
|
||||||
|
if uuid != nil {
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||||
|
uuid[9] = byte(domain)
|
||||||
|
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||||
|
}
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||||
|
// domain with the id returned by os.Getuid.
|
||||||
|
//
|
||||||
|
// NewDCEPerson(Person, uint32(os.Getuid()))
|
||||||
|
func NewDCEPerson() UUID {
|
||||||
|
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||||
|
// domain with the id returned by os.Getgid.
|
||||||
|
//
|
||||||
|
// NewDCEGroup(Group, uint32(os.Getgid()))
|
||||||
|
func NewDCEGroup() UUID {
|
||||||
|
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain returns the domain for a Version 2 UUID or false.
|
||||||
|
func (uuid UUID) Domain() (Domain, bool) {
|
||||||
|
if v, _ := uuid.Version(); v != 2 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return Domain(uuid[9]), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Id returns the id for a Version 2 UUID or false.
|
||||||
|
func (uuid UUID) Id() (uint32, bool) {
|
||||||
|
if v, _ := uuid.Version(); v != 2 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint32(uuid[0:4]), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Domain) String() string {
|
||||||
|
switch d {
|
||||||
|
case Person:
|
||||||
|
return "Person"
|
||||||
|
case Group:
|
||||||
|
return "Group"
|
||||||
|
case Org:
|
||||||
|
return "Org"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Domain%d", int(d))
|
||||||
|
}
|
8
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/doc.go
generated
vendored
Normal file
8
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/doc.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// The uuid package generates and inspects UUIDs.
|
||||||
|
//
|
||||||
|
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
|
||||||
|
package uuid
|
53
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/hash.go
generated
vendored
Normal file
53
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/hash.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Well known Name Space IDs and UUIDs
|
||||||
|
var (
|
||||||
|
NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NIL = Parse("00000000-0000-0000-0000-000000000000")
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHash returns a new UUID dervied from the hash of space concatenated with
|
||||||
|
// data generated by h. The hash should be at least 16 byte in length. The
|
||||||
|
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||||
|
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||||
|
// NewMD5 and NewSHA1.
|
||||||
|
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||||
|
h.Reset()
|
||||||
|
h.Write(space)
|
||||||
|
h.Write([]byte(data))
|
||||||
|
s := h.Sum(nil)
|
||||||
|
uuid := make([]byte, 16)
|
||||||
|
copy(uuid, s)
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||||
|
// supplied name space and data.
|
||||||
|
//
|
||||||
|
// NewHash(md5.New(), space, data, 3)
|
||||||
|
func NewMD5(space UUID, data []byte) UUID {
|
||||||
|
return NewHash(md5.New(), space, data, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||||
|
// supplied name space and data.
|
||||||
|
//
|
||||||
|
// NewHash(sha1.New(), space, data, 5)
|
||||||
|
func NewSHA1(space UUID, data []byte) UUID {
|
||||||
|
return NewHash(sha1.New(), space, data, 5)
|
||||||
|
}
|
101
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/node.go
generated
vendored
Normal file
101
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/node.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
var (
|
||||||
|
interfaces []net.Interface // cached list of interfaces
|
||||||
|
ifname string // name of interface being used
|
||||||
|
nodeID []byte // hardware for version 1 UUIDs
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeInterface returns the name of the interface from which the NodeID was
|
||||||
|
// derived. The interface "user" is returned if the NodeID was set by
|
||||||
|
// SetNodeID.
|
||||||
|
func NodeInterface() string {
|
||||||
|
return ifname
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||||
|
// If name is "" then the first usable interface found will be used or a random
|
||||||
|
// Node ID will be generated. If a named interface cannot be found then false
|
||||||
|
// is returned.
|
||||||
|
//
|
||||||
|
// SetNodeInterface never fails when name is "".
|
||||||
|
func SetNodeInterface(name string) bool {
|
||||||
|
if interfaces == nil {
|
||||||
|
var err error
|
||||||
|
interfaces, err = net.Interfaces()
|
||||||
|
if err != nil && name != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ifs := range interfaces {
|
||||||
|
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||||
|
if setNodeID(ifs.HardwareAddr) {
|
||||||
|
ifname = ifs.Name
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found no interfaces with a valid hardware address. If name
|
||||||
|
// does not specify a specific interface generate a random Node ID
|
||||||
|
// (section 4.1.6)
|
||||||
|
if name == "" {
|
||||||
|
if nodeID == nil {
|
||||||
|
nodeID = make([]byte, 6)
|
||||||
|
}
|
||||||
|
randomBits(nodeID)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||||
|
// if not already set.
|
||||||
|
func NodeID() []byte {
|
||||||
|
if nodeID == nil {
|
||||||
|
SetNodeInterface("")
|
||||||
|
}
|
||||||
|
nid := make([]byte, 6)
|
||||||
|
copy(nid, nodeID)
|
||||||
|
return nid
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||||
|
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||||
|
// Node ID is not set.
|
||||||
|
func SetNodeID(id []byte) bool {
|
||||||
|
if setNodeID(id) {
|
||||||
|
ifname = "user"
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func setNodeID(id []byte) bool {
|
||||||
|
if len(id) < 6 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if nodeID == nil {
|
||||||
|
nodeID = make([]byte, 6)
|
||||||
|
}
|
||||||
|
copy(nodeID, id)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||||
|
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) NodeID() []byte {
|
||||||
|
if len(uuid) != 16 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
node := make([]byte, 6)
|
||||||
|
copy(node, uuid[10:])
|
||||||
|
return node
|
||||||
|
}
|
132
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/time.go
generated
vendored
Normal file
132
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/time.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||||
|
// 1582.
|
||||||
|
type Time int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||||
|
unix = 2440587 // Julian day of 1 Jan 1970
|
||||||
|
epoch = unix - lillian // Days between epochs
|
||||||
|
g1582 = epoch * 86400 // seconds between epochs
|
||||||
|
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
lasttime uint64 // last time we returned
|
||||||
|
clock_seq uint16 // clock sequence for this run
|
||||||
|
|
||||||
|
timeNow = time.Now // for testing
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||||
|
// epoch of 1 Jan 1970.
|
||||||
|
func (t Time) UnixTime() (sec, nsec int64) {
|
||||||
|
sec = int64(t - g1582ns100)
|
||||||
|
nsec = (sec % 10000000) * 100
|
||||||
|
sec /= 10000000
|
||||||
|
return sec, nsec
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||||
|
// adjusts the clock sequence as needed. An error is returned if the current
|
||||||
|
// time cannot be determined.
|
||||||
|
func GetTime() (Time, error) {
|
||||||
|
defer mu.Unlock()
|
||||||
|
mu.Lock()
|
||||||
|
return getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTime() (Time, error) {
|
||||||
|
t := timeNow()
|
||||||
|
|
||||||
|
// If we don't have a clock sequence already, set one.
|
||||||
|
if clock_seq == 0 {
|
||||||
|
setClockSequence(-1)
|
||||||
|
}
|
||||||
|
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||||
|
|
||||||
|
// If time has gone backwards with this clock sequence then we
|
||||||
|
// increment the clock sequence
|
||||||
|
if now <= lasttime {
|
||||||
|
clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
|
||||||
|
}
|
||||||
|
lasttime = now
|
||||||
|
return Time(now), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockSequence returns the current clock sequence, generating one if not
|
||||||
|
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||||
|
//
|
||||||
|
// The uuid package does not use global static storage for the clock sequence or
|
||||||
|
// the last time a UUID was generated. Unless SetClockSequence a new random
|
||||||
|
// clock sequence is generated the first time a clock sequence is requested by
|
||||||
|
// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
|
||||||
|
// for
|
||||||
|
func ClockSequence() int {
|
||||||
|
defer mu.Unlock()
|
||||||
|
mu.Lock()
|
||||||
|
return clockSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
func clockSequence() int {
|
||||||
|
if clock_seq == 0 {
|
||||||
|
setClockSequence(-1)
|
||||||
|
}
|
||||||
|
return int(clock_seq & 0x3fff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||||
|
// -1 causes a new sequence to be generated.
|
||||||
|
func SetClockSequence(seq int) {
|
||||||
|
defer mu.Unlock()
|
||||||
|
mu.Lock()
|
||||||
|
setClockSequence(seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setClockSequence(seq int) {
|
||||||
|
if seq == -1 {
|
||||||
|
var b [2]byte
|
||||||
|
randomBits(b[:]) // clock sequence
|
||||||
|
seq = int(b[0])<<8 | int(b[1])
|
||||||
|
}
|
||||||
|
old_seq := clock_seq
|
||||||
|
clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||||
|
if old_seq != clock_seq {
|
||||||
|
lasttime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||||
|
// uuid. It returns false if uuid is not valid. The time is only well defined
|
||||||
|
// for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) Time() (Time, bool) {
|
||||||
|
if len(uuid) != 16 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||||
|
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||||
|
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||||
|
return Time(time), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockSequence returns the clock sequence encoded in uuid. It returns false
|
||||||
|
// if uuid is not valid. The clock sequence is only well defined for version 1
|
||||||
|
// and 2 UUIDs.
|
||||||
|
func (uuid UUID) ClockSequence() (int, bool) {
|
||||||
|
if len(uuid) != 16 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
|
||||||
|
}
|
43
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/util.go
generated
vendored
Normal file
43
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/util.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// randomBits completely fills slice b with random data.
|
||||||
|
func randomBits(b []byte) {
|
||||||
|
if _, err := io.ReadFull(rander, b); err != nil {
|
||||||
|
panic(err.Error()) // rand should never fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||||
|
var xvalues = []byte{
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
// xtob converts the the first two hex bytes of x into a byte.
|
||||||
|
func xtob(x string) (byte, bool) {
|
||||||
|
b1 := xvalues[x[0]]
|
||||||
|
b2 := xvalues[x[1]]
|
||||||
|
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||||
|
}
|
163
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid.go
generated
vendored
Normal file
163
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid.go
generated
vendored
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||||
|
// 4122.
|
||||||
|
type UUID []byte
|
||||||
|
|
||||||
|
// A Version represents a UUIDs version.
|
||||||
|
type Version byte
|
||||||
|
|
||||||
|
// A Variant represents a UUIDs variant.
|
||||||
|
type Variant byte
|
||||||
|
|
||||||
|
// Constants returned by Variant.
|
||||||
|
const (
|
||||||
|
Invalid = Variant(iota) // Invalid UUID
|
||||||
|
RFC4122 // The variant specified in RFC4122
|
||||||
|
Reserved // Reserved, NCS backward compatibility.
|
||||||
|
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||||
|
Future // Reserved for future definition.
|
||||||
|
)
|
||||||
|
|
||||||
|
var rander = rand.Reader // random function
|
||||||
|
|
||||||
|
// New returns a new random (version 4) UUID as a string. It is a convenience
|
||||||
|
// function for NewRandom().String().
|
||||||
|
func New() string {
|
||||||
|
return NewRandom().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse decodes s into a UUID or returns nil. Both the UUID form of
|
||||||
|
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
|
||||||
|
func Parse(s string) UUID {
|
||||||
|
if len(s) == 36+9 {
|
||||||
|
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s = s[9:]
|
||||||
|
} else if len(s) != 36 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
uuid := make([]byte, 16)
|
||||||
|
for i, x := range []int{
|
||||||
|
0, 2, 4, 6,
|
||||||
|
9, 11,
|
||||||
|
14, 16,
|
||||||
|
19, 21,
|
||||||
|
24, 26, 28, 30, 32, 34} {
|
||||||
|
if v, ok := xtob(s[x:]); !ok {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
uuid[i] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if uuid1 and uuid2 are equal.
|
||||||
|
func Equal(uuid1, uuid2 UUID) bool {
|
||||||
|
return bytes.Equal(uuid1, uuid2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
// , or "" if uuid is invalid.
|
||||||
|
func (uuid UUID) String() string {
|
||||||
|
if uuid == nil || len(uuid) != 16 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b := []byte(uuid)
|
||||||
|
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
|
||||||
|
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// URN returns the RFC 2141 URN form of uuid,
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||||
|
func (uuid UUID) URN() string {
|
||||||
|
if uuid == nil || len(uuid) != 16 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b := []byte(uuid)
|
||||||
|
return fmt.Sprintf("urn:uuid:%08x-%04x-%04x-%04x-%012x",
|
||||||
|
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant returns the variant encoded in uuid. It returns Invalid if
|
||||||
|
// uuid is invalid.
|
||||||
|
func (uuid UUID) Variant() Variant {
|
||||||
|
if len(uuid) != 16 {
|
||||||
|
return Invalid
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case (uuid[8] & 0xc0) == 0x80:
|
||||||
|
return RFC4122
|
||||||
|
case (uuid[8] & 0xe0) == 0xc0:
|
||||||
|
return Microsoft
|
||||||
|
case (uuid[8] & 0xe0) == 0xe0:
|
||||||
|
return Future
|
||||||
|
default:
|
||||||
|
return Reserved
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the verison of uuid. It returns false if uuid is not
|
||||||
|
// valid.
|
||||||
|
func (uuid UUID) Version() (Version, bool) {
|
||||||
|
if len(uuid) != 16 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return Version(uuid[6] >> 4), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
if v > 15 {
|
||||||
|
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("VERSION_%d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Variant) String() string {
|
||||||
|
switch v {
|
||||||
|
case RFC4122:
|
||||||
|
return "RFC4122"
|
||||||
|
case Reserved:
|
||||||
|
return "Reserved"
|
||||||
|
case Microsoft:
|
||||||
|
return "Microsoft"
|
||||||
|
case Future:
|
||||||
|
return "Future"
|
||||||
|
case Invalid:
|
||||||
|
return "Invalid"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("BadVariant%d", int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRand sets the random number generator to r, which implents io.Reader.
|
||||||
|
// If r.Read returns an error when the package requests random data then
|
||||||
|
// a panic will be issued.
|
||||||
|
//
|
||||||
|
// Calling SetRand with nil sets the random number generator to the default
|
||||||
|
// generator.
|
||||||
|
func SetRand(r io.Reader) {
|
||||||
|
if r == nil {
|
||||||
|
rander = rand.Reader
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rander = r
|
||||||
|
}
|
390
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid_test.go
generated
vendored
Normal file
390
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid_test.go
generated
vendored
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
in string
|
||||||
|
version Version
|
||||||
|
variant Variant
|
||||||
|
isuuid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var tests = []test{
|
||||||
|
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true},
|
||||||
|
|
||||||
|
{"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true},
|
||||||
|
{"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true},
|
||||||
|
{"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true},
|
||||||
|
{"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true},
|
||||||
|
|
||||||
|
{"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||||
|
{"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||||
|
{"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false},
|
||||||
|
{"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false},
|
||||||
|
{"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false},
|
||||||
|
{"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
var constants = []struct {
|
||||||
|
c interface{}
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{Person, "Person"},
|
||||||
|
{Group, "Group"},
|
||||||
|
{Org, "Org"},
|
||||||
|
{Invalid, "Invalid"},
|
||||||
|
{RFC4122, "RFC4122"},
|
||||||
|
{Reserved, "Reserved"},
|
||||||
|
{Microsoft, "Microsoft"},
|
||||||
|
{Future, "Future"},
|
||||||
|
{Domain(17), "Domain17"},
|
||||||
|
{Variant(42), "BadVariant42"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTest(t *testing.T, in string, tt test) {
|
||||||
|
uuid := Parse(in)
|
||||||
|
if ok := (uuid != nil); ok != tt.isuuid {
|
||||||
|
t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid)
|
||||||
|
}
|
||||||
|
if uuid == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := uuid.Variant(); v != tt.variant {
|
||||||
|
t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant)
|
||||||
|
}
|
||||||
|
if v, _ := uuid.Version(); v != tt.version {
|
||||||
|
t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUUID(t *testing.T) {
|
||||||
|
for _, tt := range tests {
|
||||||
|
testTest(t, tt.in, tt)
|
||||||
|
testTest(t, strings.ToUpper(tt.in), tt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConstants(t *testing.T) {
|
||||||
|
for x, tt := range constants {
|
||||||
|
v, ok := tt.c.(fmt.Stringer)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%x: %v: not a stringer", x, v)
|
||||||
|
} else if s := v.String(); s != tt.name {
|
||||||
|
v, _ := tt.c.(int)
|
||||||
|
t.Errorf("%x: Constant %T:%d gives %q, expected %q\n", x, tt.c, v, s, tt.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRandomUUID(t *testing.T) {
|
||||||
|
m := make(map[string]bool)
|
||||||
|
for x := 1; x < 32; x++ {
|
||||||
|
uuid := NewRandom()
|
||||||
|
s := uuid.String()
|
||||||
|
if m[s] {
|
||||||
|
t.Errorf("NewRandom returned duplicated UUID %s\n", s)
|
||||||
|
}
|
||||||
|
m[s] = true
|
||||||
|
if v, _ := uuid.Version(); v != 4 {
|
||||||
|
t.Errorf("Random UUID of version %s\n", v)
|
||||||
|
}
|
||||||
|
if uuid.Variant() != RFC4122 {
|
||||||
|
t.Errorf("Random UUID is variant %d\n", uuid.Variant())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
m := make(map[string]bool)
|
||||||
|
for x := 1; x < 32; x++ {
|
||||||
|
s := New()
|
||||||
|
if m[s] {
|
||||||
|
t.Errorf("New returned duplicated UUID %s\n", s)
|
||||||
|
}
|
||||||
|
m[s] = true
|
||||||
|
uuid := Parse(s)
|
||||||
|
if uuid == nil {
|
||||||
|
t.Errorf("New returned %q which does not decode\n", s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v, _ := uuid.Version(); v != 4 {
|
||||||
|
t.Errorf("Random UUID of version %s\n", v)
|
||||||
|
}
|
||||||
|
if uuid.Variant() != RFC4122 {
|
||||||
|
t.Errorf("Random UUID is variant %d\n", uuid.Variant())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clockSeq(t *testing.T, uuid UUID) int {
|
||||||
|
seq, ok := uuid.ClockSequence()
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s: invalid clock sequence\n", uuid)
|
||||||
|
}
|
||||||
|
return seq
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClockSeq(t *testing.T) {
|
||||||
|
// Fake time.Now for this test to return a monotonically advancing time; restore it at end.
|
||||||
|
defer func(orig func() time.Time) { timeNow = orig }(timeNow)
|
||||||
|
monTime := time.Now()
|
||||||
|
timeNow = func() time.Time {
|
||||||
|
monTime = monTime.Add(1 * time.Second)
|
||||||
|
return monTime
|
||||||
|
}
|
||||||
|
|
||||||
|
SetClockSequence(-1)
|
||||||
|
uuid1 := NewUUID()
|
||||||
|
uuid2 := NewUUID()
|
||||||
|
|
||||||
|
if clockSeq(t, uuid1) != clockSeq(t, uuid2) {
|
||||||
|
t.Errorf("clock sequence %d != %d\n", clockSeq(t, uuid1), clockSeq(t, uuid2))
|
||||||
|
}
|
||||||
|
|
||||||
|
SetClockSequence(-1)
|
||||||
|
uuid2 = NewUUID()
|
||||||
|
|
||||||
|
// Just on the very off chance we generated the same sequence
|
||||||
|
// two times we try again.
|
||||||
|
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
|
||||||
|
SetClockSequence(-1)
|
||||||
|
uuid2 = NewUUID()
|
||||||
|
}
|
||||||
|
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
|
||||||
|
t.Errorf("Duplicate clock sequence %d\n", clockSeq(t, uuid1))
|
||||||
|
}
|
||||||
|
|
||||||
|
SetClockSequence(0x1234)
|
||||||
|
uuid1 = NewUUID()
|
||||||
|
if seq := clockSeq(t, uuid1); seq != 0x1234 {
|
||||||
|
t.Errorf("%s: expected seq 0x1234 got 0x%04x\n", uuid1, seq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoding(t *testing.T) {
|
||||||
|
text := "7d444840-9dc0-11d1-b245-5ffdce74fad2"
|
||||||
|
urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2"
|
||||||
|
data := UUID{
|
||||||
|
0x7d, 0x44, 0x48, 0x40,
|
||||||
|
0x9d, 0xc0,
|
||||||
|
0x11, 0xd1,
|
||||||
|
0xb2, 0x45,
|
||||||
|
0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2,
|
||||||
|
}
|
||||||
|
if v := data.String(); v != text {
|
||||||
|
t.Errorf("%x: encoded to %s, expected %s\n", data, v, text)
|
||||||
|
}
|
||||||
|
if v := data.URN(); v != urn {
|
||||||
|
t.Errorf("%x: urn is %s, expected %s\n", data, v, urn)
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid := Parse(text)
|
||||||
|
if !Equal(uuid, data) {
|
||||||
|
t.Errorf("%s: decoded to %s, expected %s\n", text, uuid, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion1(t *testing.T) {
|
||||||
|
uuid1 := NewUUID()
|
||||||
|
uuid2 := NewUUID()
|
||||||
|
|
||||||
|
if Equal(uuid1, uuid2) {
|
||||||
|
t.Errorf("%s:duplicate uuid\n", uuid1)
|
||||||
|
}
|
||||||
|
if v, _ := uuid1.Version(); v != 1 {
|
||||||
|
t.Errorf("%s: version %s expected 1\n", uuid1, v)
|
||||||
|
}
|
||||||
|
if v, _ := uuid2.Version(); v != 1 {
|
||||||
|
t.Errorf("%s: version %s expected 1\n", uuid2, v)
|
||||||
|
}
|
||||||
|
n1 := uuid1.NodeID()
|
||||||
|
n2 := uuid2.NodeID()
|
||||||
|
if !bytes.Equal(n1, n2) {
|
||||||
|
t.Errorf("Different nodes %x != %x\n", n1, n2)
|
||||||
|
}
|
||||||
|
t1, ok := uuid1.Time()
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: invalid time\n", uuid1)
|
||||||
|
}
|
||||||
|
t2, ok := uuid2.Time()
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: invalid time\n", uuid2)
|
||||||
|
}
|
||||||
|
q1, ok := uuid1.ClockSequence()
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: invalid clock sequence\n", uuid1)
|
||||||
|
}
|
||||||
|
q2, ok := uuid2.ClockSequence()
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: invalid clock sequence", uuid2)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case t1 == t2 && q1 == q2:
|
||||||
|
t.Errorf("time stopped\n")
|
||||||
|
case t1 > t2 && q1 == q2:
|
||||||
|
t.Errorf("time reversed\n")
|
||||||
|
case t1 < t2 && q1 != q2:
|
||||||
|
t.Errorf("clock sequence chaned unexpectedly\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeAndTime(t *testing.T) {
|
||||||
|
// Time is February 5, 1998 12:30:23.136364800 AM GMT
|
||||||
|
|
||||||
|
uuid := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2")
|
||||||
|
node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2}
|
||||||
|
|
||||||
|
ts, ok := uuid.Time()
|
||||||
|
if ok {
|
||||||
|
c := time.Unix(ts.UnixTime())
|
||||||
|
want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC)
|
||||||
|
if !c.Equal(want) {
|
||||||
|
t.Errorf("Got time %v, want %v", c, want)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("%s: bad time\n", uuid)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(node, uuid.NodeID()) {
|
||||||
|
t.Errorf("Expected node %v got %v\n", node, uuid.NodeID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMD5(t *testing.T) {
|
||||||
|
uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String()
|
||||||
|
want := "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
||||||
|
if uuid != want {
|
||||||
|
t.Errorf("MD5: got %q expected %q\n", uuid, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSHA1(t *testing.T) {
|
||||||
|
uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String()
|
||||||
|
want := "886313e1-3b8a-5372-9b90-0c9aee199e5d"
|
||||||
|
if uuid != want {
|
||||||
|
t.Errorf("SHA1: got %q expected %q\n", uuid, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeID(t *testing.T) {
|
||||||
|
nid := []byte{1, 2, 3, 4, 5, 6}
|
||||||
|
SetNodeInterface("")
|
||||||
|
s := NodeInterface()
|
||||||
|
if s == "" || s == "user" {
|
||||||
|
t.Errorf("NodeInterface %q after SetInteface\n", s)
|
||||||
|
}
|
||||||
|
node1 := NodeID()
|
||||||
|
if node1 == nil {
|
||||||
|
t.Errorf("NodeID nil after SetNodeInterface\n", s)
|
||||||
|
}
|
||||||
|
SetNodeID(nid)
|
||||||
|
s = NodeInterface()
|
||||||
|
if s != "user" {
|
||||||
|
t.Errorf("Expected NodeInterface %q got %q\n", "user", s)
|
||||||
|
}
|
||||||
|
node2 := NodeID()
|
||||||
|
if node2 == nil {
|
||||||
|
t.Errorf("NodeID nil after SetNodeID\n", s)
|
||||||
|
}
|
||||||
|
if bytes.Equal(node1, node2) {
|
||||||
|
t.Errorf("NodeID not changed after SetNodeID\n", s)
|
||||||
|
} else if !bytes.Equal(nid, node2) {
|
||||||
|
t.Errorf("NodeID is %x, expected %x\n", node2, nid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) {
|
||||||
|
if uuid == nil {
|
||||||
|
t.Errorf("%s failed\n", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v, _ := uuid.Version(); v != 2 {
|
||||||
|
t.Errorf("%s: %s: expected version 2, got %s\n", name, uuid, v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v, ok := uuid.Domain(); !ok || v != domain {
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: %d: Domain failed\n", name, uuid)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%s: %s: expected domain %d, got %d\n", name, uuid, domain, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := uuid.Id(); !ok || v != id {
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: %d: Id failed\n", name, uuid)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%s: %s: expected id %d, got %d\n", name, uuid, id, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDCE(t *testing.T) {
|
||||||
|
testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678)
|
||||||
|
testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid()))
|
||||||
|
testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type badRand struct{}
|
||||||
|
|
||||||
|
func (r badRand) Read(buf []byte) (int, error) {
|
||||||
|
for i, _ := range buf {
|
||||||
|
buf[i] = byte(i)
|
||||||
|
}
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadRand(t *testing.T) {
|
||||||
|
SetRand(badRand{})
|
||||||
|
uuid1 := New()
|
||||||
|
uuid2 := New()
|
||||||
|
if uuid1 != uuid2 {
|
||||||
|
t.Errorf("execpted duplicates, got %q and %q\n", uuid1, uuid2)
|
||||||
|
}
|
||||||
|
SetRand(nil)
|
||||||
|
uuid1 = New()
|
||||||
|
uuid2 = New()
|
||||||
|
if uuid1 == uuid2 {
|
||||||
|
t.Errorf("unexecpted duplicates, got %q\n", uuid1)
|
||||||
|
}
|
||||||
|
}
|
41
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version1.go
generated
vendored
Normal file
41
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version1.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||||
|
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||||
|
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||||
|
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||||
|
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||||
|
// return the current NewUUID returns nil.
|
||||||
|
func NewUUID() UUID {
|
||||||
|
if nodeID == nil {
|
||||||
|
SetNodeInterface("")
|
||||||
|
}
|
||||||
|
|
||||||
|
now, err := GetTime()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid := make([]byte, 16)
|
||||||
|
|
||||||
|
time_low := uint32(now & 0xffffffff)
|
||||||
|
time_mid := uint16((now >> 32) & 0xffff)
|
||||||
|
time_hi := uint16((now >> 48) & 0x0fff)
|
||||||
|
time_hi |= 0x1000 // Version 1
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(uuid[0:], time_low)
|
||||||
|
binary.BigEndian.PutUint16(uuid[4:], time_mid)
|
||||||
|
binary.BigEndian.PutUint16(uuid[6:], time_hi)
|
||||||
|
binary.BigEndian.PutUint16(uuid[8:], clock_seq)
|
||||||
|
copy(uuid[10:], nodeID)
|
||||||
|
|
||||||
|
return uuid
|
||||||
|
}
|
25
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version4.go
generated
vendored
Normal file
25
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version4.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
// Random returns a Random (Version 4) UUID or panics.
|
||||||
|
//
|
||||||
|
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||||
|
// package.
|
||||||
|
//
|
||||||
|
// A note about uniqueness derived from from the UUID Wikipedia entry:
|
||||||
|
//
|
||||||
|
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||||
|
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||||
|
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||||
|
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||||
|
// year and having one duplicate.
|
||||||
|
func NewRandom() UUID {
|
||||||
|
uuid := make([]byte, 16)
|
||||||
|
randomBits([]byte(uuid))
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||||
|
return uuid
|
||||||
|
}
|
1
Godeps/_workspace/src/github.com/Sirupsen/logrus/.gitignore
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Sirupsen/logrus/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
logrus
|
10
Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.2
|
||||||
|
- 1.3
|
||||||
|
- tip
|
||||||
|
install:
|
||||||
|
- go get github.com/stretchr/testify
|
||||||
|
- go get github.com/stvp/go-udp-testing
|
||||||
|
- go get github.com/tobi/airbrake-go
|
||||||
|
- go get github.com/getsentry/raven-go
|
21
Godeps/_workspace/src/github.com/Sirupsen/logrus/LICENSE
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/Sirupsen/logrus/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Simon Eskildsen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
349
Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md
generated
vendored
Normal file
349
Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md
generated
vendored
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
# 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)
|
||||||
|
|
||||||
|
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||||
|
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
||||||
|
yet stable (pre 1.0), the core API is unlikely change much but please version
|
||||||
|
control your Logrus to make sure you aren't fetching latest `master` on every
|
||||||
|
build.**
|
||||||
|
|
||||||
|
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||||
|
plain text):
|
||||||
|
|
||||||
|
![Colored](http://i.imgur.com/PY7qMwd.png)
|
||||||
|
|
||||||
|
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
|
||||||
|
or Splunk:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||||
|
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||||
|
|
||||||
|
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||||
|
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||||
|
|
||||||
|
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||||
|
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||||
|
|
||||||
|
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||||
|
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||||
|
|
||||||
|
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||||
|
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||||
|
```
|
||||||
|
|
||||||
|
With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not
|
||||||
|
attached, the output is compatible with the
|
||||||
|
[l2met](http://r.32k.io/l2met-introduction) format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10
|
||||||
|
time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122
|
||||||
|
time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10
|
||||||
|
time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9
|
||||||
|
time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
}).Info("A walrus appears")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||||
|
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
|
||||||
|
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||||
|
want:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Log as JSON instead of the default ASCII formatter.
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
|
||||||
|
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||||
|
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||||
|
log.AddHook(&logrus_airbrake.AirbrakeHook{})
|
||||||
|
|
||||||
|
// Output to stderr instead of stdout, could also be a file.
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
|
||||||
|
// Only log the warning severity or above.
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For more advanced usage such as logging to multiple locations from the same
|
||||||
|
application, you can also create an instance of the `logrus` Logger:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a new instance of the logger. You can have any number of instances.
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// The API for setting attributes is a little different than the package level
|
||||||
|
// exported logger. See Godoc.
|
||||||
|
log.Out = os.Stderr
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fields
|
||||||
|
|
||||||
|
Logrus encourages careful, structured logging though logging fields instead of
|
||||||
|
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||||
|
to send event %s to topic %s with key %d")`, you should log the much more
|
||||||
|
discoverable:
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"event": event,
|
||||||
|
"topic": topic,
|
||||||
|
"key": key,
|
||||||
|
}).Fatal("Failed to send event")
|
||||||
|
```
|
||||||
|
|
||||||
|
We've found this API forces you to think about logging in a way that produces
|
||||||
|
much more useful logging messages. We've been in countless situations where just
|
||||||
|
a single added field to a log statement that was already there would've saved us
|
||||||
|
hours. The `WithFields` call is optional.
|
||||||
|
|
||||||
|
In general, with Logrus using any of the `printf`-family functions should be
|
||||||
|
seen as a hint you should add a field, however, you can still use the
|
||||||
|
`printf`-family functions with Logrus.
|
||||||
|
|
||||||
|
#### Hooks
|
||||||
|
|
||||||
|
You can add hooks for logging levels. For example to send errors to an exception
|
||||||
|
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||||
|
multiple places simultaneously, e.g. syslog.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Not the real implementation of the Airbrake hook. Just a simple sample.
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.AddHook(new(AirbrakeHook))
|
||||||
|
}
|
||||||
|
|
||||||
|
type AirbrakeHook struct{}
|
||||||
|
|
||||||
|
// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains
|
||||||
|
// the fields for the entry. See the Fields section of the README.
|
||||||
|
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
||||||
|
err := airbrake.Notify(entry.Data["error"].(error))
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"source": "airbrake",
|
||||||
|
"endpoint": airbrake.Endpoint,
|
||||||
|
}).Info("Failed to send error to Airbrake")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Levels()` returns a slice of `Levels` the hook is fired for.
|
||||||
|
func (hook *AirbrakeHook) Levels() []log.Level {
|
||||||
|
return []log.Level{
|
||||||
|
log.ErrorLevel,
|
||||||
|
log.FatalLevel,
|
||||||
|
log.PanicLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Logrus comes with built-in hooks. Add those, or your custom hook, in `init`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/syslog"
|
||||||
|
"log/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.AddHook(new(logrus_airbrake.AirbrakeHook))
|
||||||
|
|
||||||
|
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to connect to local syslog daemon")
|
||||||
|
} else {
|
||||||
|
log.AddHook(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
|
||||||
|
Send errors to an exception tracking service compatible with the Airbrake API.
|
||||||
|
Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
|
||||||
|
|
||||||
|
* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
|
||||||
|
Send errors to the Papertrail hosted logging service via UDP.
|
||||||
|
|
||||||
|
* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
|
||||||
|
Send errors to remote syslog server.
|
||||||
|
Uses standard library `log/syslog` behind the scenes.
|
||||||
|
|
||||||
|
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
|
||||||
|
Send errors to a channel in hipchat.
|
||||||
|
|
||||||
|
#### Level logging
|
||||||
|
|
||||||
|
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.Debug("Useful debugging information.")
|
||||||
|
log.Info("Something noteworthy happened!")
|
||||||
|
log.Warn("You should probably take a look at this.")
|
||||||
|
log.Error("Something failed but I'm not quitting.")
|
||||||
|
// Calls os.Exit(1) after logging
|
||||||
|
log.Fatal("Bye.")
|
||||||
|
// Calls panic() after logging
|
||||||
|
log.Panic("I'm bailing.")
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set the logging level on a `Logger`, then it will only log entries with
|
||||||
|
that severity or anything above it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
```
|
||||||
|
|
||||||
|
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||||
|
environment if your application has that.
|
||||||
|
|
||||||
|
#### Entries
|
||||||
|
|
||||||
|
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||||
|
automatically added to all logging events:
|
||||||
|
|
||||||
|
1. `time`. The timestamp when the entry was created.
|
||||||
|
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||||
|
the `AddFields` call. E.g. `Failed to send event.`
|
||||||
|
3. `level`. The logging level. E.g. `info`.
|
||||||
|
|
||||||
|
#### Environments
|
||||||
|
|
||||||
|
Logrus has no notion of environment.
|
||||||
|
|
||||||
|
If you wish for hooks and formatters to only be used in specific environments,
|
||||||
|
you should handle that yourself. For example, if your application has a global
|
||||||
|
variable `Environment`, which is a string representation of the environment you
|
||||||
|
could do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// do something here to set environment depending on an environment variable
|
||||||
|
// or command-line flag
|
||||||
|
if Environment == "production" {
|
||||||
|
log.SetFormatter(logrus.JSONFormatter)
|
||||||
|
} else {
|
||||||
|
// The TextFormatter is default, you don't actually have to do this.
|
||||||
|
log.SetFormatter(logrus.TextFormatter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration is how `logrus` was intended to be used, but JSON in
|
||||||
|
production is mostly only useful if you do log aggregation with tools like
|
||||||
|
Splunk or Logstash.
|
||||||
|
|
||||||
|
#### Formatters
|
||||||
|
|
||||||
|
The built-in logging formatters are:
|
||||||
|
|
||||||
|
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||||
|
without colors.
|
||||||
|
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||||
|
field to `true`. To force no colored output even if there is a TTY set the
|
||||||
|
`DisableColors` field to `true`
|
||||||
|
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||||
|
|
||||||
|
Third party logging formatters:
|
||||||
|
|
||||||
|
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||||
|
|
||||||
|
You can define your formatter by implementing the `Formatter` interface,
|
||||||
|
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||||
|
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||||
|
default ones (see Entries section above):
|
||||||
|
|
||||||
|
```go
|
||||||
|
type MyJSONFormatter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetFormatter(new(MyJSONFormatter))
|
||||||
|
|
||||||
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
// Note this doesn't include Time, Level and Message which are available on
|
||||||
|
// the Entry. Consult `godoc` on information about those fields or read the
|
||||||
|
// source of the official loggers.
|
||||||
|
serialized, err := json.Marshal(entry.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||||
|
}
|
||||||
|
return append(serialized, '\n'), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Rotation
|
||||||
|
|
||||||
|
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||||
|
external program (like `logrotated(8)`) that can compress and delete old log
|
||||||
|
entries. It should not be a feature of the application-level logger.
|
||||||
|
|
||||||
|
|
||||||
|
[godoc]: https://godoc.org/github.com/Sirupsen/logrus
|
248
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go
generated
vendored
Normal file
248
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go
generated
vendored
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||||
|
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
||||||
|
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
||||||
|
// passed around as much as you wish to avoid field duplication.
|
||||||
|
type Entry struct {
|
||||||
|
Logger *Logger
|
||||||
|
|
||||||
|
// Contains all the fields set by the user.
|
||||||
|
Data Fields
|
||||||
|
|
||||||
|
// Time at which the log entry was created
|
||||||
|
Time time.Time
|
||||||
|
|
||||||
|
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Level Level
|
||||||
|
|
||||||
|
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntry(logger *Logger) *Entry {
|
||||||
|
return &Entry{
|
||||||
|
Logger: logger,
|
||||||
|
// Default is three fields, give a little extra room
|
||||||
|
Data: make(Fields, 5),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a reader for the entry, which is a proxy to the formatter.
|
||||||
|
func (entry *Entry) Reader() (*bytes.Buffer, error) {
|
||||||
|
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||||
|
return bytes.NewBuffer(serialized), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the string representation from the reader and ultimately the
|
||||||
|
// formatter.
|
||||||
|
func (entry *Entry) String() (string, error) {
|
||||||
|
reader, err := entry.Reader()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a single field to the Entry.
|
||||||
|
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||||
|
return entry.WithFields(Fields{key: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a map of fields to the Entry.
|
||||||
|
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
|
data := Fields{}
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range fields {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: data}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) log(level Level, msg string) {
|
||||||
|
entry.Time = time.Now()
|
||||||
|
entry.Level = level
|
||||||
|
entry.Message = msg
|
||||||
|
|
||||||
|
if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := entry.Reader()
|
||||||
|
if err != nil {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
defer entry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
_, err = io.Copy(entry.Logger.Out, reader)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// To avoid Entry#log() returning a value that only would make sense for
|
||||||
|
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||||
|
// directly here.
|
||||||
|
if level <= PanicLevel {
|
||||||
|
panic(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Debug(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Print(args ...interface{}) {
|
||||||
|
entry.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Info(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warn(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Error(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatal(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panic(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
panic(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Printf family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.Debug(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.Info(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||||
|
entry.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.Warn(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||||
|
entry.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.Error(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.Fatal(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.Panic(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Println family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Debugln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.Debug(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infoln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.Info(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Println(args ...interface{}) {
|
||||||
|
entry.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.Warn(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningln(args ...interface{}) {
|
||||||
|
entry.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.Error(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.Fatal(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.Panic(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||||
|
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||||
|
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||||
|
// string allocation, we do the simplest thing.
|
||||||
|
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||||
|
msg := fmt.Sprintln(args...)
|
||||||
|
return msg[:len(msg)-1]
|
||||||
|
}
|
53
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEntryPanicln(t *testing.T) {
|
||||||
|
errBoom := fmt.Errorf("boom time")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
p := recover()
|
||||||
|
assert.NotNil(t, p)
|
||||||
|
|
||||||
|
switch pVal := p.(type) {
|
||||||
|
case *Entry:
|
||||||
|
assert.Equal(t, "kaboom", pVal.Message)
|
||||||
|
assert.Equal(t, errBoom, pVal.Data["err"])
|
||||||
|
default:
|
||||||
|
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &bytes.Buffer{}
|
||||||
|
entry := NewEntry(logger)
|
||||||
|
entry.WithField("err", errBoom).Panicln("kaboom")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEntryPanicf(t *testing.T) {
|
||||||
|
errBoom := fmt.Errorf("boom again")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
p := recover()
|
||||||
|
assert.NotNil(t, p)
|
||||||
|
|
||||||
|
switch pVal := p.(type) {
|
||||||
|
case *Entry:
|
||||||
|
assert.Equal(t, "kaboom true", pVal.Message)
|
||||||
|
assert.Equal(t, errBoom, pVal.Data["err"])
|
||||||
|
default:
|
||||||
|
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &bytes.Buffer{}
|
||||||
|
entry := NewEntry(logger)
|
||||||
|
entry.WithField("err", errBoom).Panicf("kaboom %v", true)
|
||||||
|
}
|
40
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go
generated
vendored
Normal file
40
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Formatter = new(logrus.JSONFormatter)
|
||||||
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"err": err,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "orca",
|
||||||
|
"size": 9009,
|
||||||
|
}).Panic("It's over 9000!")
|
||||||
|
}
|
35
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
Normal file
35
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||||
|
"github.com/tobi/airbrake-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
|
log.Hooks.Add(new(logrus_airbrake.AirbrakeHook))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml"
|
||||||
|
airbrake.ApiKey = "whatever"
|
||||||
|
airbrake.Environment = "production"
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
}
|
182
Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go
generated
vendored
Normal file
182
Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go
generated
vendored
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// std is the name of the standard logger in stdlib `log`
|
||||||
|
std = New()
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetOutput sets the standard logger output.
|
||||||
|
func SetOutput(out io.Writer) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Out = out
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFormatter sets the standard logger formatter.
|
||||||
|
func SetFormatter(formatter Formatter) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Formatter = formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the standard logger level.
|
||||||
|
func SetLevel(level Level) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Level = level
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel returns the standard logger level.
|
||||||
|
func GetLevel() Level {
|
||||||
|
return std.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHook adds a hook to the standard logger hooks.
|
||||||
|
func AddHook(hook Hook) {
|
||||||
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithField creates an entry from the standard logger and adds a field to
|
||||||
|
// it. If you want multiple fields, use `WithFields`.
|
||||||
|
//
|
||||||
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
// or Panic on the Entry it returns.
|
||||||
|
func WithField(key string, value interface{}) *Entry {
|
||||||
|
return std.WithField(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFields creates an entry from the standard logger and adds multiple
|
||||||
|
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||||
|
// once for each field.
|
||||||
|
//
|
||||||
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
// or Panic on the Entry it returns.
|
||||||
|
func WithFields(fields Fields) *Entry {
|
||||||
|
return std.WithFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at level Debug on the standard logger.
|
||||||
|
func Debug(args ...interface{}) {
|
||||||
|
std.Debug(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print logs a message at level Info on the standard logger.
|
||||||
|
func Print(args ...interface{}) {
|
||||||
|
std.Print(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a message at level Info on the standard logger.
|
||||||
|
func Info(args ...interface{}) {
|
||||||
|
std.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a message at level Warn on the standard logger.
|
||||||
|
func Warn(args ...interface{}) {
|
||||||
|
std.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs a message at level Warn on the standard logger.
|
||||||
|
func Warning(args ...interface{}) {
|
||||||
|
std.Warning(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at level Error on the standard logger.
|
||||||
|
func Error(args ...interface{}) {
|
||||||
|
std.Error(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic logs a message at level Panic on the standard logger.
|
||||||
|
func Panic(args ...interface{}) {
|
||||||
|
std.Panic(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs a message at level Fatal on the standard logger.
|
||||||
|
func Fatal(args ...interface{}) {
|
||||||
|
std.Fatal(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs a message at level Debug on the standard logger.
|
||||||
|
func Debugf(format string, args ...interface{}) {
|
||||||
|
std.Debugf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf logs a message at level Info on the standard logger.
|
||||||
|
func Printf(format string, args ...interface{}) {
|
||||||
|
std.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a message at level Info on the standard logger.
|
||||||
|
func Infof(format string, args ...interface{}) {
|
||||||
|
std.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs a message at level Warn on the standard logger.
|
||||||
|
func Warnf(format string, args ...interface{}) {
|
||||||
|
std.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf logs a message at level Warn on the standard logger.
|
||||||
|
func Warningf(format string, args ...interface{}) {
|
||||||
|
std.Warningf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs a message at level Error on the standard logger.
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
std.Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicf logs a message at level Panic on the standard logger.
|
||||||
|
func Panicf(format string, args ...interface{}) {
|
||||||
|
std.Panicf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs a message at level Fatal on the standard logger.
|
||||||
|
func Fatalf(format string, args ...interface{}) {
|
||||||
|
std.Fatalf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugln logs a message at level Debug on the standard logger.
|
||||||
|
func Debugln(args ...interface{}) {
|
||||||
|
std.Debugln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println logs a message at level Info on the standard logger.
|
||||||
|
func Println(args ...interface{}) {
|
||||||
|
std.Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infoln logs a message at level Info on the standard logger.
|
||||||
|
func Infoln(args ...interface{}) {
|
||||||
|
std.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnln logs a message at level Warn on the standard logger.
|
||||||
|
func Warnln(args ...interface{}) {
|
||||||
|
std.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningln logs a message at level Warn on the standard logger.
|
||||||
|
func Warningln(args ...interface{}) {
|
||||||
|
std.Warningln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorln logs a message at level Error on the standard logger.
|
||||||
|
func Errorln(args ...interface{}) {
|
||||||
|
std.Errorln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicln logs a message at level Panic on the standard logger.
|
||||||
|
func Panicln(args ...interface{}) {
|
||||||
|
std.Panicln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalln logs a message at level Fatal on the standard logger.
|
||||||
|
func Fatalln(args ...interface{}) {
|
||||||
|
std.Fatalln(args...)
|
||||||
|
}
|
44
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go
generated
vendored
Normal file
44
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
|
// `Entry`. It exposes all the fields, including the default ones:
|
||||||
|
//
|
||||||
|
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||||
|
// * `entry.Data["time"]`. The timestamp.
|
||||||
|
// * `entry.Data["level"]. The level the entry was logged at.
|
||||||
|
//
|
||||||
|
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||||
|
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||||
|
// logged to `logger.Out`.
|
||||||
|
type Formatter interface {
|
||||||
|
Format(*Entry) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||||
|
// dumping it. If this code wasn't there doing:
|
||||||
|
//
|
||||||
|
// logrus.WithField("level", 1).Info("hello")
|
||||||
|
//
|
||||||
|
// Would just silently drop the user provided level. Instead with this code
|
||||||
|
// it'll logged as:
|
||||||
|
//
|
||||||
|
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||||
|
//
|
||||||
|
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||||
|
// avoid code duplication between the two default formatters.
|
||||||
|
func prefixFieldClashes(entry *Entry) {
|
||||||
|
_, ok := entry.Data["time"]
|
||||||
|
if ok {
|
||||||
|
entry.Data["fields.time"] = entry.Data["time"]
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = entry.Data["msg"]
|
||||||
|
if ok {
|
||||||
|
entry.Data["fields.msg"] = entry.Data["msg"]
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = entry.Data["level"]
|
||||||
|
if ok {
|
||||||
|
entry.Data["fields.level"] = entry.Data["level"]
|
||||||
|
}
|
||||||
|
}
|
88
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go
generated
vendored
Normal file
88
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// smallFields is a small size data set for benchmarking
|
||||||
|
var smallFields = Fields{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qux",
|
||||||
|
"one": "two",
|
||||||
|
"three": "four",
|
||||||
|
}
|
||||||
|
|
||||||
|
// largeFields is a large size data set for benchmarking
|
||||||
|
var largeFields = Fields{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qux",
|
||||||
|
"one": "two",
|
||||||
|
"three": "four",
|
||||||
|
"five": "six",
|
||||||
|
"seven": "eight",
|
||||||
|
"nine": "ten",
|
||||||
|
"eleven": "twelve",
|
||||||
|
"thirteen": "fourteen",
|
||||||
|
"fifteen": "sixteen",
|
||||||
|
"seventeen": "eighteen",
|
||||||
|
"nineteen": "twenty",
|
||||||
|
"a": "b",
|
||||||
|
"c": "d",
|
||||||
|
"e": "f",
|
||||||
|
"g": "h",
|
||||||
|
"i": "j",
|
||||||
|
"k": "l",
|
||||||
|
"m": "n",
|
||||||
|
"o": "p",
|
||||||
|
"q": "r",
|
||||||
|
"s": "t",
|
||||||
|
"u": "v",
|
||||||
|
"w": "x",
|
||||||
|
"y": "z",
|
||||||
|
"this": "will",
|
||||||
|
"make": "thirty",
|
||||||
|
"entries": "yeah",
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmallTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmallJSONFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &JSONFormatter{}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeJSONFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &JSONFormatter{}, largeFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
|
||||||
|
entry := &Entry{
|
||||||
|
Time: time.Time{},
|
||||||
|
Level: InfoLevel,
|
||||||
|
Message: "message",
|
||||||
|
Data: fields,
|
||||||
|
}
|
||||||
|
var d []byte
|
||||||
|
var err error
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
d, err = formatter.Format(entry)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(d)))
|
||||||
|
}
|
||||||
|
}
|
122
Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go
generated
vendored
Normal file
122
Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
34
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks.go
generated
vendored
Normal file
34
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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
|
||||||
|
}
|
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package logrus_airbrake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/tobi/airbrake-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AirbrakeHook to send exceptions to an exception-tracking service compatible
|
||||||
|
// with the Airbrake API. You must set:
|
||||||
|
// * airbrake.Endpoint
|
||||||
|
// * airbrake.ApiKey
|
||||||
|
// * airbrake.Environment (only sends exceptions when set to "production")
|
||||||
|
//
|
||||||
|
// Before using this hook, to send an error. Entries that trigger an Error,
|
||||||
|
// Fatal or Panic should now include an "error" field to send to Airbrake.
|
||||||
|
type AirbrakeHook struct{}
|
||||||
|
|
||||||
|
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
||||||
|
if entry.Data["error"] == nil {
|
||||||
|
entry.Logger.WithFields(logrus.Fields{
|
||||||
|
"source": "airbrake",
|
||||||
|
"endpoint": airbrake.Endpoint,
|
||||||
|
}).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err, ok := entry.Data["error"].(error)
|
||||||
|
if !ok {
|
||||||
|
entry.Logger.WithFields(logrus.Fields{
|
||||||
|
"source": "airbrake",
|
||||||
|
"endpoint": airbrake.Endpoint,
|
||||||
|
}).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
airErr := airbrake.Notify(err)
|
||||||
|
if airErr != nil {
|
||||||
|
entry.Logger.WithFields(logrus.Fields{
|
||||||
|
"source": "airbrake",
|
||||||
|
"endpoint": airbrake.Endpoint,
|
||||||
|
"error": airErr,
|
||||||
|
}).Warn("Failed to send error to Airbrake")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *AirbrakeHook) Levels() []logrus.Level {
|
||||||
|
return []logrus.Level{
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.PanicLevel,
|
||||||
|
}
|
||||||
|
}
|
28
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Papertrail Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
|
||||||
|
|
||||||
|
[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts).
|
||||||
|
|
||||||
|
In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`.
|
||||||
|
|
||||||
|
For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"log/syslog"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/papertrail"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log := logrus.New()
|
||||||
|
hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package logrus_papertrail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
format = "Jan 2 15:04:05"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PapertrailHook to send logs to a logging service compatible with the Papertrail API.
|
||||||
|
type PapertrailHook struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
AppName string
|
||||||
|
UDPConn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPapertrailHook creates a hook to be added to an instance of logger.
|
||||||
|
func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) {
|
||||||
|
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port))
|
||||||
|
return &PapertrailHook{host, port, appName, conn}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire is called when a log event is fired.
|
||||||
|
func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
|
||||||
|
date := time.Now().Format(format)
|
||||||
|
payload := fmt.Sprintf("<22> %s %s: [%s] %s", date, hook.AppName, entry.Level, entry.Message)
|
||||||
|
|
||||||
|
bytesWritten, err := hook.UDPConn.Write([]byte(payload))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Levels returns the available logging levels.
|
||||||
|
func (hook *PapertrailHook) Levels() []logrus.Level {
|
||||||
|
return []logrus.Level{
|
||||||
|
logrus.PanicLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
logrus.WarnLevel,
|
||||||
|
logrus.InfoLevel,
|
||||||
|
logrus.DebugLevel,
|
||||||
|
}
|
||||||
|
}
|
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package logrus_papertrail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/stvp/go-udp-testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWritingToUDP(t *testing.T) {
|
||||||
|
port := 16661
|
||||||
|
udp.SetAddr(fmt.Sprintf(":%d", port))
|
||||||
|
|
||||||
|
hook, err := NewPapertrailHook("localhost", port, "test")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to connect to local UDP server.")
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logrus.New()
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
|
||||||
|
udp.ShouldReceive(t, "foo", func() {
|
||||||
|
log.Info("foo")
|
||||||
|
})
|
||||||
|
}
|
61
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Sentry Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
|
||||||
|
|
||||||
|
[Sentry](https://getsentry.com) provides both self-hosted and hosted
|
||||||
|
solutions for exception tracking.
|
||||||
|
Both client and server are
|
||||||
|
[open source](https://github.com/getsentry/sentry).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Every sentry application defined on the server gets a different
|
||||||
|
[DSN](https://www.getsentry.com/docs/). In the example below replace
|
||||||
|
`YOUR_DSN` with the one created for your application.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/sentry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log := logrus.New()
|
||||||
|
hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{
|
||||||
|
logrus.PanicLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Special fields
|
||||||
|
|
||||||
|
Some logrus fields have a special meaning in this hook,
|
||||||
|
these are server_name and logger.
|
||||||
|
When logs are sent to sentry these fields are treated differently.
|
||||||
|
- server_name (also known as hostname) is the name of the server which
|
||||||
|
is logging the event (hostname.example.com)
|
||||||
|
- logger is the part of the application which is logging the event.
|
||||||
|
In go this usually means setting it to the name of the package.
|
||||||
|
|
||||||
|
## Timeout
|
||||||
|
|
||||||
|
`Timeout` is the time the sentry hook will wait for a response
|
||||||
|
from the sentry server.
|
||||||
|
|
||||||
|
If this time elapses with no response from
|
||||||
|
the server an error will be returned.
|
||||||
|
|
||||||
|
If `Timeout` is set to 0 the SentryHook will not wait for a reply
|
||||||
|
and will assume a correct delivery.
|
||||||
|
|
||||||
|
The SentryHook has a default timeout of `100 milliseconds` when created
|
||||||
|
with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field:
|
||||||
|
|
||||||
|
```go
|
||||||
|
hook, _ := logrus_sentry.NewSentryHook(...)
|
||||||
|
hook.Timeout = 20*time.Seconds
|
||||||
|
```
|
100
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go
generated
vendored
Normal file
100
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package logrus_sentry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
severityMap = map[logrus.Level]raven.Severity{
|
||||||
|
logrus.DebugLevel: raven.DEBUG,
|
||||||
|
logrus.InfoLevel: raven.INFO,
|
||||||
|
logrus.WarnLevel: raven.WARNING,
|
||||||
|
logrus.ErrorLevel: raven.ERROR,
|
||||||
|
logrus.FatalLevel: raven.FATAL,
|
||||||
|
logrus.PanicLevel: raven.FATAL,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func getAndDel(d logrus.Fields, key string) (string, bool) {
|
||||||
|
var (
|
||||||
|
ok bool
|
||||||
|
v interface{}
|
||||||
|
val string
|
||||||
|
)
|
||||||
|
if v, ok = d[key]; !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok = v.(string); !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
delete(d, key)
|
||||||
|
return val, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SentryHook delivers logs to a sentry server.
|
||||||
|
type SentryHook struct {
|
||||||
|
// Timeout sets the time to wait for a delivery error from the sentry server.
|
||||||
|
// If this is set to zero the server will not wait for any response and will
|
||||||
|
// consider the message correctly sent
|
||||||
|
Timeout time.Duration
|
||||||
|
|
||||||
|
client *raven.Client
|
||||||
|
levels []logrus.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSentryHook creates a hook to be added to an instance of logger
|
||||||
|
// and initializes the raven client.
|
||||||
|
// This method sets the timeout to 100 milliseconds.
|
||||||
|
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
|
||||||
|
client, err := raven.NewClient(DSN, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &SentryHook{100 * time.Millisecond, client, levels}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when an event should be sent to sentry
|
||||||
|
// Special fields that sentry uses to give more information to the server
|
||||||
|
// are extracted from entry.Data (if they are found)
|
||||||
|
// These fields are: logger and server_name
|
||||||
|
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
|
||||||
|
packet := &raven.Packet{
|
||||||
|
Message: entry.Message,
|
||||||
|
Timestamp: raven.Timestamp(entry.Time),
|
||||||
|
Level: severityMap[entry.Level],
|
||||||
|
Platform: "go",
|
||||||
|
}
|
||||||
|
|
||||||
|
d := entry.Data
|
||||||
|
|
||||||
|
if logger, ok := getAndDel(d, "logger"); ok {
|
||||||
|
packet.Logger = logger
|
||||||
|
}
|
||||||
|
if serverName, ok := getAndDel(d, "server_name"); ok {
|
||||||
|
packet.ServerName = serverName
|
||||||
|
}
|
||||||
|
packet.Extra = map[string]interface{}(d)
|
||||||
|
|
||||||
|
_, errCh := hook.client.Capture(packet, nil)
|
||||||
|
timeout := hook.Timeout
|
||||||
|
if timeout != 0 {
|
||||||
|
timeoutCh := time.After(timeout)
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
return err
|
||||||
|
case <-timeoutCh:
|
||||||
|
return fmt.Errorf("no response from sentry server in %s", timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Levels returns the available logging levels.
|
||||||
|
func (hook *SentryHook) Levels() []logrus.Level {
|
||||||
|
return hook.levels
|
||||||
|
}
|
97
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go
generated
vendored
Normal file
97
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package logrus_sentry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
message = "error message"
|
||||||
|
server_name = "testserver.internal"
|
||||||
|
logger_name = "test.logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getTestLogger() *logrus.Logger {
|
||||||
|
l := logrus.New()
|
||||||
|
l.Out = ioutil.Discard
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTestDSN(t *testing.T, tf func(string, <-chan *raven.Packet)) {
|
||||||
|
pch := make(chan *raven.Packet, 1)
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
defer req.Body.Close()
|
||||||
|
d := json.NewDecoder(req.Body)
|
||||||
|
p := &raven.Packet{}
|
||||||
|
err := d.Decode(p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
pch <- p
|
||||||
|
}))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
fragments := strings.SplitN(s.URL, "://", 2)
|
||||||
|
dsn := fmt.Sprintf(
|
||||||
|
"%s://public:secret@%s/sentry/project-id",
|
||||||
|
fragments[0],
|
||||||
|
fragments[1],
|
||||||
|
)
|
||||||
|
tf(dsn, pch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpecialFields(t *testing.T) {
|
||||||
|
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
|
||||||
|
logger := getTestLogger()
|
||||||
|
|
||||||
|
hook, err := NewSentryHook(dsn, []logrus.Level{
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"server_name": server_name,
|
||||||
|
"logger": logger_name,
|
||||||
|
}).Error(message)
|
||||||
|
|
||||||
|
packet := <-pch
|
||||||
|
if packet.Logger != logger_name {
|
||||||
|
t.Errorf("logger should have been %s, was %s", logger_name, packet.Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.ServerName != server_name {
|
||||||
|
t.Errorf("server_name should have been %s, was %s", server_name, packet.ServerName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSentryHandler(t *testing.T) {
|
||||||
|
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
|
||||||
|
logger := getTestLogger()
|
||||||
|
hook, err := NewSentryHook(dsn, []logrus.Level{
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
|
||||||
|
logger.Error(message)
|
||||||
|
packet := <-pch
|
||||||
|
if packet.Message != message {
|
||||||
|
t.Errorf("message should have been %s, was %s", message, packet.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"log/syslog"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log := logrus.New()
|
||||||
|
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
59
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package logrus_syslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"log/syslog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SyslogHook to send logs via syslog.
|
||||||
|
type SyslogHook struct {
|
||||||
|
Writer *syslog.Writer
|
||||||
|
SyslogNetwork string
|
||||||
|
SyslogRaddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a hook to be added to an instance of logger. This is called with
|
||||||
|
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
|
||||||
|
// `if err == nil { log.Hooks.Add(hook) }`
|
||||||
|
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
|
||||||
|
w, err := syslog.Dial(network, raddr, priority, tag)
|
||||||
|
return &SyslogHook{w, network, raddr}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
|
||||||
|
line, err := entry.String()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch entry.Level {
|
||||||
|
case logrus.PanicLevel:
|
||||||
|
return hook.Writer.Crit(line)
|
||||||
|
case logrus.FatalLevel:
|
||||||
|
return hook.Writer.Crit(line)
|
||||||
|
case logrus.ErrorLevel:
|
||||||
|
return hook.Writer.Err(line)
|
||||||
|
case logrus.WarnLevel:
|
||||||
|
return hook.Writer.Warning(line)
|
||||||
|
case logrus.InfoLevel:
|
||||||
|
return hook.Writer.Info(line)
|
||||||
|
case logrus.DebugLevel:
|
||||||
|
return hook.Writer.Debug(line)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *SyslogHook) Levels() []logrus.Level {
|
||||||
|
return []logrus.Level{
|
||||||
|
logrus.PanicLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
logrus.WarnLevel,
|
||||||
|
logrus.InfoLevel,
|
||||||
|
logrus.DebugLevel,
|
||||||
|
}
|
||||||
|
}
|
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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!")
|
||||||
|
}
|
22
Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JSONFormatter struct{}
|
||||||
|
|
||||||
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
prefixFieldClashes(entry)
|
||||||
|
entry.Data["time"] = entry.Time.Format(time.RFC3339)
|
||||||
|
entry.Data["msg"] = entry.Message
|
||||||
|
entry.Data["level"] = entry.Level.String()
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(entry.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||||
|
}
|
||||||
|
return append(serialized, '\n'), nil
|
||||||
|
}
|
161
Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go
generated
vendored
Normal file
161
Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||||
|
// file, or leave it default which is `os.Stdout`. You can also set this to
|
||||||
|
// something more adventorous, such as logging to Kafka.
|
||||||
|
Out io.Writer
|
||||||
|
// Hooks for the logger instance. These allow firing events based on logging
|
||||||
|
// levels and log entries. For example, to send errors to an error tracking
|
||||||
|
// service, log to StatsD or dump the core on fatal errors.
|
||||||
|
Hooks levelHooks
|
||||||
|
// All log entries pass through the formatter before logged to Out. The
|
||||||
|
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||||
|
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||||
|
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||||
|
// own that implements the `Formatter` interface, see the `README` or included
|
||||||
|
// formatters for examples.
|
||||||
|
Formatter Formatter
|
||||||
|
// The logging level the logger should log at. This is typically (and defaults
|
||||||
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
|
// logged. `logrus.Debug` is useful in
|
||||||
|
Level Level
|
||||||
|
// Used to sync writing to the log.
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||||
|
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||||
|
// instantiate your own:
|
||||||
|
//
|
||||||
|
// var log = &Logger{
|
||||||
|
// Out: os.Stderr,
|
||||||
|
// Formatter: new(JSONFormatter),
|
||||||
|
// Hooks: make(levelHooks),
|
||||||
|
// Level: logrus.Debug,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// It's recommended to make this a global instance called `log`.
|
||||||
|
func New() *Logger {
|
||||||
|
return &Logger{
|
||||||
|
Out: os.Stdout,
|
||||||
|
Formatter: new(TextFormatter),
|
||||||
|
Hooks: make(levelHooks),
|
||||||
|
Level: InfoLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a field to the log entry, note that you it doesn't log until you call
|
||||||
|
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
|
||||||
|
// Ff you want multiple fields, use `WithFields`.
|
||||||
|
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||||
|
return NewEntry(logger).WithField(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||||
|
// each `Field`.
|
||||||
|
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||||
|
return NewEntry(logger).WithFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Debugf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Fatalf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Panicf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debug(args ...interface{}) {
|
||||||
|
NewEntry(logger).Debug(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Info(args ...interface{}) {
|
||||||
|
NewEntry(logger).Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Print(args ...interface{}) {
|
||||||
|
NewEntry(logger).Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warn(args ...interface{}) {
|
||||||
|
NewEntry(logger).Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warning(args ...interface{}) {
|
||||||
|
NewEntry(logger).Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Error(args ...interface{}) {
|
||||||
|
NewEntry(logger).Error(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
|
NewEntry(logger).Fatal(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panic(args ...interface{}) {
|
||||||
|
NewEntry(logger).Panic(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Debugln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infoln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Println(args ...interface{}) {
|
||||||
|
NewEntry(logger).Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Errorln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Fatalln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Panicln(args...)
|
||||||
|
}
|
94
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go
generated
vendored
Normal file
94
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fields type, used to pass to `WithFields`.
|
||||||
|
type Fields map[string]interface{}
|
||||||
|
|
||||||
|
// Level type
|
||||||
|
type Level uint8
|
||||||
|
|
||||||
|
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||||
|
func (level Level) String() string {
|
||||||
|
switch level {
|
||||||
|
case DebugLevel:
|
||||||
|
return "debug"
|
||||||
|
case InfoLevel:
|
||||||
|
return "info"
|
||||||
|
case WarnLevel:
|
||||||
|
return "warning"
|
||||||
|
case ErrorLevel:
|
||||||
|
return "error"
|
||||||
|
case FatalLevel:
|
||||||
|
return "fatal"
|
||||||
|
case PanicLevel:
|
||||||
|
return "panic"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||||
|
func ParseLevel(lvl string) (Level, error) {
|
||||||
|
switch lvl {
|
||||||
|
case "panic":
|
||||||
|
return PanicLevel, nil
|
||||||
|
case "fatal":
|
||||||
|
return FatalLevel, nil
|
||||||
|
case "error":
|
||||||
|
return ErrorLevel, nil
|
||||||
|
case "warn", "warning":
|
||||||
|
return WarnLevel, nil
|
||||||
|
case "info":
|
||||||
|
return InfoLevel, nil
|
||||||
|
case "debug":
|
||||||
|
return DebugLevel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var l Level
|
||||||
|
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are the different logging levels. You can set the logging level to log
|
||||||
|
// on your instance of logger, obtained with `logrus.New()`.
|
||||||
|
const (
|
||||||
|
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||||
|
// message passed to Debug, Info, ...
|
||||||
|
PanicLevel Level = iota
|
||||||
|
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
||||||
|
// logging level is set to Panic.
|
||||||
|
FatalLevel
|
||||||
|
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||||
|
// Commonly used for hooks to send errors to an error tracking service.
|
||||||
|
ErrorLevel
|
||||||
|
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||||
|
WarnLevel
|
||||||
|
// InfoLevel level. General operational entries about what's going on inside the
|
||||||
|
// application.
|
||||||
|
InfoLevel
|
||||||
|
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||||
|
DebugLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||||
|
var _ StdLogger = &log.Logger{}
|
||||||
|
|
||||||
|
// StdLogger is what your logrus-enabled library should take, that way
|
||||||
|
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||||
|
// interface, this is the closest we get, unfortunately.
|
||||||
|
type StdLogger interface {
|
||||||
|
Print(...interface{})
|
||||||
|
Printf(string, ...interface{})
|
||||||
|
Println(...interface{})
|
||||||
|
|
||||||
|
Fatal(...interface{})
|
||||||
|
Fatalf(string, ...interface{})
|
||||||
|
Fatalln(...interface{})
|
||||||
|
|
||||||
|
Panic(...interface{})
|
||||||
|
Panicf(string, ...interface{})
|
||||||
|
Panicln(...interface{})
|
||||||
|
}
|
247
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go
generated
vendored
Normal file
247
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go
generated
vendored
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
var fields Fields
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = new(JSONFormatter)
|
||||||
|
|
||||||
|
log(logger)
|
||||||
|
|
||||||
|
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assertions(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = &TextFormatter{
|
||||||
|
DisableColors: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
log(logger)
|
||||||
|
|
||||||
|
fields := make(map[string]string)
|
||||||
|
for _, kv := range strings.Split(buffer.String(), " ") {
|
||||||
|
if !strings.Contains(kv, "=") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kvArr := strings.Split(kv, "=")
|
||||||
|
key := strings.TrimSpace(kvArr[0])
|
||||||
|
val, err := strconv.Unquote(kvArr[1])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
fields[key] = val
|
||||||
|
}
|
||||||
|
assertions(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrint(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Print("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfo(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarn(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Warn("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["level"], "warning")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln("test", "test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test test")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln("test", 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test 10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln(10, 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "10 10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln(10, 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "10 10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Info("test", 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Info("test", "test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "testtest")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
var fields Fields
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = new(JSONFormatter)
|
||||||
|
|
||||||
|
localLog := logger.WithFields(Fields{
|
||||||
|
"key1": "value1",
|
||||||
|
})
|
||||||
|
|
||||||
|
localLog.WithField("key2", "value2").Info("test")
|
||||||
|
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "value2", fields["key2"])
|
||||||
|
assert.Equal(t, "value1", fields["key1"])
|
||||||
|
|
||||||
|
buffer = bytes.Buffer{}
|
||||||
|
fields = Fields{}
|
||||||
|
localLog.Info("test")
|
||||||
|
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, ok := fields["key2"]
|
||||||
|
assert.Equal(t, false, ok)
|
||||||
|
assert.Equal(t, "value1", fields["key1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("msg", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("msg", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["fields.msg"], "hello")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("time", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["fields.time"], "hello")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("level", 1).Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
assert.Equal(t, fields["fields.level"], 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
|
||||||
|
LogAndAssertText(t, func(log *Logger) {
|
||||||
|
ll := log.WithField("herp", "derp")
|
||||||
|
ll.Info("hello")
|
||||||
|
ll.Info("bye")
|
||||||
|
}, func(fields map[string]string) {
|
||||||
|
for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} {
|
||||||
|
if _, ok := fields[fieldName]; ok {
|
||||||
|
t.Fatalf("should not have prefixed %q: %v", fieldName, fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertLevelToString(t *testing.T) {
|
||||||
|
assert.Equal(t, "debug", DebugLevel.String())
|
||||||
|
assert.Equal(t, "info", InfoLevel.String())
|
||||||
|
assert.Equal(t, "warning", WarnLevel.String())
|
||||||
|
assert.Equal(t, "error", ErrorLevel.String())
|
||||||
|
assert.Equal(t, "fatal", FatalLevel.String())
|
||||||
|
assert.Equal(t, "panic", PanicLevel.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLevel(t *testing.T) {
|
||||||
|
l, err := ParseLevel("panic")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, PanicLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("fatal")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, FatalLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("error")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, ErrorLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("warn")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, WarnLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("warning")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, WarnLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("info")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, InfoLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("debug")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, DebugLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("invalid")
|
||||||
|
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
|
||||||
|
}
|
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
|
||||||
|
*/
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint32
|
||||||
|
Oflag uint32
|
||||||
|
Cflag uint32
|
||||||
|
Lflag uint32
|
||||||
|
Cc [20]uint8
|
||||||
|
Ispeed uint32
|
||||||
|
Ospeed uint32
|
||||||
|
}
|
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TCGETS
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
21
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux,!appengine darwin freebsd
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal() bool {
|
||||||
|
fd := syscall.Stdout
|
||||||
|
var termios Termios
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return err == 0
|
||||||
|
}
|
27
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
var (
|
||||||
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal() bool {
|
||||||
|
fd := syscall.Stdout
|
||||||
|
var st uint32
|
||||||
|
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
return r != 0 && e == 0
|
||||||
|
}
|
95
Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nocolor = 0
|
||||||
|
red = 31
|
||||||
|
green = 32
|
||||||
|
yellow = 33
|
||||||
|
blue = 34
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
baseTimestamp time.Time
|
||||||
|
isTerminal bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
baseTimestamp = time.Now()
|
||||||
|
isTerminal = IsTerminal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func miniTS() int {
|
||||||
|
return int(time.Since(baseTimestamp) / time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextFormatter struct {
|
||||||
|
// Set to true to bypass checking for a TTY before outputting colors.
|
||||||
|
ForceColors bool
|
||||||
|
DisableColors bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
|
||||||
|
var keys []string
|
||||||
|
for k := range entry.Data {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
|
||||||
|
prefixFieldClashes(entry)
|
||||||
|
|
||||||
|
isColored := (f.ForceColors || isTerminal) && !f.DisableColors
|
||||||
|
|
||||||
|
if isColored {
|
||||||
|
printColored(b, entry, keys)
|
||||||
|
} else {
|
||||||
|
f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
|
||||||
|
f.appendKeyValue(b, "level", entry.Level.String())
|
||||||
|
f.appendKeyValue(b, "msg", entry.Message)
|
||||||
|
for _, key := range keys {
|
||||||
|
f.appendKeyValue(b, key, entry.Data[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteByte('\n')
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
|
||||||
|
var levelColor int
|
||||||
|
switch entry.Level {
|
||||||
|
case WarnLevel:
|
||||||
|
levelColor = yellow
|
||||||
|
case ErrorLevel, FatalLevel, PanicLevel:
|
||||||
|
levelColor = red
|
||||||
|
default:
|
||||||
|
levelColor = blue
|
||||||
|
}
|
||||||
|
|
||||||
|
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||||
|
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
|
||||||
|
for _, k := range keys {
|
||||||
|
v := entry.Data[k]
|
||||||
|
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) {
|
||||||
|
switch value.(type) {
|
||||||
|
case string, error:
|
||||||
|
fmt.Fprintf(b, "%v=%q ", key, value)
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(b, "%v=%v ", key, value)
|
||||||
|
}
|
||||||
|
}
|
13
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/.travis.yml
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.1
|
||||||
|
- 1.2
|
||||||
|
- 1.3
|
||||||
|
- tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get github.com/bugsnag/panicwrap
|
||||||
|
- go get github.com/bugsnag/osext
|
||||||
|
- go get github.com/bitly/go-simplejson
|
||||||
|
- go get github.com/revel/revel
|
20
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/LICENSE.txt
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2014 Bugsnag
|
||||||
|
|
||||||
|
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.
|
489
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/README.md
generated
vendored
Normal file
489
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/README.md
generated
vendored
Normal file
@ -0,0 +1,489 @@
|
|||||||
|
Bugsnag Notifier for Golang
|
||||||
|
===========================
|
||||||
|
|
||||||
|
The Bugsnag Notifier for Golang gives you instant notification of panics, or
|
||||||
|
unexpected errors, in your golang app. Any unhandled panics will trigger a
|
||||||
|
notification to be sent to your Bugsnag project.
|
||||||
|
|
||||||
|
[Bugsnag](http://bugsnag.com) captures errors in real-time from your web,
|
||||||
|
mobile and desktop applications, helping you to understand and resolve them
|
||||||
|
as fast as possible. [Create a free account](http://bugsnag.com) to start
|
||||||
|
capturing exceptions from your applications.
|
||||||
|
|
||||||
|
## How to Install
|
||||||
|
|
||||||
|
1. Download the code
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go get github.com/bugsnag/bugsnag-go
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using with net/http apps
|
||||||
|
|
||||||
|
For a golang app based on [net/http](https://godoc.org/net/http), integrating
|
||||||
|
Bugsnag takes two steps. You should also use these instructions if you're using
|
||||||
|
the [gorilla toolkit](http://www.gorillatoolkit.org/), or the
|
||||||
|
[pat](https://github.com/bmizerany/pat/) muxer.
|
||||||
|
|
||||||
|
1. Configure bugsnag at the start of your `main()` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/bugsnag/bugsnag-go"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
APIKey: "YOUR_API_KEY_HERE",
|
||||||
|
ReleaseStage: "production",
|
||||||
|
// more configuration options
|
||||||
|
})
|
||||||
|
|
||||||
|
// rest of your program.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Wrap your server in a [bugsnag.Handler](https://godoc.org/github.com/bugsnag/bugsnag-go/#Handler)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// a. If you're using the builtin http mux, you can just pass
|
||||||
|
// bugsnag.Handler(nil) to http.ListenAndServer
|
||||||
|
http.ListenAndServe(":8080", bugsnag.Handler(nil))
|
||||||
|
|
||||||
|
// b. If you're creating a server manually yourself, you can set
|
||||||
|
// its handlers the same way
|
||||||
|
srv := http.Server{
|
||||||
|
Handler: bugsnag.Handler(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// c. If you're not using the builtin http mux, wrap your own handler
|
||||||
|
// (though make sure that it doesn't already catch panics)
|
||||||
|
http.ListenAndServe(":8080", bugsnag.Handler(handler))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using with Revel apps
|
||||||
|
|
||||||
|
There are two steps to get panic handling in [revel](https://revel.github.io) apps.
|
||||||
|
|
||||||
|
1. Add the `bugsnagrevel.Filter` immediately after the `revel.PanicFilter` in `app/init.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
import "github.com/bugsnag/bugsnag-go/revel"
|
||||||
|
|
||||||
|
revel.Filters = []revel.Filter{
|
||||||
|
revel.PanicFilter,
|
||||||
|
bugsnagrevel.Filter,
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Set bugsnag.apikey in the top section of `conf/app.conf`.
|
||||||
|
|
||||||
|
```
|
||||||
|
module.static=github.com/revel/revel/modules/static
|
||||||
|
|
||||||
|
bugsnag.apikey=YOUR_API_KEY_HERE
|
||||||
|
|
||||||
|
[dev]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using with Google App Engine
|
||||||
|
|
||||||
|
1. Configure bugsnag at the start of your `init()` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/bugsnag/bugsnag-go"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
APIKey: "YOUR_API_KEY_HERE",
|
||||||
|
})
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Wrap *every* http.Handler or http.HandlerFunc with Bugsnag:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// a. If you're using HandlerFuncs
|
||||||
|
http.HandleFunc("/", bugsnag.HandlerFunc(
|
||||||
|
func (w http.ResponseWriter, r *http.Request) {
|
||||||
|
// ...
|
||||||
|
}))
|
||||||
|
|
||||||
|
// b. If you're using Handlers
|
||||||
|
http.Handle("/", bugsnag.Handler(myHttpHandler))
|
||||||
|
```
|
||||||
|
|
||||||
|
3. In order to use Bugsnag, you must provide the current
|
||||||
|
[`appengine.Context`](https://developers.google.com/appengine/docs/go/reference#Context), or
|
||||||
|
current `*http.Request` as rawData. The easiest way to do this is to create a new notifier.
|
||||||
|
|
||||||
|
```go
|
||||||
|
c := appengine.NewContext(r)
|
||||||
|
notifier := bugsnag.New(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
notifier.Notify(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func () {
|
||||||
|
defer notifier.Recover()
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Notifying Bugsnag manually
|
||||||
|
|
||||||
|
Bugsnag will automatically handle any panics that crash your program and notify
|
||||||
|
you of them. If you've integrated with `revel` or `net/http`, then you'll also
|
||||||
|
be notified of any panics() that happen while processing a request.
|
||||||
|
|
||||||
|
Sometimes however it's useful to manually notify Bugsnag of a problem. To do this,
|
||||||
|
call [`bugsnag.Notify()`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Notify)
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err != nil {
|
||||||
|
bugsnag.Notify(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual panic handling
|
||||||
|
|
||||||
|
To avoid a panic in a goroutine from crashing your entire app, you can use
|
||||||
|
[`bugsnag.Recover()`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Recover)
|
||||||
|
to stop a panic from unwinding the stack any further. When `Recover()` is hit,
|
||||||
|
it will send any current panic to Bugsnag and then stop panicking. This is
|
||||||
|
most useful at the start of a goroutine:
|
||||||
|
|
||||||
|
```go
|
||||||
|
go func() {
|
||||||
|
defer bugsnag.Recover()
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}()
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively you can use
|
||||||
|
[`bugsnag.AutoNotify()`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Recover)
|
||||||
|
to notify bugsnag of a panic while letting the program continue to panic. This
|
||||||
|
is useful if you're using a Framework that already has some handling of panics
|
||||||
|
and you are retrofitting bugsnag support.
|
||||||
|
|
||||||
|
```go
|
||||||
|
defer bugsnag.AutoNotify()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sending Custom Data
|
||||||
|
|
||||||
|
Most functions in the Bugsnag API, including `bugsnag.Notify()`,
|
||||||
|
`bugsnag.Recover()`, `bugsnag.AutoNotify()`, and `bugsnag.Handler()` let you
|
||||||
|
attach data to the notifications that they send. To do this you pass in rawData,
|
||||||
|
which can be any of the supported types listed here. To add support for more
|
||||||
|
types of rawData see [OnBeforeNotify](#custom-data-with-onbeforenotify).
|
||||||
|
|
||||||
|
### Custom MetaData
|
||||||
|
|
||||||
|
Custom metaData appears as tabs on Bugsnag.com. You can set it by passing
|
||||||
|
a [`bugsnag.MetaData`](https://godoc.org/github.com/bugsnag/bugsnag-go/#MetaData)
|
||||||
|
object as rawData.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Notify(err,
|
||||||
|
bugsnag.MetaData{
|
||||||
|
"Account": {
|
||||||
|
"Name": Account.Name,
|
||||||
|
"Paying": Account.Plan.Premium,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request data
|
||||||
|
|
||||||
|
Bugsnag can extract interesting data from
|
||||||
|
[`*http.Request`](https://godoc.org/net/http/#Request) objects, and
|
||||||
|
[`*revel.Controller`](https://godoc.org/github.com/revel/revel/#Controller)
|
||||||
|
objects. These are automatically passed in when handling panics, and you can
|
||||||
|
pass them yourself.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (w http.ResponseWriter, r *http.Request) {
|
||||||
|
bugsnag.Notify(err, r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### User data
|
||||||
|
|
||||||
|
User data is searchable, and the `Id` powers the count of users affected. You
|
||||||
|
can set which user an error affects by passing a
|
||||||
|
[`bugsnag.User`](https://godoc.org/github.com/bugsnag/bugsnag-go/#User) object as
|
||||||
|
rawData.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Notify(err,
|
||||||
|
bugsnag.User{Id: "1234", Name: "Conrad", Email: "me@cirw.in"})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context
|
||||||
|
|
||||||
|
The context shows up prominently in the list view so that you can get an idea
|
||||||
|
of where a problem occurred. You can set it by passing a
|
||||||
|
[`bugsnag.Context`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Context)
|
||||||
|
object as rawData.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Notify(err, bugsnag.Context{"backgroundJob"})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Severity
|
||||||
|
|
||||||
|
Bugsnag supports three severities, `SeverityError`, `SeverityWarning`, and `SeverityInfo`.
|
||||||
|
You can set the severity of an error by passing one of these objects as rawData.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Notify(err, bugsnag.SeverityInfo)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
You must call `bugsnag.Configure()` at the start of your program to use Bugsnag, you pass it
|
||||||
|
a [`bugsnag.Configuration`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Configuration) object
|
||||||
|
containing any of the following values.
|
||||||
|
|
||||||
|
### APIKey
|
||||||
|
|
||||||
|
The Bugsnag API key can be found on your [Bugsnag dashboard](https://bugsnag.com) under "Settings".
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
APIKey: "YOUR_API_KEY_HERE",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Endpoint
|
||||||
|
|
||||||
|
The Bugsnag endpoint defaults to `https://notify.bugsnag.com/`. If you're using Bugsnag enterprise,
|
||||||
|
you should set this to the endpoint of your local instance.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
Endpoint: "http://bugsnag.internal:49000/",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### ReleaseStage
|
||||||
|
|
||||||
|
The ReleaseStage tracks where your app is deployed. You should set this to `production`, `staging`,
|
||||||
|
`development` or similar as appropriate.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
ReleaseStage: "development",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### NotifyReleaseStages
|
||||||
|
|
||||||
|
The list of ReleaseStages to notify in. By default Bugsnag will notify you in all release stages, but
|
||||||
|
you can use this to silence development errors.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
NotifyReleaseStages: []string{"production", "staging"},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### AppVersion
|
||||||
|
|
||||||
|
If you use a versioning scheme for deploys of your app, Bugsnag can use the `AppVersion` to only
|
||||||
|
re-open errors if they occur in later version of the app.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
AppVersion: "1.2.3",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hostname
|
||||||
|
|
||||||
|
The hostname is used to track where exceptions are coming from in the Bugsnag dashboard. The
|
||||||
|
default value is obtained from `os.Hostname()` so you won't often need to change this.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
Hostname: "go1",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### ProjectPackages
|
||||||
|
|
||||||
|
In order to determine where a crash happens Bugsnag needs to know which packages you consider to
|
||||||
|
be part of your app (as opposed to a library). By default this is set to `[]string{"main*"}`. Strings
|
||||||
|
are matched to package names using [`filepath.Match`](http://godoc.org/path/filepath#Match).
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
ProjectPackages: []string{"main", "github.com/domain/myapp/*"},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ParamsFilters
|
||||||
|
|
||||||
|
Sometimes sensitive data is accidentally included in Bugsnag MetaData. You can remove it by
|
||||||
|
setting `ParamsFilters`. Any key in the `MetaData` that includes any string in the filters
|
||||||
|
will be redacted. The default is `[]string{"password", "secret"}`, which prevents fields like
|
||||||
|
`password`, `password_confirmation` and `secret_answer` from being sent.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
ParamsFilters: []string{"password", "secret"},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logger
|
||||||
|
|
||||||
|
The Logger to write to in case of an error inside Bugsnag. This defaults to the global logger.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
Logger: app.Logger,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PanicHandler
|
||||||
|
|
||||||
|
The first time Bugsnag is configured, it wraps the running program in a panic
|
||||||
|
handler using [panicwrap](http://godoc.org/github.com/ConradIrwin/panicwrap). This
|
||||||
|
forks a sub-process which monitors unhandled panics. To prevent this, set
|
||||||
|
`PanicHandler` to `func() {}` the first time you call
|
||||||
|
`bugsnag.Configure`. This will prevent bugsnag from being able to notify you about
|
||||||
|
unhandled panics.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
PanicHandler: func() {},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Synchronous
|
||||||
|
|
||||||
|
Bugsnag usually starts a new goroutine before sending notifications. This means
|
||||||
|
that notifications can be lost if you do a bugsnag.Notify and then immediately
|
||||||
|
os.Exit. To avoid this problem, set Bugsnag to Synchronous (or just `panic()`
|
||||||
|
instead ;).
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
Synchronous: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Or just for one error:
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Notify(err, bugsnag.Configuration{Synchronous: true})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transport
|
||||||
|
|
||||||
|
The transport configures how Bugsnag makes http requests. By default we use
|
||||||
|
[`http.DefaultTransport`](http://godoc.org/net/http#RoundTripper) which handles
|
||||||
|
HTTP proxies automatically using the `$HTTP_PROXY` environment variable.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
Transport: http.DefaultTransport,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom data with OnBeforeNotify
|
||||||
|
|
||||||
|
While it's nice that you can pass `MetaData` directly into `bugsnag.Notify`,
|
||||||
|
`bugsnag.AutoNotify`, and `bugsnag.Recover`, this can be a bit cumbersome and
|
||||||
|
inefficient — you're constructing the meta-data whether or not it will actually
|
||||||
|
be used. A better idea is to pass raw data in to these functions, and add an
|
||||||
|
`OnBeforeNotify` filter that converts them into `MetaData`.
|
||||||
|
|
||||||
|
For example, lets say our system processes jobs:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Job struct{
|
||||||
|
Retry bool
|
||||||
|
UserId string
|
||||||
|
UserEmail string
|
||||||
|
Name string
|
||||||
|
Params map[string]string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can pass a job directly into Bugsnag.notify:
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Notify(err, job)
|
||||||
|
```
|
||||||
|
|
||||||
|
And then add a filter to extract information from that job and attach it to the
|
||||||
|
Bugsnag event:
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.OnBeforeNotify(
|
||||||
|
func(event *bugsnag.Event, config *bugsnag.Configuration) error {
|
||||||
|
|
||||||
|
// Search all the RawData for any *Job pointers that we're passed in
|
||||||
|
// to bugsnag.Notify() and friends.
|
||||||
|
for _, datum := range event.RawData {
|
||||||
|
if job, ok := datum.(*Job); ok {
|
||||||
|
// don't notify bugsnag about errors in retries
|
||||||
|
if job.Retry {
|
||||||
|
return fmt.Errorf("not notifying about retried jobs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the job as a tab on Bugsnag.com
|
||||||
|
event.MetaData.AddStruct("Job", job)
|
||||||
|
|
||||||
|
// set the user correctly
|
||||||
|
event.User = &User{Id: job.UserId, Email: job.UserEmail}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue notifying as normal
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
If you want to have multiple different configurations around in one program,
|
||||||
|
you can use `bugsnag.New()` to create multiple independent instances of
|
||||||
|
Bugsnag. You can use these without calling `bugsnag.Configure()`, but bear in
|
||||||
|
mind that until you call `bugsnag.Configure()` unhandled panics will not be
|
||||||
|
sent to bugsnag.
|
||||||
|
|
||||||
|
```go
|
||||||
|
notifier := bugsnag.New(bugsnag.Configuration{
|
||||||
|
APIKey: "YOUR_OTHER_API_KEY",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
In fact any place that lets you pass in `rawData` also allows you to pass in
|
||||||
|
configuration. For example to send http errors to one bugsnag project, you
|
||||||
|
could do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Handler(nil, bugsnag.Configuration{APIKey: "YOUR_OTHER_API_KEY"})
|
||||||
|
```
|
||||||
|
|
||||||
|
### GroupingHash
|
||||||
|
|
||||||
|
If you need to override Bugsnag's grouping algorithm, you can set the
|
||||||
|
`GroupingHash` in an `OnBeforeNotify`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.OnBeforeNotify(
|
||||||
|
func (event *bugsnag.Event, config *bugsnag.Configuration) error {
|
||||||
|
event.GroupingHash = calculateGroupingHash(event)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
76
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/appengine.go
generated
vendored
Normal file
76
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/appengine.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"appengine"
|
||||||
|
"appengine/urlfetch"
|
||||||
|
"appengine/user"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func defaultPanicHandler() {}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
OnBeforeNotify(appengineMiddleware)
|
||||||
|
}
|
||||||
|
|
||||||
|
func appengineMiddleware(event *Event, config *Configuration) (err error) {
|
||||||
|
var c appengine.Context
|
||||||
|
|
||||||
|
for _, datum := range event.RawData {
|
||||||
|
if r, ok := datum.(*http.Request); ok {
|
||||||
|
c = appengine.NewContext(r)
|
||||||
|
break
|
||||||
|
} else if context, ok := datum.(appengine.Context); ok {
|
||||||
|
c = context
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
return fmt.Errorf("No appengine context given")
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can only use the builtin http library if you pay for appengine,
|
||||||
|
// so we use the appengine urlfetch service instead.
|
||||||
|
config.Transport = &urlfetch.Transport{
|
||||||
|
Context: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything written to stderr/stdout is discarded, so lets log to the request.
|
||||||
|
config.Logger = log.New(appengineWriter{c}, config.Logger.Prefix(), config.Logger.Flags())
|
||||||
|
|
||||||
|
// Set the releaseStage appropriately
|
||||||
|
if config.ReleaseStage == "" {
|
||||||
|
if appengine.IsDevAppServer() {
|
||||||
|
config.ReleaseStage = "development"
|
||||||
|
} else {
|
||||||
|
config.ReleaseStage = "production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.User == nil {
|
||||||
|
u := user.Current(c)
|
||||||
|
if u != nil {
|
||||||
|
event.User = &User{
|
||||||
|
Id: u.ID,
|
||||||
|
Email: u.Email,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert an appengine.Context into an io.Writer so we can create a log.Logger.
|
||||||
|
type appengineWriter struct {
|
||||||
|
appengine.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c appengineWriter) Write(b []byte) (int, error) {
|
||||||
|
c.Warningf(string(b))
|
||||||
|
return len(b), nil
|
||||||
|
}
|
131
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/bugsnag.go
generated
vendored
Normal file
131
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/bugsnag.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bugsnag/bugsnag-go/errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
// Fixes a bug with SHA-384 intermediate certs on some platforms.
|
||||||
|
// - https://github.com/bugsnag/bugsnag-go/issues/9
|
||||||
|
_ "crypto/sha512"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The current version of bugsnag-go.
|
||||||
|
const VERSION = "1.0.2"
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
|
var middleware middlewareStack
|
||||||
|
|
||||||
|
// The configuration for the default bugsnag notifier.
|
||||||
|
var Config Configuration
|
||||||
|
|
||||||
|
var defaultNotifier = Notifier{&Config, nil}
|
||||||
|
|
||||||
|
// Configure Bugsnag. The only required setting is the APIKey, which can be
|
||||||
|
// obtained by clicking on "Settings" in your Bugsnag dashboard. This function
|
||||||
|
// is also responsible for installing the global panic handler, so it should be
|
||||||
|
// called as early as possible in your initialization process.
|
||||||
|
func Configure(config Configuration) {
|
||||||
|
Config.update(&config)
|
||||||
|
once.Do(Config.PanicHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify sends an error to Bugsnag along with the current stack trace. The
|
||||||
|
// rawData is used to send extra information along with the error. For example
|
||||||
|
// you can pass the current http.Request to Bugsnag to see information about it
|
||||||
|
// in the dashboard, or set the severity of the notification.
|
||||||
|
func Notify(err error, rawData ...interface{}) error {
|
||||||
|
return defaultNotifier.Notify(errors.New(err, 1), rawData...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoNotify logs a panic on a goroutine and then repanics.
|
||||||
|
// It should only be used in places that have existing panic handlers further
|
||||||
|
// up the stack. See bugsnag.Recover(). The rawData is used to send extra
|
||||||
|
// information along with any panics that are handled this way.
|
||||||
|
// Usage: defer bugsnag.AutoNotify()
|
||||||
|
func AutoNotify(rawData ...interface{}) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
rawData = defaultNotifier.addDefaultSeverity(rawData, SeverityError)
|
||||||
|
defaultNotifier.Notify(errors.New(err, 2), rawData...)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recover logs a panic on a goroutine and then recovers.
|
||||||
|
// The rawData is used to send extra information along with
|
||||||
|
// any panics that are handled this way
|
||||||
|
// Usage: defer bugsnag.Recover()
|
||||||
|
func Recover(rawData ...interface{}) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
rawData = defaultNotifier.addDefaultSeverity(rawData, SeverityWarning)
|
||||||
|
defaultNotifier.Notify(errors.New(err, 2), rawData...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnBeforeNotify adds a callback to be run before a notification is sent to
|
||||||
|
// Bugsnag. It can be used to modify the event or its MetaData. Changes made
|
||||||
|
// to the configuration are local to notifying about this event. To prevent the
|
||||||
|
// event from being sent to Bugsnag return an error, this error will be
|
||||||
|
// returned from bugsnag.Notify() and the event will not be sent.
|
||||||
|
func OnBeforeNotify(callback func(event *Event, config *Configuration) error) {
|
||||||
|
middleware.OnBeforeNotify(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler creates an http Handler that notifies Bugsnag any panics that
|
||||||
|
// happen. It then repanics so that the default http Server panic handler can
|
||||||
|
// handle the panic too. The rawData is used to send extra information along
|
||||||
|
// with any panics that are handled this way.
|
||||||
|
func Handler(h http.Handler, rawData ...interface{}) http.Handler {
|
||||||
|
notifier := New(rawData...)
|
||||||
|
if h == nil {
|
||||||
|
h = http.DefaultServeMux
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer notifier.AutoNotify(r)
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFunc creates an http HandlerFunc that notifies Bugsnag about any
|
||||||
|
// panics that happen. It then repanics so that the default http Server panic
|
||||||
|
// handler can handle the panic too. The rawData is used to send extra
|
||||||
|
// information along with any panics that are handled this way. If you have
|
||||||
|
// already wrapped your http server using bugsnag.Handler() you don't also need
|
||||||
|
// to wrap each HandlerFunc.
|
||||||
|
func HandlerFunc(h http.HandlerFunc, rawData ...interface{}) http.HandlerFunc {
|
||||||
|
notifier := New(rawData...)
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer notifier.AutoNotify(r)
|
||||||
|
h(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Set up builtin middlewarez
|
||||||
|
OnBeforeNotify(httpRequestMiddleware)
|
||||||
|
|
||||||
|
// Default configuration
|
||||||
|
Config.update(&Configuration{
|
||||||
|
APIKey: "",
|
||||||
|
Endpoint: "https://notify.bugsnag.com/",
|
||||||
|
Hostname: "",
|
||||||
|
AppVersion: "",
|
||||||
|
ReleaseStage: "",
|
||||||
|
ParamsFilters: []string{"password", "secret"},
|
||||||
|
// * for app-engine
|
||||||
|
ProjectPackages: []string{"main*"},
|
||||||
|
NotifyReleaseStages: nil,
|
||||||
|
Logger: log.New(os.Stdout, log.Prefix(), log.Flags()),
|
||||||
|
PanicHandler: defaultPanicHandler,
|
||||||
|
Transport: http.DefaultTransport,
|
||||||
|
})
|
||||||
|
|
||||||
|
hostname, err := os.Hostname()
|
||||||
|
if err == nil {
|
||||||
|
Config.Hostname = hostname
|
||||||
|
}
|
||||||
|
}
|
461
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/bugsnag_test.go
generated
vendored
Normal file
461
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/bugsnag_test.go
generated
vendored
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bitly/go-simplejson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigure(t *testing.T) {
|
||||||
|
Configure(Configuration{
|
||||||
|
APIKey: testAPIKey,
|
||||||
|
})
|
||||||
|
|
||||||
|
if Config.APIKey != testAPIKey {
|
||||||
|
t.Errorf("Setting APIKey didn't work")
|
||||||
|
}
|
||||||
|
|
||||||
|
if New().Config.APIKey != testAPIKey {
|
||||||
|
t.Errorf("Setting APIKey didn't work for new notifiers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var postedJSON = make(chan []byte, 10)
|
||||||
|
var testOnce sync.Once
|
||||||
|
var testEndpoint string
|
||||||
|
var testAPIKey = "166f5ad3590596f9aa8d601ea89af845"
|
||||||
|
|
||||||
|
func startTestServer() {
|
||||||
|
testOnce.Do(func() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
postedJSON <- body
|
||||||
|
})
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
testEndpoint = "http://" + l.Addr().String() + "/"
|
||||||
|
|
||||||
|
go http.Serve(l, mux)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type _recurse struct {
|
||||||
|
*_recurse
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
startTestServer()
|
||||||
|
|
||||||
|
recurse := _recurse{}
|
||||||
|
recurse._recurse = &recurse
|
||||||
|
|
||||||
|
OnBeforeNotify(func(event *Event, config *Configuration) error {
|
||||||
|
if event.Context == "testing" {
|
||||||
|
event.GroupingHash = "lol"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
Notify(fmt.Errorf("hello world"),
|
||||||
|
Configuration{
|
||||||
|
APIKey: testAPIKey,
|
||||||
|
Endpoint: testEndpoint,
|
||||||
|
ReleaseStage: "test",
|
||||||
|
AppVersion: "1.2.3",
|
||||||
|
Hostname: "web1",
|
||||||
|
ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"},
|
||||||
|
},
|
||||||
|
User{Id: "123", Name: "Conrad", Email: "me@cirw.in"},
|
||||||
|
Context{"testing"},
|
||||||
|
MetaData{"test": {
|
||||||
|
"password": "sneaky",
|
||||||
|
"value": "able",
|
||||||
|
"broken": complex(1, 2),
|
||||||
|
"recurse": recurse,
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
|
||||||
|
json, err := simplejson.NewJson(<-postedJSON)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if json.Get("apiKey").MustString() != testAPIKey {
|
||||||
|
t.Errorf("Wrong api key in payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
if json.GetPath("notifier", "name").MustString() != "Bugsnag Go" {
|
||||||
|
t.Errorf("Wrong notifier name in payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
event := json.Get("events").GetIndex(0)
|
||||||
|
|
||||||
|
for k, value := range map[string]string{
|
||||||
|
"payloadVersion": "2",
|
||||||
|
"severity": "warning",
|
||||||
|
"context": "testing",
|
||||||
|
"groupingHash": "lol",
|
||||||
|
"app.releaseStage": "test",
|
||||||
|
"app.version": "1.2.3",
|
||||||
|
"device.hostname": "web1",
|
||||||
|
"user.id": "123",
|
||||||
|
"user.name": "Conrad",
|
||||||
|
"user.email": "me@cirw.in",
|
||||||
|
"metaData.test.password": "[REDACTED]",
|
||||||
|
"metaData.test.value": "able",
|
||||||
|
"metaData.test.broken": "[complex128]",
|
||||||
|
"metaData.test.recurse._recurse": "[RECURSION]",
|
||||||
|
} {
|
||||||
|
key := strings.Split(k, ".")
|
||||||
|
if event.GetPath(key...).MustString() != value {
|
||||||
|
t.Errorf("Wrong %v: %v != %v", key, event.GetPath(key...).MustString(), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exception := event.Get("exceptions").GetIndex(0)
|
||||||
|
|
||||||
|
if exception.Get("message").MustString() != "hello world" {
|
||||||
|
t.Errorf("Wrong message in payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exception.Get("errorClass").MustString() != "*errors.errorString" {
|
||||||
|
t.Errorf("Wrong errorClass in payload: %v", exception.Get("errorClass").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
frame0 := exception.Get("stacktrace").GetIndex(0)
|
||||||
|
if frame0.Get("file").MustString() != "bugsnag_test.go" ||
|
||||||
|
frame0.Get("method").MustString() != "TestNotify" ||
|
||||||
|
frame0.Get("inProject").MustBool() != true ||
|
||||||
|
frame0.Get("lineNumber").MustInt() == 0 {
|
||||||
|
t.Errorf("Wrong frame0")
|
||||||
|
}
|
||||||
|
|
||||||
|
frame1 := exception.Get("stacktrace").GetIndex(1)
|
||||||
|
|
||||||
|
if frame1.Get("file").MustString() != "testing/testing.go" ||
|
||||||
|
frame1.Get("method").MustString() != "tRunner" ||
|
||||||
|
frame1.Get("inProject").MustBool() != false ||
|
||||||
|
frame1.Get("lineNumber").MustInt() == 0 {
|
||||||
|
t.Errorf("Wrong frame1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func crashyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c := make(chan int)
|
||||||
|
close(c)
|
||||||
|
c <- 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCrashyServer(rawData ...interface{}) (net.Listener, error) {
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", crashyHandler)
|
||||||
|
srv := http.Server{
|
||||||
|
Addr: l.Addr().String(),
|
||||||
|
Handler: Handler(mux, rawData...),
|
||||||
|
ErrorLog: log.New(ioutil.Discard, log.Prefix(), 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
go srv.Serve(l)
|
||||||
|
return l, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandler(t *testing.T) {
|
||||||
|
startTestServer()
|
||||||
|
|
||||||
|
l, err := runCrashyServer(Configuration{
|
||||||
|
APIKey: testAPIKey,
|
||||||
|
Endpoint: testEndpoint,
|
||||||
|
ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"},
|
||||||
|
Logger: log.New(ioutil.Discard, log.Prefix(), log.Flags()),
|
||||||
|
}, SeverityInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
http.Get("http://" + l.Addr().String() + "/ok?foo=bar")
|
||||||
|
l.Close()
|
||||||
|
|
||||||
|
json, err := simplejson.NewJson(<-postedJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if json.Get("apiKey").MustString() != testAPIKey {
|
||||||
|
t.Errorf("Wrong api key in payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
if json.GetPath("notifier", "name").MustString() != "Bugsnag Go" {
|
||||||
|
t.Errorf("Wrong notifier name in payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
event := json.Get("events").GetIndex(0)
|
||||||
|
|
||||||
|
for k, value := range map[string]string{
|
||||||
|
"payloadVersion": "2",
|
||||||
|
"severity": "info",
|
||||||
|
"user.id": "127.0.0.1",
|
||||||
|
"metaData.Request.Url": "http://" + l.Addr().String() + "/ok?foo=bar",
|
||||||
|
"metaData.Request.Method": "GET",
|
||||||
|
} {
|
||||||
|
key := strings.Split(k, ".")
|
||||||
|
if event.GetPath(key...).MustString() != value {
|
||||||
|
t.Errorf("Wrong %v: %v != %v", key, event.GetPath(key...).MustString(), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.GetPath("metaData", "Request", "Params", "foo").GetIndex(0).MustString() != "bar" {
|
||||||
|
t.Errorf("missing GET params in request metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.GetPath("metaData", "Headers", "Accept-Encoding").GetIndex(0).MustString() != "gzip" {
|
||||||
|
t.Errorf("missing GET params in request metadata: %v", event.GetPath("metaData", "Headers"))
|
||||||
|
}
|
||||||
|
|
||||||
|
exception := event.Get("exceptions").GetIndex(0)
|
||||||
|
|
||||||
|
if exception.Get("message").MustString() != "runtime error: send on closed channel" {
|
||||||
|
t.Errorf("Wrong message in payload: %v", exception.Get("message").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if exception.Get("errorClass").MustString() != "runtime.errorCString" {
|
||||||
|
t.Errorf("Wrong errorClass in payload: %v", exception.Get("errorClass").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:CI these are probably dependent on go version.
|
||||||
|
frame0 := exception.Get("stacktrace").GetIndex(0)
|
||||||
|
if frame0.Get("file").MustString() != "runtime/panic.c" ||
|
||||||
|
frame0.Get("method").MustString() != "panicstring" ||
|
||||||
|
frame0.Get("inProject").MustBool() != false ||
|
||||||
|
frame0.Get("lineNumber").MustInt() == 0 {
|
||||||
|
t.Errorf("Wrong frame0: %v", frame0)
|
||||||
|
}
|
||||||
|
|
||||||
|
frame3 := exception.Get("stacktrace").GetIndex(3)
|
||||||
|
|
||||||
|
if frame3.Get("file").MustString() != "bugsnag_test.go" ||
|
||||||
|
frame3.Get("method").MustString() != "crashyHandler" ||
|
||||||
|
frame3.Get("inProject").MustBool() != true ||
|
||||||
|
frame3.Get("lineNumber").MustInt() == 0 {
|
||||||
|
t.Errorf("Wrong frame3: %v", frame3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAutoNotify(t *testing.T) {
|
||||||
|
|
||||||
|
var panicked interface{}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
panicked = recover()
|
||||||
|
}()
|
||||||
|
defer AutoNotify(Configuration{Endpoint: testEndpoint, APIKey: testAPIKey})
|
||||||
|
|
||||||
|
panic("eggs")
|
||||||
|
}()
|
||||||
|
|
||||||
|
if panicked.(string) != "eggs" {
|
||||||
|
t.Errorf("didn't re-panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := simplejson.NewJson(<-postedJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
event := json.Get("events").GetIndex(0)
|
||||||
|
|
||||||
|
if event.Get("severity").MustString() != "error" {
|
||||||
|
t.Errorf("severity should be error")
|
||||||
|
}
|
||||||
|
exception := event.Get("exceptions").GetIndex(0)
|
||||||
|
|
||||||
|
if exception.Get("message").MustString() != "eggs" {
|
||||||
|
t.Errorf("caught wrong panic")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecover(t *testing.T) {
|
||||||
|
var panicked interface{}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
panicked = recover()
|
||||||
|
}()
|
||||||
|
defer Recover(Configuration{Endpoint: testEndpoint, APIKey: testAPIKey})
|
||||||
|
|
||||||
|
panic("ham")
|
||||||
|
}()
|
||||||
|
|
||||||
|
if panicked != nil {
|
||||||
|
t.Errorf("re-panick'd")
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := simplejson.NewJson(<-postedJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
event := json.Get("events").GetIndex(0)
|
||||||
|
|
||||||
|
if event.Get("severity").MustString() != "warning" {
|
||||||
|
t.Errorf("severity should be warning")
|
||||||
|
}
|
||||||
|
exception := event.Get("exceptions").GetIndex(0)
|
||||||
|
|
||||||
|
if exception.Get("message").MustString() != "ham" {
|
||||||
|
t.Errorf("caught wrong panic")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var createAccount = handleGet
|
||||||
|
|
||||||
|
type _job struct {
|
||||||
|
Name string
|
||||||
|
Process func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAutoNotify() interface{} {
|
||||||
|
return func(w http.ResponseWriter, request *http.Request) {
|
||||||
|
defer AutoNotify(request, Context{"createAccount"})
|
||||||
|
|
||||||
|
createAccount(w, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRecover(job _job) {
|
||||||
|
go func() {
|
||||||
|
defer Recover(Context{job.Name}, SeverityWarning)
|
||||||
|
|
||||||
|
job.Process()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleConfigure() {
|
||||||
|
Configure(Configuration{
|
||||||
|
APIKey: "YOUR_API_KEY_HERE",
|
||||||
|
|
||||||
|
ReleaseStage: "production",
|
||||||
|
|
||||||
|
// See Configuration{} for other fields
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleHandler() {
|
||||||
|
// Set up your http handlers as usual
|
||||||
|
http.HandleFunc("/", handleGet)
|
||||||
|
|
||||||
|
// use bugsnag.Handler(nil) to wrap the default http handlers
|
||||||
|
// so that Bugsnag is automatically notified about panics.
|
||||||
|
http.ListenAndServe(":1234", Handler(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleHandler_customServer() {
|
||||||
|
// If you're using a custom server, set the handlers explicitly.
|
||||||
|
http.HandleFunc("/", handleGet)
|
||||||
|
|
||||||
|
srv := http.Server{
|
||||||
|
Addr: ":1234",
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
// use bugsnag.Handler(nil) to wrap the default http handlers
|
||||||
|
// so that Bugsnag is automatically notified about panics.
|
||||||
|
Handler: Handler(nil),
|
||||||
|
}
|
||||||
|
srv.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleHandler_customHandlers() {
|
||||||
|
// If you're using custom handlers, wrap the handlers explicitly.
|
||||||
|
handler := http.NewServeMux()
|
||||||
|
http.HandleFunc("/", handleGet)
|
||||||
|
// use bugsnag.Handler(handler) to wrap the handlers so that Bugsnag is
|
||||||
|
// automatically notified about panics
|
||||||
|
http.ListenAndServe(":1234", Handler(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNotify() {
|
||||||
|
_, err := net.Listen("tcp", ":80")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
Notify(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNotify_details(userID string) {
|
||||||
|
_, err := net.Listen("tcp", ":80")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
Notify(err,
|
||||||
|
// show as low-severity
|
||||||
|
SeverityInfo,
|
||||||
|
// set the context
|
||||||
|
Context{"createlistener"},
|
||||||
|
// pass the user id in to count users affected.
|
||||||
|
User{Id: userID},
|
||||||
|
// custom meta-data tab
|
||||||
|
MetaData{
|
||||||
|
"Listen": {
|
||||||
|
"Protocol": "tcp",
|
||||||
|
"Port": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Job struct {
|
||||||
|
Retry bool
|
||||||
|
UserId string
|
||||||
|
UserEmail string
|
||||||
|
Name string
|
||||||
|
Params map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleOnBeforeNotify() {
|
||||||
|
OnBeforeNotify(func(event *Event, config *Configuration) error {
|
||||||
|
|
||||||
|
// Search all the RawData for any *Job pointers that we're passed in
|
||||||
|
// to bugsnag.Notify() and friends.
|
||||||
|
for _, datum := range event.RawData {
|
||||||
|
if job, ok := datum.(*Job); ok {
|
||||||
|
// don't notify bugsnag about errors in retries
|
||||||
|
if job.Retry {
|
||||||
|
return fmt.Errorf("bugsnag middleware: not notifying about job retry")
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the job as a tab on Bugsnag.com
|
||||||
|
event.MetaData.AddStruct("Job", job)
|
||||||
|
|
||||||
|
// set the user correctly
|
||||||
|
event.User = &User{Id: job.UserId, Email: job.UserEmail}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue notifying as normal
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
159
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/configuration.go
generated
vendored
Normal file
159
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/configuration.go
generated
vendored
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configuration sets up and customizes communication with the Bugsnag API.
|
||||||
|
type Configuration struct {
|
||||||
|
// Your Bugsnag API key, e.g. "c9d60ae4c7e70c4b6c4ebd3e8056d2b8". You can
|
||||||
|
// find this by clicking Settings on https://bugsnag.com/.
|
||||||
|
APIKey string
|
||||||
|
// The Endpoint to notify about crashes. This defaults to
|
||||||
|
// "https://notify.bugsnag.com/", if you're using Bugsnag Enterprise then
|
||||||
|
// set it to your internal Bugsnag endpoint.
|
||||||
|
Endpoint string
|
||||||
|
|
||||||
|
// The current release stage. This defaults to "production" and is used to
|
||||||
|
// filter errors in the Bugsnag dashboard.
|
||||||
|
ReleaseStage string
|
||||||
|
// The currently running version of the app. This is used to filter errors
|
||||||
|
// in the Bugsnag dasboard. If you set this then Bugsnag will only re-open
|
||||||
|
// resolved errors if they happen in different app versions.
|
||||||
|
AppVersion string
|
||||||
|
// The hostname of the current server. This defaults to the return value of
|
||||||
|
// os.Hostname() and is graphed in the Bugsnag dashboard.
|
||||||
|
Hostname string
|
||||||
|
|
||||||
|
// The Release stages to notify in. If you set this then bugsnag-go will
|
||||||
|
// only send notifications to Bugsnag if the ReleaseStage is listed here.
|
||||||
|
NotifyReleaseStages []string
|
||||||
|
|
||||||
|
// packages that are part of your app. Bugsnag uses this to determine how
|
||||||
|
// to group errors and how to display them on your dashboard. You should
|
||||||
|
// include any packages that are part of your app, and exclude libraries
|
||||||
|
// and helpers. You can list wildcards here, and they'll be expanded using
|
||||||
|
// filepath.Glob. The default value is []string{"main*"}
|
||||||
|
ProjectPackages []string
|
||||||
|
|
||||||
|
// Any meta-data that matches these filters will be marked as [REDACTED]
|
||||||
|
// before sending a Notification to Bugsnag. It defaults to
|
||||||
|
// []string{"password", "secret"} so that request parameters like password,
|
||||||
|
// password_confirmation and auth_secret will not be sent to Bugsnag.
|
||||||
|
ParamsFilters []string
|
||||||
|
|
||||||
|
// The PanicHandler is used by Bugsnag to catch unhandled panics in your
|
||||||
|
// application. The default panicHandler uses mitchellh's panicwrap library,
|
||||||
|
// and you can disable this feature by passing an empty: func() {}
|
||||||
|
PanicHandler func()
|
||||||
|
|
||||||
|
// The logger that Bugsnag should log to. Uses the same defaults as go's
|
||||||
|
// builtin logging package. bugsnag-go logs whenever it notifies Bugsnag
|
||||||
|
// of an error, and when any error occurs inside the library itself.
|
||||||
|
Logger *log.Logger
|
||||||
|
// The http Transport to use, defaults to the default http Transport. This
|
||||||
|
// can be configured if you are in an environment like Google App Engine
|
||||||
|
// that has stringent conditions on making http requests.
|
||||||
|
Transport http.RoundTripper
|
||||||
|
// Whether bugsnag should notify synchronously. This defaults to false which
|
||||||
|
// causes bugsnag-go to spawn a new goroutine for each notification.
|
||||||
|
Synchronous bool
|
||||||
|
// TODO: remember to update the update() function when modifying this struct
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *Configuration) update(other *Configuration) *Configuration {
|
||||||
|
if other.APIKey != "" {
|
||||||
|
config.APIKey = other.APIKey
|
||||||
|
}
|
||||||
|
if other.Endpoint != "" {
|
||||||
|
config.Endpoint = other.Endpoint
|
||||||
|
}
|
||||||
|
if other.Hostname != "" {
|
||||||
|
config.Hostname = other.Hostname
|
||||||
|
}
|
||||||
|
if other.AppVersion != "" {
|
||||||
|
config.AppVersion = other.AppVersion
|
||||||
|
}
|
||||||
|
if other.ReleaseStage != "" {
|
||||||
|
config.ReleaseStage = other.ReleaseStage
|
||||||
|
}
|
||||||
|
if other.ParamsFilters != nil {
|
||||||
|
config.ParamsFilters = other.ParamsFilters
|
||||||
|
}
|
||||||
|
if other.ProjectPackages != nil {
|
||||||
|
config.ProjectPackages = other.ProjectPackages
|
||||||
|
}
|
||||||
|
if other.Logger != nil {
|
||||||
|
config.Logger = other.Logger
|
||||||
|
}
|
||||||
|
if other.NotifyReleaseStages != nil {
|
||||||
|
config.NotifyReleaseStages = other.NotifyReleaseStages
|
||||||
|
}
|
||||||
|
if other.PanicHandler != nil {
|
||||||
|
config.PanicHandler = other.PanicHandler
|
||||||
|
}
|
||||||
|
if other.Transport != nil {
|
||||||
|
config.Transport = other.Transport
|
||||||
|
}
|
||||||
|
if other.Synchronous {
|
||||||
|
config.Synchronous = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *Configuration) merge(other *Configuration) *Configuration {
|
||||||
|
return config.clone().update(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *Configuration) clone() *Configuration {
|
||||||
|
clone := *config
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *Configuration) isProjectPackage(pkg string) bool {
|
||||||
|
for _, p := range config.ProjectPackages {
|
||||||
|
if match, _ := filepath.Match(p, pkg); match {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *Configuration) stripProjectPackages(file string) string {
|
||||||
|
for _, p := range config.ProjectPackages {
|
||||||
|
if len(p) > 2 && p[len(p)-2] == '/' && p[len(p)-1] == '*' {
|
||||||
|
p = p[:len(p)-1]
|
||||||
|
} else {
|
||||||
|
p = p + "/"
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(file, p) {
|
||||||
|
return strings.TrimPrefix(file, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *Configuration) log(fmt string, args ...interface{}) {
|
||||||
|
if config != nil && config.Logger != nil {
|
||||||
|
config.Logger.Printf(fmt, args...)
|
||||||
|
} else {
|
||||||
|
log.Printf(fmt, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *Configuration) notifyInReleaseStage() bool {
|
||||||
|
if config.NotifyReleaseStages == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, r := range config.NotifyReleaseStages {
|
||||||
|
if r == config.ReleaseStage {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
58
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/configuration_test.go
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/configuration_test.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNotifyReleaseStages(t *testing.T) {
|
||||||
|
|
||||||
|
var testCases = []struct {
|
||||||
|
stage string
|
||||||
|
configured []string
|
||||||
|
notify bool
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
stage: "production",
|
||||||
|
notify: true,
|
||||||
|
msg: "Should notify in all release stages by default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stage: "production",
|
||||||
|
configured: []string{"development", "production"},
|
||||||
|
notify: true,
|
||||||
|
msg: "Failed to notify in configured release stage",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stage: "staging",
|
||||||
|
configured: []string{"development", "production"},
|
||||||
|
notify: false,
|
||||||
|
msg: "Failed to prevent notification in excluded release stage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
Configure(Configuration{ReleaseStage: testCase.stage, NotifyReleaseStages: testCase.configured})
|
||||||
|
|
||||||
|
if Config.notifyInReleaseStage() != testCase.notify {
|
||||||
|
t.Error(testCase.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProjectPackages(t *testing.T) {
|
||||||
|
Configure(Configuration{ProjectPackages: []string{"main", "github.com/ConradIrwin/*"}})
|
||||||
|
if !Config.isProjectPackage("main") {
|
||||||
|
t.Error("literal project package doesn't work")
|
||||||
|
}
|
||||||
|
if !Config.isProjectPackage("github.com/ConradIrwin/foo") {
|
||||||
|
t.Error("wildcard project package doesn't work")
|
||||||
|
}
|
||||||
|
if Config.isProjectPackage("runtime") {
|
||||||
|
t.Error("wrong packges being marked in project")
|
||||||
|
}
|
||||||
|
if Config.isProjectPackage("github.com/ConradIrwin/foo/bar") {
|
||||||
|
t.Error("wrong packges being marked in project")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
69
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/doc.go
generated
vendored
Normal file
69
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/doc.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
Package bugsnag captures errors in real-time and reports them to Bugsnag (http://bugsnag.com).
|
||||||
|
|
||||||
|
Using bugsnag-go is a three-step process.
|
||||||
|
|
||||||
|
1. As early as possible in your program configure the notifier with your APIKey. This sets up
|
||||||
|
handling of panics that would otherwise crash your app.
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
APIKey: "YOUR_API_KEY_HERE",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
2. Add bugsnag to places that already catch panics. For example you should add it to the HTTP server
|
||||||
|
when you call ListenAndServer:
|
||||||
|
|
||||||
|
http.ListenAndServe(":8080", bugsnag.Handler(nil))
|
||||||
|
|
||||||
|
If that's not possible, for example because you're using Google App Engine, you can also wrap each
|
||||||
|
HTTP handler manually:
|
||||||
|
|
||||||
|
http.HandleFunc("/" bugsnag.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
|
||||||
|
...
|
||||||
|
})
|
||||||
|
|
||||||
|
3. To notify Bugsnag of an error that is not a panic, pass it to bugsnag.Notify. This will also
|
||||||
|
log the error message using the configured Logger.
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
bugsnag.Notify(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
For detailed integration instructions see https://bugsnag.com/docs/notifiers/go.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
|
||||||
|
The only required configuration is the Bugsnag API key which can be obtained by clicking "Settings"
|
||||||
|
on the top of https://bugsnag.com/ after signing up. We also recommend you set the ReleaseStage
|
||||||
|
and AppVersion if these make sense for your deployment workflow.
|
||||||
|
|
||||||
|
RawData
|
||||||
|
|
||||||
|
If you need to attach extra data to Bugsnag notifications you can do that using
|
||||||
|
the rawData mechanism. Most of the functions that send errors to Bugsnag allow
|
||||||
|
you to pass in any number of interface{} values as rawData. The rawData can
|
||||||
|
consist of the Severity, Context, User or MetaData types listed below, and
|
||||||
|
there is also builtin support for *http.Requests.
|
||||||
|
|
||||||
|
bugsnag.Notify(err, bugsnag.SeverityError)
|
||||||
|
|
||||||
|
If you want to add custom tabs to your bugsnag dashboard you can pass any value in as rawData,
|
||||||
|
and then process it into the event's metadata using a bugsnag.OnBeforeNotify() hook.
|
||||||
|
|
||||||
|
bugsnag.Notify(err, account)
|
||||||
|
|
||||||
|
bugsnag.OnBeforeNotify(func (e *bugsnag.Event, c *bugsnag.Configuration) {
|
||||||
|
for datum := range e.RawData {
|
||||||
|
if account, ok := datum.(Account); ok {
|
||||||
|
e.MetaData.Add("account", "name", account.Name)
|
||||||
|
e.MetaData.Add("account", "url", account.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
If necessary you can pass Configuration in as rawData, or modify the Configuration object passed
|
||||||
|
into OnBeforeNotify hooks. Configuration passed in this way only affects the current notification.
|
||||||
|
*/
|
||||||
|
package bugsnag
|
6
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/README.md
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/README.md
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Adds stacktraces to errors in golang.
|
||||||
|
|
||||||
|
This was made to help build the Bugsnag notifier but can be used standalone if
|
||||||
|
you like to have stacktraces on errors.
|
||||||
|
|
||||||
|
See [Godoc](https://godoc.org/github.com/bugsnag/bugsnag-go/errors) for the API docs.
|
90
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/error.go
generated
vendored
Normal file
90
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/error.go
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Package errors provides errors that have stack-traces.
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The maximum number of stackframes on any error.
|
||||||
|
var MaxStackDepth = 50
|
||||||
|
|
||||||
|
// Error is an error with an attached stacktrace. It can be used
|
||||||
|
// wherever the builtin error interface is expected.
|
||||||
|
type Error struct {
|
||||||
|
Err error
|
||||||
|
stack []uintptr
|
||||||
|
frames []StackFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
// New makes an Error from the given value. If that value is already an
|
||||||
|
// error then it will be used directly, if not, it will be passed to
|
||||||
|
// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
|
||||||
|
// to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
|
||||||
|
func New(e interface{}, skip int) *Error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch e := e.(type) {
|
||||||
|
case *Error:
|
||||||
|
return e
|
||||||
|
case error:
|
||||||
|
err = e
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("%v", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
stack := make([]uintptr, MaxStackDepth)
|
||||||
|
length := runtime.Callers(2+skip, stack[:])
|
||||||
|
return &Error{
|
||||||
|
Err: err,
|
||||||
|
stack: stack[:length],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf creates a new error with the given message. You can use it
|
||||||
|
// as a drop-in replacement for fmt.Errorf() to provide descriptive
|
||||||
|
// errors in return values.
|
||||||
|
func Errorf(format string, a ...interface{}) *Error {
|
||||||
|
return New(fmt.Errorf(format, a...), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the underlying error's message.
|
||||||
|
func (err *Error) Error() string {
|
||||||
|
return err.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack returns the callstack formatted the same way that go does
|
||||||
|
// in runtime/debug.Stack()
|
||||||
|
func (err *Error) Stack() []byte {
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
|
||||||
|
for _, frame := range err.StackFrames() {
|
||||||
|
buf.WriteString(frame.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackFrames returns an array of frames containing information about the
|
||||||
|
// stack.
|
||||||
|
func (err *Error) StackFrames() []StackFrame {
|
||||||
|
if err.frames == nil {
|
||||||
|
err.frames = make([]StackFrame, len(err.stack))
|
||||||
|
|
||||||
|
for i, pc := range err.stack {
|
||||||
|
err.frames[i] = NewStackFrame(pc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err.frames
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeName returns the type this error. e.g. *errors.stringError.
|
||||||
|
func (err *Error) TypeName() string {
|
||||||
|
if _, ok := err.Err.(uncaughtPanic); ok {
|
||||||
|
return "panic"
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(err.Err).String()
|
||||||
|
}
|
117
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/error_test.go
generated
vendored
Normal file
117
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/error_test.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"runtime/debug"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStackFormatMatches(t *testing.T) {
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != 'a' {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := [][]byte{Errorf("hi").Stack(), debug.Stack()}
|
||||||
|
|
||||||
|
// Ignore the first line (as it contains the PC of the .Stack() call)
|
||||||
|
bs[0] = bytes.SplitN(bs[0], []byte("\n"), 2)[1]
|
||||||
|
bs[1] = bytes.SplitN(bs[1], []byte("\n"), 2)[1]
|
||||||
|
|
||||||
|
if bytes.Compare(bs[0], bs[1]) != 0 {
|
||||||
|
t.Errorf("Stack didn't match")
|
||||||
|
t.Errorf("%s", bs[0])
|
||||||
|
t.Errorf("%s", bs[1])
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
a()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipWorks(t *testing.T) {
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != 'a' {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := [][]byte{New("hi", 2).Stack(), debug.Stack()}
|
||||||
|
|
||||||
|
// should skip four lines of debug.Stack()
|
||||||
|
bs[1] = bytes.SplitN(bs[1], []byte("\n"), 5)[4]
|
||||||
|
|
||||||
|
if bytes.Compare(bs[0], bs[1]) != 0 {
|
||||||
|
t.Errorf("Stack didn't match")
|
||||||
|
t.Errorf("%s", bs[0])
|
||||||
|
t.Errorf("%s", bs[1])
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
a()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewError(t *testing.T) {
|
||||||
|
|
||||||
|
e := func() error {
|
||||||
|
return New("hi", 1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if e.Error() != "hi" {
|
||||||
|
t.Errorf("Constructor with a string failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if New(fmt.Errorf("yo"), 0).Error() != "yo" {
|
||||||
|
t.Errorf("Constructor with an error failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if New(e, 0) != e {
|
||||||
|
t.Errorf("Constructor with an Error failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if New(nil, 0).Error() != "<nil>" {
|
||||||
|
t.Errorf("Constructor with nil failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleErrorf(x int) (int, error) {
|
||||||
|
if x%2 == 1 {
|
||||||
|
return 0, Errorf("can only halve even numbers, got %d", x)
|
||||||
|
}
|
||||||
|
return x / 2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewError() (error, error) {
|
||||||
|
// Wrap io.EOF with the current stack-trace and return it
|
||||||
|
return nil, New(io.EOF, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewError_skip() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
// skip 1 frame (the deferred function) and then return the wrapped err
|
||||||
|
err = New(err, 1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleError_Stack(err Error) {
|
||||||
|
fmt.Printf("Error: %s\n%s", err.Error(), err.Stack())
|
||||||
|
}
|
||||||
|
|
||||||
|
func a() error {
|
||||||
|
b(5)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func b(i int) {
|
||||||
|
c()
|
||||||
|
}
|
||||||
|
|
||||||
|
func c() {
|
||||||
|
panic('a')
|
||||||
|
}
|
127
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/parse_panic.go
generated
vendored
Normal file
127
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/parse_panic.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type uncaughtPanic struct{ message string }
|
||||||
|
|
||||||
|
func (p uncaughtPanic) Error() string {
|
||||||
|
return p.message
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePanic allows you to get an error object from the output of a go program
|
||||||
|
// that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
|
||||||
|
func ParsePanic(text string) (*Error, error) {
|
||||||
|
lines := strings.Split(text, "\n")
|
||||||
|
|
||||||
|
state := "start"
|
||||||
|
|
||||||
|
var message string
|
||||||
|
var stack []StackFrame
|
||||||
|
|
||||||
|
for i := 0; i < len(lines); i++ {
|
||||||
|
line := lines[i]
|
||||||
|
|
||||||
|
if state == "start" {
|
||||||
|
if strings.HasPrefix(line, "panic: ") {
|
||||||
|
message = strings.TrimPrefix(line, "panic: ")
|
||||||
|
state = "seek"
|
||||||
|
} else {
|
||||||
|
return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if state == "seek" {
|
||||||
|
if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
|
||||||
|
state = "parsing"
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if state == "parsing" {
|
||||||
|
if line == "" {
|
||||||
|
state = "done"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
createdBy := false
|
||||||
|
if strings.HasPrefix(line, "created by ") {
|
||||||
|
line = strings.TrimPrefix(line, "created by ")
|
||||||
|
createdBy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
|
||||||
|
if i >= len(lines) {
|
||||||
|
return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
frame, err := parsePanicFrame(line, lines[i], createdBy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stack = append(stack, *frame)
|
||||||
|
if createdBy {
|
||||||
|
state = "done"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == "done" || state == "parsing" {
|
||||||
|
return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
|
||||||
|
}
|
||||||
|
return nil, Errorf("could not parse panic: %v", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lines we're passing look like this:
|
||||||
|
//
|
||||||
|
// main.(*foo).destruct(0xc208067e98)
|
||||||
|
// /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
|
||||||
|
func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
|
||||||
|
idx := strings.LastIndex(name, "(")
|
||||||
|
if idx == -1 && !createdBy {
|
||||||
|
return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
|
||||||
|
}
|
||||||
|
if idx != -1 {
|
||||||
|
name = name[:idx]
|
||||||
|
}
|
||||||
|
pkg := ""
|
||||||
|
|
||||||
|
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
|
||||||
|
pkg += name[:lastslash] + "/"
|
||||||
|
name = name[lastslash+1:]
|
||||||
|
}
|
||||||
|
if period := strings.Index(name, "."); period >= 0 {
|
||||||
|
pkg += name[:period]
|
||||||
|
name = name[period+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
name = strings.Replace(name, "·", ".", -1)
|
||||||
|
|
||||||
|
if !strings.HasPrefix(line, "\t") {
|
||||||
|
return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = strings.LastIndex(line, ":")
|
||||||
|
if idx == -1 {
|
||||||
|
return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
|
||||||
|
}
|
||||||
|
file := line[1:idx]
|
||||||
|
|
||||||
|
number := line[idx+1:]
|
||||||
|
if idx = strings.Index(number, " +"); idx > -1 {
|
||||||
|
number = number[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
lno, err := strconv.ParseInt(number, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StackFrame{
|
||||||
|
File: file,
|
||||||
|
LineNumber: int(lno),
|
||||||
|
Package: pkg,
|
||||||
|
Name: name,
|
||||||
|
}, nil
|
||||||
|
}
|
142
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/parse_panic_test.go
generated
vendored
Normal file
142
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/parse_panic_test.go
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var createdBy = `panic: hello!
|
||||||
|
|
||||||
|
goroutine 54 [running]:
|
||||||
|
runtime.panic(0x35ce40, 0xc208039db0)
|
||||||
|
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
|
||||||
|
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
|
||||||
|
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
|
||||||
|
created by github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.App.Index
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:14 +0x3e
|
||||||
|
|
||||||
|
goroutine 16 [IO wait]:
|
||||||
|
net.runtime_pollWait(0x911c30, 0x72, 0x0)
|
||||||
|
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
|
||||||
|
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
|
||||||
|
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
|
||||||
|
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
|
||||||
|
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
|
||||||
|
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
|
||||||
|
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
|
||||||
|
github.com/revel/revel.Run(0xe6d9)
|
||||||
|
/0/go/src/github.com/revel/revel/server.go:113 +0x926
|
||||||
|
main.main()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
|
||||||
|
`
|
||||||
|
|
||||||
|
var normalSplit = `panic: hello!
|
||||||
|
|
||||||
|
goroutine 54 [running]:
|
||||||
|
runtime.panic(0x35ce40, 0xc208039db0)
|
||||||
|
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
|
||||||
|
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
|
||||||
|
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
|
||||||
|
|
||||||
|
goroutine 16 [IO wait]:
|
||||||
|
net.runtime_pollWait(0x911c30, 0x72, 0x0)
|
||||||
|
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
|
||||||
|
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
|
||||||
|
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
|
||||||
|
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
|
||||||
|
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
|
||||||
|
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
|
||||||
|
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
|
||||||
|
github.com/revel/revel.Run(0xe6d9)
|
||||||
|
/0/go/src/github.com/revel/revel/server.go:113 +0x926
|
||||||
|
main.main()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
|
||||||
|
`
|
||||||
|
|
||||||
|
var lastGoroutine = `panic: hello!
|
||||||
|
|
||||||
|
goroutine 16 [IO wait]:
|
||||||
|
net.runtime_pollWait(0x911c30, 0x72, 0x0)
|
||||||
|
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
|
||||||
|
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
|
||||||
|
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
|
||||||
|
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
|
||||||
|
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
|
||||||
|
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
|
||||||
|
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
|
||||||
|
github.com/revel/revel.Run(0xe6d9)
|
||||||
|
/0/go/src/github.com/revel/revel/server.go:113 +0x926
|
||||||
|
main.main()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
|
||||||
|
|
||||||
|
goroutine 54 [running]:
|
||||||
|
runtime.panic(0x35ce40, 0xc208039db0)
|
||||||
|
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
|
||||||
|
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
|
||||||
|
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
|
||||||
|
`
|
||||||
|
|
||||||
|
var result = []StackFrame{
|
||||||
|
StackFrame{File: "/0/c/go/src/pkg/runtime/panic.c", LineNumber: 279, Name: "panic", Package: "runtime"},
|
||||||
|
StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 13, Name: "func.001", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers"},
|
||||||
|
StackFrame{File: "/0/c/go/src/pkg/net/http/server.go", LineNumber: 1698, Name: "(*Server).Serve", Package: "net/http"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultCreatedBy = append(result,
|
||||||
|
StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 14, Name: "App.Index", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers", ProgramCounter: 0x0})
|
||||||
|
|
||||||
|
func TestParsePanic(t *testing.T) {
|
||||||
|
|
||||||
|
todo := map[string]string{
|
||||||
|
"createdBy": createdBy,
|
||||||
|
"normalSplit": normalSplit,
|
||||||
|
"lastGoroutine": lastGoroutine,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range todo {
|
||||||
|
Err, err := ParsePanic(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Err.TypeName() != "panic" {
|
||||||
|
t.Errorf("Wrong type: %s", Err.TypeName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if Err.Error() != "hello!" {
|
||||||
|
t.Errorf("Wrong message: %s", Err.TypeName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if Err.StackFrames()[0].Func() != nil {
|
||||||
|
t.Errorf("Somehow managed to find a func...")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := result
|
||||||
|
if key == "createdBy" {
|
||||||
|
result = resultCreatedBy
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(Err.StackFrames(), result) {
|
||||||
|
t.Errorf("Wrong stack for %s: %#v", key, Err.StackFrames())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/stackframe.go
generated
vendored
Normal file
97
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/stackframe.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A StackFrame contains all necessary information about to generate a line
|
||||||
|
// in a callstack.
|
||||||
|
type StackFrame struct {
|
||||||
|
File string
|
||||||
|
LineNumber int
|
||||||
|
Name string
|
||||||
|
Package string
|
||||||
|
ProgramCounter uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStackFrame popoulates a stack frame object from the program counter.
|
||||||
|
func NewStackFrame(pc uintptr) (frame StackFrame) {
|
||||||
|
|
||||||
|
frame = StackFrame{ProgramCounter: pc}
|
||||||
|
if frame.Func() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
frame.Package, frame.Name = packageAndName(frame.Func())
|
||||||
|
|
||||||
|
// pc -1 because the program counters we use are usually return addresses,
|
||||||
|
// and we want to show the line that corresponds to the function call
|
||||||
|
frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Func returns the function that this stackframe corresponds to
|
||||||
|
func (frame *StackFrame) Func() *runtime.Func {
|
||||||
|
if frame.ProgramCounter == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return runtime.FuncForPC(frame.ProgramCounter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the stackframe formatted in the same way as go does
|
||||||
|
// in runtime/debug.Stack()
|
||||||
|
func (frame *StackFrame) String() string {
|
||||||
|
str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
|
||||||
|
|
||||||
|
source, err := frame.SourceLine()
|
||||||
|
if err != nil {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SourceLine gets the line of code (from File and Line) of the original source if possible
|
||||||
|
func (frame *StackFrame) SourceLine() (string, error) {
|
||||||
|
data, err := ioutil.ReadFile(frame.File)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := bytes.Split(data, []byte{'\n'})
|
||||||
|
if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
|
||||||
|
return "???", nil
|
||||||
|
}
|
||||||
|
// -1 because line-numbers are 1 based, but our array is 0 based
|
||||||
|
return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func packageAndName(fn *runtime.Func) (string, string) {
|
||||||
|
name := fn.Name()
|
||||||
|
pkg := ""
|
||||||
|
|
||||||
|
// The name includes the path name to the package, which is unnecessary
|
||||||
|
// since the file name is already included. Plus, it has center dots.
|
||||||
|
// That is, we see
|
||||||
|
// runtime/debug.*T·ptrmethod
|
||||||
|
// and want
|
||||||
|
// *T.ptrmethod
|
||||||
|
// Since the package path might contains dots (e.g. code.google.com/...),
|
||||||
|
// we first remove the path prefix if there is one.
|
||||||
|
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
|
||||||
|
pkg += name[:lastslash] + "/"
|
||||||
|
name = name[lastslash+1:]
|
||||||
|
}
|
||||||
|
if period := strings.Index(name, "."); period >= 0 {
|
||||||
|
pkg += name[:period]
|
||||||
|
name = name[period+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
name = strings.Replace(name, "·", ".", -1)
|
||||||
|
return pkg, name
|
||||||
|
}
|
134
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/event.go
generated
vendored
Normal file
134
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/event.go
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bugsnag/bugsnag-go/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context is the context of the error in Bugsnag.
|
||||||
|
// This can be passed to Notify, Recover or AutoNotify as rawData.
|
||||||
|
type Context struct {
|
||||||
|
String string
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents the searchable user-data on Bugsnag. The Id is also used
|
||||||
|
// to determine the number of users affected by a bug. This can be
|
||||||
|
// passed to Notify, Recover or AutoNotify as rawData.
|
||||||
|
type User struct {
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the severity of the error on Bugsnag. These values can be
|
||||||
|
// passed to Notify, Recover or AutoNotify as rawData.
|
||||||
|
var (
|
||||||
|
SeverityError = severity{"error"}
|
||||||
|
SeverityWarning = severity{"warning"}
|
||||||
|
SeverityInfo = severity{"info"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// The severity tag type, private so that people can only use Error,Warning,Info
|
||||||
|
type severity struct {
|
||||||
|
String string
|
||||||
|
}
|
||||||
|
|
||||||
|
// The form of stacktrace that Bugsnag expects
|
||||||
|
type stackFrame struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
File string `json:"file"`
|
||||||
|
LineNumber int `json:"lineNumber"`
|
||||||
|
InProject bool `json:"inProject,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event represents a payload of data that gets sent to Bugsnag.
|
||||||
|
// This is passed to each OnBeforeNotify hook.
|
||||||
|
type Event struct {
|
||||||
|
|
||||||
|
// The original error that caused this event, not sent to Bugsnag.
|
||||||
|
Error *errors.Error
|
||||||
|
|
||||||
|
// The rawData affecting this error, not sent to Bugsnag.
|
||||||
|
RawData []interface{}
|
||||||
|
|
||||||
|
// The error class to be sent to Bugsnag. This defaults to the type name of the Error, for
|
||||||
|
// example *error.String
|
||||||
|
ErrorClass string
|
||||||
|
// The error message to be sent to Bugsnag. This defaults to the return value of Error.Error()
|
||||||
|
Message string
|
||||||
|
// The stacktrrace of the error to be sent to Bugsnag.
|
||||||
|
Stacktrace []stackFrame
|
||||||
|
|
||||||
|
// The context to be sent to Bugsnag. This should be set to the part of the app that was running,
|
||||||
|
// e.g. for http requests, set it to the path.
|
||||||
|
Context string
|
||||||
|
// The severity of the error. Can be SeverityError, SeverityWarning or SeverityInfo.
|
||||||
|
Severity severity
|
||||||
|
// The grouping hash is used to override Bugsnag's grouping. Set this if you'd like all errors with
|
||||||
|
// the same grouping hash to group together in the dashboard.
|
||||||
|
GroupingHash string
|
||||||
|
|
||||||
|
// User data to send to Bugsnag. This is searchable on the dashboard.
|
||||||
|
User *User
|
||||||
|
// Other MetaData to send to Bugsnag. Appears as a set of tabbed tables in the dashboard.
|
||||||
|
MetaData MetaData
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEvent(err *errors.Error, rawData []interface{}, notifier *Notifier) (*Event, *Configuration) {
|
||||||
|
|
||||||
|
config := notifier.Config
|
||||||
|
event := &Event{
|
||||||
|
Error: err,
|
||||||
|
RawData: append(notifier.RawData, rawData...),
|
||||||
|
|
||||||
|
ErrorClass: err.TypeName(),
|
||||||
|
Message: err.Error(),
|
||||||
|
Stacktrace: make([]stackFrame, len(err.StackFrames())),
|
||||||
|
|
||||||
|
Severity: SeverityWarning,
|
||||||
|
|
||||||
|
MetaData: make(MetaData),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, datum := range event.RawData {
|
||||||
|
switch datum := datum.(type) {
|
||||||
|
case severity:
|
||||||
|
event.Severity = datum
|
||||||
|
|
||||||
|
case Context:
|
||||||
|
event.Context = datum.String
|
||||||
|
|
||||||
|
case Configuration:
|
||||||
|
config = config.merge(&datum)
|
||||||
|
|
||||||
|
case MetaData:
|
||||||
|
event.MetaData.Update(datum)
|
||||||
|
|
||||||
|
case User:
|
||||||
|
event.User = &datum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, frame := range err.StackFrames() {
|
||||||
|
file := frame.File
|
||||||
|
inProject := config.isProjectPackage(frame.Package)
|
||||||
|
|
||||||
|
// remove $GOROOT and $GOHOME from other frames
|
||||||
|
if idx := strings.Index(file, frame.Package); idx > -1 {
|
||||||
|
file = file[idx:]
|
||||||
|
}
|
||||||
|
if inProject {
|
||||||
|
file = config.stripProjectPackages(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Stacktrace[i] = stackFrame{
|
||||||
|
Method: frame.Name,
|
||||||
|
File: file,
|
||||||
|
LineNumber: frame.LineNumber,
|
||||||
|
InProject: inProject,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return event, config
|
||||||
|
}
|
43
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/json_tags.go
generated
vendored
Normal file
43
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/json_tags.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// The code is stripped from:
|
||||||
|
// http://golang.org/src/pkg/encoding/json/tags.go?m=text
|
||||||
|
|
||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tagOptions is the string following a comma in a struct field's "json"
|
||||||
|
// tag, or the empty string. It does not include the leading comma.
|
||||||
|
type tagOptions string
|
||||||
|
|
||||||
|
// parseTag splits a struct field's json tag into its name and
|
||||||
|
// comma-separated options.
|
||||||
|
func parseTag(tag string) (string, tagOptions) {
|
||||||
|
if idx := strings.Index(tag, ","); idx != -1 {
|
||||||
|
return tag[:idx], tagOptions(tag[idx+1:])
|
||||||
|
}
|
||||||
|
return tag, tagOptions("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains reports whether a comma-separated list of options
|
||||||
|
// contains a particular substr flag. substr must be surrounded by a
|
||||||
|
// string boundary or commas.
|
||||||
|
func (o tagOptions) Contains(optionName string) bool {
|
||||||
|
if len(o) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s := string(o)
|
||||||
|
for s != "" {
|
||||||
|
var next string
|
||||||
|
i := strings.Index(s, ",")
|
||||||
|
if i >= 0 {
|
||||||
|
s, next = s[:i], s[i+1:]
|
||||||
|
}
|
||||||
|
if s == optionName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s = next
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
185
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/metadata.go
generated
vendored
Normal file
185
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/metadata.go
generated
vendored
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetaData is added to the Bugsnag dashboard in tabs. Each tab is
|
||||||
|
// a map of strings -> values. You can pass MetaData to Notify, Recover
|
||||||
|
// and AutoNotify as rawData.
|
||||||
|
type MetaData map[string]map[string]interface{}
|
||||||
|
|
||||||
|
// Update the meta-data with more information. Tabs are merged together such
|
||||||
|
// that unique keys from both sides are preserved, and duplicate keys end up
|
||||||
|
// with the provided values.
|
||||||
|
func (meta MetaData) Update(other MetaData) {
|
||||||
|
for name, tab := range other {
|
||||||
|
|
||||||
|
if meta[name] == nil {
|
||||||
|
meta[name] = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range tab {
|
||||||
|
meta[name][key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add creates a tab of Bugsnag meta-data.
|
||||||
|
// If the tab doesn't yet exist it will be created.
|
||||||
|
// If the key already exists, it will be overwritten.
|
||||||
|
func (meta MetaData) Add(tab string, key string, value interface{}) {
|
||||||
|
if meta[tab] == nil {
|
||||||
|
meta[tab] = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
meta[tab][key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStruct creates a tab of Bugsnag meta-data.
|
||||||
|
// The struct will be converted to an Object using the
|
||||||
|
// reflect library so any private fields will not be exported.
|
||||||
|
// As a safety measure, if you pass a non-struct the value will be
|
||||||
|
// sent to Bugsnag under the "Extra data" tab.
|
||||||
|
func (meta MetaData) AddStruct(tab string, obj interface{}) {
|
||||||
|
val := sanitizer{}.Sanitize(obj)
|
||||||
|
content, ok := val.(map[string]interface{})
|
||||||
|
if ok {
|
||||||
|
meta[tab] = content
|
||||||
|
} else {
|
||||||
|
// Wasn't a struct
|
||||||
|
meta.Add("Extra data", tab, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any values from meta-data that have keys matching the filters,
|
||||||
|
// and any that are recursive data-structures
|
||||||
|
func (meta MetaData) sanitize(filters []string) interface{} {
|
||||||
|
return sanitizer{
|
||||||
|
Filters: filters,
|
||||||
|
Seen: make([]interface{}, 0),
|
||||||
|
}.Sanitize(meta)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sanitizer is used to remove filtered params and recursion from meta-data.
|
||||||
|
type sanitizer struct {
|
||||||
|
Filters []string
|
||||||
|
Seen []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sanitizer) Sanitize(data interface{}) interface{} {
|
||||||
|
for _, s := range s.Seen {
|
||||||
|
// TODO: we don't need deep equal here, just type-ignoring equality
|
||||||
|
if reflect.DeepEqual(data, s) {
|
||||||
|
return "[RECURSION]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitizers are passed by value, so we can modify s and it only affects
|
||||||
|
// s.Seen for nested calls.
|
||||||
|
s.Seen = append(s.Seen, data)
|
||||||
|
|
||||||
|
t := reflect.TypeOf(data)
|
||||||
|
v := reflect.ValueOf(data)
|
||||||
|
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Bool,
|
||||||
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||||
|
reflect.Float32, reflect.Float64:
|
||||||
|
return data
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
return data
|
||||||
|
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return s.Sanitize(v.Elem().Interface())
|
||||||
|
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
ret := make([]interface{}, v.Len())
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
ret[i] = s.Sanitize(v.Index(i).Interface())
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
return s.sanitizeMap(v)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
return s.sanitizeStruct(v, t)
|
||||||
|
|
||||||
|
// Things JSON can't serialize:
|
||||||
|
// case t.Chan, t.Func, reflect.Complex64, reflect.Complex128, reflect.UnsafePointer:
|
||||||
|
default:
|
||||||
|
return "[" + t.String() + "]"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sanitizer) sanitizeMap(v reflect.Value) interface{} {
|
||||||
|
ret := make(map[string]interface{})
|
||||||
|
|
||||||
|
for _, key := range v.MapKeys() {
|
||||||
|
val := s.Sanitize(v.MapIndex(key).Interface())
|
||||||
|
newKey := fmt.Sprintf("%v", key.Interface())
|
||||||
|
|
||||||
|
if s.shouldRedact(newKey) {
|
||||||
|
val = "[REDACTED]"
|
||||||
|
}
|
||||||
|
|
||||||
|
ret[newKey] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} {
|
||||||
|
ret := make(map[string]interface{})
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
|
||||||
|
val := v.Field(i)
|
||||||
|
// Don't export private fields
|
||||||
|
if !val.CanInterface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := t.Field(i).Name
|
||||||
|
var opts tagOptions
|
||||||
|
|
||||||
|
// Parse JSON tags. Supports name and "omitempty"
|
||||||
|
if jsonTag := t.Field(i).Tag.Get("json"); len(jsonTag) != 0 {
|
||||||
|
name, opts = parseTag(jsonTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.shouldRedact(name) {
|
||||||
|
ret[name] = "[REDACTED]"
|
||||||
|
} else {
|
||||||
|
sanitized := s.Sanitize(val.Interface())
|
||||||
|
if str, ok := sanitized.(string); ok {
|
||||||
|
if !(opts.Contains("omitempty") && len(str) == 0) {
|
||||||
|
ret[name] = str
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret[name] = sanitized
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sanitizer) shouldRedact(key string) bool {
|
||||||
|
for _, filter := range s.Filters {
|
||||||
|
if strings.Contains(strings.ToLower(filter), strings.ToLower(key)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
182
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/metadata_test.go
generated
vendored
Normal file
182
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/metadata_test.go
generated
vendored
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/bugsnag/bugsnag-go/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _account struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Plan struct {
|
||||||
|
Premium bool
|
||||||
|
}
|
||||||
|
Password string
|
||||||
|
secret string
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmptyEmail string `json:"emptyemail,omitempty"`
|
||||||
|
NotEmptyEmail string `json:"not_empty_email,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type _broken struct {
|
||||||
|
Me *_broken
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
var account = _account{}
|
||||||
|
var notifier = New(Configuration{})
|
||||||
|
|
||||||
|
func TestMetaDataAdd(t *testing.T) {
|
||||||
|
m := MetaData{
|
||||||
|
"one": {
|
||||||
|
"key": "value",
|
||||||
|
"override": false,
|
||||||
|
}}
|
||||||
|
|
||||||
|
m.Add("one", "override", true)
|
||||||
|
m.Add("one", "new", "key")
|
||||||
|
m.Add("new", "tab", account)
|
||||||
|
|
||||||
|
m.AddStruct("lol", "not really a struct")
|
||||||
|
m.AddStruct("account", account)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(m, MetaData{
|
||||||
|
"one": {
|
||||||
|
"key": "value",
|
||||||
|
"override": true,
|
||||||
|
"new": "key",
|
||||||
|
},
|
||||||
|
"new": {
|
||||||
|
"tab": account,
|
||||||
|
},
|
||||||
|
"Extra data": {
|
||||||
|
"lol": "not really a struct",
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"ID": "",
|
||||||
|
"Name": "",
|
||||||
|
"Plan": map[string]interface{}{
|
||||||
|
"Premium": false,
|
||||||
|
},
|
||||||
|
"Password": "",
|
||||||
|
"email": "",
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
t.Errorf("metadata.Add didn't work: %#v", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetaDataUpdate(t *testing.T) {
|
||||||
|
|
||||||
|
m := MetaData{
|
||||||
|
"one": {
|
||||||
|
"key": "value",
|
||||||
|
"override": false,
|
||||||
|
}}
|
||||||
|
|
||||||
|
m.Update(MetaData{
|
||||||
|
"one": {
|
||||||
|
"override": true,
|
||||||
|
"new": "key",
|
||||||
|
},
|
||||||
|
"new": {
|
||||||
|
"tab": account,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(m, MetaData{
|
||||||
|
"one": {
|
||||||
|
"key": "value",
|
||||||
|
"override": true,
|
||||||
|
"new": "key",
|
||||||
|
},
|
||||||
|
"new": {
|
||||||
|
"tab": account,
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
t.Errorf("metadata.Update didn't work: %#v", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetaDataSanitize(t *testing.T) {
|
||||||
|
|
||||||
|
var broken = _broken{}
|
||||||
|
broken.Me = &broken
|
||||||
|
broken.Data = "ohai"
|
||||||
|
account.Name = "test"
|
||||||
|
account.ID = "test"
|
||||||
|
account.secret = "hush"
|
||||||
|
account.Email = "example@example.com"
|
||||||
|
account.EmptyEmail = ""
|
||||||
|
account.NotEmptyEmail = "not_empty_email@example.com"
|
||||||
|
|
||||||
|
m := MetaData{
|
||||||
|
"one": {
|
||||||
|
"bool": true,
|
||||||
|
"int": 7,
|
||||||
|
"float": 7.1,
|
||||||
|
"complex": complex(1, 1),
|
||||||
|
"func": func() {},
|
||||||
|
"unsafe": unsafe.Pointer(broken.Me),
|
||||||
|
"string": "string",
|
||||||
|
"password": "secret",
|
||||||
|
"array": []hash{{
|
||||||
|
"creditcard": "1234567812345678",
|
||||||
|
"broken": broken,
|
||||||
|
}},
|
||||||
|
"broken": broken,
|
||||||
|
"account": account,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
n := m.sanitize([]string{"password", "creditcard"})
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(n, map[string]interface{}{
|
||||||
|
"one": map[string]interface{}{
|
||||||
|
"bool": true,
|
||||||
|
"int": 7,
|
||||||
|
"float": 7.1,
|
||||||
|
"complex": "[complex128]",
|
||||||
|
"string": "string",
|
||||||
|
"unsafe": "[unsafe.Pointer]",
|
||||||
|
"func": "[func()]",
|
||||||
|
"password": "[REDACTED]",
|
||||||
|
"array": []interface{}{map[string]interface{}{
|
||||||
|
"creditcard": "[REDACTED]",
|
||||||
|
"broken": map[string]interface{}{
|
||||||
|
"Me": "[RECURSION]",
|
||||||
|
"Data": "ohai",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"broken": map[string]interface{}{
|
||||||
|
"Me": "[RECURSION]",
|
||||||
|
"Data": "ohai",
|
||||||
|
},
|
||||||
|
"account": map[string]interface{}{
|
||||||
|
"ID": "test",
|
||||||
|
"Name": "test",
|
||||||
|
"Plan": map[string]interface{}{
|
||||||
|
"Premium": false,
|
||||||
|
},
|
||||||
|
"Password": "[REDACTED]",
|
||||||
|
"email": "example@example.com",
|
||||||
|
"not_empty_email": "not_empty_email@example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
t.Errorf("metadata.Sanitize didn't work: %#v", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleMetaData() {
|
||||||
|
notifier.Notify(errors.Errorf("hi world"),
|
||||||
|
MetaData{"Account": {
|
||||||
|
"id": account.ID,
|
||||||
|
"name": account.Name,
|
||||||
|
"paying?": account.Plan.Premium,
|
||||||
|
}})
|
||||||
|
}
|
96
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/middleware.go
generated
vendored
Normal file
96
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/middleware.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
beforeFunc func(*Event, *Configuration) error
|
||||||
|
|
||||||
|
// MiddlewareStacks keep middleware in the correct order. They are
|
||||||
|
// called in reverse order, so if you add a new middleware it will
|
||||||
|
// be called before all existing middleware.
|
||||||
|
middlewareStack struct {
|
||||||
|
before []beforeFunc
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddMiddleware adds a new middleware to the outside of the existing ones,
|
||||||
|
// when the middlewareStack is Run it will be run before all middleware that
|
||||||
|
// have been added before.
|
||||||
|
func (stack *middlewareStack) OnBeforeNotify(middleware beforeFunc) {
|
||||||
|
stack.before = append(stack.before, middleware)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run causes all the middleware to be run. If they all permit it the next callback
|
||||||
|
// will be called with all the middleware on the stack.
|
||||||
|
func (stack *middlewareStack) Run(event *Event, config *Configuration, next func() error) error {
|
||||||
|
// run all the before filters in reverse order
|
||||||
|
for i := range stack.before {
|
||||||
|
before := stack.before[len(stack.before)-i-1]
|
||||||
|
|
||||||
|
err := stack.runBeforeFilter(before, event, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stack *middlewareStack) runBeforeFilter(f beforeFunc, event *Event, config *Configuration) error {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
config.log("bugsnag/middleware: unexpected panic: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return f(event, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// catchMiddlewarePanic is used to log any panics that happen inside Middleware,
|
||||||
|
// we wouldn't want to not notify Bugsnag in this case.
|
||||||
|
func catchMiddlewarePanic(event *Event, config *Configuration, next func() error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpRequestMiddleware is added OnBeforeNotify by default. It takes information
|
||||||
|
// from an http.Request passed in as rawData, and adds it to the Event. You can
|
||||||
|
// use this as a template for writing your own Middleware.
|
||||||
|
func httpRequestMiddleware(event *Event, config *Configuration) error {
|
||||||
|
for _, datum := range event.RawData {
|
||||||
|
if request, ok := datum.(*http.Request); ok {
|
||||||
|
proto := "http://"
|
||||||
|
if request.TLS != nil {
|
||||||
|
proto = "https://"
|
||||||
|
}
|
||||||
|
|
||||||
|
event.MetaData.Update(MetaData{
|
||||||
|
"Request": {
|
||||||
|
"RemoteAddr": request.RemoteAddr,
|
||||||
|
"Method": request.Method,
|
||||||
|
"Url": proto + request.Host + request.RequestURI,
|
||||||
|
"Params": request.URL.Query(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add headers as a separate tab.
|
||||||
|
event.MetaData.AddStruct("Headers", request.Header)
|
||||||
|
|
||||||
|
// Default context to Path
|
||||||
|
if event.Context == "" {
|
||||||
|
event.Context = request.URL.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default user.id to IP so that users-affected works.
|
||||||
|
if event.User == nil {
|
||||||
|
ip := request.RemoteAddr
|
||||||
|
if idx := strings.LastIndex(ip, ":"); idx != -1 {
|
||||||
|
ip = ip[:idx]
|
||||||
|
}
|
||||||
|
event.User = &User{Id: ip}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
88
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/middleware_test.go
generated
vendored
Normal file
88
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/middleware_test.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMiddlewareOrder(t *testing.T) {
|
||||||
|
|
||||||
|
result := make([]int, 0, 7)
|
||||||
|
stack := middlewareStack{}
|
||||||
|
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
|
||||||
|
result = append(result, 2)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
|
||||||
|
result = append(result, 1)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
|
||||||
|
result = append(result, 0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
stack.Run(nil, nil, func() error {
|
||||||
|
result = append(result, 3)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(result, []int{0, 1, 2, 3}) {
|
||||||
|
t.Errorf("unexpected middleware order %v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBeforeNotifyReturnErr(t *testing.T) {
|
||||||
|
|
||||||
|
stack := middlewareStack{}
|
||||||
|
err := fmt.Errorf("test")
|
||||||
|
|
||||||
|
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
called := false
|
||||||
|
|
||||||
|
e := stack.Run(nil, nil, func() error {
|
||||||
|
called = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if e != err {
|
||||||
|
t.Errorf("Middleware didn't return the error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if called == true {
|
||||||
|
t.Errorf("Notify was called when BeforeNotify returned False")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBeforeNotifyPanic(t *testing.T) {
|
||||||
|
|
||||||
|
stack := middlewareStack{}
|
||||||
|
|
||||||
|
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
|
||||||
|
panic("oops")
|
||||||
|
})
|
||||||
|
|
||||||
|
called := false
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
|
||||||
|
stack.Run(nil, &Configuration{Logger: log.New(b, log.Prefix(), 0)}, func() error {
|
||||||
|
called = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
logged := b.String()
|
||||||
|
|
||||||
|
if logged != "bugsnag/middleware: unexpected panic: oops\n" {
|
||||||
|
t.Errorf("Logged: %s", logged)
|
||||||
|
}
|
||||||
|
|
||||||
|
if called == false {
|
||||||
|
t.Errorf("Notify was not called when BeforeNotify panicked")
|
||||||
|
}
|
||||||
|
}
|
95
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/notifier.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/notifier.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/bugsnag/bugsnag-go/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notifier sends errors to Bugsnag.
|
||||||
|
type Notifier struct {
|
||||||
|
Config *Configuration
|
||||||
|
RawData []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new notifier.
|
||||||
|
// You can pass an instance of bugsnag.Configuration in rawData to change the configuration.
|
||||||
|
// Other values of rawData will be passed to Notify.
|
||||||
|
func New(rawData ...interface{}) *Notifier {
|
||||||
|
config := Config.clone()
|
||||||
|
for i, datum := range rawData {
|
||||||
|
if c, ok := datum.(Configuration); ok {
|
||||||
|
config.update(&c)
|
||||||
|
rawData[i] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Notifier{
|
||||||
|
Config: config,
|
||||||
|
RawData: rawData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify sends an error to Bugsnag. Any rawData you pass here will be sent to
|
||||||
|
// Bugsnag after being converted to JSON. e.g. bugsnag.SeverityError, bugsnag.Context,
|
||||||
|
// or bugsnag.MetaData.
|
||||||
|
func (notifier *Notifier) Notify(err error, rawData ...interface{}) (e error) {
|
||||||
|
event, config := newEvent(errors.New(err, 1), rawData, notifier)
|
||||||
|
|
||||||
|
// Never block, start throwing away errors if we have too many.
|
||||||
|
e = middleware.Run(event, config, func() error {
|
||||||
|
config.log("notifying bugsnag: %s", event.Message)
|
||||||
|
if config.notifyInReleaseStage() {
|
||||||
|
if config.Synchronous {
|
||||||
|
return (&payload{event, config}).deliver()
|
||||||
|
}
|
||||||
|
go (&payload{event, config}).deliver()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("not notifying in %s", config.ReleaseStage)
|
||||||
|
})
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
config.log("bugsnag.Notify: %v", e)
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoNotify notifies Bugsnag of any panics, then repanics.
|
||||||
|
// It sends along any rawData that gets passed in.
|
||||||
|
// Usage: defer AutoNotify()
|
||||||
|
func (notifier *Notifier) AutoNotify(rawData ...interface{}) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
rawData = notifier.addDefaultSeverity(rawData, SeverityError)
|
||||||
|
notifier.Notify(errors.New(err, 2), rawData...)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recover logs any panics, then recovers.
|
||||||
|
// It sends along any rawData that gets passed in.
|
||||||
|
// Usage: defer Recover()
|
||||||
|
func (notifier *Notifier) Recover(rawData ...interface{}) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
rawData = notifier.addDefaultSeverity(rawData, SeverityWarning)
|
||||||
|
notifier.Notify(errors.New(err, 2), rawData...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (notifier *Notifier) dontPanic() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
notifier.Config.log("bugsnag/notifier.Notify: panic! %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a severity to raw data only if the default is not set.
|
||||||
|
func (notifier *Notifier) addDefaultSeverity(rawData []interface{}, s severity) []interface{} {
|
||||||
|
|
||||||
|
for _, datum := range append(notifier.RawData, rawData...) {
|
||||||
|
if _, ok := datum.(severity); ok {
|
||||||
|
return rawData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(rawData, s)
|
||||||
|
}
|
27
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/panicwrap.go
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/panicwrap.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bugsnag/panicwrap"
|
||||||
|
"github.com/bugsnag/bugsnag-go/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE: this function does not return when you call it, instead it
|
||||||
|
// re-exec()s the current process with panic monitoring.
|
||||||
|
func defaultPanicHandler() {
|
||||||
|
defer defaultNotifier.dontPanic()
|
||||||
|
|
||||||
|
err := panicwrap.BasicMonitor(func(output string) {
|
||||||
|
toNotify, err := errors.ParsePanic(output)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
defaultNotifier.Config.log("bugsnag.handleUncaughtPanic: %v", err)
|
||||||
|
}
|
||||||
|
Notify(toNotify, SeverityError, Configuration{Synchronous: true})
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
defaultNotifier.Config.log("bugsnag.handleUncaughtPanic: %v", err)
|
||||||
|
}
|
||||||
|
}
|
79
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/panicwrap_test.go
generated
vendored
Normal file
79
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/panicwrap_test.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bitly/go-simplejson"
|
||||||
|
"github.com/mitchellh/osext"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPanicHandler(t *testing.T) {
|
||||||
|
startTestServer()
|
||||||
|
|
||||||
|
exePath, err := osext.Executable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the same trick as panicwrap() to re-run ourselves.
|
||||||
|
// In the init() block below, we will then panic.
|
||||||
|
cmd := exec.Command(exePath, os.Args[1:]...)
|
||||||
|
cmd.Env = append(os.Environ(), "BUGSNAG_API_KEY="+testAPIKey, "BUGSNAG_ENDPOINT="+testEndpoint, "please_panic=please_panic")
|
||||||
|
|
||||||
|
if err = cmd.Start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cmd.Wait(); err.Error() != "exit status 2" {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := simplejson.NewJson(<-postedJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
event := json.Get("events").GetIndex(0)
|
||||||
|
|
||||||
|
if event.Get("severity").MustString() != "error" {
|
||||||
|
t.Errorf("severity should be error")
|
||||||
|
}
|
||||||
|
exception := event.Get("exceptions").GetIndex(0)
|
||||||
|
|
||||||
|
if exception.Get("message").MustString() != "ruh roh" {
|
||||||
|
t.Errorf("caught wrong panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exception.Get("errorClass").MustString() != "panic" {
|
||||||
|
t.Errorf("caught wrong panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
frame := exception.Get("stacktrace").GetIndex(1)
|
||||||
|
|
||||||
|
// Yeah, we just caught a panic from the init() function below and sent it to the server running above (mindblown)
|
||||||
|
if frame.Get("inProject").MustBool() != true ||
|
||||||
|
frame.Get("file").MustString() != "panicwrap_test.go" ||
|
||||||
|
frame.Get("method").MustString() != "panick" ||
|
||||||
|
frame.Get("lineNumber").MustInt() == 0 {
|
||||||
|
t.Errorf("stack trace seemed wrong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if os.Getenv("please_panic") != "" {
|
||||||
|
Configure(Configuration{APIKey: os.Getenv("BUGSNAG_API_KEY"), Endpoint: os.Getenv("BUGSNAG_ENDPOINT"), ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"}})
|
||||||
|
go func() {
|
||||||
|
panick()
|
||||||
|
}()
|
||||||
|
// Plenty of time to crash, it shouldn't need any of it.
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func panick() {
|
||||||
|
panic("ruh roh")
|
||||||
|
}
|
96
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/payload.go
generated
vendored
Normal file
96
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/payload.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type payload struct {
|
||||||
|
*Event
|
||||||
|
*Configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
type hash map[string]interface{}
|
||||||
|
|
||||||
|
func (p *payload) deliver() error {
|
||||||
|
|
||||||
|
if len(p.APIKey) != 32 {
|
||||||
|
return fmt.Errorf("bugsnag/payload.deliver: invalid api key")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := json.Marshal(p)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bugsnag/payload.deliver: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Transport: p.Transport,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Post(p.Endpoint, "application/json", bytes.NewBuffer(buf))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bugsnag/payload.deliver: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("bugsnag/payload.deliver: Got HTTP %s\n", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *payload) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
|
data := hash{
|
||||||
|
"apiKey": p.APIKey,
|
||||||
|
|
||||||
|
"notifier": hash{
|
||||||
|
"name": "Bugsnag Go",
|
||||||
|
"url": "https://github.com/bugsnag/bugsnag-go",
|
||||||
|
"version": VERSION,
|
||||||
|
},
|
||||||
|
|
||||||
|
"events": []hash{
|
||||||
|
{
|
||||||
|
"payloadVersion": "2",
|
||||||
|
"exceptions": []hash{
|
||||||
|
{
|
||||||
|
"errorClass": p.ErrorClass,
|
||||||
|
"message": p.Message,
|
||||||
|
"stacktrace": p.Stacktrace,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"severity": p.Severity.String,
|
||||||
|
"app": hash{
|
||||||
|
"releaseStage": p.ReleaseStage,
|
||||||
|
},
|
||||||
|
"user": p.User,
|
||||||
|
"metaData": p.MetaData.sanitize(p.ParamsFilters),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
event := data["events"].([]hash)[0]
|
||||||
|
|
||||||
|
if p.Context != "" {
|
||||||
|
event["context"] = p.Context
|
||||||
|
}
|
||||||
|
if p.GroupingHash != "" {
|
||||||
|
event["groupingHash"] = p.GroupingHash
|
||||||
|
}
|
||||||
|
if p.Hostname != "" {
|
||||||
|
event["device"] = hash{
|
||||||
|
"hostname": p.Hostname,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.AppVersion != "" {
|
||||||
|
event["app"].(hash)["version"] = p.AppVersion
|
||||||
|
}
|
||||||
|
return json.Marshal(data)
|
||||||
|
|
||||||
|
}
|
60
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/revel/bugsnagrevel.go
generated
vendored
Normal file
60
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/revel/bugsnagrevel.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Package bugsnagrevel adds Bugsnag to revel.
|
||||||
|
// It lets you pass *revel.Controller into bugsnag.Notify(),
|
||||||
|
// and provides a Filter to catch errors.
|
||||||
|
package bugsnagrevel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bugsnag/bugsnag-go"
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
|
// Filter should be added to the filter chain just after the PanicFilter.
|
||||||
|
// It sends errors to Bugsnag automatically. Configuration is read out of
|
||||||
|
// conf/app.conf, you should set bugsnag.apikey, and can also set
|
||||||
|
// bugsnag.endpoint, bugsnag.releasestage, bugsnag.appversion,
|
||||||
|
// bugsnag.projectroot, bugsnag.projectpackages if needed.
|
||||||
|
func Filter(c *revel.Controller, fc []revel.Filter) {
|
||||||
|
defer bugsnag.AutoNotify(c)
|
||||||
|
fc[0](c, fc[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add support to bugsnag for reading data out of *revel.Controllers
|
||||||
|
func middleware(event *bugsnag.Event, config *bugsnag.Configuration) error {
|
||||||
|
for _, datum := range event.RawData {
|
||||||
|
if controller, ok := datum.(*revel.Controller); ok {
|
||||||
|
// make the request visible to the builtin HttpMIddleware
|
||||||
|
event.RawData = append(event.RawData, controller.Request.Request)
|
||||||
|
event.Context = controller.Action
|
||||||
|
event.MetaData.AddStruct("Session", controller.Session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
revel.OnAppStart(func() {
|
||||||
|
bugsnag.OnBeforeNotify(middleware)
|
||||||
|
|
||||||
|
var projectPackages []string
|
||||||
|
if packages, ok := revel.Config.String("bugsnag.projectpackages"); ok {
|
||||||
|
projectPackages = strings.Split(packages, ",")
|
||||||
|
} else {
|
||||||
|
projectPackages = []string{revel.ImportPath + "/app/*", revel.ImportPath + "/app"}
|
||||||
|
}
|
||||||
|
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
APIKey: revel.Config.StringDefault("bugsnag.apikey", ""),
|
||||||
|
Endpoint: revel.Config.StringDefault("bugsnag.endpoint", ""),
|
||||||
|
AppVersion: revel.Config.StringDefault("bugsnag.appversion", ""),
|
||||||
|
ReleaseStage: revel.Config.StringDefault("bugsnag.releasestage", revel.RunMode),
|
||||||
|
ProjectPackages: projectPackages,
|
||||||
|
Logger: revel.ERROR,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
20
Godeps/_workspace/src/github.com/bugsnag/osext/LICENSE
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/bugsnag/osext/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2012 Daniel Theophanes
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
|
||||||
|
3. This notice may not be removed or altered from any source
|
||||||
|
distribution.
|
32
Godeps/_workspace/src/github.com/bugsnag/osext/osext.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/bugsnag/osext/osext.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// Extensions to the standard "os" package.
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
// Executable returns an absolute path that can be used to
|
||||||
|
// re-invoke the current program.
|
||||||
|
// It may not be valid after the current program exits.
|
||||||
|
func Executable() (string, error) {
|
||||||
|
p, err := executable()
|
||||||
|
return filepath.Clean(p), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns same path as Executable, returns just the folder
|
||||||
|
// path. Excludes the executable name.
|
||||||
|
func ExecutableFolder() (string, error) {
|
||||||
|
p, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
folder, _ := filepath.Split(p)
|
||||||
|
return folder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Depricated. Same as Executable().
|
||||||
|
func GetExePath() (exePath string, err error) {
|
||||||
|
return Executable()
|
||||||
|
}
|
16
Godeps/_workspace/src/github.com/bugsnag/osext/osext_plan9.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/bugsnag/osext/osext_plan9.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 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 osext
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
f, err := Open("/proc/" + itoa(Getpid()) + "/text")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return syscall.Fd2path(int(f.Fd()))
|
||||||
|
}
|
25
Godeps/_workspace/src/github.com/bugsnag/osext/osext_procfs.go
generated
vendored
Normal file
25
Godeps/_workspace/src/github.com/bugsnag/osext/osext_procfs.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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 linux netbsd openbsd
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
return os.Readlink("/proc/self/exe")
|
||||||
|
case "netbsd":
|
||||||
|
return os.Readlink("/proc/curproc/exe")
|
||||||
|
case "openbsd":
|
||||||
|
return os.Readlink("/proc/curproc/file")
|
||||||
|
}
|
||||||
|
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
|
||||||
|
}
|
64
Godeps/_workspace/src/github.com/bugsnag/osext/osext_sysctl.go
generated
vendored
Normal file
64
Godeps/_workspace/src/github.com/bugsnag/osext/osext_sysctl.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// 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 darwin freebsd
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var startUpcwd, getwdError = os.Getwd()
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
var mib [4]int32
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "freebsd":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
|
||||||
|
case "darwin":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := uintptr(0)
|
||||||
|
// get length
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
|
if err != 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if n == 0 { // shouldn't happen
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
buf := make([]byte, n)
|
||||||
|
_, _, err = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
|
if err != 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if n == 0 { // shouldn't happen
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
for i, v := range buf {
|
||||||
|
if v == 0 {
|
||||||
|
buf = buf[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf[0] != '/' {
|
||||||
|
if getwdError != nil {
|
||||||
|
return string(buf), getwdError
|
||||||
|
} else {
|
||||||
|
if buf[0] == '.' {
|
||||||
|
buf = buf[1:]
|
||||||
|
}
|
||||||
|
if startUpcwd[len(startUpcwd)-1] != '/' {
|
||||||
|
return startUpcwd + "/" + string(buf), nil
|
||||||
|
}
|
||||||
|
return startUpcwd + string(buf), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
79
Godeps/_workspace/src/github.com/bugsnag/osext/osext_test.go
generated
vendored
Normal file
79
Godeps/_workspace/src/github.com/bugsnag/osext/osext_test.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// 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 darwin linux freebsd netbsd windows
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
oexec "os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH"
|
||||||
|
|
||||||
|
func TestExecPath(t *testing.T) {
|
||||||
|
ep, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExecPath failed: %v", err)
|
||||||
|
}
|
||||||
|
// we want fn to be of the form "dir/prog"
|
||||||
|
dir := filepath.Dir(filepath.Dir(ep))
|
||||||
|
fn, err := filepath.Rel(dir, ep)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("filepath.Rel: %v", err)
|
||||||
|
}
|
||||||
|
cmd := &oexec.Cmd{}
|
||||||
|
// make child start with a relative program path
|
||||||
|
cmd.Dir = dir
|
||||||
|
cmd.Path = fn
|
||||||
|
// forge argv[0] for child, so that we can verify we could correctly
|
||||||
|
// get real path of the executable without influenced by argv[0].
|
||||||
|
cmd.Args = []string{"-", "-test.run=XXXX"}
|
||||||
|
cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)}
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("exec(self) failed: %v", err)
|
||||||
|
}
|
||||||
|
outs := string(out)
|
||||||
|
if !filepath.IsAbs(outs) {
|
||||||
|
t.Fatalf("Child returned %q, want an absolute path", out)
|
||||||
|
}
|
||||||
|
if !sameFile(outs, ep) {
|
||||||
|
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameFile(fn1, fn2 string) bool {
|
||||||
|
fi1, err := os.Stat(fn1)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fi2, err := os.Stat(fn2)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return os.SameFile(fi1, fi2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if e := os.Getenv(execPath_EnvVar); e != "" {
|
||||||
|
// first chdir to another path
|
||||||
|
dir := "/"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
dir = filepath.VolumeName(".")
|
||||||
|
}
|
||||||
|
os.Chdir(dir)
|
||||||
|
if ep, err := Executable(); err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(os.Stderr, ep)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
34
Godeps/_workspace/src/github.com/bugsnag/osext/osext_windows.go
generated
vendored
Normal file
34
Godeps/_workspace/src/github.com/bugsnag/osext/osext_windows.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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 osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel = syscall.MustLoadDLL("kernel32.dll")
|
||||||
|
getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetModuleFileName() with hModule = NULL
|
||||||
|
func executable() (exePath string, err error) {
|
||||||
|
return getModuleFileName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModuleFileName() (string, error) {
|
||||||
|
var n uint32
|
||||||
|
b := make([]uint16, syscall.MAX_PATH)
|
||||||
|
size := uint32(len(b))
|
||||||
|
|
||||||
|
r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
|
||||||
|
n = uint32(r0)
|
||||||
|
if n == 0 {
|
||||||
|
return "", e1
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(b[0:n])), nil
|
||||||
|
}
|
21
Godeps/_workspace/src/github.com/bugsnag/panicwrap/LICENSE
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/bugsnag/panicwrap/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 Mitchell Hashimoto
|
||||||
|
|
||||||
|
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.
|
101
Godeps/_workspace/src/github.com/bugsnag/panicwrap/README.md
generated
vendored
Normal file
101
Godeps/_workspace/src/github.com/bugsnag/panicwrap/README.md
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
# panicwrap
|
||||||
|
|
||||||
|
panicwrap is a Go library that re-executes a Go binary and monitors stderr
|
||||||
|
output from the binary for a panic. When it find a panic, it executes a
|
||||||
|
user-defined handler function. Stdout, stderr, stdin, signals, and exit
|
||||||
|
codes continue to work as normal, making the existence of panicwrap mostly
|
||||||
|
invisble to the end user until a panic actually occurs.
|
||||||
|
|
||||||
|
Since a panic is truly a bug in the program meant to crash the runtime,
|
||||||
|
globally catching panics within Go applications is not supposed to be possible.
|
||||||
|
Despite this, it is often useful to have a way to know when panics occur.
|
||||||
|
panicwrap allows you to do something with these panics, such as writing them
|
||||||
|
to a file, so that you can track when panics occur.
|
||||||
|
|
||||||
|
panicwrap is ***not a panic recovery system***. Panics indicate serious
|
||||||
|
problems with your application and _should_ crash the runtime. panicwrap
|
||||||
|
is just meant as a way to monitor for panics. If you still think this is
|
||||||
|
the worst idea ever, read the section below on why.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* **SIMPLE!**
|
||||||
|
* Works with all Go applications on all platforms Go supports
|
||||||
|
* Custom behavior when a panic occurs
|
||||||
|
* Stdout, stderr, stdin, exit codes, and signals continue to work as
|
||||||
|
expected.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Using panicwrap is simple. It behaves a lot like `fork`, if you know
|
||||||
|
how that works. A basic example is shown below.
|
||||||
|
|
||||||
|
Because it would be sad to panic while capturing a panic, it is recommended
|
||||||
|
that the handler functions for panicwrap remain relatively simple and well
|
||||||
|
tested. panicwrap itself contains many tests.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/panicwrap"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
exitStatus, err := panicwrap.BasicWrap(panicHandler)
|
||||||
|
if err != nil {
|
||||||
|
// Something went wrong setting up the panic wrapper. Unlikely,
|
||||||
|
// but possible.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If exitStatus >= 0, then we're the parent process and the panicwrap
|
||||||
|
// re-executed ourselves and completed. Just exit with the proper status.
|
||||||
|
if exitStatus >= 0 {
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, exitStatus < 0 means we're the child. Continue executing as
|
||||||
|
// normal...
|
||||||
|
|
||||||
|
// Let's say we panic
|
||||||
|
panic("oh shucks")
|
||||||
|
}
|
||||||
|
|
||||||
|
func panicHandler(output string) {
|
||||||
|
// output contains the full output (including stack traces) of the
|
||||||
|
// panic. Put it in a file or something.
|
||||||
|
fmt.Printf("The child panicked:\n\n%s\n", output)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## How Does it Work?
|
||||||
|
|
||||||
|
panicwrap works by re-executing the running program (retaining arguments,
|
||||||
|
environmental variables, etc.) and monitoring the stderr of the program.
|
||||||
|
Since Go always outputs panics in a predictable way with a predictable
|
||||||
|
exit code, panicwrap is able to reliably detect panics and allow the parent
|
||||||
|
process to handle them.
|
||||||
|
|
||||||
|
## WHY?! Panics should CRASH!
|
||||||
|
|
||||||
|
Yes, panics _should_ crash. They are 100% always indicative of bugs.
|
||||||
|
However, in some cases, such as user-facing programs (programs like
|
||||||
|
[Packer](http://github.com/mitchellh/packer) or
|
||||||
|
[Docker](http://github.com/dotcloud/docker)), it is up to the user to
|
||||||
|
report such panics. This is unreliable, at best, and it would be better if the
|
||||||
|
program could have a way to automatically report panics. panicwrap provides
|
||||||
|
a way to do this.
|
||||||
|
|
||||||
|
For backend applications, it is easier to detect crashes (since the application
|
||||||
|
exits). However, it is still nice sometimes to more intelligently log
|
||||||
|
panics in some way. For example, at [HashiCorp](http://www.hashicorp.com),
|
||||||
|
we use panicwrap to log panics to timestamped files with some additional
|
||||||
|
data (configuration settings at the time, environmental variables, etc.)
|
||||||
|
|
||||||
|
The goal of panicwrap is _not_ to hide panics. It is instead to provide
|
||||||
|
a clean mechanism for handling them before bubbling the up to the user
|
||||||
|
and ultimately crashing.
|
63
Godeps/_workspace/src/github.com/bugsnag/panicwrap/monitor.go
generated
vendored
Normal file
63
Godeps/_workspace/src/github.com/bugsnag/panicwrap/monitor.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package panicwrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bugsnag/osext"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func monitor(c *WrapConfig) (int, error) {
|
||||||
|
|
||||||
|
// If we're the child process, absorb panics.
|
||||||
|
if Wrapped(c) {
|
||||||
|
panicCh := make(chan string)
|
||||||
|
|
||||||
|
go trackPanic(os.Stdin, os.Stderr, c.DetectDuration, panicCh)
|
||||||
|
|
||||||
|
// Wait on the panic data
|
||||||
|
panicTxt := <-panicCh
|
||||||
|
if panicTxt != "" {
|
||||||
|
if !c.HidePanic {
|
||||||
|
os.Stderr.Write([]byte(panicTxt))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Handler(panicTxt)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
exePath, err := osext.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
cmd := exec.Command(exePath, os.Args[1:]...)
|
||||||
|
|
||||||
|
read, write, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Stdin = read
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = append(os.Environ(), c.CookieKey+"="+c.CookieValue)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = syscall.Dup2(int(write.Fd()), int(os.Stderr.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, nil
|
||||||
|
}
|
7
Godeps/_workspace/src/github.com/bugsnag/panicwrap/monitor_windows.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/bugsnag/panicwrap/monitor_windows.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package panicwrap
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func monitor(c *WrapConfig) (int, error) {
|
||||||
|
return -1, fmt.Errorf("Monitor is not supported on windows")
|
||||||
|
}
|
339
Godeps/_workspace/src/github.com/bugsnag/panicwrap/panicwrap.go
generated
vendored
Normal file
339
Godeps/_workspace/src/github.com/bugsnag/panicwrap/panicwrap.go
generated
vendored
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
// The panicwrap package provides functions for capturing and handling
|
||||||
|
// panics in your application. It does this by re-executing the running
|
||||||
|
// application and monitoring stderr for any panics. At the same time,
|
||||||
|
// stdout/stderr/etc. are set to the same values so that data is shuttled
|
||||||
|
// through properly, making the existence of panicwrap mostly transparent.
|
||||||
|
//
|
||||||
|
// Panics are only detected when the subprocess exits with a non-zero
|
||||||
|
// exit status, since this is the only time panics are real. Otherwise,
|
||||||
|
// "panic-like" output is ignored.
|
||||||
|
package panicwrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/bugsnag/osext"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_COOKIE_KEY = "cccf35992f8f3cd8d1d28f0109dd953e26664531"
|
||||||
|
DEFAULT_COOKIE_VAL = "7c28215aca87789f95b406b8dd91aa5198406750"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerFunc is the type called when a panic is detected.
|
||||||
|
type HandlerFunc func(string)
|
||||||
|
|
||||||
|
// WrapConfig is the configuration for panicwrap when wrapping an existing
|
||||||
|
// binary. To get started, in general, you only need the BasicWrap function
|
||||||
|
// that will set this up for you. However, for more customizability,
|
||||||
|
// WrapConfig and Wrap can be used.
|
||||||
|
type WrapConfig struct {
|
||||||
|
// Handler is the function called when a panic occurs.
|
||||||
|
Handler HandlerFunc
|
||||||
|
|
||||||
|
// The cookie key and value are used within environmental variables
|
||||||
|
// to tell the child process that it is already executing so that
|
||||||
|
// wrap doesn't re-wrap itself.
|
||||||
|
CookieKey string
|
||||||
|
CookieValue string
|
||||||
|
|
||||||
|
// If true, the panic will not be mirrored to the configured writer
|
||||||
|
// and will instead ONLY go to the handler. This lets you effectively
|
||||||
|
// hide panics from the end user. This is not recommended because if
|
||||||
|
// your handler fails, the panic is effectively lost.
|
||||||
|
HidePanic bool
|
||||||
|
|
||||||
|
// If true, panicwrap will boot a monitor sub-process and let the parent
|
||||||
|
// run the app. This mode is useful for processes run under supervisors
|
||||||
|
// like runit as signals get sent to the correct codebase. This is not
|
||||||
|
// supported when GOOS=windows, and ignores c.Stderr and c.Stdout.
|
||||||
|
Monitor bool
|
||||||
|
|
||||||
|
// The amount of time that a process must exit within after detecting
|
||||||
|
// a panic header for panicwrap to assume it is a panic. Defaults to
|
||||||
|
// 300 milliseconds.
|
||||||
|
DetectDuration time.Duration
|
||||||
|
|
||||||
|
// The writer to send the stderr to. If this is nil, then it defaults
|
||||||
|
// to os.Stderr.
|
||||||
|
Writer io.Writer
|
||||||
|
|
||||||
|
// The writer to send stdout to. If this is nil, then it defaults to
|
||||||
|
// os.Stdout.
|
||||||
|
Stdout io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicWrap calls Wrap with the given handler function, using defaults
|
||||||
|
// for everything else. See Wrap and WrapConfig for more information on
|
||||||
|
// functionality and return values.
|
||||||
|
func BasicWrap(f HandlerFunc) (int, error) {
|
||||||
|
return Wrap(&WrapConfig{
|
||||||
|
Handler: f,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicMonitor calls Wrap with Monitor set to true on supported platforms.
|
||||||
|
// It forks your program and runs it again form the start. In one process
|
||||||
|
// BasicMonitor never returns, it just listens on stderr of the other process,
|
||||||
|
// and calls your handler when a panic is seen. In the other it either returns
|
||||||
|
// nil to indicate that the panic monitoring is enabled, or an error to indicate
|
||||||
|
// that something else went wrong.
|
||||||
|
func BasicMonitor(f HandlerFunc) error {
|
||||||
|
exitStatus, err := Wrap(&WrapConfig{
|
||||||
|
Handler: f,
|
||||||
|
Monitor: runtime.GOOS != "windows",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus >= 0 {
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap wraps the current executable in a handler to catch panics. It
|
||||||
|
// returns an error if there was an error during the wrapping process.
|
||||||
|
// If the error is nil, then the int result indicates the exit status of the
|
||||||
|
// child process. If the exit status is -1, then this is the child process,
|
||||||
|
// and execution should continue as normal. Otherwise, this is the parent
|
||||||
|
// process and the child successfully ran already, and you should exit the
|
||||||
|
// process with the returned exit status.
|
||||||
|
//
|
||||||
|
// This function should be called very very early in your program's execution.
|
||||||
|
// Ideally, this runs as the first line of code of main.
|
||||||
|
//
|
||||||
|
// Once this is called, the given WrapConfig shouldn't be modified or used
|
||||||
|
// any further.
|
||||||
|
func Wrap(c *WrapConfig) (int, error) {
|
||||||
|
if c.Handler == nil {
|
||||||
|
return -1, errors.New("Handler must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DetectDuration == 0 {
|
||||||
|
c.DetectDuration = 300 * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Writer == nil {
|
||||||
|
c.Writer = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Monitor {
|
||||||
|
return monitor(c)
|
||||||
|
} else {
|
||||||
|
return wrap(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrap(c *WrapConfig) (int, error) {
|
||||||
|
|
||||||
|
// If we're already wrapped, exit out.
|
||||||
|
if Wrapped(c) {
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the path to our current executable
|
||||||
|
exePath, err := osext.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipe the stderr so we can read all the data as we look for panics
|
||||||
|
stderr_r, stderr_w := io.Pipe()
|
||||||
|
|
||||||
|
// doneCh is closed when we're done, signaling any other goroutines
|
||||||
|
// to end immediately.
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
|
||||||
|
// panicCh is the channel on which the panic text will actually be
|
||||||
|
// sent.
|
||||||
|
panicCh := make(chan string)
|
||||||
|
|
||||||
|
// On close, make sure to finish off the copying of data to stderr
|
||||||
|
defer func() {
|
||||||
|
defer close(doneCh)
|
||||||
|
stderr_w.Close()
|
||||||
|
<-panicCh
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Start the goroutine that will watch stderr for any panics
|
||||||
|
go trackPanic(stderr_r, c.Writer, c.DetectDuration, panicCh)
|
||||||
|
|
||||||
|
// Create the writer for stdout that we're going to use
|
||||||
|
var stdout_w io.Writer = os.Stdout
|
||||||
|
if c.Stdout != nil {
|
||||||
|
stdout_w = c.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a subcommand to re-execute ourselves. We make sure to
|
||||||
|
// set the environmental variable to include our cookie. We also
|
||||||
|
// set stdin/stdout to match the config. Finally, we pipe stderr
|
||||||
|
// through ourselves in order to watch for panics.
|
||||||
|
cmd := exec.Command(exePath, os.Args[1:]...)
|
||||||
|
cmd.Env = append(os.Environ(), c.CookieKey+"="+c.CookieValue)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = stdout_w
|
||||||
|
cmd.Stderr = stderr_w
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen to signals and capture them forever. We allow the child
|
||||||
|
// process to handle them in some way.
|
||||||
|
sigCh := make(chan os.Signal)
|
||||||
|
signal.Notify(sigCh, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
defer signal.Stop(sigCh)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-doneCh:
|
||||||
|
return
|
||||||
|
case <-sigCh:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
exitErr, ok := err.(*exec.ExitError)
|
||||||
|
if !ok {
|
||||||
|
// This is some other kind of subprocessing error.
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exitStatus := 1
|
||||||
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||||
|
exitStatus = status.ExitStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the writer end so that the tracker goroutine ends at some point
|
||||||
|
stderr_w.Close()
|
||||||
|
|
||||||
|
// Wait on the panic data
|
||||||
|
panicTxt := <-panicCh
|
||||||
|
if panicTxt != "" {
|
||||||
|
if !c.HidePanic {
|
||||||
|
c.Writer.Write([]byte(panicTxt))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Handler(panicTxt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapped checks if we're already wrapped according to the configuration
|
||||||
|
// given.
|
||||||
|
//
|
||||||
|
// Wrapped is very cheap and can be used early to short-circuit some pre-wrap
|
||||||
|
// logic your application may have.
|
||||||
|
func Wrapped(c *WrapConfig) bool {
|
||||||
|
if c.CookieKey == "" {
|
||||||
|
c.CookieKey = DEFAULT_COOKIE_KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.CookieValue == "" {
|
||||||
|
c.CookieValue = DEFAULT_COOKIE_VAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the cookie key/value match our environment, then we are the
|
||||||
|
// child, so just exit now and tell the caller that we're the child
|
||||||
|
return os.Getenv(c.CookieKey) == c.CookieValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// trackPanic monitors the given reader for a panic. If a panic is detected,
|
||||||
|
// it is outputted on the result channel. This will close the channel once
|
||||||
|
// it is complete.
|
||||||
|
func trackPanic(r io.Reader, w io.Writer, dur time.Duration, result chan<- string) {
|
||||||
|
defer close(result)
|
||||||
|
|
||||||
|
var panicTimer <-chan time.Time
|
||||||
|
panicBuf := new(bytes.Buffer)
|
||||||
|
panicHeader := []byte("panic:")
|
||||||
|
|
||||||
|
tempBuf := make([]byte, 2048)
|
||||||
|
for {
|
||||||
|
var buf []byte
|
||||||
|
var n int
|
||||||
|
|
||||||
|
if panicTimer == nil && panicBuf.Len() > 0 {
|
||||||
|
// We're not tracking a panic but the buffer length is
|
||||||
|
// greater than 0. We need to clear out that buffer, but
|
||||||
|
// look for another panic along the way.
|
||||||
|
|
||||||
|
// First, remove the previous panic header so we don't loop
|
||||||
|
w.Write(panicBuf.Next(len(panicHeader)))
|
||||||
|
|
||||||
|
// Next, assume that this is our new buffer to inspect
|
||||||
|
n = panicBuf.Len()
|
||||||
|
buf = make([]byte, n)
|
||||||
|
copy(buf, panicBuf.Bytes())
|
||||||
|
panicBuf.Reset()
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
buf = tempBuf
|
||||||
|
n, err = r.Read(buf)
|
||||||
|
if n <= 0 && err == io.EOF {
|
||||||
|
if panicBuf.Len() > 0 {
|
||||||
|
// We were tracking a panic, assume it was a panic
|
||||||
|
// and return that as the result.
|
||||||
|
result <- panicBuf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if panicTimer != nil {
|
||||||
|
// We're tracking what we think is a panic right now.
|
||||||
|
// If the timer ended, then it is not a panic.
|
||||||
|
isPanic := true
|
||||||
|
select {
|
||||||
|
case <-panicTimer:
|
||||||
|
isPanic = false
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matter what, buffer the text some more.
|
||||||
|
panicBuf.Write(buf[0:n])
|
||||||
|
|
||||||
|
if !isPanic {
|
||||||
|
// It isn't a panic, stop tracking. Clean-up will happen
|
||||||
|
// on the next iteration.
|
||||||
|
panicTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
flushIdx := n
|
||||||
|
idx := bytes.Index(buf[0:n], panicHeader)
|
||||||
|
if idx >= 0 {
|
||||||
|
flushIdx = idx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush to stderr what isn't a panic
|
||||||
|
w.Write(buf[0:flushIdx])
|
||||||
|
|
||||||
|
if idx < 0 {
|
||||||
|
// Not a panic so just continue along
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a panic header. Write we assume is a panic os far.
|
||||||
|
panicBuf.Write(buf[idx:n])
|
||||||
|
panicTimer = time.After(dur)
|
||||||
|
}
|
||||||
|
}
|
360
Godeps/_workspace/src/github.com/bugsnag/panicwrap/panicwrap_test.go
generated
vendored
Normal file
360
Godeps/_workspace/src/github.com/bugsnag/panicwrap/panicwrap_test.go
generated
vendored
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
package panicwrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func helperProcess(s ...string) *exec.Cmd {
|
||||||
|
cs := []string{"-test.run=TestHelperProcess", "--"}
|
||||||
|
cs = append(cs, s...)
|
||||||
|
env := []string{
|
||||||
|
"GO_WANT_HELPER_PROCESS=1",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(os.Args[0], cs...)
|
||||||
|
cmd.Env = append(env, os.Environ()...)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is executed by `helperProcess` in a separate process in order to
|
||||||
|
// provider a proper sub-process environment to test some of our functionality.
|
||||||
|
func TestHelperProcess(*testing.T) {
|
||||||
|
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the arguments to our helper, which are the arguments past
|
||||||
|
// the "--" in the command line.
|
||||||
|
args := os.Args
|
||||||
|
for len(args) > 0 {
|
||||||
|
if args[0] == "--" {
|
||||||
|
args = args[1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
args = args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "No command\n")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
panicHandler := func(s string) {
|
||||||
|
fmt.Fprintf(os.Stdout, "wrapped: %d", len(s))
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, args := args[0], args[1:]
|
||||||
|
switch cmd {
|
||||||
|
case "no-panic-ordered-output":
|
||||||
|
exitStatus, err := BasicWrap(panicHandler)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus < 0 {
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
os.Stdout.Write([]byte("a"))
|
||||||
|
os.Stderr.Write([]byte("b"))
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
case "no-panic-output":
|
||||||
|
fmt.Fprint(os.Stdout, "i am output")
|
||||||
|
fmt.Fprint(os.Stderr, "stderr out")
|
||||||
|
os.Exit(0)
|
||||||
|
case "panic-boundary":
|
||||||
|
exitStatus, err := BasicWrap(panicHandler)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus < 0 {
|
||||||
|
// Simulate a panic but on two boundaries...
|
||||||
|
fmt.Fprint(os.Stderr, "pan")
|
||||||
|
os.Stderr.Sync()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
fmt.Fprint(os.Stderr, "ic: oh crap")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
case "panic-long":
|
||||||
|
exitStatus, err := BasicWrap(panicHandler)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus < 0 {
|
||||||
|
// Make a fake panic by faking the header and adding a
|
||||||
|
// bunch of garbage.
|
||||||
|
fmt.Fprint(os.Stderr, "panic: foo\n\n")
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
fmt.Fprint(os.Stderr, "foobarbaz")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep so that it dumps the previous data
|
||||||
|
//time.Sleep(1 * time.Millisecond)
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// Make a real panic
|
||||||
|
panic("I AM REAL!")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
case "panic":
|
||||||
|
hidePanic := false
|
||||||
|
if args[0] == "hide" {
|
||||||
|
hidePanic = true
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &WrapConfig{
|
||||||
|
Handler: panicHandler,
|
||||||
|
HidePanic: hidePanic,
|
||||||
|
}
|
||||||
|
|
||||||
|
exitStatus, err := Wrap(config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus < 0 {
|
||||||
|
panic("uh oh")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
case "wrapped":
|
||||||
|
child := false
|
||||||
|
if len(args) > 0 && args[0] == "child" {
|
||||||
|
child = true
|
||||||
|
}
|
||||||
|
config := &WrapConfig{
|
||||||
|
Handler: panicHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
exitStatus, err := Wrap(config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus < 0 {
|
||||||
|
if child {
|
||||||
|
fmt.Printf("%v", Wrapped(config))
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !child {
|
||||||
|
fmt.Printf("%v", Wrapped(config))
|
||||||
|
}
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
case "panic-monitor":
|
||||||
|
|
||||||
|
config := &WrapConfig{
|
||||||
|
Handler: panicHandler,
|
||||||
|
HidePanic: true,
|
||||||
|
Monitor: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
exitStatus, err := Wrap(config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus != -1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("uh oh")
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "Unknown command: %q\n", cmd)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicWrap_Output(t *testing.T) {
|
||||||
|
stderr := new(bytes.Buffer)
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("no-panic-output")
|
||||||
|
p.Stdout = stdout
|
||||||
|
p.Stderr = stderr
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "i am output") {
|
||||||
|
t.Fatalf("didn't forward: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stderr.String(), "stderr out") {
|
||||||
|
t.Fatalf("didn't forward: %#v", stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO(mitchellh): This property would be nice to gain.
|
||||||
|
func TestPanicWrap_Output_Order(t *testing.T) {
|
||||||
|
output := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("no-panic-ordered-output")
|
||||||
|
p.Stdout = output
|
||||||
|
p.Stderr = output
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedBuf := new(bytes.Buffer)
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
expectedBuf.WriteString("ab")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(output.String())
|
||||||
|
expected := strings.TrimSpace(expectedBuf.String())
|
||||||
|
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestPanicWrap_panicHide(t *testing.T) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
stderr := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("panic", "hide")
|
||||||
|
p.Stdout = stdout
|
||||||
|
p.Stderr = stderr
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "wrapped:") {
|
||||||
|
t.Fatalf("didn't wrap: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(stderr.String(), "panic:") {
|
||||||
|
t.Fatalf("shouldn't have panic: %#v", stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicWrap_panicShow(t *testing.T) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
stderr := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("panic", "show")
|
||||||
|
p.Stdout = stdout
|
||||||
|
p.Stderr = stderr
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "wrapped:") {
|
||||||
|
t.Fatalf("didn't wrap: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stderr.String(), "panic:") {
|
||||||
|
t.Fatalf("should have panic: %#v", stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicWrap_panicLong(t *testing.T) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("panic-long")
|
||||||
|
p.Stdout = stdout
|
||||||
|
p.Stderr = new(bytes.Buffer)
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "wrapped:") {
|
||||||
|
t.Fatalf("didn't wrap: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicWrap_panicBoundary(t *testing.T) {
|
||||||
|
// TODO(mitchellh): panics are currently lost on boundaries
|
||||||
|
t.SkipNow()
|
||||||
|
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("panic-boundary")
|
||||||
|
p.Stdout = stdout
|
||||||
|
//p.Stderr = new(bytes.Buffer)
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "wrapped: 1015") {
|
||||||
|
t.Fatalf("didn't wrap: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicWrap_monitor(t *testing.T) {
|
||||||
|
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("panic-monitor")
|
||||||
|
p.Stdout = stdout
|
||||||
|
//p.Stderr = new(bytes.Buffer)
|
||||||
|
if err := p.Run(); err == nil || err.Error() != "exit status 2" {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "wrapped:") {
|
||||||
|
t.Fatalf("didn't wrap: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapped(t *testing.T) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("wrapped", "child")
|
||||||
|
p.Stdout = stdout
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "true") {
|
||||||
|
t.Fatalf("bad: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapped_parent(t *testing.T) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("wrapped")
|
||||||
|
p.Stdout = stdout
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "false") {
|
||||||
|
t.Fatalf("bad: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
74
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/attempt.go
generated
vendored
Normal file
74
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/attempt.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AttemptStrategy represents a strategy for waiting for an action
|
||||||
|
// to complete successfully. This is an internal type used by the
|
||||||
|
// implementation of other goamz packages.
|
||||||
|
type AttemptStrategy struct {
|
||||||
|
Total time.Duration // total duration of attempt.
|
||||||
|
Delay time.Duration // interval between each try in the burst.
|
||||||
|
Min int // minimum number of retries; overrides Total
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attempt struct {
|
||||||
|
strategy AttemptStrategy
|
||||||
|
last time.Time
|
||||||
|
end time.Time
|
||||||
|
force bool
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start begins a new sequence of attempts for the given strategy.
|
||||||
|
func (s AttemptStrategy) Start() *Attempt {
|
||||||
|
now := time.Now()
|
||||||
|
return &Attempt{
|
||||||
|
strategy: s,
|
||||||
|
last: now,
|
||||||
|
end: now.Add(s.Total),
|
||||||
|
force: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next waits until it is time to perform the next attempt or returns
|
||||||
|
// false if it is time to stop trying.
|
||||||
|
func (a *Attempt) Next() bool {
|
||||||
|
now := time.Now()
|
||||||
|
sleep := a.nextSleep(now)
|
||||||
|
if !a.force && !now.Add(sleep).Before(a.end) && a.strategy.Min <= a.count {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
a.force = false
|
||||||
|
if sleep > 0 && a.count > 0 {
|
||||||
|
time.Sleep(sleep)
|
||||||
|
now = time.Now()
|
||||||
|
}
|
||||||
|
a.count++
|
||||||
|
a.last = now
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attempt) nextSleep(now time.Time) time.Duration {
|
||||||
|
sleep := a.strategy.Delay - now.Sub(a.last)
|
||||||
|
if sleep < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return sleep
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNext returns whether another attempt will be made if the current
|
||||||
|
// one fails. If it returns true, the following call to Next is
|
||||||
|
// guaranteed to return true.
|
||||||
|
func (a *Attempt) HasNext() bool {
|
||||||
|
if a.force || a.strategy.Min > a.count {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
if now.Add(a.nextSleep(now)).Before(a.end) {
|
||||||
|
a.force = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
57
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/attempt_test.go
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/attempt_test.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package aws_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/crowdmob/goamz/aws"
|
||||||
|
"gopkg.in/check.v1"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (S) TestAttemptTiming(c *check.C) {
|
||||||
|
testAttempt := aws.AttemptStrategy{
|
||||||
|
Total: 0.25e9,
|
||||||
|
Delay: 0.1e9,
|
||||||
|
}
|
||||||
|
want := []time.Duration{0, 0.1e9, 0.2e9, 0.2e9}
|
||||||
|
got := make([]time.Duration, 0, len(want)) // avoid allocation when testing timing
|
||||||
|
t0 := time.Now()
|
||||||
|
for a := testAttempt.Start(); a.Next(); {
|
||||||
|
got = append(got, time.Now().Sub(t0))
|
||||||
|
}
|
||||||
|
got = append(got, time.Now().Sub(t0))
|
||||||
|
c.Assert(got, check.HasLen, len(want))
|
||||||
|
const margin = 0.01e9
|
||||||
|
for i, got := range want {
|
||||||
|
lo := want[i] - margin
|
||||||
|
hi := want[i] + margin
|
||||||
|
if got < lo || got > hi {
|
||||||
|
c.Errorf("attempt %d want %g got %g", i, want[i].Seconds(), got.Seconds())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (S) TestAttemptNextHasNext(c *check.C) {
|
||||||
|
a := aws.AttemptStrategy{}.Start()
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.Next(), check.Equals, false)
|
||||||
|
|
||||||
|
a = aws.AttemptStrategy{}.Start()
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.HasNext(), check.Equals, false)
|
||||||
|
c.Assert(a.Next(), check.Equals, false)
|
||||||
|
|
||||||
|
a = aws.AttemptStrategy{Total: 2e8}.Start()
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.HasNext(), check.Equals, true)
|
||||||
|
time.Sleep(2e8)
|
||||||
|
c.Assert(a.HasNext(), check.Equals, true)
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.Next(), check.Equals, false)
|
||||||
|
|
||||||
|
a = aws.AttemptStrategy{Total: 1e8, Min: 2}.Start()
|
||||||
|
time.Sleep(1e8)
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.HasNext(), check.Equals, true)
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.HasNext(), check.Equals, false)
|
||||||
|
c.Assert(a.Next(), check.Equals, false)
|
||||||
|
}
|
616
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/aws.go
generated
vendored
Normal file
616
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/aws.go
generated
vendored
Normal file
@ -0,0 +1,616 @@
|
|||||||
|
//
|
||||||
|
// goamz - Go packages to interact with the Amazon Web Services.
|
||||||
|
//
|
||||||
|
// https://wiki.ubuntu.com/goamz
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 Canonical Ltd.
|
||||||
|
//
|
||||||
|
// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
|
||||||
|
//
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Regular expressions for INI files
|
||||||
|
var (
|
||||||
|
iniSectionRegexp = regexp.MustCompile(`^\s*\[([^\[\]]+)\]\s*$`)
|
||||||
|
iniSettingRegexp = regexp.MustCompile(`^\s*(.+?)\s*=\s*(.*\S)\s*$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines the valid signers
|
||||||
|
const (
|
||||||
|
V2Signature = iota
|
||||||
|
V4Signature = iota
|
||||||
|
Route53Signature = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines the service endpoint and correct Signer implementation to use
|
||||||
|
// to sign requests for this endpoint
|
||||||
|
type ServiceInfo struct {
|
||||||
|
Endpoint string
|
||||||
|
Signer uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region defines the URLs where AWS services may be accessed.
|
||||||
|
//
|
||||||
|
// See http://goo.gl/d8BP1 for more details.
|
||||||
|
type Region struct {
|
||||||
|
Name string // the canonical name of this region.
|
||||||
|
EC2Endpoint string
|
||||||
|
S3Endpoint string
|
||||||
|
S3BucketEndpoint string // Not needed by AWS S3. Use ${bucket} for bucket name.
|
||||||
|
S3LocationConstraint bool // true if this region requires a LocationConstraint declaration.
|
||||||
|
S3LowercaseBucket bool // true if the region requires bucket names to be lower case.
|
||||||
|
SDBEndpoint string
|
||||||
|
SNSEndpoint string
|
||||||
|
SQSEndpoint string
|
||||||
|
SESEndpoint string
|
||||||
|
IAMEndpoint string
|
||||||
|
ELBEndpoint string
|
||||||
|
DynamoDBEndpoint string
|
||||||
|
CloudWatchServicepoint ServiceInfo
|
||||||
|
AutoScalingEndpoint string
|
||||||
|
RDSEndpoint ServiceInfo
|
||||||
|
KinesisEndpoint string
|
||||||
|
STSEndpoint string
|
||||||
|
CloudFormationEndpoint string
|
||||||
|
ElastiCacheEndpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
var Regions = map[string]Region{
|
||||||
|
APNortheast.Name: APNortheast,
|
||||||
|
APSoutheast.Name: APSoutheast,
|
||||||
|
APSoutheast2.Name: APSoutheast2,
|
||||||
|
EUCentral.Name: EUCentral,
|
||||||
|
EUWest.Name: EUWest,
|
||||||
|
USEast.Name: USEast,
|
||||||
|
USWest.Name: USWest,
|
||||||
|
USWest2.Name: USWest2,
|
||||||
|
USGovWest.Name: USGovWest,
|
||||||
|
SAEast.Name: SAEast,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Designates a signer interface suitable for signing AWS requests, params
|
||||||
|
// should be appropriately encoded for the request before signing.
|
||||||
|
//
|
||||||
|
// A signer should be initialized with Auth and the appropriate endpoint.
|
||||||
|
type Signer interface {
|
||||||
|
Sign(method, path string, params map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An AWS Service interface with the API to query the AWS service
|
||||||
|
//
|
||||||
|
// Supplied as an easy way to mock out service calls during testing.
|
||||||
|
type AWSService interface {
|
||||||
|
// Queries the AWS service at a given method/path with the params and
|
||||||
|
// returns an http.Response and error
|
||||||
|
Query(method, path string, params map[string]string) (*http.Response, error)
|
||||||
|
// Builds an error given an XML payload in the http.Response, can be used
|
||||||
|
// to process an error if the status code is not 200 for example.
|
||||||
|
BuildError(r *http.Response) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements a Server Query/Post API to easily query AWS services and build
|
||||||
|
// errors when desired
|
||||||
|
type Service struct {
|
||||||
|
service ServiceInfo
|
||||||
|
signer Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a base set of params for an action
|
||||||
|
func MakeParams(action string) map[string]string {
|
||||||
|
params := make(map[string]string)
|
||||||
|
params["Action"] = action
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new AWS server to handle making requests
|
||||||
|
func NewService(auth Auth, service ServiceInfo) (s *Service, err error) {
|
||||||
|
var signer Signer
|
||||||
|
switch service.Signer {
|
||||||
|
case V2Signature:
|
||||||
|
signer, err = NewV2Signer(auth, service)
|
||||||
|
// case V4Signature:
|
||||||
|
// signer, err = NewV4Signer(auth, service, Regions["eu-west-1"])
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Unsupported signer for service")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s = &Service{service: service, signer: signer}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Query(method, path string, params map[string]string) (resp *http.Response, err error) {
|
||||||
|
params["Timestamp"] = time.Now().UTC().Format(time.RFC3339)
|
||||||
|
u, err := url.Parse(s.service.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u.Path = path
|
||||||
|
|
||||||
|
s.signer.Sign(method, path, params)
|
||||||
|
if method == "GET" {
|
||||||
|
u.RawQuery = multimap(params).Encode()
|
||||||
|
resp, err = http.Get(u.String())
|
||||||
|
} else if method == "POST" {
|
||||||
|
resp, err = http.PostForm(u.String(), multimap(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) BuildError(r *http.Response) error {
|
||||||
|
errors := ErrorResponse{}
|
||||||
|
xml.NewDecoder(r.Body).Decode(&errors)
|
||||||
|
var err Error
|
||||||
|
err = errors.Errors
|
||||||
|
err.RequestId = errors.RequestId
|
||||||
|
err.StatusCode = r.StatusCode
|
||||||
|
if err.Message == "" {
|
||||||
|
err.Message = r.Status
|
||||||
|
}
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceError interface {
|
||||||
|
error
|
||||||
|
ErrorCode() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Errors Error `xml:"Error"`
|
||||||
|
RequestId string // A unique ID for tracking the request
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
StatusCode int
|
||||||
|
Type string
|
||||||
|
Code string
|
||||||
|
Message string
|
||||||
|
RequestId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *Error) Error() string {
|
||||||
|
return fmt.Sprintf("Type: %s, Code: %s, Message: %s",
|
||||||
|
err.Type, err.Code, err.Message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *Error) ErrorCode() string {
|
||||||
|
return err.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
AccessKey, SecretKey string
|
||||||
|
token string
|
||||||
|
expiration time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) Token() string {
|
||||||
|
if a.token == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if time.Since(a.expiration) >= -30*time.Second { //in an ideal world this should be zero assuming the instance is synching it's clock
|
||||||
|
*a, _ = GetAuth("", "", "", time.Time{})
|
||||||
|
}
|
||||||
|
return a.token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) Expiration() time.Time {
|
||||||
|
return a.expiration
|
||||||
|
}
|
||||||
|
|
||||||
|
// To be used with other APIs that return auth credentials such as STS
|
||||||
|
func NewAuth(accessKey, secretKey, token string, expiration time.Time) *Auth {
|
||||||
|
return &Auth{
|
||||||
|
AccessKey: accessKey,
|
||||||
|
SecretKey: secretKey,
|
||||||
|
token: token,
|
||||||
|
expiration: expiration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseMetadata
|
||||||
|
type ResponseMetadata struct {
|
||||||
|
RequestId string // A unique ID for tracking the request
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseResponse struct {
|
||||||
|
ResponseMetadata ResponseMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
var unreserved = make([]bool, 128)
|
||||||
|
var hex = "0123456789ABCDEF"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// RFC3986
|
||||||
|
u := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890-_.~"
|
||||||
|
for _, c := range u {
|
||||||
|
unreserved[c] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func multimap(p map[string]string) url.Values {
|
||||||
|
q := make(url.Values, len(p))
|
||||||
|
for k, v := range p {
|
||||||
|
q[k] = []string{v}
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
type credentials struct {
|
||||||
|
Code string
|
||||||
|
LastUpdated string
|
||||||
|
Type string
|
||||||
|
AccessKeyId string
|
||||||
|
SecretAccessKey string
|
||||||
|
Token string
|
||||||
|
Expiration string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMetaData retrieves instance metadata about the current machine.
|
||||||
|
//
|
||||||
|
// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html for more details.
|
||||||
|
func GetMetaData(path string) (contents []byte, err error) {
|
||||||
|
c := http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Dial: func(netw, addr string) (net.Conn, error) {
|
||||||
|
deadline := time.Now().Add(5 * time.Second)
|
||||||
|
c, err := net.DialTimeout(netw, addr, time.Second*2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.SetDeadline(deadline)
|
||||||
|
return c, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
url := "http://169.254.169.254/latest/meta-data/" + path
|
||||||
|
|
||||||
|
resp, err := c.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
err = fmt.Errorf("Code %d returned for url %s", resp.StatusCode, url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return []byte(body), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRegion(regionName string) (region Region) {
|
||||||
|
region = Regions[regionName]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInstanceCredentials creates an Auth based on the instance's role credentials.
|
||||||
|
// If the running instance is not in EC2 or does not have a valid IAM role, an error will be returned.
|
||||||
|
// For more info about setting up IAM roles, see http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
|
||||||
|
func GetInstanceCredentials() (cred credentials, err error) {
|
||||||
|
credentialPath := "iam/security-credentials/"
|
||||||
|
|
||||||
|
// Get the instance role
|
||||||
|
role, err := GetMetaData(credentialPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the instance role credentials
|
||||||
|
credentialJSON, err := GetMetaData(credentialPath + string(role))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(credentialJSON), &cred)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuth creates an Auth based on either passed in credentials,
|
||||||
|
// environment information or instance based role credentials.
|
||||||
|
func GetAuth(accessKey string, secretKey, token string, expiration time.Time) (auth Auth, err error) {
|
||||||
|
// First try passed in credentials
|
||||||
|
if accessKey != "" && secretKey != "" {
|
||||||
|
return Auth{accessKey, secretKey, token, expiration}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next try to get auth from the environment
|
||||||
|
auth, err = EnvAuth()
|
||||||
|
if err == nil {
|
||||||
|
// Found auth, return
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next try getting auth from the instance role
|
||||||
|
cred, err := GetInstanceCredentials()
|
||||||
|
if err == nil {
|
||||||
|
// Found auth, return
|
||||||
|
auth.AccessKey = cred.AccessKeyId
|
||||||
|
auth.SecretKey = cred.SecretAccessKey
|
||||||
|
auth.token = cred.Token
|
||||||
|
exptdate, err := time.Parse("2006-01-02T15:04:05Z", cred.Expiration)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Error Parsing expiration date: cred.Expiration :%s , error: %s \n", cred.Expiration, err)
|
||||||
|
}
|
||||||
|
auth.expiration = exptdate
|
||||||
|
return auth, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next try getting auth from the credentials file
|
||||||
|
auth, err = CredentialFileAuth("", "", time.Minute*5)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//err = errors.New("No valid AWS authentication found")
|
||||||
|
err = fmt.Errorf("No valid AWS authentication found: %s", err)
|
||||||
|
return auth, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnvAuth creates an Auth based on environment information.
|
||||||
|
// The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment
|
||||||
|
// variables are used.
|
||||||
|
func EnvAuth() (auth Auth, err error) {
|
||||||
|
auth.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
|
||||||
|
if auth.AccessKey == "" {
|
||||||
|
auth.AccessKey = os.Getenv("AWS_ACCESS_KEY")
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
|
||||||
|
if auth.SecretKey == "" {
|
||||||
|
auth.SecretKey = os.Getenv("AWS_SECRET_KEY")
|
||||||
|
}
|
||||||
|
if auth.AccessKey == "" {
|
||||||
|
err = errors.New("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
|
||||||
|
}
|
||||||
|
if auth.SecretKey == "" {
|
||||||
|
err = errors.New("AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CredentialFileAuth creates and Auth based on a credentials file. The file
|
||||||
|
// contains various authentication profiles for use with AWS.
|
||||||
|
//
|
||||||
|
// The credentials file, which is used by other AWS SDKs, is documented at
|
||||||
|
// http://blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs
|
||||||
|
func CredentialFileAuth(filePath string, profile string, expiration time.Duration) (auth Auth, err error) {
|
||||||
|
if profile == "" {
|
||||||
|
profile = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
if filePath == "" {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return auth, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath = path.Join(u.HomeDir, ".aws", "credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the file, then parse the INI
|
||||||
|
contents, err := ioutil.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles := parseINI(string(contents))
|
||||||
|
profileData, ok := profiles[profile]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("The credentials file did not contain the profile")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keyId, ok := profileData["aws_access_key_id"]
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("The credentials file did not contain required attribute aws_access_key_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secretKey, ok := profileData["aws_secret_access_key"]
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("The credentials file did not contain required attribute aws_secret_access_key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.AccessKey = keyId
|
||||||
|
auth.SecretKey = secretKey
|
||||||
|
|
||||||
|
if token, ok := profileData["aws_session_token"]; ok {
|
||||||
|
auth.token = token
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.expiration = time.Now().Add(expiration)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseINI takes the contents of a credentials file and returns a map, whose keys
|
||||||
|
// are the various profiles, and whose values are maps of the settings for the
|
||||||
|
// profiles
|
||||||
|
func parseINI(fileContents string) map[string]map[string]string {
|
||||||
|
profiles := make(map[string]map[string]string)
|
||||||
|
|
||||||
|
lines := strings.Split(fileContents, "\n")
|
||||||
|
|
||||||
|
var currentSection map[string]string
|
||||||
|
for _, line := range lines {
|
||||||
|
// remove comments, which start with a semi-colon
|
||||||
|
if split := strings.Split(line, ";"); len(split) > 1 {
|
||||||
|
line = split[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the line is the start of a profile.
|
||||||
|
//
|
||||||
|
// for example:
|
||||||
|
// [default]
|
||||||
|
//
|
||||||
|
// otherwise, check for the proper setting
|
||||||
|
// property=value
|
||||||
|
if sectMatch := iniSectionRegexp.FindStringSubmatch(line); len(sectMatch) == 2 {
|
||||||
|
currentSection = make(map[string]string)
|
||||||
|
profiles[sectMatch[1]] = currentSection
|
||||||
|
} else if setMatch := iniSettingRegexp.FindStringSubmatch(line); len(setMatch) == 3 && currentSection != nil {
|
||||||
|
currentSection[setMatch[1]] = setMatch[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode takes a string and URI-encodes it in a way suitable
|
||||||
|
// to be used in AWS signatures.
|
||||||
|
func Encode(s string) string {
|
||||||
|
encode := false
|
||||||
|
for i := 0; i != len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c > 127 || !unreserved[c] {
|
||||||
|
encode = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !encode {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
e := make([]byte, len(s)*3)
|
||||||
|
ei := 0
|
||||||
|
for i := 0; i != len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c > 127 || !unreserved[c] {
|
||||||
|
e[ei] = '%'
|
||||||
|
e[ei+1] = hex[c>>4]
|
||||||
|
e[ei+2] = hex[c&0xF]
|
||||||
|
ei += 3
|
||||||
|
} else {
|
||||||
|
e[ei] = c
|
||||||
|
ei += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(e[:ei])
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialTimeout(network, addr string) (net.Conn, error) {
|
||||||
|
return net.DialTimeout(network, addr, time.Duration(2*time.Second))
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstanceRegion() string {
|
||||||
|
transport := http.Transport{Dial: dialTimeout}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("http://169.254.169.254/latest/meta-data/placement/availability-zone")
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
b := string(body)
|
||||||
|
region := b[:len(b)-1]
|
||||||
|
return region
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstanceId() string {
|
||||||
|
transport := http.Transport{Dial: dialTimeout}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("http://169.254.169.254/latest/meta-data/instance-id")
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstanceType() string {
|
||||||
|
transport := http.Transport{Dial: dialTimeout}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("http://169.254.169.254/latest/meta-data/instance-type")
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServerLocalIp() string {
|
||||||
|
transport := http.Transport{Dial: dialTimeout}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("http://169.254.169.254/latest/meta-data/local-ipv4")
|
||||||
|
if err != nil {
|
||||||
|
return "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServerPublicIp() string {
|
||||||
|
transport := http.Transport{Dial: dialTimeout}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("http://169.254.169.254/latest/meta-data/public-ipv4")
|
||||||
|
if err != nil {
|
||||||
|
return "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/aws_test.go
generated
vendored
Normal file
140
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/aws_test.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package aws_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/crowdmob/goamz/aws"
|
||||||
|
"gopkg.in/check.v1"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
check.TestingT(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = check.Suite(&S{})
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
environ []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) SetUpSuite(c *check.C) {
|
||||||
|
s.environ = os.Environ()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TearDownTest(c *check.C) {
|
||||||
|
os.Clearenv()
|
||||||
|
for _, kv := range s.environ {
|
||||||
|
l := strings.SplitN(kv, "=", 2)
|
||||||
|
os.Setenv(l[0], l[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestEnvAuthNoSecret(c *check.C) {
|
||||||
|
os.Clearenv()
|
||||||
|
_, err := aws.EnvAuth()
|
||||||
|
c.Assert(err, check.ErrorMatches, "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestEnvAuthNoAccess(c *check.C) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("AWS_SECRET_ACCESS_KEY", "foo")
|
||||||
|
_, err := aws.EnvAuth()
|
||||||
|
c.Assert(err, check.ErrorMatches, "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestEnvAuth(c *check.C) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
||||||
|
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
||||||
|
auth, err := aws.EnvAuth()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(auth, check.Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestEnvAuthAlt(c *check.C) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("AWS_SECRET_KEY", "secret")
|
||||||
|
os.Setenv("AWS_ACCESS_KEY", "access")
|
||||||
|
auth, err := aws.EnvAuth()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(auth, check.Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestGetAuthStatic(c *check.C) {
|
||||||
|
exptdate := time.Now().Add(time.Hour)
|
||||||
|
auth, err := aws.GetAuth("access", "secret", "token", exptdate)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(auth.AccessKey, check.Equals, "access")
|
||||||
|
c.Assert(auth.SecretKey, check.Equals, "secret")
|
||||||
|
c.Assert(auth.Token(), check.Equals, "token")
|
||||||
|
c.Assert(auth.Expiration(), check.Equals, exptdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestGetAuthEnv(c *check.C) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
||||||
|
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
||||||
|
auth, err := aws.GetAuth("", "", "", time.Time{})
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(auth, check.Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestEncode(c *check.C) {
|
||||||
|
c.Assert(aws.Encode("foo"), check.Equals, "foo")
|
||||||
|
c.Assert(aws.Encode("/"), check.Equals, "%2F")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestRegionsAreNamed(c *check.C) {
|
||||||
|
for n, r := range aws.Regions {
|
||||||
|
c.Assert(n, check.Equals, r.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestCredentialsFileAuth(c *check.C) {
|
||||||
|
file, err := ioutil.TempFile("", "creds")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iniFile := `
|
||||||
|
|
||||||
|
[default] ; comment 123
|
||||||
|
aws_access_key_id = keyid1 ;comment
|
||||||
|
aws_secret_access_key=key1
|
||||||
|
|
||||||
|
[profile2]
|
||||||
|
aws_access_key_id = keyid2 ;comment
|
||||||
|
aws_secret_access_key=key2
|
||||||
|
aws_session_token=token1
|
||||||
|
|
||||||
|
`
|
||||||
|
_, err = file.WriteString(iniFile)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check non-existant profile
|
||||||
|
_, err = aws.CredentialFileAuth(file.Name(), "no profile", 30*time.Minute)
|
||||||
|
c.Assert(err, check.Not(check.Equals), nil)
|
||||||
|
|
||||||
|
defaultProfile, err := aws.CredentialFileAuth(file.Name(), "default", 30*time.Minute)
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
c.Assert(defaultProfile.AccessKey, check.Equals, "keyid1")
|
||||||
|
c.Assert(defaultProfile.SecretKey, check.Equals, "key1")
|
||||||
|
c.Assert(defaultProfile.Token(), check.Equals, "")
|
||||||
|
|
||||||
|
profile2, err := aws.CredentialFileAuth(file.Name(), "profile2", 30*time.Minute)
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
c.Assert(profile2.AccessKey, check.Equals, "keyid2")
|
||||||
|
c.Assert(profile2.SecretKey, check.Equals, "key2")
|
||||||
|
c.Assert(profile2.Token(), check.Equals, "token1")
|
||||||
|
}
|
124
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/client.go
generated
vendored
Normal file
124
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/client.go
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RetryableFunc func(*http.Request, *http.Response, error) bool
|
||||||
|
type WaitFunc func(try int)
|
||||||
|
type DeadlineFunc func() time.Time
|
||||||
|
|
||||||
|
type ResilientTransport struct {
|
||||||
|
// Timeout is the maximum amount of time a dial will wait for
|
||||||
|
// a connect to complete.
|
||||||
|
//
|
||||||
|
// The default is no timeout.
|
||||||
|
//
|
||||||
|
// With or without a timeout, the operating system may impose
|
||||||
|
// its own earlier timeout. For instance, TCP timeouts are
|
||||||
|
// often around 3 minutes.
|
||||||
|
DialTimeout time.Duration
|
||||||
|
|
||||||
|
// MaxTries, if non-zero, specifies the number of times we will retry on
|
||||||
|
// failure. Retries are only attempted for temporary network errors or known
|
||||||
|
// safe failures.
|
||||||
|
MaxTries int
|
||||||
|
Deadline DeadlineFunc
|
||||||
|
ShouldRetry RetryableFunc
|
||||||
|
Wait WaitFunc
|
||||||
|
transport *http.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience method for creating an http client
|
||||||
|
func NewClient(rt *ResilientTransport) *http.Client {
|
||||||
|
rt.transport = &http.Transport{
|
||||||
|
Dial: func(netw, addr string) (net.Conn, error) {
|
||||||
|
c, err := net.DialTimeout(netw, addr, rt.DialTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.SetDeadline(rt.Deadline())
|
||||||
|
return c, nil
|
||||||
|
},
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
}
|
||||||
|
// TODO: Would be nice is ResilientTransport allowed clients to initialize
|
||||||
|
// with http.Transport attributes.
|
||||||
|
return &http.Client{
|
||||||
|
Transport: rt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var retryingTransport = &ResilientTransport{
|
||||||
|
Deadline: func() time.Time {
|
||||||
|
return time.Now().Add(5 * time.Second)
|
||||||
|
},
|
||||||
|
DialTimeout: 10 * time.Second,
|
||||||
|
MaxTries: 3,
|
||||||
|
ShouldRetry: awsRetry,
|
||||||
|
Wait: ExpBackoff,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exported default client
|
||||||
|
var RetryingClient = NewClient(retryingTransport)
|
||||||
|
|
||||||
|
func (t *ResilientTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return t.tries(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry a request a maximum of t.MaxTries times.
|
||||||
|
// We'll only retry if the proper criteria are met.
|
||||||
|
// If a wait function is specified, wait that amount of time
|
||||||
|
// In between requests.
|
||||||
|
func (t *ResilientTransport) tries(req *http.Request) (res *http.Response, err error) {
|
||||||
|
for try := 0; try < t.MaxTries; try += 1 {
|
||||||
|
res, err = t.transport.RoundTrip(req)
|
||||||
|
|
||||||
|
if !t.ShouldRetry(req, res, err) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if res != nil {
|
||||||
|
res.Body.Close()
|
||||||
|
}
|
||||||
|
if t.Wait != nil {
|
||||||
|
t.Wait(try)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpBackoff(try int) {
|
||||||
|
time.Sleep(100 * time.Millisecond *
|
||||||
|
time.Duration(math.Exp2(float64(try))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func LinearBackoff(try int) {
|
||||||
|
time.Sleep(time.Duration(try*100) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide if we should retry a request.
|
||||||
|
// In general, the criteria for retrying a request is described here
|
||||||
|
// http://docs.aws.amazon.com/general/latest/gr/api-retries.html
|
||||||
|
func awsRetry(req *http.Request, res *http.Response, err error) bool {
|
||||||
|
retry := false
|
||||||
|
|
||||||
|
// Retry if there's a temporary network error.
|
||||||
|
if neterr, ok := err.(net.Error); ok {
|
||||||
|
if neterr.Temporary() {
|
||||||
|
retry = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry if we get a 5xx series error.
|
||||||
|
if res != nil {
|
||||||
|
if res.StatusCode >= 500 && res.StatusCode < 600 {
|
||||||
|
retry = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retry
|
||||||
|
}
|
29
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/export_test.go
generated
vendored
Normal file
29
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/export_test.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V4Signer:
|
||||||
|
// Exporting methods for testing
|
||||||
|
|
||||||
|
func (s *V4Signer) RequestTime(req *http.Request) time.Time {
|
||||||
|
return s.requestTime(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4Signer) CanonicalRequest(req *http.Request) string {
|
||||||
|
return s.canonicalRequest(req, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4Signer) StringToSign(t time.Time, creq string) string {
|
||||||
|
return s.stringToSign(t, creq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4Signer) Signature(t time.Time, sts string) string {
|
||||||
|
return s.signature(t, sts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4Signer) Authorization(header http.Header, t time.Time, signature string) string {
|
||||||
|
return s.authorization(header, t, signature)
|
||||||
|
}
|
231
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/regions.go
generated
vendored
Normal file
231
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/regions.go
generated
vendored
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
var USGovWest = Region{
|
||||||
|
"us-gov-west-1",
|
||||||
|
"https://ec2.us-gov-west-1.amazonaws.com",
|
||||||
|
"https://s3-fips-us-gov-west-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
"https://sns.us-gov-west-1.amazonaws.com",
|
||||||
|
"https://sqs.us-gov-west-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
"https://iam.us-gov.amazonaws.com",
|
||||||
|
"https://elasticloadbalancing.us-gov-west-1.amazonaws.com",
|
||||||
|
"https://dynamodb.us-gov-west-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://monitoring.us-gov-west-1.amazonaws.com", V2Signature},
|
||||||
|
"https://autoscaling.us-gov-west-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://rds.us-gov-west-1.amazonaws.com", V2Signature},
|
||||||
|
"",
|
||||||
|
"https://sts.amazonaws.com",
|
||||||
|
"https://cloudformation.us-gov-west-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
|
||||||
|
var USEast = Region{
|
||||||
|
"us-east-1",
|
||||||
|
"https://ec2.us-east-1.amazonaws.com",
|
||||||
|
"https://s3.amazonaws.com",
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"https://sdb.amazonaws.com",
|
||||||
|
"https://sns.us-east-1.amazonaws.com",
|
||||||
|
"https://sqs.us-east-1.amazonaws.com",
|
||||||
|
"https://email.us-east-1.amazonaws.com",
|
||||||
|
"https://iam.amazonaws.com",
|
||||||
|
"https://elasticloadbalancing.us-east-1.amazonaws.com",
|
||||||
|
"https://dynamodb.us-east-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://monitoring.us-east-1.amazonaws.com", V2Signature},
|
||||||
|
"https://autoscaling.us-east-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://rds.us-east-1.amazonaws.com", V2Signature},
|
||||||
|
"https://kinesis.us-east-1.amazonaws.com",
|
||||||
|
"https://sts.amazonaws.com",
|
||||||
|
"https://cloudformation.us-east-1.amazonaws.com",
|
||||||
|
"https://elasticache.us-east-1.amazonaws.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var USWest = Region{
|
||||||
|
"us-west-1",
|
||||||
|
"https://ec2.us-west-1.amazonaws.com",
|
||||||
|
"https://s3-us-west-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"https://sdb.us-west-1.amazonaws.com",
|
||||||
|
"https://sns.us-west-1.amazonaws.com",
|
||||||
|
"https://sqs.us-west-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
"https://iam.amazonaws.com",
|
||||||
|
"https://elasticloadbalancing.us-west-1.amazonaws.com",
|
||||||
|
"https://dynamodb.us-west-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://monitoring.us-west-1.amazonaws.com", V2Signature},
|
||||||
|
"https://autoscaling.us-west-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://rds.us-west-1.amazonaws.com", V2Signature},
|
||||||
|
"",
|
||||||
|
"https://sts.amazonaws.com",
|
||||||
|
"https://cloudformation.us-west-1.amazonaws.com",
|
||||||
|
"https://elasticache.us-west-1.amazonaws.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var USWest2 = Region{
|
||||||
|
"us-west-2",
|
||||||
|
"https://ec2.us-west-2.amazonaws.com",
|
||||||
|
"https://s3-us-west-2.amazonaws.com",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"https://sdb.us-west-2.amazonaws.com",
|
||||||
|
"https://sns.us-west-2.amazonaws.com",
|
||||||
|
"https://sqs.us-west-2.amazonaws.com",
|
||||||
|
"https://email.us-west-2.amazonaws.com",
|
||||||
|
"https://iam.amazonaws.com",
|
||||||
|
"https://elasticloadbalancing.us-west-2.amazonaws.com",
|
||||||
|
"https://dynamodb.us-west-2.amazonaws.com",
|
||||||
|
ServiceInfo{"https://monitoring.us-west-2.amazonaws.com", V2Signature},
|
||||||
|
"https://autoscaling.us-west-2.amazonaws.com",
|
||||||
|
ServiceInfo{"https://rds.us-west-2.amazonaws.com", V2Signature},
|
||||||
|
"https://kinesis.us-west-2.amazonaws.com",
|
||||||
|
"https://sts.amazonaws.com",
|
||||||
|
"https://cloudformation.us-west-2.amazonaws.com",
|
||||||
|
"https://elasticache.us-west-2.amazonaws.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var EUWest = Region{
|
||||||
|
"eu-west-1",
|
||||||
|
"https://ec2.eu-west-1.amazonaws.com",
|
||||||
|
"https://s3-eu-west-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"https://sdb.eu-west-1.amazonaws.com",
|
||||||
|
"https://sns.eu-west-1.amazonaws.com",
|
||||||
|
"https://sqs.eu-west-1.amazonaws.com",
|
||||||
|
"https://email.eu-west-1.amazonaws.com",
|
||||||
|
"https://iam.amazonaws.com",
|
||||||
|
"https://elasticloadbalancing.eu-west-1.amazonaws.com",
|
||||||
|
"https://dynamodb.eu-west-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://monitoring.eu-west-1.amazonaws.com", V2Signature},
|
||||||
|
"https://autoscaling.eu-west-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://rds.eu-west-1.amazonaws.com", V2Signature},
|
||||||
|
"https://kinesis.eu-west-1.amazonaws.com",
|
||||||
|
"https://sts.amazonaws.com",
|
||||||
|
"https://cloudformation.eu-west-1.amazonaws.com",
|
||||||
|
"https://elasticache.eu-west-1.amazonaws.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var EUCentral = Region{
|
||||||
|
"eu-central-1",
|
||||||
|
"https://ec2.eu-central-1.amazonaws.com",
|
||||||
|
"https://s3-eu-central-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"https://sdb.eu-central-1.amazonaws.com",
|
||||||
|
"https://sns.eu-central-1.amazonaws.com",
|
||||||
|
"https://sqs.eu-central-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
"https://iam.amazonaws.com",
|
||||||
|
"https://elasticloadbalancing.eu-central-1.amazonaws.com",
|
||||||
|
"https://dynamodb.eu-central-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://monitoring.eu-central-1.amazonaws.com", V2Signature},
|
||||||
|
"https://autoscaling.eu-central-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://rds.eu-central-1.amazonaws.com", V2Signature},
|
||||||
|
"https://kinesis.eu-central-1.amazonaws.com",
|
||||||
|
"https://sts.amazonaws.com",
|
||||||
|
"https://cloudformation.eu-central-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
|
||||||
|
var APSoutheast = Region{
|
||||||
|
"ap-southeast-1",
|
||||||
|
"https://ec2.ap-southeast-1.amazonaws.com",
|
||||||
|
"https://s3-ap-southeast-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"https://sdb.ap-southeast-1.amazonaws.com",
|
||||||
|
"https://sns.ap-southeast-1.amazonaws.com",
|
||||||
|
"https://sqs.ap-southeast-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
"https://iam.amazonaws.com",
|
||||||
|
"https://elasticloadbalancing.ap-southeast-1.amazonaws.com",
|
||||||
|
"https://dynamodb.ap-southeast-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://monitoring.ap-southeast-1.amazonaws.com", V2Signature},
|
||||||
|
"https://autoscaling.ap-southeast-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://rds.ap-southeast-1.amazonaws.com", V2Signature},
|
||||||
|
"https://kinesis.ap-southeast-1.amazonaws.com",
|
||||||
|
"https://sts.amazonaws.com",
|
||||||
|
"https://cloudformation.ap-southeast-1.amazonaws.com",
|
||||||
|
"https://elasticache.ap-southeast-1.amazonaws.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var APSoutheast2 = Region{
|
||||||
|
"ap-southeast-2",
|
||||||
|
"https://ec2.ap-southeast-2.amazonaws.com",
|
||||||
|
"https://s3-ap-southeast-2.amazonaws.com",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"https://sdb.ap-southeast-2.amazonaws.com",
|
||||||
|
"https://sns.ap-southeast-2.amazonaws.com",
|
||||||
|
"https://sqs.ap-southeast-2.amazonaws.com",
|
||||||
|
"",
|
||||||
|
"https://iam.amazonaws.com",
|
||||||
|
"https://elasticloadbalancing.ap-southeast-2.amazonaws.com",
|
||||||
|
"https://dynamodb.ap-southeast-2.amazonaws.com",
|
||||||
|
ServiceInfo{"https://monitoring.ap-southeast-2.amazonaws.com", V2Signature},
|
||||||
|
"https://autoscaling.ap-southeast-2.amazonaws.com",
|
||||||
|
ServiceInfo{"https://rds.ap-southeast-2.amazonaws.com", V2Signature},
|
||||||
|
"https://kinesis.ap-southeast-2.amazonaws.com",
|
||||||
|
"https://sts.amazonaws.com",
|
||||||
|
"https://cloudformation.ap-southeast-2.amazonaws.com",
|
||||||
|
"https://elasticache.ap-southeast-2.amazonaws.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var APNortheast = Region{
|
||||||
|
"ap-northeast-1",
|
||||||
|
"https://ec2.ap-northeast-1.amazonaws.com",
|
||||||
|
"https://s3-ap-northeast-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"https://sdb.ap-northeast-1.amazonaws.com",
|
||||||
|
"https://sns.ap-northeast-1.amazonaws.com",
|
||||||
|
"https://sqs.ap-northeast-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
"https://iam.amazonaws.com",
|
||||||
|
"https://elasticloadbalancing.ap-northeast-1.amazonaws.com",
|
||||||
|
"https://dynamodb.ap-northeast-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://monitoring.ap-northeast-1.amazonaws.com", V2Signature},
|
||||||
|
"https://autoscaling.ap-northeast-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://rds.ap-northeast-1.amazonaws.com", V2Signature},
|
||||||
|
"https://kinesis.ap-northeast-1.amazonaws.com",
|
||||||
|
"https://sts.amazonaws.com",
|
||||||
|
"https://cloudformation.ap-northeast-1.amazonaws.com",
|
||||||
|
"https://elasticache.ap-northeast-1.amazonaws.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var SAEast = Region{
|
||||||
|
"sa-east-1",
|
||||||
|
"https://ec2.sa-east-1.amazonaws.com",
|
||||||
|
"https://s3-sa-east-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"https://sdb.sa-east-1.amazonaws.com",
|
||||||
|
"https://sns.sa-east-1.amazonaws.com",
|
||||||
|
"https://sqs.sa-east-1.amazonaws.com",
|
||||||
|
"",
|
||||||
|
"https://iam.amazonaws.com",
|
||||||
|
"https://elasticloadbalancing.sa-east-1.amazonaws.com",
|
||||||
|
"https://dynamodb.sa-east-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://monitoring.sa-east-1.amazonaws.com", V2Signature},
|
||||||
|
"https://autoscaling.sa-east-1.amazonaws.com",
|
||||||
|
ServiceInfo{"https://rds.sa-east-1.amazonaws.com", V2Signature},
|
||||||
|
"",
|
||||||
|
"https://sts.amazonaws.com",
|
||||||
|
"https://cloudformation.sa-east-1.amazonaws.com",
|
||||||
|
"https://elasticache.sa-east-1.amazonaws.com",
|
||||||
|
}
|
136
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/retry.go
generated
vendored
Normal file
136
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/retry.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxDelay = 20 * time.Second
|
||||||
|
defaultScale = 300 * time.Millisecond
|
||||||
|
throttlingScale = 500 * time.Millisecond
|
||||||
|
throttlingScaleRange = throttlingScale / 4
|
||||||
|
defaultMaxRetries = 3
|
||||||
|
dynamoDBScale = 25 * time.Millisecond
|
||||||
|
dynamoDBMaxRetries = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// A RetryPolicy encapsulates a strategy for implementing client retries.
|
||||||
|
//
|
||||||
|
// Default implementations are provided which match the AWS SDKs.
|
||||||
|
type RetryPolicy interface {
|
||||||
|
// ShouldRetry returns whether a client should retry a failed request.
|
||||||
|
ShouldRetry(target string, r *http.Response, err error, numRetries int) bool
|
||||||
|
|
||||||
|
// Delay returns the time a client should wait before issuing a retry.
|
||||||
|
Delay(target string, r *http.Response, err error, numRetries int) time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultRetryPolicy implements the AWS SDK default retry policy.
|
||||||
|
//
|
||||||
|
// It will retry up to 3 times, and uses an exponential backoff with a scale
|
||||||
|
// factor of 300ms (300ms, 600ms, 1200ms). If the retry is because of
|
||||||
|
// throttling, the delay will also include some randomness.
|
||||||
|
//
|
||||||
|
// See https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/PredefinedRetryPolicies.java#L90.
|
||||||
|
type DefaultRetryPolicy struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldRetry implements the RetryPolicy ShouldRetry method.
|
||||||
|
func (policy DefaultRetryPolicy) ShouldRetry(target string, r *http.Response, err error, numRetries int) bool {
|
||||||
|
return shouldRetry(r, err, numRetries, defaultMaxRetries)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay implements the RetryPolicy Delay method.
|
||||||
|
func (policy DefaultRetryPolicy) Delay(target string, r *http.Response, err error, numRetries int) time.Duration {
|
||||||
|
scale := defaultScale
|
||||||
|
if err, ok := err.(*Error); ok && isThrottlingException(err) {
|
||||||
|
scale = throttlingScale + time.Duration(rand.Int63n(int64(throttlingScaleRange)))
|
||||||
|
}
|
||||||
|
return exponentialBackoff(numRetries, scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynamoDBRetryPolicy implements the AWS SDK DynamoDB retry policy.
|
||||||
|
//
|
||||||
|
// It will retry up to 10 times, and uses an exponential backoff with a scale
|
||||||
|
// factor of 25ms (25ms, 50ms, 100ms, ...).
|
||||||
|
//
|
||||||
|
// See https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/PredefinedRetryPolicies.java#L103.
|
||||||
|
type DynamoDBRetryPolicy struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldRetry implements the RetryPolicy ShouldRetry method.
|
||||||
|
func (policy DynamoDBRetryPolicy) ShouldRetry(target string, r *http.Response, err error, numRetries int) bool {
|
||||||
|
return shouldRetry(r, err, numRetries, dynamoDBMaxRetries)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay implements the RetryPolicy Delay method.
|
||||||
|
func (policy DynamoDBRetryPolicy) Delay(target string, r *http.Response, err error, numRetries int) time.Duration {
|
||||||
|
return exponentialBackoff(numRetries, dynamoDBScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeverRetryPolicy never retries requests and returns immediately on failure.
|
||||||
|
type NeverRetryPolicy struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldRetry implements the RetryPolicy ShouldRetry method.
|
||||||
|
func (policy NeverRetryPolicy) ShouldRetry(target string, r *http.Response, err error, numRetries int) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay implements the RetryPolicy Delay method.
|
||||||
|
func (policy NeverRetryPolicy) Delay(target string, r *http.Response, err error, numRetries int) time.Duration {
|
||||||
|
return time.Duration(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldRetry determines if we should retry the request.
|
||||||
|
//
|
||||||
|
// See http://docs.aws.amazon.com/general/latest/gr/api-retries.html.
|
||||||
|
func shouldRetry(r *http.Response, err error, numRetries int, maxRetries int) bool {
|
||||||
|
// Once we've exceeded the max retry attempts, game over.
|
||||||
|
if numRetries >= maxRetries {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always retry temporary network errors.
|
||||||
|
if err, ok := err.(net.Error); ok && err.Temporary() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always retry 5xx responses.
|
||||||
|
if r != nil && r.StatusCode >= 500 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always retry throttling exceptions.
|
||||||
|
if err, ok := err.(ServiceError); ok && isThrottlingException(err) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other classes of failures indicate a problem with the request. Retrying
|
||||||
|
// won't help.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func exponentialBackoff(numRetries int, scale time.Duration) time.Duration {
|
||||||
|
if numRetries < 0 {
|
||||||
|
return time.Duration(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
delay := (1 << uint(numRetries)) * scale
|
||||||
|
if delay > maxDelay {
|
||||||
|
return maxDelay
|
||||||
|
}
|
||||||
|
return delay
|
||||||
|
}
|
||||||
|
|
||||||
|
func isThrottlingException(err ServiceError) bool {
|
||||||
|
switch err.ErrorCode() {
|
||||||
|
case "Throttling", "ThrottlingException", "ProvisionedThroughputExceededException":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
303
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/retry_test.go
generated
vendored
Normal file
303
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/retry_test.go
generated
vendored
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testInput struct {
|
||||||
|
res *http.Response
|
||||||
|
err error
|
||||||
|
numRetries int
|
||||||
|
}
|
||||||
|
|
||||||
|
type testResult struct {
|
||||||
|
shouldRetry bool
|
||||||
|
delay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
input testInput
|
||||||
|
defaultResult testResult
|
||||||
|
dynamoDBResult testResult
|
||||||
|
}
|
||||||
|
|
||||||
|
var testCases = []testCase{
|
||||||
|
// Test nil fields
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
err: nil,
|
||||||
|
res: nil,
|
||||||
|
numRetries: 0,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: 300 * time.Millisecond,
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: 25 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Test 3 different throttling exceptions
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
err: &Error{
|
||||||
|
Code: "Throttling",
|
||||||
|
},
|
||||||
|
numRetries: 0,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 617165505 * time.Nanosecond, // account for randomness with known seed
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 25 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
err: &Error{
|
||||||
|
Code: "ThrottlingException",
|
||||||
|
},
|
||||||
|
numRetries: 0,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 579393152 * time.Nanosecond, // account for randomness with known seed
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 25 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
err: &Error{
|
||||||
|
Code: "ProvisionedThroughputExceededException",
|
||||||
|
},
|
||||||
|
numRetries: 1,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 1105991654 * time.Nanosecond, // account for randomness with known seed
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 50 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Test a fake throttling exception
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
err: &Error{
|
||||||
|
Code: "MyMadeUpThrottlingCode",
|
||||||
|
},
|
||||||
|
numRetries: 0,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: 300 * time.Millisecond,
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: 25 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Test 5xx errors
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
res: &http.Response{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
|
numRetries: 1,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 600 * time.Millisecond,
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 50 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
res: &http.Response{
|
||||||
|
StatusCode: http.StatusServiceUnavailable,
|
||||||
|
},
|
||||||
|
numRetries: 1,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 600 * time.Millisecond,
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 50 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Test a random 400 error
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
res: &http.Response{
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
numRetries: 1,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: 600 * time.Millisecond,
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: 50 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Test a temporary net.Error
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
res: &http.Response{},
|
||||||
|
err: &net.DNSError{
|
||||||
|
IsTimeout: true,
|
||||||
|
},
|
||||||
|
numRetries: 2,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 1200 * time.Millisecond,
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 100 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Test a non-temporary net.Error
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
res: &http.Response{},
|
||||||
|
err: &net.DNSError{
|
||||||
|
IsTimeout: false,
|
||||||
|
},
|
||||||
|
numRetries: 3,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: 2400 * time.Millisecond,
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: 200 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Assert failure after hitting max default retries
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
err: &Error{
|
||||||
|
Code: "ProvisionedThroughputExceededException",
|
||||||
|
},
|
||||||
|
numRetries: defaultMaxRetries,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: 4313582352 * time.Nanosecond, // account for randomness with known seed
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: true,
|
||||||
|
delay: 200 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Assert failure after hitting max DynamoDB retries
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
err: &Error{
|
||||||
|
Code: "ProvisionedThroughputExceededException",
|
||||||
|
},
|
||||||
|
numRetries: dynamoDBMaxRetries,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: maxDelay,
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: maxDelay,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Assert we never go over the maxDelay value
|
||||||
|
testCase{
|
||||||
|
input: testInput{
|
||||||
|
numRetries: 25,
|
||||||
|
},
|
||||||
|
defaultResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: maxDelay,
|
||||||
|
},
|
||||||
|
dynamoDBResult: testResult{
|
||||||
|
shouldRetry: false,
|
||||||
|
delay: maxDelay,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultRetryPolicy(t *testing.T) {
|
||||||
|
rand.Seed(0)
|
||||||
|
var policy RetryPolicy
|
||||||
|
policy = &DefaultRetryPolicy{}
|
||||||
|
for _, test := range testCases {
|
||||||
|
res := test.input.res
|
||||||
|
err := test.input.err
|
||||||
|
numRetries := test.input.numRetries
|
||||||
|
|
||||||
|
shouldRetry := policy.ShouldRetry("", res, err, numRetries)
|
||||||
|
if shouldRetry != test.defaultResult.shouldRetry {
|
||||||
|
t.Errorf("ShouldRetry returned %v, expected %v res=%#v err=%#v numRetries=%d", shouldRetry, test.defaultResult.shouldRetry, res, err, numRetries)
|
||||||
|
}
|
||||||
|
delay := policy.Delay("", res, err, numRetries)
|
||||||
|
if delay != test.defaultResult.delay {
|
||||||
|
t.Errorf("Delay returned %v, expected %v res=%#v err=%#v numRetries=%d", delay, test.defaultResult.delay, res, err, numRetries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDynamoDBRetryPolicy(t *testing.T) {
|
||||||
|
var policy RetryPolicy
|
||||||
|
policy = &DynamoDBRetryPolicy{}
|
||||||
|
for _, test := range testCases {
|
||||||
|
res := test.input.res
|
||||||
|
err := test.input.err
|
||||||
|
numRetries := test.input.numRetries
|
||||||
|
|
||||||
|
shouldRetry := policy.ShouldRetry("", res, err, numRetries)
|
||||||
|
if shouldRetry != test.dynamoDBResult.shouldRetry {
|
||||||
|
t.Errorf("ShouldRetry returned %v, expected %v res=%#v err=%#v numRetries=%d", shouldRetry, test.dynamoDBResult.shouldRetry, res, err, numRetries)
|
||||||
|
}
|
||||||
|
delay := policy.Delay("", res, err, numRetries)
|
||||||
|
if delay != test.dynamoDBResult.delay {
|
||||||
|
t.Errorf("Delay returned %v, expected %v res=%#v err=%#v numRetries=%d", delay, test.dynamoDBResult.delay, res, err, numRetries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNeverRetryPolicy(t *testing.T) {
|
||||||
|
var policy RetryPolicy
|
||||||
|
policy = &NeverRetryPolicy{}
|
||||||
|
for _, test := range testCases {
|
||||||
|
res := test.input.res
|
||||||
|
err := test.input.err
|
||||||
|
numRetries := test.input.numRetries
|
||||||
|
|
||||||
|
shouldRetry := policy.ShouldRetry("", res, err, numRetries)
|
||||||
|
if shouldRetry {
|
||||||
|
t.Errorf("ShouldRetry returned %v, expected %v res=%#v err=%#v numRetries=%d", shouldRetry, false, res, err, numRetries)
|
||||||
|
}
|
||||||
|
delay := policy.Delay("", res, err, numRetries)
|
||||||
|
if delay != time.Duration(0) {
|
||||||
|
t.Errorf("Delay returned %v, expected %v res=%#v err=%#v numRetries=%d", delay, time.Duration(0), res, err, numRetries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
381
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/sign.go
generated
vendored
Normal file
381
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/sign.go
generated
vendored
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type V2Signer struct {
|
||||||
|
auth Auth
|
||||||
|
service ServiceInfo
|
||||||
|
host string
|
||||||
|
}
|
||||||
|
|
||||||
|
var b64 = base64.StdEncoding
|
||||||
|
|
||||||
|
func NewV2Signer(auth Auth, service ServiceInfo) (*V2Signer, error) {
|
||||||
|
u, err := url.Parse(service.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &V2Signer{auth: auth, service: service, host: u.Host}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V2Signer) Sign(method, path string, params map[string]string) {
|
||||||
|
params["AWSAccessKeyId"] = s.auth.AccessKey
|
||||||
|
params["SignatureVersion"] = "2"
|
||||||
|
params["SignatureMethod"] = "HmacSHA256"
|
||||||
|
if s.auth.Token() != "" {
|
||||||
|
params["SecurityToken"] = s.auth.Token()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AWS specifies that the parameters in a signed request must
|
||||||
|
// be provided in the natural order of the keys. This is distinct
|
||||||
|
// from the natural order of the encoded value of key=value.
|
||||||
|
// Percent and gocheck.Equals affect the sorting order.
|
||||||
|
var keys, sarray []string
|
||||||
|
for k, _ := range params {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, k := range keys {
|
||||||
|
sarray = append(sarray, Encode(k)+"="+Encode(params[k]))
|
||||||
|
}
|
||||||
|
joined := strings.Join(sarray, "&")
|
||||||
|
payload := method + "\n" + s.host + "\n" + path + "\n" + joined
|
||||||
|
hash := hmac.New(sha256.New, []byte(s.auth.SecretKey))
|
||||||
|
hash.Write([]byte(payload))
|
||||||
|
signature := make([]byte, b64.EncodedLen(hash.Size()))
|
||||||
|
b64.Encode(signature, hash.Sum(nil))
|
||||||
|
|
||||||
|
params["Signature"] = string(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common date formats for signing requests
|
||||||
|
const (
|
||||||
|
ISO8601BasicFormat = "20060102T150405Z"
|
||||||
|
ISO8601BasicFormatShort = "20060102"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Route53Signer struct {
|
||||||
|
auth Auth
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRoute53Signer(auth Auth) *Route53Signer {
|
||||||
|
return &Route53Signer{auth: auth}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCurrentDate fetches the date stamp from the aws servers to
|
||||||
|
// ensure the auth headers are within 5 minutes of the server time
|
||||||
|
func (s *Route53Signer) getCurrentDate() string {
|
||||||
|
response, err := http.Get("https://route53.amazonaws.com/date")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Print("Unable to get date from amazon: ", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Body.Close()
|
||||||
|
return response.Header.Get("Date")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates the authorize signature based on the date stamp and secret key
|
||||||
|
func (s *Route53Signer) getHeaderAuthorize(message string) string {
|
||||||
|
hmacSha256 := hmac.New(sha256.New, []byte(s.auth.SecretKey))
|
||||||
|
hmacSha256.Write([]byte(message))
|
||||||
|
cryptedString := hmacSha256.Sum(nil)
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(cryptedString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds all the required headers for AWS Route53 API to the request
|
||||||
|
// including the authorization
|
||||||
|
func (s *Route53Signer) Sign(req *http.Request) {
|
||||||
|
date := s.getCurrentDate()
|
||||||
|
authHeader := fmt.Sprintf("AWS3-HTTPS AWSAccessKeyId=%s,Algorithm=%s,Signature=%s",
|
||||||
|
s.auth.AccessKey, "HmacSHA256", s.getHeaderAuthorize(date))
|
||||||
|
|
||||||
|
req.Header.Set("Host", req.Host)
|
||||||
|
req.Header.Set("X-Amzn-Authorization", authHeader)
|
||||||
|
req.Header.Set("X-Amz-Date", date)
|
||||||
|
req.Header.Set("Content-Type", "application/xml")
|
||||||
|
if s.auth.Token() != "" {
|
||||||
|
req.Header.Set("X-Amzn-Security-Token", s.auth.Token())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The V4Signer encapsulates all of the functionality to sign a request with the AWS
|
||||||
|
Signature Version 4 Signing Process. (http://goo.gl/u1OWZz)
|
||||||
|
*/
|
||||||
|
type V4Signer struct {
|
||||||
|
auth Auth
|
||||||
|
serviceName string
|
||||||
|
region Region
|
||||||
|
// Add the x-amz-content-sha256 header
|
||||||
|
IncludeXAmzContentSha256 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return a new instance of a V4Signer capable of signing AWS requests.
|
||||||
|
*/
|
||||||
|
func NewV4Signer(auth Auth, serviceName string, region Region) *V4Signer {
|
||||||
|
return &V4Signer{
|
||||||
|
auth: auth,
|
||||||
|
serviceName: serviceName,
|
||||||
|
region: region,
|
||||||
|
IncludeXAmzContentSha256: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sign a request according to the AWS Signature Version 4 Signing Process. (http://goo.gl/u1OWZz)
|
||||||
|
|
||||||
|
The signed request will include an "x-amz-date" header with a current timestamp if a valid "x-amz-date"
|
||||||
|
or "date" header was not available in the original request. In addition, AWS Signature Version 4 requires
|
||||||
|
the "host" header to be a signed header, therefor the Sign method will manually set a "host" header from
|
||||||
|
the request.Host.
|
||||||
|
|
||||||
|
The signed request will include a new "Authorization" header indicating that the request has been signed.
|
||||||
|
|
||||||
|
Any changes to the request after signing the request will invalidate the signature.
|
||||||
|
*/
|
||||||
|
func (s *V4Signer) Sign(req *http.Request) {
|
||||||
|
req.Header.Set("host", req.Host) // host header must be included as a signed header
|
||||||
|
payloadHash := s.payloadHash(req)
|
||||||
|
if s.IncludeXAmzContentSha256 {
|
||||||
|
req.Header.Set("x-amz-content-sha256", payloadHash) // x-amz-content-sha256 contains the payload hash
|
||||||
|
}
|
||||||
|
t := s.requestTime(req) // Get request time
|
||||||
|
creq := s.canonicalRequest(req, payloadHash) // Build canonical request
|
||||||
|
sts := s.stringToSign(t, creq) // Build string to sign
|
||||||
|
signature := s.signature(t, sts) // Calculate the AWS Signature Version 4
|
||||||
|
auth := s.authorization(req.Header, t, signature) // Create Authorization header value
|
||||||
|
req.Header.Set("Authorization", auth) // Add Authorization header to request
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
requestTime method will parse the time from the request "x-amz-date" or "date" headers.
|
||||||
|
If the "x-amz-date" header is present, that will take priority over the "date" header.
|
||||||
|
If neither header is defined or we are unable to parse either header as a valid date
|
||||||
|
then we will create a new "x-amz-date" header with the current time.
|
||||||
|
*/
|
||||||
|
func (s *V4Signer) requestTime(req *http.Request) time.Time {
|
||||||
|
|
||||||
|
// Get "x-amz-date" header
|
||||||
|
date := req.Header.Get("x-amz-date")
|
||||||
|
|
||||||
|
// Attempt to parse as ISO8601BasicFormat
|
||||||
|
t, err := time.Parse(ISO8601BasicFormat, date)
|
||||||
|
if err == nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to parse as http.TimeFormat
|
||||||
|
t, err = time.Parse(http.TimeFormat, date)
|
||||||
|
if err == nil {
|
||||||
|
req.Header.Set("x-amz-date", t.Format(ISO8601BasicFormat))
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get "date" header
|
||||||
|
date = req.Header.Get("date")
|
||||||
|
|
||||||
|
// Attempt to parse as http.TimeFormat
|
||||||
|
t, err = time.Parse(http.TimeFormat, date)
|
||||||
|
if err == nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a current time header to be used
|
||||||
|
t = time.Now().UTC()
|
||||||
|
req.Header.Set("x-amz-date", t.Format(ISO8601BasicFormat))
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
canonicalRequest method creates the canonical request according to Task 1 of the AWS Signature Version 4 Signing Process. (http://goo.gl/eUUZ3S)
|
||||||
|
|
||||||
|
CanonicalRequest =
|
||||||
|
HTTPRequestMethod + '\n' +
|
||||||
|
CanonicalURI + '\n' +
|
||||||
|
CanonicalQueryString + '\n' +
|
||||||
|
CanonicalHeaders + '\n' +
|
||||||
|
SignedHeaders + '\n' +
|
||||||
|
HexEncode(Hash(Payload))
|
||||||
|
|
||||||
|
payloadHash is optional; use the empty string and it will be calculated from the request
|
||||||
|
*/
|
||||||
|
func (s *V4Signer) canonicalRequest(req *http.Request, payloadHash string) string {
|
||||||
|
if payloadHash == "" {
|
||||||
|
payloadHash = s.payloadHash(req)
|
||||||
|
}
|
||||||
|
c := new(bytes.Buffer)
|
||||||
|
fmt.Fprintf(c, "%s\n", req.Method)
|
||||||
|
fmt.Fprintf(c, "%s\n", s.canonicalURI(req.URL))
|
||||||
|
fmt.Fprintf(c, "%s\n", s.canonicalQueryString(req.URL))
|
||||||
|
fmt.Fprintf(c, "%s\n\n", s.canonicalHeaders(req.Header))
|
||||||
|
fmt.Fprintf(c, "%s\n", s.signedHeaders(req.Header))
|
||||||
|
fmt.Fprintf(c, "%s", payloadHash)
|
||||||
|
return c.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4Signer) canonicalURI(u *url.URL) string {
|
||||||
|
u = &url.URL{Path: u.Path}
|
||||||
|
canonicalPath := u.String()
|
||||||
|
|
||||||
|
slash := strings.HasSuffix(canonicalPath, "/")
|
||||||
|
canonicalPath = path.Clean(canonicalPath)
|
||||||
|
|
||||||
|
if canonicalPath == "" || canonicalPath == "." {
|
||||||
|
canonicalPath = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
if canonicalPath != "/" && slash {
|
||||||
|
canonicalPath += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return canonicalPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4Signer) canonicalQueryString(u *url.URL) string {
|
||||||
|
var a []string
|
||||||
|
for k, vs := range u.Query() {
|
||||||
|
k = url.QueryEscape(k)
|
||||||
|
for _, v := range vs {
|
||||||
|
if v == "" {
|
||||||
|
a = append(a, k+"=")
|
||||||
|
} else {
|
||||||
|
v = url.QueryEscape(v)
|
||||||
|
a = append(a, k+"="+v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(a)
|
||||||
|
return strings.Join(a, "&")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4Signer) canonicalHeaders(h http.Header) string {
|
||||||
|
i, a := 0, make([]string, len(h))
|
||||||
|
for k, v := range h {
|
||||||
|
for j, w := range v {
|
||||||
|
v[j] = strings.Trim(w, " ")
|
||||||
|
}
|
||||||
|
sort.Strings(v)
|
||||||
|
a[i] = strings.ToLower(k) + ":" + strings.Join(v, ",")
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(a)
|
||||||
|
return strings.Join(a, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4Signer) signedHeaders(h http.Header) string {
|
||||||
|
i, a := 0, make([]string, len(h))
|
||||||
|
for k, _ := range h {
|
||||||
|
a[i] = strings.ToLower(k)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(a)
|
||||||
|
return strings.Join(a, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4Signer) payloadHash(req *http.Request) string {
|
||||||
|
var b []byte
|
||||||
|
if req.Body == nil {
|
||||||
|
b = []byte("")
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
b, err = ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: I REALLY DON'T LIKE THIS PANIC!!!!
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
|
||||||
|
return s.hash(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
stringToSign method creates the string to sign accorting to Task 2 of the AWS Signature Version 4 Signing Process. (http://goo.gl/es1PAu)
|
||||||
|
|
||||||
|
StringToSign =
|
||||||
|
Algorithm + '\n' +
|
||||||
|
RequestDate + '\n' +
|
||||||
|
CredentialScope + '\n' +
|
||||||
|
HexEncode(Hash(CanonicalRequest))
|
||||||
|
*/
|
||||||
|
func (s *V4Signer) stringToSign(t time.Time, creq string) string {
|
||||||
|
w := new(bytes.Buffer)
|
||||||
|
fmt.Fprint(w, "AWS4-HMAC-SHA256\n")
|
||||||
|
fmt.Fprintf(w, "%s\n", t.Format(ISO8601BasicFormat))
|
||||||
|
fmt.Fprintf(w, "%s\n", s.credentialScope(t))
|
||||||
|
fmt.Fprintf(w, "%s", s.hash(creq))
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4Signer) credentialScope(t time.Time) string {
|
||||||
|
return fmt.Sprintf("%s/%s/%s/aws4_request", t.Format(ISO8601BasicFormatShort), s.region.Name, s.serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
signature method calculates the AWS Signature Version 4 according to Task 3 of the AWS Signature Version 4 Signing Process. (http://goo.gl/j0Yqe1)
|
||||||
|
|
||||||
|
signature = HexEncode(HMAC(derived-signing-key, string-to-sign))
|
||||||
|
*/
|
||||||
|
func (s *V4Signer) signature(t time.Time, sts string) string {
|
||||||
|
h := s.hmac(s.derivedKey(t), []byte(sts))
|
||||||
|
return fmt.Sprintf("%x", h)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
derivedKey method derives a signing key to be used for signing a request.
|
||||||
|
|
||||||
|
kSecret = Your AWS Secret Access Key
|
||||||
|
kDate = HMAC("AWS4" + kSecret, Date)
|
||||||
|
kRegion = HMAC(kDate, Region)
|
||||||
|
kService = HMAC(kRegion, Service)
|
||||||
|
kSigning = HMAC(kService, "aws4_request")
|
||||||
|
*/
|
||||||
|
func (s *V4Signer) derivedKey(t time.Time) []byte {
|
||||||
|
h := s.hmac([]byte("AWS4"+s.auth.SecretKey), []byte(t.Format(ISO8601BasicFormatShort)))
|
||||||
|
h = s.hmac(h, []byte(s.region.Name))
|
||||||
|
h = s.hmac(h, []byte(s.serviceName))
|
||||||
|
h = s.hmac(h, []byte("aws4_request"))
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
authorization method generates the authorization header value.
|
||||||
|
*/
|
||||||
|
func (s *V4Signer) authorization(header http.Header, t time.Time, signature string) string {
|
||||||
|
w := new(bytes.Buffer)
|
||||||
|
fmt.Fprint(w, "AWS4-HMAC-SHA256 ")
|
||||||
|
fmt.Fprintf(w, "Credential=%s/%s, ", s.auth.AccessKey, s.credentialScope(t))
|
||||||
|
fmt.Fprintf(w, "SignedHeaders=%s, ", s.signedHeaders(header))
|
||||||
|
fmt.Fprintf(w, "Signature=%s", signature)
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// hash method calculates the sha256 hash for a given string
|
||||||
|
func (s *V4Signer) hash(in string) string {
|
||||||
|
h := sha256.New()
|
||||||
|
fmt.Fprintf(h, "%s", in)
|
||||||
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// hmac method calculates the sha256 hmac for a given slice of bytes
|
||||||
|
func (s *V4Signer) hmac(key, data []byte) []byte {
|
||||||
|
h := hmac.New(sha256.New, key)
|
||||||
|
h.Write(data)
|
||||||
|
return h.Sum(nil)
|
||||||
|
}
|
569
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/sign_test.go
generated
vendored
Normal file
569
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/sign_test.go
generated
vendored
Normal file
@ -0,0 +1,569 @@
|
|||||||
|
package aws_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/crowdmob/goamz/aws"
|
||||||
|
"gopkg.in/check.v1"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = check.Suite(&V4SignerSuite{})
|
||||||
|
|
||||||
|
type V4SignerSuite struct {
|
||||||
|
auth aws.Auth
|
||||||
|
region aws.Region
|
||||||
|
cases []V4SignerSuiteCase
|
||||||
|
}
|
||||||
|
|
||||||
|
type V4SignerSuiteCase struct {
|
||||||
|
label string
|
||||||
|
request V4SignerSuiteCaseRequest
|
||||||
|
canonicalRequest string
|
||||||
|
stringToSign string
|
||||||
|
signature string
|
||||||
|
authorization string
|
||||||
|
}
|
||||||
|
|
||||||
|
type V4SignerSuiteCaseRequest struct {
|
||||||
|
method string
|
||||||
|
host string
|
||||||
|
url string
|
||||||
|
headers []string
|
||||||
|
body string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4SignerSuite) SetUpSuite(c *check.C) {
|
||||||
|
s.auth = aws.Auth{AccessKey: "AKIDEXAMPLE", SecretKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"}
|
||||||
|
s.region = aws.USEast
|
||||||
|
|
||||||
|
// Test cases from the Signature Version 4 Test Suite (http://goo.gl/nguvs0)
|
||||||
|
s.cases = append(s.cases,
|
||||||
|
|
||||||
|
// get-header-key-duplicate
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-header-key-duplicate",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT", "ZOO:zoobar", "zoo:foobar", "zoo:zoobar"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\nzoo:foobar,zoobar,zoobar\n\ndate;host;zoo\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n3c52f0eaae2b61329c0a332e3fa15842a37bc5812cf4d80eb64784308850e313",
|
||||||
|
signature: "54afcaaf45b331f81cd2edb974f7b824ff4dd594cbbaa945ed636b48477368ed",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=54afcaaf45b331f81cd2edb974f7b824ff4dd594cbbaa945ed636b48477368ed",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-header-value-order
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-header-value-order",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT", "p:z", "p:a", "p:p", "p:a"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\np:a,a,p,z\n\ndate;host;p\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n94c0389fefe0988cbbedc8606f0ca0b485b48da010d09fc844b45b697c8924fe",
|
||||||
|
signature: "d2973954263943b11624a11d1c963ca81fb274169c7868b2858c04f083199e3d",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;p, Signature=d2973954263943b11624a11d1c963ca81fb274169c7868b2858c04f083199e3d",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-header-value-trim
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-header-value-trim",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT", "p: phfft "},
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\np:phfft\n\ndate;host;p\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\ndddd1902add08da1ac94782b05f9278c08dc7468db178a84f8950d93b30b1f35",
|
||||||
|
signature: "debf546796015d6f6ded8626f5ce98597c33b47b9164cf6b17b4642036fcb592",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;p, Signature=debf546796015d6f6ded8626f5ce98597c33b47b9164cf6b17b4642036fcb592",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-empty
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-relative-relative",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
||||||
|
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-single-relative
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-relative-relative",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/.",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
||||||
|
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-multiple-relative
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-relative-relative",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/./././",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
||||||
|
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-relative-relative
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-relative-relative",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/foo/bar/../..",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
||||||
|
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-relative
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-relative",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/foo/..",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
||||||
|
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-slash-dot-slash
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-slash-dot-slash",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/./",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
||||||
|
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-slash-pointless-dot
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-slash-pointless-dot",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/./foo",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/foo\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n8021a97572ee460f87ca67f4e8c0db763216d84715f5424a843a5312a3321e2d",
|
||||||
|
signature: "910e4d6c9abafaf87898e1eb4c929135782ea25bb0279703146455745391e63a",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=910e4d6c9abafaf87898e1eb4c929135782ea25bb0279703146455745391e63a",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-slash
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-slash",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "//",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
||||||
|
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-slashes
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-slashes",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "//foo//",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/foo/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n6bb4476ee8745730c9cb79f33a0c70baa6d8af29c0077fa12e4e8f1dd17e7098",
|
||||||
|
signature: "b00392262853cfe3201e47ccf945601079e9b8a7f51ee4c3d9ee4f187aa9bf19",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b00392262853cfe3201e47ccf945601079e9b8a7f51ee4c3d9ee4f187aa9bf19",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-space
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-space",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/%20/foo",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/%20/foo\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n69c45fb9fe3fd76442b5086e50b2e9fec8298358da957b293ef26e506fdfb54b",
|
||||||
|
signature: "f309cfbd10197a230c42dd17dbf5cca8a0722564cb40a872d25623cfa758e374",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=f309cfbd10197a230c42dd17dbf5cca8a0722564cb40a872d25623cfa758e374",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-unreserved
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-unreserved",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\ndf63ee3247c0356c696a3b21f8d8490b01fa9cd5bc6550ef5ef5f4636b7b8901",
|
||||||
|
signature: "830cc36d03f0f84e6ee4953fbe701c1c8b71a0372c63af9255aa364dd183281e",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=830cc36d03f0f84e6ee4953fbe701c1c8b71a0372c63af9255aa364dd183281e",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-utf8
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-utf8",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/%E1%88%B4",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/%E1%88%B4\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n27ba31df5dbc6e063d8f87d62eb07143f7f271c5330a917840586ac1c85b6f6b",
|
||||||
|
signature: "8d6634c189aa8c75c2e51e106b6b5121bed103fdb351f7d7d4381c738823af74",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=8d6634c189aa8c75c2e51e106b6b5121bed103fdb351f7d7d4381c738823af74",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-vanilla-empty-query-key
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-vanilla-empty-query-key",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/?foo=bar",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\nfoo=bar\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n0846c2945b0832deb7a463c66af5c4f8bd54ec28c438e67a214445b157c9ddf8",
|
||||||
|
signature: "56c054473fd260c13e4e7393eb203662195f5d4a1fada5314b8b52b23f985e9f",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=56c054473fd260c13e4e7393eb203662195f5d4a1fada5314b8b52b23f985e9f",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-vanilla-query-order-key-case
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-vanilla-query-order-key-case",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/?foo=Zoo&foo=aha",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\nfoo=Zoo&foo=aha\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\ne25f777ba161a0f1baf778a87faf057187cf5987f17953320e3ca399feb5f00d",
|
||||||
|
signature: "be7148d34ebccdc6423b19085378aa0bee970bdc61d144bd1a8c48c33079ab09",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=be7148d34ebccdc6423b19085378aa0bee970bdc61d144bd1a8c48c33079ab09",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-vanilla-query-order-key
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-vanilla-query-order-key",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/?a=foo&b=foo",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\na=foo&b=foo\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n2f23d14fe13caebf6dfda346285c6d9c14f49eaca8f5ec55c627dd7404f7a727",
|
||||||
|
signature: "0dc122f3b28b831ab48ba65cb47300de53fbe91b577fe113edac383730254a3b",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=0dc122f3b28b831ab48ba65cb47300de53fbe91b577fe113edac383730254a3b",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-vanilla-query-order-value
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-vanilla-query-order-value",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/?foo=b&foo=a",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\nfoo=a&foo=b\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n33dffc220e89131f8f6157a35c40903daa658608d9129ff9489e5cf5bbd9b11b",
|
||||||
|
signature: "feb926e49e382bec75c9d7dcb2a1b6dc8aa50ca43c25d2bc51143768c0875acc",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=feb926e49e382bec75c9d7dcb2a1b6dc8aa50ca43c25d2bc51143768c0875acc",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-vanilla-query-unreserved
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-vanilla-query-unreserved",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/?-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\nd2578f3156d4c9d180713d1ff20601d8a3eed0dd35447d24603d7d67414bd6b5",
|
||||||
|
signature: "f1498ddb4d6dae767d97c466fb92f1b59a2c71ca29ac954692663f9db03426fb",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=f1498ddb4d6dae767d97c466fb92f1b59a2c71ca29ac954692663f9db03426fb",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-vanilla-query
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-vanilla-query",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
||||||
|
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-vanilla-ut8-query
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-vanilla-ut8-query",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/?ሴ=bar",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n%E1%88%B4=bar\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\nde5065ff39c131e6c2e2bd19cd9345a794bf3b561eab20b8d97b2093fc2a979e",
|
||||||
|
signature: "6fb359e9a05394cc7074e0feb42573a2601abc0c869a953e8c5c12e4e01f1a8c",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=6fb359e9a05394cc7074e0feb42573a2601abc0c869a953e8c5c12e4e01f1a8c",
|
||||||
|
},
|
||||||
|
|
||||||
|
// get-vanilla
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "get-vanilla",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "GET",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "GET\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n366b91fb121d72a00f46bbe8d395f53a102b06dfb7e79636515208ed3fa606b1",
|
||||||
|
signature: "b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470",
|
||||||
|
},
|
||||||
|
|
||||||
|
// post-header-key-case
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "post-header-key-case",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n05da62cee468d24ae84faff3c39f1b85540de60243c1bcaace39c0a2acc7b2c4",
|
||||||
|
signature: "22902d79e148b64e7571c3565769328423fe276eae4b26f83afceda9e767f726",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=22902d79e148b64e7571c3565769328423fe276eae4b26f83afceda9e767f726",
|
||||||
|
},
|
||||||
|
|
||||||
|
// post-header-key-sort
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "post-header-key-sort",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT", "ZOO:zoobar"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\nzoo:zoobar\n\ndate;host;zoo\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n34e1bddeb99e76ee01d63b5e28656111e210529efeec6cdfd46a48e4c734545d",
|
||||||
|
signature: "b7a95a52518abbca0964a999a880429ab734f35ebbf1235bd79a5de87756dc4a",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=b7a95a52518abbca0964a999a880429ab734f35ebbf1235bd79a5de87756dc4a",
|
||||||
|
},
|
||||||
|
|
||||||
|
// post-header-value-case
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "post-header-value-case",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"DATE:Mon, 09 Sep 2011 23:36:00 GMT", "zoo:ZOOBAR"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\nzoo:ZOOBAR\n\ndate;host;zoo\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n3aae6d8274b8c03e2cc96fc7d6bda4b9bd7a0a184309344470b2c96953e124aa",
|
||||||
|
signature: "273313af9d0c265c531e11db70bbd653f3ba074c1009239e8559d3987039cad7",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=273313af9d0c265c531e11db70bbd653f3ba074c1009239e8559d3987039cad7",
|
||||||
|
},
|
||||||
|
|
||||||
|
// post-vanilla-empty-query-value
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "post-vanilla-empty-query-value",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/?foo=bar",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\nfoo=bar\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\ncd4f39132d8e60bb388831d734230460872b564871c47f5de62e62d1a68dbe1e",
|
||||||
|
signature: "b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92",
|
||||||
|
},
|
||||||
|
|
||||||
|
// post-vanilla-query
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "post-vanilla-query",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/?foo=bar",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\nfoo=bar\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\ncd4f39132d8e60bb388831d734230460872b564871c47f5de62e62d1a68dbe1e",
|
||||||
|
signature: "b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92",
|
||||||
|
},
|
||||||
|
|
||||||
|
// post-vanilla
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "post-vanilla",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\n\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ndate;host\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n05da62cee468d24ae84faff3c39f1b85540de60243c1bcaace39c0a2acc7b2c4",
|
||||||
|
signature: "22902d79e148b64e7571c3565769328423fe276eae4b26f83afceda9e767f726",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=22902d79e148b64e7571c3565769328423fe276eae4b26f83afceda9e767f726",
|
||||||
|
},
|
||||||
|
|
||||||
|
// post-x-www-form-urlencoded-parameters
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "post-x-www-form-urlencoded-parameters",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"Content-Type:application/x-www-form-urlencoded; charset=utf8", "Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
body: "foo=bar",
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\n\ncontent-type:application/x-www-form-urlencoded; charset=utf8\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ncontent-type;date;host\n3ba8907e7a252327488df390ed517c45b96dead033600219bdca7107d1d3f88a",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\nc4115f9e54b5cecf192b1eaa23b8e88ed8dc5391bd4fde7b3fff3d9c9fe0af1f",
|
||||||
|
signature: "b105eb10c6d318d2294de9d49dd8b031b55e3c3fe139f2e637da70511e9e7b71",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=content-type;date;host, Signature=b105eb10c6d318d2294de9d49dd8b031b55e3c3fe139f2e637da70511e9e7b71",
|
||||||
|
},
|
||||||
|
|
||||||
|
// post-x-www-form-urlencoded
|
||||||
|
V4SignerSuiteCase{
|
||||||
|
label: "post-x-www-form-urlencoded",
|
||||||
|
request: V4SignerSuiteCaseRequest{
|
||||||
|
method: "POST",
|
||||||
|
host: "host.foo.com",
|
||||||
|
url: "/",
|
||||||
|
headers: []string{"Content-Type:application/x-www-form-urlencoded", "Date:Mon, 09 Sep 2011 23:36:00 GMT"},
|
||||||
|
body: "foo=bar",
|
||||||
|
},
|
||||||
|
canonicalRequest: "POST\n/\n\ncontent-type:application/x-www-form-urlencoded\ndate:Mon, 09 Sep 2011 23:36:00 GMT\nhost:host.foo.com\n\ncontent-type;date;host\n3ba8907e7a252327488df390ed517c45b96dead033600219bdca7107d1d3f88a",
|
||||||
|
stringToSign: "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/host/aws4_request\n4c5c6e4b52fb5fb947a8733982a8a5a61b14f04345cbfe6e739236c76dd48f74",
|
||||||
|
signature: "5a15b22cf462f047318703b92e6f4f38884e4a7ab7b1d6426ca46a8bd1c26cbc",
|
||||||
|
authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=content-type;date;host, Signature=5a15b22cf462f047318703b92e6f4f38884e4a7ab7b1d6426ca46a8bd1c26cbc",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V4SignerSuite) TestCases(c *check.C) {
|
||||||
|
signer := aws.NewV4Signer(s.auth, "host", s.region)
|
||||||
|
|
||||||
|
for _, testCase := range s.cases {
|
||||||
|
|
||||||
|
req, err := http.NewRequest(testCase.request.method, "http://"+testCase.request.host+testCase.request.url, strings.NewReader(testCase.request.body))
|
||||||
|
c.Assert(err, check.IsNil, check.Commentf("Testcase: %s", testCase.label))
|
||||||
|
for _, v := range testCase.request.headers {
|
||||||
|
h := strings.SplitN(v, ":", 2)
|
||||||
|
req.Header.Add(h[0], h[1])
|
||||||
|
}
|
||||||
|
req.Header.Set("host", req.Host)
|
||||||
|
|
||||||
|
t := signer.RequestTime(req)
|
||||||
|
|
||||||
|
canonicalRequest := signer.CanonicalRequest(req)
|
||||||
|
c.Check(canonicalRequest, check.Equals, testCase.canonicalRequest, check.Commentf("Testcase: %s", testCase.label))
|
||||||
|
|
||||||
|
stringToSign := signer.StringToSign(t, canonicalRequest)
|
||||||
|
c.Check(stringToSign, check.Equals, testCase.stringToSign, check.Commentf("Testcase: %s", testCase.label))
|
||||||
|
|
||||||
|
signature := signer.Signature(t, stringToSign)
|
||||||
|
c.Check(signature, check.Equals, testCase.signature, check.Commentf("Testcase: %s", testCase.label))
|
||||||
|
|
||||||
|
authorization := signer.Authorization(req.Header, t, signature)
|
||||||
|
c.Check(authorization, check.Equals, testCase.authorization, check.Commentf("Testcase: %s", testCase.label))
|
||||||
|
|
||||||
|
signer.Sign(req)
|
||||||
|
c.Check(req.Header.Get("Authorization"), check.Equals, testCase.authorization, check.Commentf("Testcase: %s", testCase.label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleV4Signer() {
|
||||||
|
// Get auth from env vars
|
||||||
|
auth, err := aws.EnvAuth()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a signer with the auth, name of the service, and aws region
|
||||||
|
signer := aws.NewV4Signer(auth, "dynamodb", aws.USEast)
|
||||||
|
|
||||||
|
// Create a request
|
||||||
|
req, err := http.NewRequest("POST", aws.USEast.DynamoDBEndpoint, strings.NewReader("sample_request"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date or x-amz-date header is required to sign a request
|
||||||
|
req.Header.Add("Date", time.Now().UTC().Format(http.TimeFormat))
|
||||||
|
|
||||||
|
// Sign the request
|
||||||
|
signer.Sign(req)
|
||||||
|
|
||||||
|
// Issue signed request
|
||||||
|
http.DefaultClient.Do(req)
|
||||||
|
}
|
143
Godeps/_workspace/src/github.com/crowdmob/goamz/cloudfront/cloudfront.go
generated
vendored
Normal file
143
Godeps/_workspace/src/github.com/crowdmob/goamz/cloudfront/cloudfront.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package cloudfront
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/crowdmob/goamz/aws"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CloudFront struct {
|
||||||
|
BaseURL string
|
||||||
|
keyPairId string
|
||||||
|
key *rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
var base64Replacer = strings.NewReplacer("=", "_", "+", "-", "/", "~")
|
||||||
|
|
||||||
|
func NewKeyLess(auth aws.Auth, baseurl string) *CloudFront {
|
||||||
|
return &CloudFront{keyPairId: auth.AccessKey, BaseURL: baseurl}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(baseurl string, key *rsa.PrivateKey, keyPairId string) *CloudFront {
|
||||||
|
return &CloudFront{
|
||||||
|
BaseURL: baseurl,
|
||||||
|
keyPairId: keyPairId,
|
||||||
|
key: key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type epochTime struct {
|
||||||
|
EpochTime int64 `json:"AWS:EpochTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type condition struct {
|
||||||
|
DateLessThan epochTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type statement struct {
|
||||||
|
Resource string
|
||||||
|
Condition condition
|
||||||
|
}
|
||||||
|
|
||||||
|
type policy struct {
|
||||||
|
Statement []statement
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPolicy(resource string, expireTime time.Time) ([]byte, error) {
|
||||||
|
p := &policy{
|
||||||
|
Statement: []statement{
|
||||||
|
statement{
|
||||||
|
Resource: resource,
|
||||||
|
Condition: condition{
|
||||||
|
DateLessThan: epochTime{
|
||||||
|
EpochTime: expireTime.Truncate(time.Millisecond).Unix(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cf *CloudFront) generateSignature(policy []byte) (string, error) {
|
||||||
|
hash := sha1.New()
|
||||||
|
_, err := hash.Write(policy)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hashed := hash.Sum(nil)
|
||||||
|
var signed []byte
|
||||||
|
if cf.key.Validate() == nil {
|
||||||
|
signed, err = rsa.SignPKCS1v15(nil, cf.key, crypto.SHA1, hashed)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signed = hashed
|
||||||
|
}
|
||||||
|
encoded := base64Replacer.Replace(base64.StdEncoding.EncodeToString(signed))
|
||||||
|
|
||||||
|
return encoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a signed url using RSAwithSHA1 as specified by
|
||||||
|
// http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html#private-content-canned-policy-creating-signature
|
||||||
|
func (cf *CloudFront) CannedSignedURL(path, queryString string, expires time.Time) (string, error) {
|
||||||
|
resource := cf.BaseURL + path
|
||||||
|
if queryString != "" {
|
||||||
|
resource = path + "?" + queryString
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := buildPolicy(resource, expires)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature, err := cf.generateSignature(policy)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOOD: Do this once
|
||||||
|
uri, err := url.Parse(cf.BaseURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
uri.RawQuery = queryString
|
||||||
|
if queryString != "" {
|
||||||
|
uri.RawQuery += "&"
|
||||||
|
}
|
||||||
|
|
||||||
|
expireTime := expires.Truncate(time.Millisecond).Unix()
|
||||||
|
|
||||||
|
uri.Path = path
|
||||||
|
uri.RawQuery += fmt.Sprintf("Expires=%d&Signature=%s&Key-Pair-Id=%s", expireTime, signature, cf.keyPairId)
|
||||||
|
|
||||||
|
return uri.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cloudfront *CloudFront) SignedURL(path, querystrings string, expires time.Time) string {
|
||||||
|
policy := `{"Statement":[{"Resource":"` + path + "?" + querystrings + `,"Condition":{"DateLessThan":{"AWS:EpochTime":` + strconv.FormatInt(expires.Truncate(time.Millisecond).Unix(), 10) + `}}}]}`
|
||||||
|
|
||||||
|
hash := sha1.New()
|
||||||
|
hash.Write([]byte(policy))
|
||||||
|
b := hash.Sum(nil)
|
||||||
|
he := base64.StdEncoding.EncodeToString(b)
|
||||||
|
|
||||||
|
policySha1 := he
|
||||||
|
|
||||||
|
url := cloudfront.BaseURL + path + "?" + querystrings + "&Expires=" + strconv.FormatInt(expires.Unix(), 10) + "&Signature=" + policySha1 + "&Key-Pair-Id=" + cloudfront.keyPairId
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
52
Godeps/_workspace/src/github.com/crowdmob/goamz/cloudfront/cloudfront_test.go
generated
vendored
Normal file
52
Godeps/_workspace/src/github.com/crowdmob/goamz/cloudfront/cloudfront_test.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package cloudfront
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSignedCannedURL(t *testing.T) {
|
||||||
|
rawKey, err := ioutil.ReadFile("testdata/key.pem")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pemKey, _ := pem.Decode(rawKey)
|
||||||
|
privateKey, err := x509.ParsePKCS1PrivateKey(pemKey.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cf := &CloudFront{
|
||||||
|
key: privateKey,
|
||||||
|
keyPairId: "test-key-pair-1231245",
|
||||||
|
BaseURL: "https://cloudfront.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
expireTime, err := time.Parse(time.RFC3339, "2014-03-28T14:00:21Z")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := make(url.Values)
|
||||||
|
query.Add("test", "value")
|
||||||
|
|
||||||
|
uri, err := cf.CannedSignedURL("test", "test=value", expireTime)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := parsed.Query().Get("Signature")
|
||||||
|
if signature == "" {
|
||||||
|
t.Fatal("Encoded signature is empty")
|
||||||
|
}
|
||||||
|
}
|
15
Godeps/_workspace/src/github.com/crowdmob/goamz/cloudfront/testdata/key.pem
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/crowdmob/goamz/cloudfront/testdata/key.pem
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXAIBAAKBgQC0yMzp9DkPAE99DhsEaGkqougLvtmDKri4bZj0fFjmGmjyyjz9
|
||||||
|
hlrsr87LHVWzH/7igK7040HG1UqypX3ijtJa9+6BKHwBBctboU3y4GfwFwVAOumY
|
||||||
|
9UytFpyPlgUFrffZLQAywKkT24OgcfEj0G5kiQn760wFnmSUtOuITo708QIDAQAB
|
||||||
|
AoGAJUA6+PoZx72Io3wElSPuh5qJteHdb+mdpmLu4XG936wRc/W4G4VTtvGC6tdg
|
||||||
|
kUhGfOWHJ26sXwwUGDuBdO146m0DkBTuIooy97afpL6hXgL5v4ELHbbuFJcf4Geg
|
||||||
|
/UAuexvRT1HenYFQ/iXM0LlqI33i8cFRc1A+j0Gseo07gAECQQDYFCn7OUokX+Q8
|
||||||
|
M2Cwhu7JT1obmP2HwsBtXl0CDDxtOQkuYJP/UqvtdYPz/kRn3yQjoynaCTHYrFz/
|
||||||
|
H8oN1nNhAkEA1i9TEpo7RbanIyT4vbc1/5xfjE7Pj0lnGku0QXFp/S+8YxbqhjrQ
|
||||||
|
4Qp7TTXIPPqvQhhEpAGGspM460K3F6h7kQJBANJCbMeFa9wRY2ohJIkiA+HoUWph
|
||||||
|
aPNeUxkZpa+EcJhn08NJPzpIG/ypSYl3duEMhYIYF3WPVO3ea2/mYxsr/oECQFj5
|
||||||
|
td/fdEoEk7AU1sQxDNyPwF2QC8dxbcRNuKcLD0Wfg/oB9hEm88jYytoLQpCabx3c
|
||||||
|
6P7cp3EdmaKZx2erlRECQDYTSK2tS0+VoXSV9JbU08Pbu53j3Zhmp4l0csP+l7EU
|
||||||
|
U+rRQzKho4X9vpR/VpRGXbw8tTIhojNpHh5ofryVfgk=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user