Use Godep to vendor distribution dependencies
As we get closer to release, we need to ensure that builds are repeatable. Godep provides a workable solution to managing dependencies in Go to support this requirement. This commit should be bolstered by updates to documentation and build configuration. Signed-off-by: Stephen J Day <stephen.day@docker.com>master
							parent
							
								
									e9e26bd362
								
							
						
					
					
						commit
						fc2a840e8f
					
				|  | @ -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