Merge pull request #59 from stevvooe/use-godep-for-dependencies
Use Godep to vendor distribution dependenciesmaster
						commit
						d64e70b0e1
					
				| 
						 | 
				
			
			@ -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"
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
/pkg
 | 
			
		||||
/bin
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
logrus
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
The MIT License (MIT)
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2014 Simon Eskildsen
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in
 | 
			
		||||
all copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
THE SOFTWARE.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,349 @@
 | 
			
		|||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/Sirupsen/logrus)
 | 
			
		||||
 | 
			
		||||
Logrus is a structured logger for Go (golang), completely API compatible with
 | 
			
		||||
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
 | 
			
		||||
yet stable (pre 1.0), the core API is unlikely change much but please version
 | 
			
		||||
control your Logrus to make sure you aren't fetching latest `master` on every
 | 
			
		||||
build.**
 | 
			
		||||
 | 
			
		||||
Nicely color-coded in development (when a TTY is attached, otherwise just
 | 
			
		||||
plain text):
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
 | 
			
		||||
or Splunk:
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
 | 
			
		||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
 | 
			
		||||
 | 
			
		||||
{"level":"warning","msg":"The group's number increased tremendously!",
 | 
			
		||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
 | 
			
		||||
 | 
			
		||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
 | 
			
		||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
 | 
			
		||||
 | 
			
		||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
 | 
			
		||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
 | 
			
		||||
 | 
			
		||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
 | 
			
		||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not
 | 
			
		||||
attached, the output is compatible with the
 | 
			
		||||
[l2met](http://r.32k.io/l2met-introduction) format:
 | 
			
		||||
 | 
			
		||||
```text
 | 
			
		||||
time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10
 | 
			
		||||
time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122
 | 
			
		||||
time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10
 | 
			
		||||
time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9
 | 
			
		||||
time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Example
 | 
			
		||||
 | 
			
		||||
The simplest way to use Logrus is simply the package-level exported logger:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
  log "github.com/Sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
  log.WithFields(log.Fields{
 | 
			
		||||
    "animal": "walrus",
 | 
			
		||||
  }).Info("A walrus appears")
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Note that it's completely api-compatible with the stdlib logger, so you can
 | 
			
		||||
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
 | 
			
		||||
and you'll now have the flexibility of Logrus. You can customize it all you
 | 
			
		||||
want:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
  "os"
 | 
			
		||||
  log "github.com/Sirupsen/logrus"
 | 
			
		||||
  "github.com/Sirupsen/logrus/hooks/airbrake"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
  // Log as JSON instead of the default ASCII formatter.
 | 
			
		||||
  log.SetFormatter(&log.JSONFormatter{})
 | 
			
		||||
 | 
			
		||||
  // Use the Airbrake hook to report errors that have Error severity or above to
 | 
			
		||||
  // an exception tracker. You can create custom hooks, see the Hooks section.
 | 
			
		||||
  log.AddHook(&logrus_airbrake.AirbrakeHook{})
 | 
			
		||||
 | 
			
		||||
  // Output to stderr instead of stdout, could also be a file.
 | 
			
		||||
  log.SetOutput(os.Stderr)
 | 
			
		||||
 | 
			
		||||
  // Only log the warning severity or above.
 | 
			
		||||
  log.SetLevel(log.WarnLevel)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
  log.WithFields(log.Fields{
 | 
			
		||||
    "animal": "walrus",
 | 
			
		||||
    "size":   10,
 | 
			
		||||
  }).Info("A group of walrus emerges from the ocean")
 | 
			
		||||
 | 
			
		||||
  log.WithFields(log.Fields{
 | 
			
		||||
    "omg":    true,
 | 
			
		||||
    "number": 122,
 | 
			
		||||
  }).Warn("The group's number increased tremendously!")
 | 
			
		||||
 | 
			
		||||
  log.WithFields(log.Fields{
 | 
			
		||||
    "omg":    true,
 | 
			
		||||
    "number": 100,
 | 
			
		||||
  }).Fatal("The ice breaks!")
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
For more advanced usage such as logging to multiple locations from the same
 | 
			
		||||
application, you can also create an instance of the `logrus` Logger:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
  "github.com/Sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Create a new instance of the logger. You can have any number of instances.
 | 
			
		||||
var log = logrus.New()
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
  // The API for setting attributes is a little different than the package level
 | 
			
		||||
  // exported logger. See Godoc.
 | 
			
		||||
  log.Out = os.Stderr
 | 
			
		||||
 | 
			
		||||
  log.WithFields(logrus.Fields{
 | 
			
		||||
    "animal": "walrus",
 | 
			
		||||
    "size":   10,
 | 
			
		||||
  }).Info("A group of walrus emerges from the ocean")
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Fields
 | 
			
		||||
 | 
			
		||||
Logrus encourages careful, structured logging though logging fields instead of
 | 
			
		||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
 | 
			
		||||
to send event %s to topic %s with key %d")`, you should log the much more
 | 
			
		||||
discoverable:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
log.WithFields(log.Fields{
 | 
			
		||||
  "event": event,
 | 
			
		||||
  "topic": topic,
 | 
			
		||||
  "key": key,
 | 
			
		||||
}).Fatal("Failed to send event")
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
We've found this API forces you to think about logging in a way that produces
 | 
			
		||||
much more useful logging messages. We've been in countless situations where just
 | 
			
		||||
a single added field to a log statement that was already there would've saved us
 | 
			
		||||
hours. The `WithFields` call is optional.
 | 
			
		||||
 | 
			
		||||
In general, with Logrus using any of the `printf`-family functions should be
 | 
			
		||||
seen as a hint you should add a field, however, you can still use the
 | 
			
		||||
`printf`-family functions with Logrus.
 | 
			
		||||
 | 
			
		||||
#### Hooks
 | 
			
		||||
 | 
			
		||||
You can add hooks for logging levels. For example to send errors to an exception
 | 
			
		||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
 | 
			
		||||
multiple places simultaneously, e.g. syslog.
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
// Not the real implementation of the Airbrake hook. Just a simple sample.
 | 
			
		||||
import (
 | 
			
		||||
  log "github.com/Sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
  log.AddHook(new(AirbrakeHook))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AirbrakeHook struct{}
 | 
			
		||||
 | 
			
		||||
// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains
 | 
			
		||||
// the fields for the entry. See the Fields section of the README.
 | 
			
		||||
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
 | 
			
		||||
  err := airbrake.Notify(entry.Data["error"].(error))
 | 
			
		||||
  if err != nil {
 | 
			
		||||
    log.WithFields(log.Fields{
 | 
			
		||||
      "source":   "airbrake",
 | 
			
		||||
      "endpoint": airbrake.Endpoint,
 | 
			
		||||
    }).Info("Failed to send error to Airbrake")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// `Levels()` returns a slice of `Levels` the hook is fired for.
 | 
			
		||||
func (hook *AirbrakeHook) Levels() []log.Level {
 | 
			
		||||
  return []log.Level{
 | 
			
		||||
    log.ErrorLevel,
 | 
			
		||||
    log.FatalLevel,
 | 
			
		||||
    log.PanicLevel,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Logrus comes with built-in hooks. Add those, or your custom hook, in `init`:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
import (
 | 
			
		||||
  log "github.com/Sirupsen/logrus"
 | 
			
		||||
  "github.com/Sirupsen/logrus/hooks/airbrake"
 | 
			
		||||
  "github.com/Sirupsen/logrus/hooks/syslog"
 | 
			
		||||
  "log/syslog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
  log.AddHook(new(logrus_airbrake.AirbrakeHook))
 | 
			
		||||
 | 
			
		||||
  hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
 | 
			
		||||
  if err != nil {
 | 
			
		||||
    log.Error("Unable to connect to local syslog daemon")
 | 
			
		||||
  } else {
 | 
			
		||||
    log.AddHook(hook)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
 | 
			
		||||
  Send errors to an exception tracking service compatible with the Airbrake API.
 | 
			
		||||
  Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
 | 
			
		||||
 | 
			
		||||
* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
 | 
			
		||||
  Send errors to the Papertrail hosted logging service via UDP.
 | 
			
		||||
 | 
			
		||||
* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
 | 
			
		||||
  Send errors to remote syslog server.
 | 
			
		||||
  Uses standard library `log/syslog` behind the scenes.
 | 
			
		||||
 | 
			
		||||
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
 | 
			
		||||
  Send errors to a channel in hipchat.
 | 
			
		||||
 | 
			
		||||
#### Level logging
 | 
			
		||||
 | 
			
		||||
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
log.Debug("Useful debugging information.")
 | 
			
		||||
log.Info("Something noteworthy happened!")
 | 
			
		||||
log.Warn("You should probably take a look at this.")
 | 
			
		||||
log.Error("Something failed but I'm not quitting.")
 | 
			
		||||
// Calls os.Exit(1) after logging
 | 
			
		||||
log.Fatal("Bye.")
 | 
			
		||||
// Calls panic() after logging
 | 
			
		||||
log.Panic("I'm bailing.")
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You can set the logging level on a `Logger`, then it will only log entries with
 | 
			
		||||
that severity or anything above it:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
 | 
			
		||||
log.SetLevel(log.InfoLevel)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
 | 
			
		||||
environment if your application has that.
 | 
			
		||||
 | 
			
		||||
#### Entries
 | 
			
		||||
 | 
			
		||||
Besides the fields added with `WithField` or `WithFields` some fields are
 | 
			
		||||
automatically added to all logging events:
 | 
			
		||||
 | 
			
		||||
1. `time`. The timestamp when the entry was created.
 | 
			
		||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
 | 
			
		||||
   the `AddFields` call. E.g. `Failed to send event.`
 | 
			
		||||
3. `level`. The logging level. E.g. `info`.
 | 
			
		||||
 | 
			
		||||
#### Environments
 | 
			
		||||
 | 
			
		||||
Logrus has no notion of environment.
 | 
			
		||||
 | 
			
		||||
If you wish for hooks and formatters to only be used in specific environments,
 | 
			
		||||
you should handle that yourself. For example, if your application has a global
 | 
			
		||||
variable `Environment`, which is a string representation of the environment you
 | 
			
		||||
could do:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
import (
 | 
			
		||||
  log "github.com/Sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
init() {
 | 
			
		||||
  // do something here to set environment depending on an environment variable
 | 
			
		||||
  // or command-line flag
 | 
			
		||||
  if Environment == "production" {
 | 
			
		||||
    log.SetFormatter(logrus.JSONFormatter)
 | 
			
		||||
  } else {
 | 
			
		||||
    // The TextFormatter is default, you don't actually have to do this.
 | 
			
		||||
    log.SetFormatter(logrus.TextFormatter)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This configuration is how `logrus` was intended to be used, but JSON in
 | 
			
		||||
production is mostly only useful if you do log aggregation with tools like
 | 
			
		||||
Splunk or Logstash.
 | 
			
		||||
 | 
			
		||||
#### Formatters
 | 
			
		||||
 | 
			
		||||
The built-in logging formatters are:
 | 
			
		||||
 | 
			
		||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
 | 
			
		||||
  without colors.
 | 
			
		||||
  * *Note:* to force colored output when there is no TTY, set the `ForceColors`
 | 
			
		||||
    field to `true`.  To force no colored output even if there is a TTY  set the
 | 
			
		||||
    `DisableColors` field to `true`
 | 
			
		||||
* `logrus.JSONFormatter`. Logs fields as JSON.
 | 
			
		||||
 | 
			
		||||
Third party logging formatters:
 | 
			
		||||
 | 
			
		||||
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
 | 
			
		||||
 | 
			
		||||
You can define your formatter by implementing the `Formatter` interface,
 | 
			
		||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
 | 
			
		||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
 | 
			
		||||
default ones (see Entries section above):
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
type MyJSONFormatter struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
log.SetFormatter(new(MyJSONFormatter))
 | 
			
		||||
 | 
			
		||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
 | 
			
		||||
  // Note this doesn't include Time, Level and Message which are available on
 | 
			
		||||
  // the Entry. Consult `godoc` on information about those fields or read the
 | 
			
		||||
  // source of the official loggers.
 | 
			
		||||
  serialized, err := json.Marshal(entry.Data)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
      return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
 | 
			
		||||
    }
 | 
			
		||||
  return append(serialized, '\n'), nil
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Rotation
 | 
			
		||||
 | 
			
		||||
Log rotation is not provided with Logrus. Log rotation should be done by an
 | 
			
		||||
external program (like `logrotated(8)`) that can compress and delete old log
 | 
			
		||||
entries. It should not be a feature of the application-level logger.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[godoc]: https://godoc.org/github.com/Sirupsen/logrus
 | 
			
		||||
| 
						 | 
				
			
			@ -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]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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!")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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...)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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...)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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{})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
    })
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
		}})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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()))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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",
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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