use vendored logrus (closes #306)
This commit is contained in:
parent
f4d5707500
commit
8ec6c76ecb
2
Makefile
2
Makefile
@ -56,6 +56,8 @@ lint: lint-go
|
||||
|
||||
lint-go:
|
||||
cd $(SRCDIR) && ! go fmt $(project)/... | awk '/./ {print "ERROR: go fmt made unexpected changes:", $$0}' | grep .
|
||||
cd $(SRCDIR) && go mod tidy
|
||||
cd $(SRCDIR) && go mod vendor
|
||||
cd $(SRCDIR) && go vet $(project)/...
|
||||
|
||||
test: test-go
|
||||
|
@ -42,8 +42,6 @@ This repository is structured as follows:
|
||||
Abstract back-end storage model.
|
||||
* `mock`
|
||||
Partial in-memory storage implementation for unit testing only.
|
||||
* `logrus`
|
||||
Logging library, forked from `github.com/Sirupsen/logrus`.
|
||||
* `metrics`
|
||||
Metrics endpoint for prometheus to query.
|
||||
* `openpgp`
|
||||
@ -75,7 +73,7 @@ These were added with the following commands:
|
||||
git subtree add --prefix=src/hockeypuck/server https://github.com/hockeypuck/server master --squash
|
||||
git subtree add --prefix=src/hockeypuck/testing https://github.com/hockeypuck/testing master --squash
|
||||
|
||||
(Note that the mgohkp back end has since been removed)
|
||||
(Note that the logrus and mgohkp trees have since been removed)
|
||||
|
||||
The upstream Github projects have been archived.
|
||||
Any new development on Hockeypuck should be proposed here.
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
|
@ -38,7 +38,7 @@ import (
|
||||
|
||||
cf "hockeypuck/conflux"
|
||||
"hockeypuck/conflux/recon"
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type prefixTree struct {
|
||||
|
@ -29,7 +29,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/tomb.v2"
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
gc "gopkg.in/check.v1"
|
||||
"gopkg.in/tomb.v2"
|
||||
|
@ -6,19 +6,15 @@ require (
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c
|
||||
github.com/bugsnag/bugsnag-go v2.2.0+incompatible
|
||||
github.com/carbocation/interpose v0.0.0-20161206215253-723534742ba3
|
||||
github.com/getsentry/raven-go v0.2.0
|
||||
github.com/hashicorp/golang-lru v1.0.2
|
||||
github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stvp/go-udp-testing v0.0.0-20171104055251-c4434f09ec13
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/syndtr/goleveldb v0.0.0-20200815110645-5c35d600f0ca
|
||||
github.com/tobi/airbrake-go v0.0.0-20151005181455-a3cdd910a3ff
|
||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e
|
||||
gopkg.in/basen.v1 v1.0.0-20150613233243-308119dd1d4c
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||
@ -29,15 +25,11 @@ require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||
github.com/bugsnag/panicwrap v1.3.4 // indirect
|
||||
github.com/carbocation/handlers v0.0.0-20140528190747-c939c6d9ef31 // indirect
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.6 // indirect
|
||||
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e // indirect
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/goods/httpbuf v0.0.0-20120503183857-5709e9bb814c // indirect
|
||||
@ -46,7 +38,6 @@ require (
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/interpose/middleware v0.0.0-20150216143757-05ed56ed52fa // indirect
|
||||
github.com/justinas/nosurf v0.0.0-20190416172904-05988550ea18 // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
@ -54,18 +45,15 @@ require (
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/phyber/negroni-gzip v0.0.0-20180113114010-ef6356a5d029 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/urfave/negroni v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.15.0 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 // indirect
|
||||
)
|
||||
|
||||
|
@ -8,19 +8,11 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/bugsnag/bugsnag-go v2.2.0+incompatible h1:ewHS3GgD2fBtipwmaa4Gcfhum4PSAHIwVypmjXyp/Dg=
|
||||
github.com/bugsnag/bugsnag-go v2.2.0+incompatible/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/panicwrap v1.3.4 h1:A6sXFtDGsgU/4BLf5JT0o5uYg3EeKgGx3Sfs+/uk3pU=
|
||||
github.com/bugsnag/panicwrap v1.3.4/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/carbocation/handlers v0.0.0-20140528190747-c939c6d9ef31 h1:SDMgCFII5drFRIyAaihze9ceRMpTt1FW6Q5jjpc2u4c=
|
||||
github.com/carbocation/handlers v0.0.0-20140528190747-c939c6d9ef31/go.mod h1:iGISoFvZYz358DFlmHvYFlh4CgRdzPLXB2NJE48x6lY=
|
||||
github.com/carbocation/interpose v0.0.0-20161206215253-723534742ba3 h1:RtCys6GUprNaPOP04Zuo65wS10PMbSPPZNvIb9xYYLE=
|
||||
github.com/carbocation/interpose v0.0.0-20161206215253-723534742ba3/go.mod h1:4PGcghc3ZjA/uozANO8lCHo/gnHyMsm8iFYppSkVE/M=
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
@ -37,8 +29,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@ -76,8 +66,6 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/justinas/nosurf v0.0.0-20190416172904-05988550ea18 h1:ci3v0mUqcCewO25ntt7hprt2ZMNA0AWI6s6qV0rSpc0=
|
||||
github.com/justinas/nosurf v0.0.0-20190416172904-05988550ea18/go.mod h1:Aucr5I5chr4OCuuVB4LTuHVrKHBuyRSo7vM2hqrcb7E=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@ -129,12 +117,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stvp/go-udp-testing v0.0.0-20171104055251-c4434f09ec13 h1:WYRIgR83bWdH2zjqXalfLuQYtgBG1KKxDRxinx2ygMI=
|
||||
github.com/stvp/go-udp-testing v0.0.0-20171104055251-c4434f09ec13/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
|
||||
github.com/syndtr/goleveldb v0.0.0-20200815110645-5c35d600f0ca h1:sfc0HNWRJzVnj3//j4OHj0uzakLfAGH34CJ9jHYXdqM=
|
||||
github.com/syndtr/goleveldb v0.0.0-20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
|
||||
github.com/tobi/airbrake-go v0.0.0-20151005181455-a3cdd910a3ff h1:cnykn0Sc8aVuDg3OUWlWj0dRH4359BTkhoI9MBDLmqI=
|
||||
github.com/tobi/airbrake-go v0.0.0-20151005181455-a3cdd910a3ff/go.mod h1:oDQe1wQu4UeC9B+tlVxYGgiN42g06QsyC1hMxUtDT9I=
|
||||
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
|
@ -40,7 +40,7 @@ import (
|
||||
"hockeypuck/hkp/jsonhkp"
|
||||
"hockeypuck/hkp/sks"
|
||||
"hockeypuck/hkp/storage"
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hockeypuck/openpgp"
|
||||
)
|
||||
|
||||
|
@ -27,7 +27,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/tomb.v2"
|
||||
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hockeypuck/openpgp"
|
||||
|
||||
"hockeypuck/hkp/storage"
|
||||
|
@ -36,7 +36,7 @@ import (
|
||||
"hockeypuck/conflux/recon"
|
||||
"hockeypuck/conflux/recon/leveldb"
|
||||
"hockeypuck/hkp/storage"
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hockeypuck/openpgp"
|
||||
)
|
||||
|
||||
|
1
src/hockeypuck/logrus/.gitignore
vendored
1
src/hockeypuck/logrus/.gitignore
vendored
@ -1 +0,0 @@
|
||||
logrus
|
@ -1,8 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- tip
|
||||
install:
|
||||
- go get -t ./...
|
@ -1,7 +0,0 @@
|
||||
# 0.7.3
|
||||
|
||||
formatter/\*: allow configuration of timestamp layout
|
||||
|
||||
# 0.7.2
|
||||
|
||||
formatter/text: Add configuration option for time format (#158)
|
@ -1,349 +0,0 @@
|
||||
# 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) [][godoc]
|
||||
|
||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
||||
yet stable (pre 1.0). Logrus itself is completely stable and has been used in
|
||||
many large deployments. The core API is unlikely to change much but please
|
||||
version control your Logrus to make sure you aren't fetching latest `master` on
|
||||
every build.**
|
||||
|
||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||
plain text):
|
||||
|
||||

|
||||
|
||||
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
|
||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
||||
|
||||
```text
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||
exit status 1
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
```
|
||||
|
||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
|
||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||
want:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"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(airbrake.NewHook("https://example.com", "xyz", "development"))
|
||||
|
||||
// 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!")
|
||||
|
||||
// A common pattern is to re-use fields between logging statements by re-using
|
||||
// the logrus.Entry returned from WithFields()
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"common": "this is a common field",
|
||||
"other": "I also should be logged always",
|
||||
})
|
||||
|
||||
contextLogger.Info("I'll be logged with common and other field")
|
||||
contextLogger.Info("Me too")
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage such as logging to multiple locations from the same
|
||||
application, you can also create an instance of the `logrus` Logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Create a new instance of the logger. You can have any number of instances.
|
||||
var log = logrus.New()
|
||||
|
||||
func main() {
|
||||
// The API for setting attributes is a little different than the package level
|
||||
// exported logger. See Godoc.
|
||||
log.Out = os.Stderr
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
}
|
||||
```
|
||||
|
||||
#### Fields
|
||||
|
||||
Logrus encourages careful, structured logging though logging fields instead of
|
||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||
to send event %s to topic %s with key %d")`, you should log the much more
|
||||
discoverable:
|
||||
|
||||
```go
|
||||
log.WithFields(log.Fields{
|
||||
"event": event,
|
||||
"topic": topic,
|
||||
"key": key,
|
||||
}).Fatal("Failed to send event")
|
||||
```
|
||||
|
||||
We've found this API forces you to think about logging in a way that produces
|
||||
much more useful logging messages. We've been in countless situations where just
|
||||
a single added field to a log statement that was already there would've saved us
|
||||
hours. The `WithFields` call is optional.
|
||||
|
||||
In general, with Logrus using any of the `printf`-family functions should be
|
||||
seen as a hint you should add a field, however, you can still use the
|
||||
`printf`-family functions with Logrus.
|
||||
|
||||
#### Hooks
|
||||
|
||||
You can add hooks for logging levels. For example to send errors to an exception
|
||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||
multiple places simultaneously, e.g. syslog.
|
||||
|
||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||
`init`:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||
"github.com/Sirupsen/logrus/hooks/syslog"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development"))
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
| Hook | Description |
|
||||
| ----- | ----------- |
|
||||
| [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. |
|
||||
| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. |
|
||||
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||
| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
||||
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||
| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) |
|
||||
|
||||
#### 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.
|
||||
* `logrus_logstash.LogstashFormatter`. Logs fields as Logstash Events (http://logstash.net).
|
||||
|
||||
```go
|
||||
logrus.SetFormatter(&logrus_logstash.LogstashFormatter{Type: “application_name"})
|
||||
```
|
||||
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
#### Logger as an `io.Writer`
|
||||
|
||||
Logrus can be transormed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
||||
|
||||
```go
|
||||
w := logger.Writer()
|
||||
defer w.Close()
|
||||
|
||||
srv := http.Server{
|
||||
// create a stdlib log.Logger that writes to
|
||||
// logrus.Logger.
|
||||
ErrorLog: log.New(w, "", 0),
|
||||
}
|
||||
```
|
||||
|
||||
Each line written to that writer will be printed the usual way, using formatters
|
||||
and hooks. The level for those entries is `info`.
|
||||
|
||||
#### Rotation
|
||||
|
||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||
external program (like `logrotate(8)`) that can compress and delete old log
|
||||
entries. It should not be a feature of the application-level logger.
|
||||
|
||||
|
||||
[godoc]: https://godoc.org/github.com/Sirupsen/logrus
|
@ -1,252 +0,0 @@
|
||||
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) Warning(args ...interface{}) {
|
||||
entry.Warn(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Error(args ...interface{}) {
|
||||
if entry.Logger.Level >= ErrorLevel {
|
||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatal(args ...interface{}) {
|
||||
if entry.Logger.Level >= FatalLevel {
|
||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||
}
|
||||
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]
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
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)
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"hockeypuck/logrus"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
func init() {
|
||||
log.Formatter = new(logrus.JSONFormatter)
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
log.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"err": err,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
||||
}()
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 8,
|
||||
}).Debug("Started observing beach")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"temperature": -4,
|
||||
}).Debug("Temperature changes")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "orca",
|
||||
"size": 9009,
|
||||
}).Panic("It's over 9000!")
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"hockeypuck/logrus"
|
||||
"hockeypuck/logrus/hooks/airbrake"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
func init() {
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
log.Hooks.Add(airbrake.NewHook("https://example.com", "xyz", "development"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import "time"
|
||||
|
||||
const DefaultTimestampFormat = time.RFC3339
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
//
|
||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||
// * `entry.Data["time"]`. The timestamp.
|
||||
// * `entry.Data["level"]. The level the entry was logged at.
|
||||
//
|
||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||
// logged to `logger.Out`.
|
||||
type Formatter interface {
|
||||
Format(*Entry) ([]byte, error)
|
||||
}
|
||||
|
||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
//
|
||||
// Would just silently drop the user provided level. Instead with this code
|
||||
// it'll logged as:
|
||||
//
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
func prefixFieldClashes(data Fields) {
|
||||
_, ok := data["time"]
|
||||
if ok {
|
||||
data["fields.time"] = data["time"]
|
||||
}
|
||||
|
||||
_, ok = data["msg"]
|
||||
if ok {
|
||||
data["fields.msg"] = data["msg"]
|
||||
}
|
||||
|
||||
_, ok = data["level"]
|
||||
if ok {
|
||||
data["fields.level"] = data["level"]
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
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)))
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package logstash
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"hockeypuck/logrus"
|
||||
)
|
||||
|
||||
// Formatter generates json in logstash format.
|
||||
// Logstash site: http://logstash.net/
|
||||
type LogstashFormatter struct {
|
||||
Type string // if not empty use for logstash type field.
|
||||
|
||||
// TimestampFormat sets the format used for timestamps.
|
||||
TimestampFormat string
|
||||
}
|
||||
|
||||
func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
entry.Data["@version"] = 1
|
||||
|
||||
if f.TimestampFormat == "" {
|
||||
f.TimestampFormat = logrus.DefaultTimestampFormat
|
||||
}
|
||||
|
||||
entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat)
|
||||
|
||||
// set message field
|
||||
v, ok := entry.Data["message"]
|
||||
if ok {
|
||||
entry.Data["fields.message"] = v
|
||||
}
|
||||
entry.Data["message"] = entry.Message
|
||||
|
||||
// set level field
|
||||
v, ok = entry.Data["level"]
|
||||
if ok {
|
||||
entry.Data["fields.level"] = v
|
||||
}
|
||||
entry.Data["level"] = entry.Level.String()
|
||||
|
||||
// set type field
|
||||
if f.Type != "" {
|
||||
v, ok = entry.Data["type"]
|
||||
if ok {
|
||||
entry.Data["fields.type"] = v
|
||||
}
|
||||
entry.Data["type"] = f.Type
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package logstash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"hockeypuck/logrus"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogstashFormatter(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
lf := LogstashFormatter{Type: "abc"}
|
||||
|
||||
fields := logrus.Fields{
|
||||
"message": "def",
|
||||
"level": "ijk",
|
||||
"type": "lmn",
|
||||
"one": 1,
|
||||
"pi": 3.14,
|
||||
"bool": true,
|
||||
}
|
||||
|
||||
entry := logrus.WithFields(fields)
|
||||
entry.Message = "msg"
|
||||
entry.Level = logrus.InfoLevel
|
||||
|
||||
b, _ := lf.Format(entry)
|
||||
|
||||
var data map[string]interface{}
|
||||
dec := json.NewDecoder(bytes.NewReader(b))
|
||||
dec.UseNumber()
|
||||
dec.Decode(&data)
|
||||
|
||||
// base fields
|
||||
assert.Equal(json.Number("1"), data["@version"])
|
||||
assert.NotEmpty(data["@timestamp"])
|
||||
assert.Equal("abc", data["type"])
|
||||
assert.Equal("msg", data["message"])
|
||||
assert.Equal("info", data["level"])
|
||||
|
||||
// substituted fields
|
||||
assert.Equal("def", data["fields.message"])
|
||||
assert.Equal("ijk", data["fields.level"])
|
||||
assert.Equal("lmn", data["fields.type"])
|
||||
|
||||
// formats
|
||||
assert.Equal(json.Number("1"), data["one"])
|
||||
assert.Equal(json.Number("3.14"), data["pi"])
|
||||
assert.Equal(true, data["bool"])
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type TestHook struct {
|
||||
Fired bool
|
||||
}
|
||||
|
||||
func (hook *TestHook) Fire(entry *Entry) error {
|
||||
hook.Fired = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *TestHook) Levels() []Level {
|
||||
return []Level{
|
||||
DebugLevel,
|
||||
InfoLevel,
|
||||
WarnLevel,
|
||||
ErrorLevel,
|
||||
FatalLevel,
|
||||
PanicLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookFires(t *testing.T) {
|
||||
hook := new(TestHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
assert.Equal(t, hook.Fired, false)
|
||||
|
||||
log.Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, hook.Fired, true)
|
||||
})
|
||||
}
|
||||
|
||||
type ModifyHook struct {
|
||||
}
|
||||
|
||||
func (hook *ModifyHook) Fire(entry *Entry) error {
|
||||
entry.Data["wow"] = "whale"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *ModifyHook) Levels() []Level {
|
||||
return []Level{
|
||||
DebugLevel,
|
||||
InfoLevel,
|
||||
WarnLevel,
|
||||
ErrorLevel,
|
||||
FatalLevel,
|
||||
PanicLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookCanModifyEntry(t *testing.T) {
|
||||
hook := new(ModifyHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
log.WithField("wow", "elephant").Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["wow"], "whale")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCanFireMultipleHooks(t *testing.T) {
|
||||
hook1 := new(ModifyHook)
|
||||
hook2 := new(TestHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook1)
|
||||
log.Hooks.Add(hook2)
|
||||
|
||||
log.WithField("wow", "elephant").Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["wow"], "whale")
|
||||
assert.Equal(t, hook2.Fired, true)
|
||||
})
|
||||
}
|
||||
|
||||
type ErrorHook struct {
|
||||
Fired bool
|
||||
}
|
||||
|
||||
func (hook *ErrorHook) Fire(entry *Entry) error {
|
||||
hook.Fired = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *ErrorHook) Levels() []Level {
|
||||
return []Level{
|
||||
ErrorLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
|
||||
hook := new(ErrorHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
log.Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, hook.Fired, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestErrorHookShouldFireOnError(t *testing.T) {
|
||||
hook := new(ErrorHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
log.Error("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, hook.Fired, true)
|
||||
})
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package airbrake
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tobi/airbrake-go"
|
||||
"hockeypuck/logrus"
|
||||
)
|
||||
|
||||
// AirbrakeHook to send exceptions to an exception-tracking service compatible
|
||||
// with the Airbrake API.
|
||||
type airbrakeHook struct {
|
||||
APIKey string
|
||||
Endpoint string
|
||||
Environment string
|
||||
}
|
||||
|
||||
func NewHook(endpoint, apiKey, env string) *airbrakeHook {
|
||||
return &airbrakeHook{
|
||||
APIKey: apiKey,
|
||||
Endpoint: endpoint,
|
||||
Environment: env,
|
||||
}
|
||||
}
|
||||
|
||||
func (hook *airbrakeHook) Fire(entry *logrus.Entry) error {
|
||||
airbrake.ApiKey = hook.APIKey
|
||||
airbrake.Endpoint = hook.Endpoint
|
||||
airbrake.Environment = hook.Environment
|
||||
|
||||
var notifyErr error
|
||||
err, ok := entry.Data["error"].(error)
|
||||
if ok {
|
||||
notifyErr = err
|
||||
} else {
|
||||
notifyErr = errors.New(entry.Message)
|
||||
}
|
||||
|
||||
airErr := airbrake.Notify(notifyErr)
|
||||
if airErr != nil {
|
||||
return fmt.Errorf("Failed to send error to Airbrake: %s", airErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *airbrakeHook) Levels() []logrus.Level {
|
||||
return []logrus.Level{
|
||||
logrus.ErrorLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.PanicLevel,
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
package airbrake
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hockeypuck/logrus"
|
||||
)
|
||||
|
||||
type notice struct {
|
||||
Error NoticeError `xml:"error"`
|
||||
}
|
||||
type NoticeError struct {
|
||||
Class string `xml:"class"`
|
||||
Message string `xml:"message"`
|
||||
}
|
||||
|
||||
type customErr struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *customErr) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
const (
|
||||
testAPIKey = "abcxyz"
|
||||
testEnv = "development"
|
||||
expectedClass = "*airbrake.customErr"
|
||||
expectedMsg = "foo"
|
||||
unintendedMsg = "Airbrake will not see this string"
|
||||
)
|
||||
|
||||
var (
|
||||
noticeError = make(chan NoticeError, 1)
|
||||
)
|
||||
|
||||
// TestLogEntryMessageReceived checks if invoking Logrus' log.Error
|
||||
// method causes an XML payload containing the log entry message is received
|
||||
// by a HTTP server emulating an Airbrake-compatible endpoint.
|
||||
func TestLogEntryMessageReceived(t *testing.T) {
|
||||
log := logrus.New()
|
||||
ts := startAirbrakeServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
hook := NewHook(ts.URL, testAPIKey, "production")
|
||||
log.Hooks.Add(hook)
|
||||
|
||||
log.Error(expectedMsg)
|
||||
|
||||
select {
|
||||
case received := <-noticeError:
|
||||
if received.Message != expectedMsg {
|
||||
t.Errorf("Unexpected message received: %s", received.Message)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Error("Timed out; no notice received by Airbrake API")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLogEntryMessageReceived confirms that, when passing an error type using
|
||||
// logrus.Fields, a HTTP server emulating an Airbrake endpoint receives the
|
||||
// error message returned by the Error() method on the error interface
|
||||
// rather than the logrus.Entry.Message string.
|
||||
func TestLogEntryWithErrorReceived(t *testing.T) {
|
||||
log := logrus.New()
|
||||
ts := startAirbrakeServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
hook := NewHook(ts.URL, testAPIKey, "production")
|
||||
log.Hooks.Add(hook)
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"error": &customErr{expectedMsg},
|
||||
}).Error(unintendedMsg)
|
||||
|
||||
select {
|
||||
case received := <-noticeError:
|
||||
if received.Message != expectedMsg {
|
||||
t.Errorf("Unexpected message received: %s", received.Message)
|
||||
}
|
||||
if received.Class != expectedClass {
|
||||
t.Errorf("Unexpected error class: %s", received.Class)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Error("Timed out; no notice received by Airbrake API")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLogEntryWithNonErrorTypeNotReceived confirms that, when passing a
|
||||
// non-error type using logrus.Fields, a HTTP server emulating an Airbrake
|
||||
// endpoint receives the logrus.Entry.Message string.
|
||||
//
|
||||
// Only error types are supported when setting the 'error' field using
|
||||
// logrus.WithFields().
|
||||
func TestLogEntryWithNonErrorTypeNotReceived(t *testing.T) {
|
||||
log := logrus.New()
|
||||
ts := startAirbrakeServer(t)
|
||||
defer ts.Close()
|
||||
|
||||
hook := NewHook(ts.URL, testAPIKey, "production")
|
||||
log.Hooks.Add(hook)
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"error": expectedMsg,
|
||||
}).Error(unintendedMsg)
|
||||
|
||||
select {
|
||||
case received := <-noticeError:
|
||||
if received.Message != unintendedMsg {
|
||||
t.Errorf("Unexpected message received: %s", received.Message)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Error("Timed out; no notice received by Airbrake API")
|
||||
}
|
||||
}
|
||||
|
||||
func startAirbrakeServer(t *testing.T) *httptest.Server {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var notice notice
|
||||
if err := xml.NewDecoder(r.Body).Decode(¬ice); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
r.Body.Close()
|
||||
|
||||
noticeError <- notice.Error
|
||||
}))
|
||||
|
||||
return ts
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
package logrus_bugsnag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/bugsnag/bugsnag-go"
|
||||
"hockeypuck/logrus"
|
||||
)
|
||||
|
||||
type bugsnagHook struct{}
|
||||
|
||||
// ErrBugsnagUnconfigured is returned if NewBugsnagHook is called before
|
||||
// bugsnag.Configure. Bugsnag must be configured before the hook.
|
||||
var ErrBugsnagUnconfigured = errors.New("bugsnag must be configured before installing this logrus hook")
|
||||
|
||||
// ErrBugsnagSendFailed indicates that the hook failed to submit an error to
|
||||
// bugsnag. The error was successfully generated, but `bugsnag.Notify()`
|
||||
// failed.
|
||||
type ErrBugsnagSendFailed struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e ErrBugsnagSendFailed) Error() string {
|
||||
return "failed to send error to Bugsnag: " + e.err.Error()
|
||||
}
|
||||
|
||||
// NewBugsnagHook initializes a logrus hook which sends exceptions to an
|
||||
// exception-tracking service compatible with the Bugsnag API. Before using
|
||||
// this hook, you must call bugsnag.Configure(). The returned object should be
|
||||
// registered with a log via `AddHook()`
|
||||
//
|
||||
// Entries that trigger an Error, Fatal or Panic should now include an "error"
|
||||
// field to send to Bugsnag.
|
||||
func NewBugsnagHook() (*bugsnagHook, error) {
|
||||
if bugsnag.Config.APIKey == "" {
|
||||
return nil, ErrBugsnagUnconfigured
|
||||
}
|
||||
return &bugsnagHook{}, nil
|
||||
}
|
||||
|
||||
// Fire forwards an error to Bugsnag. Given a logrus.Entry, it extracts the
|
||||
// "error" field (or the Message if the error isn't present) and sends it off.
|
||||
func (hook *bugsnagHook) Fire(entry *logrus.Entry) error {
|
||||
var notifyErr error
|
||||
err, ok := entry.Data["error"].(error)
|
||||
if ok {
|
||||
notifyErr = err
|
||||
} else {
|
||||
notifyErr = errors.New(entry.Message)
|
||||
}
|
||||
|
||||
bugsnagErr := bugsnag.Notify(notifyErr)
|
||||
if bugsnagErr != nil {
|
||||
return ErrBugsnagSendFailed{bugsnagErr}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Levels enumerates the log levels on which the error should be forwarded to
|
||||
// bugsnag: everything at or above the "Error" level.
|
||||
func (hook *bugsnagHook) Levels() []logrus.Level {
|
||||
return []logrus.Level{
|
||||
logrus.ErrorLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.PanicLevel,
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package logrus_bugsnag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bugsnag/bugsnag-go"
|
||||
"hockeypuck/logrus"
|
||||
)
|
||||
|
||||
type notice struct {
|
||||
Events []struct {
|
||||
Exceptions []struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"exceptions"`
|
||||
} `json:"events"`
|
||||
}
|
||||
|
||||
func TestNoticeReceived(t *testing.T) {
|
||||
msg := make(chan string, 1)
|
||||
expectedMsg := "foo"
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var notice notice
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(data, ¬ice); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_ = r.Body.Close()
|
||||
|
||||
msg <- notice.Events[0].Exceptions[0].Message
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
hook := &bugsnagHook{}
|
||||
|
||||
bugsnag.Configure(bugsnag.Configuration{
|
||||
Endpoint: ts.URL,
|
||||
ReleaseStage: "production",
|
||||
APIKey: "12345678901234567890123456789012",
|
||||
Synchronous: true,
|
||||
})
|
||||
|
||||
log := logrus.New()
|
||||
log.Hooks.Add(hook)
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"error": errors.New(expectedMsg),
|
||||
}).Error("Bugsnag will not see this string")
|
||||
|
||||
select {
|
||||
case received := <-msg:
|
||||
if received != expectedMsg {
|
||||
t.Errorf("Unexpected message received: %s", received)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Error("Timed out; no notice received by Bugsnag API")
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
# 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)
|
||||
}
|
||||
}
|
||||
```
|
@ -1,55 +0,0 @@
|
||||
package logrus_papertrail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"hockeypuck/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)
|
||||
msg, _ := entry.String()
|
||||
payload := fmt.Sprintf("<22> %s %s: %s", date, hook.AppName, msg)
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package logrus_papertrail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stvp/go-udp-testing"
|
||||
"hockeypuck/logrus"
|
||||
)
|
||||
|
||||
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")
|
||||
})
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
# 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.Second
|
||||
```
|
@ -1,100 +0,0 @@
|
||||
package logrus_sentry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"hockeypuck/logrus"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
package logrus_sentry
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"hockeypuck/logrus"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"log/syslog"
|
||||
"github.com/Sirupsen/logrus"
|
||||
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log := logrus.New()
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
|
||||
if err == nil {
|
||||
log.Hooks.Add(hook)
|
||||
}
|
||||
}
|
||||
```
|
@ -1,59 +0,0 @@
|
||||
package logrus_syslog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hockeypuck/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,
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package logrus_syslog
|
||||
|
||||
import (
|
||||
"hockeypuck/logrus"
|
||||
"log/syslog"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLocalhostAddAndPrint(t *testing.T) {
|
||||
log := logrus.New()
|
||||
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unable to connect to local syslog.")
|
||||
}
|
||||
|
||||
log.Hooks.Add(hook)
|
||||
|
||||
for _, level := range hook.Levels() {
|
||||
if len(log.Hooks[level]) != 1 {
|
||||
t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Congratulations!")
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type JSONFormatter struct {
|
||||
// TimestampFormat sets the format used for marshaling timestamps.
|
||||
TimestampFormat string
|
||||
}
|
||||
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+3)
|
||||
for k, v := range entry.Data {
|
||||
switch v := v.(type) {
|
||||
case error:
|
||||
// Otherwise errors are ignored by `encoding/json`
|
||||
// https://github.com/Sirupsen/logrus/issues/137
|
||||
data[k] = v.Error()
|
||||
default:
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
prefixFieldClashes(data)
|
||||
|
||||
if f.TimestampFormat == "" {
|
||||
f.TimestampFormat = DefaultTimestampFormat
|
||||
}
|
||||
|
||||
data["time"] = entry.Time.Format(f.TimestampFormat)
|
||||
data["msg"] = entry.Message
|
||||
data["level"] = entry.Level.String()
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestErrorNotLost(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("error", errors.New("wild walrus")))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
entry := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &entry)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||
}
|
||||
|
||||
if entry["error"] != "wild walrus" {
|
||||
t.Fatal("Error field not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorNotLostOnFieldNotNamedError(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("omg", errors.New("wild walrus")))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
entry := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &entry)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||
}
|
||||
|
||||
if entry["omg"] != "wild walrus" {
|
||||
t.Fatal("Error field not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldClashWithTime(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("time", "right now!"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
entry := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &entry)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||
}
|
||||
|
||||
if entry["fields.time"] != "right now!" {
|
||||
t.Fatal("fields.time not set to original time field")
|
||||
}
|
||||
|
||||
if entry["time"] != "0001-01-01T00:00:00Z" {
|
||||
t.Fatal("time field not set to current time, was: ", entry["time"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldClashWithMsg(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("msg", "something"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
entry := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &entry)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||
}
|
||||
|
||||
if entry["fields.msg"] != "something" {
|
||||
t.Fatal("fields.msg not set to original msg field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldClashWithLevel(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("level", "something"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
entry := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &entry)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||
}
|
||||
|
||||
if entry["fields.level"] != "something" {
|
||||
t.Fatal("fields.level not set to original level field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEntryEndsWithNewline(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("level", "something"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
if b[len(b)-1] != '\n' {
|
||||
t.Fatal("Expected JSON log entry to end with a newline")
|
||||
}
|
||||
}
|
@ -1,203 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||
// file, or leave it default which is `os.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.DebugLevel,
|
||||
// }
|
||||
//
|
||||
// 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{}) {
|
||||
if logger.Level >= DebugLevel {
|
||||
NewEntry(logger).Debugf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||
if logger.Level >= InfoLevel {
|
||||
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{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warnf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warnf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||
if logger.Level >= ErrorLevel {
|
||||
NewEntry(logger).Errorf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||
if logger.Level >= FatalLevel {
|
||||
NewEntry(logger).Fatalf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||
if logger.Level >= PanicLevel {
|
||||
NewEntry(logger).Panicf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(args ...interface{}) {
|
||||
if logger.Level >= DebugLevel {
|
||||
NewEntry(logger).Debug(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(args ...interface{}) {
|
||||
if logger.Level >= InfoLevel {
|
||||
NewEntry(logger).Info(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Print(args ...interface{}) {
|
||||
NewEntry(logger).Info(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warn(args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warn(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warn(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(args ...interface{}) {
|
||||
if logger.Level >= ErrorLevel {
|
||||
NewEntry(logger).Error(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatal(args ...interface{}) {
|
||||
if logger.Level >= FatalLevel {
|
||||
NewEntry(logger).Fatal(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Panic(args ...interface{}) {
|
||||
if logger.Level >= PanicLevel {
|
||||
NewEntry(logger).Panic(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugln(args ...interface{}) {
|
||||
if logger.Level >= DebugLevel {
|
||||
NewEntry(logger).Debugln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Infoln(args ...interface{}) {
|
||||
if logger.Level >= InfoLevel {
|
||||
NewEntry(logger).Infoln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Println(args ...interface{}) {
|
||||
NewEntry(logger).Println(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnln(args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warnln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningln(args ...interface{}) {
|
||||
if logger.Level >= WarnLevel {
|
||||
NewEntry(logger).Warnln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorln(args ...interface{}) {
|
||||
if logger.Level >= ErrorLevel {
|
||||
NewEntry(logger).Errorln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||
if logger.Level >= FatalLevel {
|
||||
NewEntry(logger).Fatalln(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicln(args ...interface{}) {
|
||||
if logger.Level >= PanicLevel {
|
||||
NewEntry(logger).Panicln(args...)
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
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{})
|
||||
}
|
@ -1,301 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
|
||||
var buffer bytes.Buffer
|
||||
var fields Fields
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = new(JSONFormatter)
|
||||
|
||||
log(logger)
|
||||
|
||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assertions(fields)
|
||||
}
|
||||
|
||||
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = &TextFormatter{
|
||||
DisableColors: true,
|
||||
}
|
||||
|
||||
log(logger)
|
||||
|
||||
fields := make(map[string]string)
|
||||
for _, kv := range strings.Split(buffer.String(), " ") {
|
||||
if !strings.Contains(kv, "=") {
|
||||
continue
|
||||
}
|
||||
kvArr := strings.Split(kv, "=")
|
||||
key := strings.TrimSpace(kvArr[0])
|
||||
val := kvArr[1]
|
||||
if kvArr[1][0] == '"' {
|
||||
var err error
|
||||
val, err = strconv.Unquote(val)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
fields[key] = val
|
||||
}
|
||||
assertions(fields)
|
||||
}
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["level"], "info")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["level"], "info")
|
||||
})
|
||||
}
|
||||
|
||||
func TestWarn(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Warn("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["level"], "warning")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln("test", "test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test test")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln("test", 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test 10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln(10, 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "10 10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln(10, 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "10 10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Info("test", 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Info("test", "test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "testtest")
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
var fields Fields
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = new(JSONFormatter)
|
||||
|
||||
localLog := logger.WithFields(Fields{
|
||||
"key1": "value1",
|
||||
})
|
||||
|
||||
localLog.WithField("key2", "value2").Info("test")
|
||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "value2", fields["key2"])
|
||||
assert.Equal(t, "value1", fields["key1"])
|
||||
|
||||
buffer = bytes.Buffer{}
|
||||
fields = Fields{}
|
||||
localLog.Info("test")
|
||||
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, ok := fields["key2"]
|
||||
assert.Equal(t, false, ok)
|
||||
assert.Equal(t, "value1", fields["key1"])
|
||||
}
|
||||
|
||||
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("msg", "hello").Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("msg", "hello").Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["fields.msg"], "hello")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("time", "hello").Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["fields.time"], "hello")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("level", 1).Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["level"], "info")
|
||||
assert.Equal(t, fields["fields.level"], float64(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 TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
|
||||
|
||||
var buffer bytes.Buffer
|
||||
var fields Fields
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = new(JSONFormatter)
|
||||
|
||||
llog := logger.WithField("context", "eating raw fish")
|
||||
|
||||
llog.Info("looks delicious")
|
||||
|
||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.NoError(t, err, "should have decoded first message")
|
||||
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
|
||||
assert.Equal(t, fields["msg"], "looks delicious")
|
||||
assert.Equal(t, fields["context"], "eating raw fish")
|
||||
|
||||
buffer.Reset()
|
||||
|
||||
llog.Warn("omg it is!")
|
||||
|
||||
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.NoError(t, err, "should have decoded second message")
|
||||
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
|
||||
assert.Equal(t, fields["msg"], "omg it is!")
|
||||
assert.Equal(t, fields["context"], "eating raw fish")
|
||||
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
|
||||
|
||||
}
|
||||
|
||||
func TestConvertLevelToString(t *testing.T) {
|
||||
assert.Equal(t, "debug", DebugLevel.String())
|
||||
assert.Equal(t, "info", InfoLevel.String())
|
||||
assert.Equal(t, "warning", WarnLevel.String())
|
||||
assert.Equal(t, "error", ErrorLevel.String())
|
||||
assert.Equal(t, "fatal", FatalLevel.String())
|
||||
assert.Equal(t, "panic", PanicLevel.String())
|
||||
}
|
||||
|
||||
func TestParseLevel(t *testing.T) {
|
||||
l, err := ParseLevel("panic")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, PanicLevel, l)
|
||||
|
||||
l, err = ParseLevel("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())
|
||||
}
|
||||
|
||||
func TestGetSetLevelRace(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
if i%2 == 0 {
|
||||
SetLevel(InfoLevel)
|
||||
} else {
|
||||
GetLevel()
|
||||
}
|
||||
}(i)
|
||||
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
|
||||
type Termios syscall.Termios
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
|
||||
*/
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
|
||||
type Termios struct {
|
||||
Iflag uint32
|
||||
Oflag uint32
|
||||
Cflag uint32
|
||||
Lflag uint32
|
||||
Cc [20]uint8
|
||||
Ispeed uint32
|
||||
Ospeed uint32
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TCGETS
|
||||
|
||||
type Termios syscall.Termios
|
@ -1,22 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || darwin || freebsd || openbsd
|
||||
// +build linux darwin freebsd openbsd
|
||||
|
||||
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
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
|
||||
type Termios syscall.Termios
|
@ -1,28 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +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
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
nocolor = 0
|
||||
red = 31
|
||||
green = 32
|
||||
yellow = 33
|
||||
blue = 34
|
||||
gray = 37
|
||||
)
|
||||
|
||||
var (
|
||||
baseTimestamp time.Time
|
||||
isTerminal bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
baseTimestamp = time.Now()
|
||||
isTerminal = IsTerminal()
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Force disabling colors.
|
||||
DisableColors bool
|
||||
|
||||
// Disable timestamp logging. useful when output is redirected to logging
|
||||
// system that already adds timestamps.
|
||||
DisableTimestamp bool
|
||||
|
||||
// Enable logging the full timestamp when a TTY is attached instead of just
|
||||
// the time passed since beginning of execution.
|
||||
FullTimestamp bool
|
||||
|
||||
// TimestampFormat to use for display when a full timestamp is printed
|
||||
TimestampFormat string
|
||||
|
||||
// The fields are sorted by default for a consistent output. For applications
|
||||
// that log extremely frequently and don't use the JSON formatter this may not
|
||||
// be desired.
|
||||
DisableSorting bool
|
||||
}
|
||||
|
||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
var keys []string = make([]string, 0, len(entry.Data))
|
||||
for k := range entry.Data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
if !f.DisableSorting {
|
||||
sort.Strings(keys)
|
||||
}
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
prefixFieldClashes(entry.Data)
|
||||
|
||||
isColored := (f.ForceColors || isTerminal) && !f.DisableColors
|
||||
|
||||
if f.TimestampFormat == "" {
|
||||
f.TimestampFormat = DefaultTimestampFormat
|
||||
}
|
||||
if isColored {
|
||||
f.printColored(b, entry, keys)
|
||||
} else {
|
||||
if !f.DisableTimestamp {
|
||||
f.appendKeyValue(b, "time", entry.Time.Format(f.TimestampFormat))
|
||||
}
|
||||
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 (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string) {
|
||||
var levelColor int
|
||||
switch entry.Level {
|
||||
case DebugLevel:
|
||||
levelColor = gray
|
||||
case WarnLevel:
|
||||
levelColor = yellow
|
||||
case ErrorLevel, FatalLevel, PanicLevel:
|
||||
levelColor = red
|
||||
default:
|
||||
levelColor = blue
|
||||
}
|
||||
|
||||
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||
|
||||
if !f.FullTimestamp {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
|
||||
} else {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(f.TimestampFormat), entry.Message)
|
||||
}
|
||||
for _, k := range keys {
|
||||
v := entry.Data[k]
|
||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func needsQuoting(text string) bool {
|
||||
for _, ch := range text {
|
||||
if !((ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
ch == '-' || ch == '.') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) {
|
||||
switch value.(type) {
|
||||
case string:
|
||||
if needsQuoting(value.(string)) {
|
||||
fmt.Fprintf(b, "%v=%s ", key, value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%v=%q ", key, value)
|
||||
}
|
||||
case error:
|
||||
if needsQuoting(value.(error).Error()) {
|
||||
fmt.Fprintf(b, "%v=%s ", key, value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%v=%q ", key, value)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintf(b, "%v=%v ", key, value)
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestQuoting(t *testing.T) {
|
||||
tf := &TextFormatter{DisableColors: true}
|
||||
|
||||
checkQuoting := func(q bool, value interface{}) {
|
||||
b, _ := tf.Format(WithField("test", value))
|
||||
idx := bytes.Index(b, ([]byte)("test="))
|
||||
cont := bytes.Contains(b[idx+5:], []byte{'"'})
|
||||
if cont != q {
|
||||
if q {
|
||||
t.Errorf("quoting expected for: %#v", value)
|
||||
} else {
|
||||
t.Errorf("quoting not expected for: %#v", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkQuoting(false, "abcd")
|
||||
checkQuoting(false, "v1.0")
|
||||
checkQuoting(false, "1234567890")
|
||||
checkQuoting(true, "/foobar")
|
||||
checkQuoting(true, "x y")
|
||||
checkQuoting(true, "x,y")
|
||||
checkQuoting(false, errors.New("invalid"))
|
||||
checkQuoting(true, errors.New("invalid argument"))
|
||||
}
|
||||
|
||||
func TestTimestampFormat(t *testing.T) {
|
||||
checkTimeStr := func(format string) {
|
||||
customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format}
|
||||
customStr, _ := customFormatter.Format(WithField("test", "test"))
|
||||
timeStart := bytes.Index(customStr, ([]byte)("time="))
|
||||
timeEnd := bytes.Index(customStr, ([]byte)("level="))
|
||||
timeStr := customStr[timeStart+5 : timeEnd-1]
|
||||
if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' {
|
||||
timeStr = timeStr[1 : len(timeStr)-1]
|
||||
}
|
||||
if format == "" {
|
||||
format = time.RFC3339
|
||||
}
|
||||
_, e := time.Parse(format, (string)(timeStr))
|
||||
if e != nil {
|
||||
t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e)
|
||||
}
|
||||
}
|
||||
|
||||
checkTimeStr("2006-01-02T15:04:05.000000000Z07:00")
|
||||
checkTimeStr("Mon Jan _2 15:04:05 2006")
|
||||
checkTimeStr("")
|
||||
}
|
||||
|
||||
// TODO add tests for sorting etc., this requires a parser for the text
|
||||
// formatter output.
|
@ -1,31 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func (logger *Logger) Writer() *io.PipeWriter {
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
go logger.writerScanner(reader)
|
||||
runtime.SetFinalizer(writer, writerFinalizer)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
func (logger *Logger) writerScanner(reader *io.PipeReader) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
logger.Print(scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
logger.Errorf("Error while reading from Writer: %s", err)
|
||||
}
|
||||
reader.Close()
|
||||
}
|
||||
|
||||
func writerFinalizer(writer *io.PipeWriter) {
|
||||
writer.Close()
|
||||
}
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"gopkg.in/tomb.v2"
|
||||
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Metrics struct {
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hockeypuck/openpgp"
|
||||
)
|
||||
|
||||
|
@ -35,7 +35,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var ErrMissingSignature = fmt.Errorf("key material missing an expected signature")
|
||||
|
@ -20,7 +20,7 @@ package openpgp
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -35,7 +35,7 @@ import (
|
||||
|
||||
"hockeypuck/hkp/jsonhkp"
|
||||
hkpstorage "hockeypuck/hkp/storage"
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hockeypuck/openpgp"
|
||||
)
|
||||
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Die prints the error and exits with a non-zero exit code
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
cf "hockeypuck/conflux"
|
||||
"hockeypuck/hkp/sks"
|
||||
"hockeypuck/hkp/storage"
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hockeypuck/openpgp"
|
||||
"hockeypuck/server"
|
||||
"hockeypuck/server/cmd"
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
cf "hockeypuck/conflux"
|
||||
"hockeypuck/hkp/sks"
|
||||
"hockeypuck/hkp/storage"
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hockeypuck/server"
|
||||
"hockeypuck/server/cmd"
|
||||
)
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
"hockeypuck/hkp"
|
||||
"hockeypuck/hkp/sks"
|
||||
"hockeypuck/hkp/storage"
|
||||
log "hockeypuck/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hockeypuck/metrics"
|
||||
"hockeypuck/openpgp"
|
||||
"hockeypuck/pghkp"
|
||||
|
5
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/.gitignore
generated
vendored
5
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/.gitignore
generated
vendored
@ -1,5 +0,0 @@
|
||||
# Ignore maze runner generated files
|
||||
maze_output
|
||||
vendor
|
||||
|
||||
features/fixtures/testbuild
|
366
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/CHANGELOG.md
generated
vendored
366
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/CHANGELOG.md
generated
vendored
@ -1,366 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## 2.2.0 (2022-10-12)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Support pkg/errors `Unwrap()` on `errors.Error` objects
|
||||
[#194](https://github.com/bugsnag/bugsnag-go/pull/194)
|
||||
[Jayce Pulsipher](https://github.com/jaycetde)
|
||||
|
||||
* Document double star glob patterns are available for `ProjectPackages`
|
||||
subpackage names.
|
||||
[#184](https://github.com/bugsnag/bugsnag-go/pull/184)
|
||||
[Genta Kamitani](https://github.com/genkami)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Replace the gofrs/uuid dependency to maintain support for older versions of Go
|
||||
[#196](https://github.com/bugsnag/bugsnag-go/pull/196)
|
||||
|
||||
## 1.9.1 (2022-10-12)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Replace the gofrs/uuid dependency to maintain support for older versions of Go
|
||||
[#196](https://github.com/bugsnag/bugsnag-go/pull/196)
|
||||
|
||||
## 2.1.2 (2021-08-24)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Update panicwrap dependency to v1.3.4 which fixes build support for linux & darwin arm64.
|
||||
|
||||
## 2.1.1 (2021-04-19)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Update panicwrap dependency to 1.3.2, adding support for darwin arm64
|
||||
|
||||
## 2.1.0 (2021-01-27)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Support appending metadata through environment variables prefixed with
|
||||
`BUGSNAG_METADATA_`
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix `GOPATH`, `SourceRoot` and project package path stripping from stack
|
||||
traces on Windows by using the correct path separators.
|
||||
|
||||
## 2.0.0 (2021-01-18)
|
||||
|
||||
The v2 release adds support for Go modules, removes web framework
|
||||
integrations from the main repository, and supports library configuration
|
||||
through environment variables.
|
||||
|
||||
The new module is available via:
|
||||
|
||||
```go
|
||||
import "github.com/bugsnag/bugsnag-go/v2"
|
||||
```
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Removed `Configuration.Endpoint`. Use `Configuration.Endpoints` instead. For
|
||||
more info and an example, see the [Upgrading guide](./UPGRADING.md)
|
||||
* Web framework integrations have been moved to separate repositories:
|
||||
* [bugsnag-go-gin](https://github.com/bugsnag/bugsnag-go-gin)
|
||||
* [bugsnag-go-negroni](https://github.com/bugsnag/bugsnag-go-negroni)
|
||||
* [bugsnag-go-revel](https://github.com/bugsnag/bugsnag-go-revel)
|
||||
* The `martini` framework integration has been retired
|
||||
* `bugsnag.VERSION` has been renamed `bugsnag.Version`
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Support configuring Bugsnag through environment variables
|
||||
* Support reporting panics caused by overflowing the stack
|
||||
|
||||
## 1.9.0 (2021-01-05)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Support capturing "fatal error"-style panics from go, such as from concurrent
|
||||
map read/writes, out of memory errors, and nil goroutines.
|
||||
|
||||
## 1.8.0 (2020-12-03)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Support unwrapping the underlying causes from an error, including attached
|
||||
stack trace contents if available.
|
||||
|
||||
Any reported error which implements the following interface:
|
||||
|
||||
```go
|
||||
type errorWithCause interface {
|
||||
Unwrap() error
|
||||
}
|
||||
```
|
||||
|
||||
will have the cause included as a previous error in the resulting event. The
|
||||
cause information will be available on the Bugsnag dashboard and is available
|
||||
for inspection in callbacks on the `errors.Error` object.
|
||||
|
||||
```go
|
||||
bugsnag.OnBeforeNotify(func(event *bugsnag.Event, config *bugsnag.Configuration) error {
|
||||
if event.Error.Cause != nil {
|
||||
fmt.Printf("This error was caused by %v", event.Error.Cause.Error())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
## 1.7.0 (2020-11-18)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Support for changing the handled-ness of an event prior to delivery. This
|
||||
allows for otherwise handled events to affect a project's stability score.
|
||||
|
||||
```go
|
||||
bugsnag.Notify(err, func(event *bugsnag.Event) {
|
||||
event.Unhandled = true
|
||||
})
|
||||
```
|
||||
|
||||
## 1.6.0 (2020-11-12)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Extract stacktrace contents on errors wrapped by
|
||||
[`pkg/errors`](https://github.com/pkg/errors).
|
||||
[#144](https://github.com/bugsnag/bugsnag-go/pull/144)
|
||||
* Support modifying an individual event using a callback function argument.
|
||||
|
||||
```go
|
||||
bugsnag.Notify(err, func(event *bugsnag.Event) {
|
||||
event.ErrorClass = "Unexpected Termination"
|
||||
event.MetaData.Update(loadJobData())
|
||||
|
||||
if event.Stacktrace[0].File = "mylogger.go" {
|
||||
event.Stacktrace = event.Stacktrace[1:]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The stack trace of an event is now mutable so frames can be removed or
|
||||
modified.
|
||||
[#146](https://github.com/bugsnag/bugsnag-go/pull/146)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Send web framework name with severity reason if set. Previously this value was
|
||||
ignored, obscuring the severity reason for failed web requests captured by
|
||||
bugsnag middleware.
|
||||
[#143](https://github.com/bugsnag/bugsnag-go/pull/143)
|
||||
|
||||
## 1.5.4 (2020-10-28)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Account for inlined frames when unwinding stack traces by using
|
||||
`runtime.CallersFrames`.
|
||||
[#114](https://github.com/bugsnag/bugsnag-go/pull/114)
|
||||
[#140](https://github.com/bugsnag/bugsnag-go/pull/140)
|
||||
|
||||
## 1.5.3 (2019-07-11)
|
||||
|
||||
This release adds runtime version data to the report and session payloads, which will show up under the Device tab in the Bugsnag dashboard.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Ignore Gin unit tests when running against the latest version of Gin on Go versions below 1.10 as Gin has dropped support for these versions.
|
||||
[#121](https://github.com/bugsnag/bugsnag-go/pull/121)
|
||||
* Introduce runtime version data to the report and session payloads. Additionally adds the OS name to reports.
|
||||
[#122](https://github.com/bugsnag/bugsnag-go/pull/122)
|
||||
|
||||
## 1.5.2 (2019-05-20)
|
||||
|
||||
This release adds `"access_token"` to the default list of keys to filter and introduces filtering of URL query parameters under the request tab.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Adds filtering of URL parameters in the request tab of an event. Additionally adds `access_token` to the `ParamsFilters` by default.
|
||||
[#117](https://github.com/bugsnag/bugsnag-go/pull/117)
|
||||
[Adam Renberg Tamm](https://github.com/tgwizard)
|
||||
* Ignore Gin unit tests when running against the latest version of Gin on Go 1.7 as Gin has dropped support for Go 1.6 and Go 1.7.
|
||||
[#118](https://github.com/bugsnag/bugsnag-go/pull/118)
|
||||
|
||||
## 1.5.1 (2019-04-15)
|
||||
|
||||
This release re-introduces prioritizing user specified error classes over the inferred error class.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixes a bug introduced in `v1.4.0` where `bugsnag.Notify(err, bugsnag.ErrorClass{Name: "MyCustomErrorClass"})` is not respected.
|
||||
[#115](https://github.com/bugsnag/bugsnag-go/pull/115)
|
||||
|
||||
## 1.5.0 (2019-03-26)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Testing improvements [#105](https://github.com/bugsnag/bugsnag-go/pull/105)
|
||||
* Only run full test suite on PRs targeting master
|
||||
* Test against the latest release of go (currently 1.12) rather than go's unstable master branch
|
||||
* App engine has not been supported for a while. This release removes the app engine-specific code and tests from the codebase [#109](https://github.com/bugsnag/bugsnag-go/pull/109).
|
||||
|
||||
## 1.4.1 (2019-03-18)
|
||||
|
||||
This release fixes a compilation error on Windows.
|
||||
Due to a missing implementation in the Go library, Windows users may have to send two interrupt signals to interrupt the application. Other signals are unaffected.
|
||||
|
||||
Additionally, ensure data sanitisation behaves the same for both request data and metadata.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Use the `os` package instead of `syscall` to re-send signals, as `syscall` varies per platform, which caused a compilation error.
|
||||
|
||||
* Make sure that all data sanitization using `Config.ParamsFilters` behaves the same.
|
||||
[#104](https://github.com/bugsnag/bugsnag-go/pull/104)
|
||||
[Adam Renberg Tamm](https://github.com/tgwizard)
|
||||
|
||||
## 1.4.0 (2018-11-19)
|
||||
|
||||
This release is a big non-breaking revamp of the notifier. Most importantly, this release introduces session tracking to Go applications.
|
||||
|
||||
As of this release we require that you use Go 1.7 or higher.
|
||||
|
||||
### Features
|
||||
|
||||
* Session tracking to be able to show a stability score in the dashboard. Automatic recording of sessions for net/http, gin, revel, negroni and martini. Automatic capturing of sessions can be disabled using the `AutoCaptureSessions` configuration parameter.
|
||||
* Automatic recording of HTTP request information such as HTTP method, headers, URL and query parameters.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Migrate report payload version from 3 to 4.
|
||||
* Improve test coverage and introduce maze runner tests. Simplify integration tests for Negroni, Gin and Martini.
|
||||
* Deprecate the use of the old `Endpoint` configuration parameter, and allow users of on-premise to configure both the notify endpoint and the sessions endpoint.
|
||||
* `bugsnag.Notify()` now accepts a `context.Context` object, generally from `*http.Request`'s `r.Context()`, which Bugsnag can extract session and request information from.
|
||||
* Improve and augment examples (`bugsnag_example_test.go`) for documentation.
|
||||
* Improve example applications (`examples/` directory) to get up and running faster.
|
||||
* Clarify and improve GoDocs.
|
||||
* Improved serialization performance and safety of the report payload.
|
||||
* Filter HTTP headers based on the `FiltersParams`.
|
||||
* Revel enhancements:
|
||||
* Ensure all non-code configuration options are configurable from config file.
|
||||
* Stop using deprecated logger.
|
||||
* Attempt to configure a what we can from the revel configuration options.
|
||||
* Make NotifyReleaseStages work consistently with other notifiers, both for sessions and for reports.
|
||||
* Also filter out 'authorization' and 'cookie' by default, to match other notifiers.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Address compile errors test failures that failed the build.
|
||||
* Don't crash when calling `bugsnag.Notify(nil)`
|
||||
* Other minor bug fixes that came to light after improving test coverage.
|
||||
|
||||
## 1.3.2 (2018-10-05)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Ensure error reports for fatal crashes gets sent
|
||||
[#77](https://github.com/bugsnag/bugsnag-go/pull/77)
|
||||
|
||||
## 1.3.1 (2018-03-14)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Add support for Revel v0.18
|
||||
[#63](https://github.com/bugsnag/bugsnag-go/pull/63)
|
||||
[Cameron Halter](https://github.com/EightB1ts)
|
||||
|
||||
## 1.3.0 (2017-10-02)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Track whether an error report was captured automatically
|
||||
* Add SourceRoot as a configuration option, defaulting to `$GOPATH`
|
||||
|
||||
## 1.2.2 (2017-08-25)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Point osext dependency at upstream, update with fixes
|
||||
|
||||
## 1.2.1 (2017-07-31)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Improve goroutine panic reporting by sending reports synchronously in the
|
||||
case that a goroutine is about to be cleaned up
|
||||
[#52](https://github.com/bugsnag/bugsnag-go/pull/52)
|
||||
|
||||
## 1.2.0 (2017-07-03)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Support custom stack frame implementations
|
||||
[alexanderwilling](https://github.com/alexanderwilling)
|
||||
[#43](https://github.com/bugsnag/bugsnag-go/issues/43)
|
||||
|
||||
* Support app.type in error reports
|
||||
[Jascha Ephraim](https://github.com/jaschaephraim)
|
||||
[#51](https://github.com/bugsnag/bugsnag-go/pull/51)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Mend nil pointer panic in metadata
|
||||
[Johan Sageryd](https://github.com/jsageryd)
|
||||
[#46](https://github.com/bugsnag/bugsnag-go/pull/46)
|
||||
|
||||
## 1.1.1 (2016-12-16)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Replace empty error class property in reports with "error"
|
||||
|
||||
## 1.1.0 (2016-11-07)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Add middleware for Gin
|
||||
[Mike Bull](https://github.com/bullmo)
|
||||
[#40](https://github.com/bugsnag/bugsnag-go/pull/40)
|
||||
|
||||
* Add middleware for Negroni
|
||||
[am-manideep](https://github.com/am-manideep)
|
||||
[#28](https://github.com/bugsnag/bugsnag-go/pull/28)
|
||||
|
||||
* Support stripping subpackage names
|
||||
[Facundo Ferrer](https://github.com/fjferrer)
|
||||
[#25](https://github.com/bugsnag/bugsnag-go/pull/25)
|
||||
|
||||
* Support using `ErrorWithCallers` to create a stacktrace for errors
|
||||
[Conrad Irwin](https://github.com/ConradIrwin)
|
||||
[#35](https://github.com/bugsnag/bugsnag-go/pull/35)
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Avoid swallowing errors which occur upon delivery
|
||||
|
||||
1.0.4
|
||||
-----
|
||||
|
||||
- Fix appengine integration broken by 1.0.3
|
||||
|
||||
1.0.3
|
||||
-----
|
||||
|
||||
- Allow any Logger with a Printf method.
|
||||
|
||||
1.0.2
|
||||
-----
|
||||
|
||||
- Use bugsnag copies of dependencies to avoid potential link rot
|
||||
|
||||
1.0.1
|
||||
-----
|
||||
|
||||
- gofmt/golint/govet docs improvements.
|
||||
|
||||
1.0.0
|
||||
-----
|
90
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/CONTRIBUTING.md
generated
vendored
90
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/CONTRIBUTING.md
generated
vendored
@ -1,90 +0,0 @@
|
||||
Contributing
|
||||
============
|
||||
|
||||
- [Fork](https://help.github.com/articles/fork-a-repo) the [notifier on github](https://github.com/bugsnag/bugsnag-go)
|
||||
- Build and test your changes
|
||||
- Commit and push until you are happy with your contribution
|
||||
- [Make a pull request](https://help.github.com/articles/using-pull-requests)
|
||||
- Thanks!
|
||||
|
||||
|
||||
Installing the go development environment
|
||||
-------------------------------------
|
||||
|
||||
1. Install homebrew
|
||||
|
||||
```
|
||||
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
|
||||
```
|
||||
|
||||
1. Install go
|
||||
|
||||
```
|
||||
brew install go --cross-compile-all
|
||||
```
|
||||
|
||||
1. Configure `$GOPATH` in `~/.bashrc`
|
||||
|
||||
```
|
||||
export GOPATH="$HOME/go"
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
```
|
||||
|
||||
Downloading the code
|
||||
--------------------
|
||||
|
||||
You can download the code and its dependencies using
|
||||
|
||||
```
|
||||
go get -t github.com/bugsnag/bugsnag-go/v2
|
||||
```
|
||||
|
||||
It will be put into "$GOPATH/src/github.com/bugsnag/bugsnag-go"
|
||||
|
||||
Then install depend
|
||||
|
||||
|
||||
Running Tests
|
||||
-------------
|
||||
|
||||
You can run the tests with
|
||||
|
||||
```shell
|
||||
go test ./...
|
||||
```
|
||||
|
||||
Making PRs
|
||||
----------
|
||||
|
||||
All PRs should target the `next` branch as their base. This means that we can land them and stage them for a release without making multiple changes to `master` (which would cause multiple releases due to `go get`'s behaviour).
|
||||
|
||||
The exception to this rule is for an urgent bug fix when `next` is already ahead of `master`. See [hotfixes](#hotfixes) for what to do then.
|
||||
|
||||
Releasing a New Version
|
||||
-----------------------
|
||||
|
||||
If you are a project maintainer, you can build and release a new version of
|
||||
`bugsnag-go` as follows:
|
||||
|
||||
#### Planned releases
|
||||
|
||||
**Prerequisite**: All code changes should already have been reviewed and PR'd into the `next` branch before making a release.
|
||||
|
||||
1. Decide on a version number and date for this release
|
||||
1. Add an entry (or update the `TBD` entry if it exists) for this release in `CHANGELOG.md` so that it includes the version number, release date and granular description of what changed
|
||||
1. Update the README if necessary
|
||||
1. Update the version number in `v2/bugsnag.go` and verify that tests pass.
|
||||
1. Commit these changes `git commit -am "Preparing release"`
|
||||
1. Create a PR from `next` -> `master` titled `Release vX.X.X`, adding a description to help the reviewer understand the scope of the release
|
||||
1. Await PR approval and CI pass
|
||||
1. Merge to master on GitHub, using the UI to set the merge commit message to be `vX.X.X`
|
||||
1. Create a release from current `master` on GitHub called `vX.X.X`. Copy and paste the markdown from this release's notes in `CHANGELOG.md` (this will create a git tag for you).
|
||||
1. Ensure setup guides for Go (and its frameworks) on docs.bugsnag.com are correct and up to date.
|
||||
1. Merge `master` into `next` (since we just did a merge commit the other way, this will be a fastforward update) and push it so that it is ready for future PRs.
|
||||
|
||||
|
||||
#### Hotfixes
|
||||
|
||||
If a `next` branch already exists and is ahead of `master` but there is a bug fix which needs to go out urgently, check out the latest `master` and create a new hotfix branch `git checkout -b hotfix`. You can then proceed to follow the above steps, substituting `next` for `hotfix`.
|
||||
|
||||
Once released, ensure `master` is merged into `next` so that the changes made on `hotfix` are included.
|
3
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/Gemfile
generated
vendored
3
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/Gemfile
generated
vendored
@ -1,3 +0,0 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', branch: 'v1'
|
55
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/Gemfile.lock
generated
vendored
55
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/Gemfile.lock
generated
vendored
@ -1,55 +0,0 @@
|
||||
GIT
|
||||
remote: https://github.com/bugsnag/maze-runner
|
||||
revision: 7377529a77eb7585afc66cd2080fcdc4eea3306a
|
||||
branch: v1
|
||||
specs:
|
||||
bugsnag-maze-runner (1.1.0)
|
||||
cucumber (~> 3.1.0)
|
||||
cucumber-expressions (= 5.0.15)
|
||||
minitest (~> 5.0)
|
||||
os (~> 1.0.0)
|
||||
rack (~> 2.0.0)
|
||||
rake (~> 12.3.3)
|
||||
test-unit (~> 3.2.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
backports (3.21.0)
|
||||
builder (3.2.4)
|
||||
cucumber (3.1.0)
|
||||
builder (>= 2.1.2)
|
||||
cucumber-core (~> 3.1.0)
|
||||
cucumber-expressions (~> 5.0.4)
|
||||
cucumber-wire (~> 0.0.1)
|
||||
diff-lcs (~> 1.3)
|
||||
gherkin (~> 5.0)
|
||||
multi_json (>= 1.7.5, < 2.0)
|
||||
multi_test (>= 0.1.2)
|
||||
cucumber-core (3.1.0)
|
||||
backports (>= 3.8.0)
|
||||
cucumber-tag_expressions (~> 1.1.0)
|
||||
gherkin (>= 5.0.0)
|
||||
cucumber-expressions (5.0.15)
|
||||
cucumber-tag_expressions (1.1.1)
|
||||
cucumber-wire (0.0.1)
|
||||
diff-lcs (1.4.4)
|
||||
gherkin (5.1.0)
|
||||
minitest (5.14.4)
|
||||
multi_json (1.15.0)
|
||||
multi_test (0.1.2)
|
||||
os (1.0.1)
|
||||
power_assert (2.0.0)
|
||||
rack (2.0.9)
|
||||
rake (12.3.3)
|
||||
test-unit (3.2.9)
|
||||
power_assert
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
bugsnag-maze-runner!
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
20
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/LICENSE.txt
generated
vendored
20
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/LICENSE.txt
generated
vendored
@ -1,20 +0,0 @@
|
||||
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.
|
63
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/Makefile
generated
vendored
63
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/Makefile
generated
vendored
@ -1,63 +0,0 @@
|
||||
TEST?=./...
|
||||
|
||||
export GO111MODULE=auto
|
||||
|
||||
default: alldeps test
|
||||
|
||||
deps:
|
||||
go get -v -d ./...
|
||||
|
||||
alldeps:
|
||||
go get -v -d -t ./...
|
||||
|
||||
updatedeps:
|
||||
go get -v -d -u ./...
|
||||
|
||||
test: alldeps
|
||||
@# skipping Gin if the Go version is lower than 1.9, as the latest version of Gin has dropped support for these versions.
|
||||
@if [ "$(GO_VERSION)" = "1.7" ] || [ "$(GO_VERSION)" = "1.8" ] || [ "$(GO_VERSION)" = "1.9" ]; then \
|
||||
go test . ./errors ./martini ./negroni ./sessions ./headers; \
|
||||
else \
|
||||
go test . ./errors ./gin ./martini ./negroni ./sessions ./headers; \
|
||||
fi
|
||||
@go vet 2>/dev/null ; if [ $$? -eq 3 ]; then \
|
||||
go get golang.org/x/tools/cmd/vet; \
|
||||
fi
|
||||
@go vet $(TEST) ; if [ $$? -eq 1 ]; then \
|
||||
echo "go-vet: Issues running go vet ./..."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
maze:
|
||||
bundle install
|
||||
bundle exec bugsnag-maze-runner
|
||||
|
||||
ci: alldeps test
|
||||
|
||||
bench:
|
||||
go test --bench=.*
|
||||
|
||||
testsetup:
|
||||
gem update --system
|
||||
gem install bundler
|
||||
bundle install
|
||||
|
||||
testplain: testsetup
|
||||
bundle exec bugsnag-maze-runner -c features/plain_features
|
||||
|
||||
testnethttp: testsetup
|
||||
bundle exec bugsnag-maze-runner -c features/net_http_features
|
||||
|
||||
testgin: testsetup
|
||||
bundle exec bugsnag-maze-runner -c features/gin_features
|
||||
|
||||
testmartini: testsetup
|
||||
bundle exec bugsnag-maze-runner -c features/martini_features
|
||||
|
||||
testnegroni: testsetup
|
||||
bundle exec bugsnag-maze-runner -c features/negroni_features
|
||||
|
||||
testrevel: testsetup
|
||||
bundle exec bugsnag-maze-runner -c features/revel_features
|
||||
|
||||
.PHONY: bin checkversion ci default deps generate releasebin test testacc testrace updatedeps testsetup testplain testnethttp testgin testmartini testrevel
|
46
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/README.md
generated
vendored
46
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/README.md
generated
vendored
@ -1,46 +0,0 @@
|
||||
# Bugsnag error reporter for Go
|
||||
[](https://github.com/bugsnag/bugsnag-go/releases)
|
||||
[](https://travis-ci.com/bugsnag/bugsnag-go?branch=master)
|
||||
[](http://godoc.org/github.com/bugsnag/bugsnag-go)
|
||||
|
||||
Automatically detect crashes and report errors in your Go apps. Get alerts about errors and panics in real-time, including detailed error reports with diagnostic information. Understand and resolve issues as fast as possible.
|
||||
|
||||
Learn more about Bugsnag's [Go error monitoring and error reporting](https://www.bugsnag.com/platforms/go-lang-error-reporting/) solution.
|
||||
|
||||
## Features
|
||||
|
||||
* Automatically report unhandled errors and panics
|
||||
* Report handled errors
|
||||
* Attach user information to determine how many people are affected by a crash
|
||||
* Send customized diagnostic data
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. [Create a Bugsnag account](https://bugsnag.com)
|
||||
2. Complete the instructions in the integration guide for your framework:
|
||||
* [Martini](https://docs.bugsnag.com/platforms/go/martini)
|
||||
* [Negroni](https://docs.bugsnag.com/platforms/go/negroni)
|
||||
* [net/http](https://docs.bugsnag.com/platforms/go/net-http)
|
||||
* [Revel](https://docs.bugsnag.com/platforms/go/revel)
|
||||
* [Other Go apps](https://docs.bugsnag.com/platforms/go/other)
|
||||
3. Relax!
|
||||
|
||||
## Support
|
||||
|
||||
* Read the configuration reference:
|
||||
* [Martini](https://docs.bugsnag.com/platforms/go/martini/configuration-options/)
|
||||
* [Negroni](https://docs.bugsnag.com/platforms/go/negroni/configuration-options/)
|
||||
* [net/http](https://docs.bugsnag.com/platforms/go/net-http/configuration-options/)
|
||||
* [Revel](https://docs.bugsnag.com/platforms/go/revel/configuration-options/)
|
||||
* [Other Go apps](https://docs.bugsnag.com/platforms/go/other/configuration-options/)
|
||||
* [Search open and closed issues](https://github.com/bugsnag/bugsnag-go/issues?utf8=✓&q=is%3Aissue) for similar problems
|
||||
* [Report a bug or request a feature](https://github.com/bugsnag/bugsnag-go/issues/new)
|
||||
|
||||
## Contributing
|
||||
|
||||
All contributors are welcome! For information on how to build, test and release `bugsnag-go`, see our [contributing guide](CONTRIBUTING.md).
|
||||
|
||||
|
||||
## License
|
||||
|
||||
The Bugsnag exception reporter for Go is free software released under the MIT License. See [LICENSE.txt](LICENSE.txt) for details.
|
57
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/UPGRADING.md
generated
vendored
57
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/UPGRADING.md
generated
vendored
@ -1,57 +0,0 @@
|
||||
# Upgrading guide
|
||||
|
||||
## v1 to v2
|
||||
|
||||
The v2 release adds support for Go modules, removes web framework
|
||||
integrations from the main repository, and supports library configuration
|
||||
through environment variables. The following breaking changes occurred as a part
|
||||
of this release:
|
||||
|
||||
### Importing the package
|
||||
|
||||
```diff+go
|
||||
- import "github.com/bugsnag/bugsnag-go"
|
||||
+ import "github.com/bugsnag/bugsnag-go/v2"
|
||||
```
|
||||
|
||||
### Removed `Configuration.Endpoint`
|
||||
|
||||
The `Endpoint` configuration option was deprecated as a part of the v1.4.0
|
||||
release in November 2018. It was replaced with `Endpoints`, which includes
|
||||
options for configuring both event and session delivery.
|
||||
|
||||
```diff+go
|
||||
- config.Endpoint = "https://notify.myserver.example.com"
|
||||
+ config.Endpoints = {
|
||||
+ Notify: "https://notify.myserver.example.com",
|
||||
+ Sessions: "https://sessions.myserver.example.com"
|
||||
+ }
|
||||
```
|
||||
|
||||
### Moved web framework integrations into separate repositories
|
||||
|
||||
Integrations with Negroni, Revel, and Gin now live in separate repositories, to
|
||||
prevent implicit dependencies on every framework and to improve the ease of
|
||||
updating each integration independently.
|
||||
|
||||
```diff+go
|
||||
- import "github.com/bugsnag/bugsnag-go/negroni"
|
||||
+ import "github.com/bugsnag/bugsnag-go-negroni"
|
||||
```
|
||||
|
||||
```diff+go
|
||||
- import "github.com/bugsnag/bugsnag-go/revel"
|
||||
+ import "github.com/bugsnag/bugsnag-go-revel"
|
||||
```
|
||||
|
||||
```diff+go
|
||||
- import "github.com/bugsnag/bugsnag-go/gin"
|
||||
+ import "github.com/bugsnag/bugsnag-go-gin"
|
||||
```
|
||||
|
||||
### Renamed constants for platform consistency
|
||||
|
||||
```diff+go
|
||||
- bugsnag.VERSION
|
||||
+ bugsnag.Version
|
||||
```
|
275
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/bugsnag.go
generated
vendored
275
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/bugsnag.go
generated
vendored
@ -1,275 +0,0 @@
|
||||
package bugsnag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bugsnag/bugsnag-go/device"
|
||||
"github.com/bugsnag/bugsnag-go/errors"
|
||||
"github.com/bugsnag/bugsnag-go/sessions"
|
||||
|
||||
// Fixes a bug with SHA-384 intermediate certs on some platforms.
|
||||
// - https://github.com/bugsnag/bugsnag-go/issues/9
|
||||
_ "crypto/sha512"
|
||||
)
|
||||
|
||||
// VERSION defines the version of this Bugsnag notifier
|
||||
const VERSION = "1.9.1"
|
||||
|
||||
var panicHandlerOnce sync.Once
|
||||
var sessionTrackerOnce sync.Once
|
||||
var middleware middlewareStack
|
||||
|
||||
// Config is the configuration for the default bugsnag notifier.
|
||||
var Config Configuration
|
||||
var sessionTrackingConfig sessions.SessionTrackingConfiguration
|
||||
|
||||
// DefaultSessionPublishInterval defines how often sessions should be sent to
|
||||
// Bugsnag.
|
||||
// Deprecated: Exposed for developer sanity in testing. Modify at own risk.
|
||||
var DefaultSessionPublishInterval = 60 * time.Second
|
||||
var defaultNotifier = Notifier{&Config, nil}
|
||||
var sessionTracker sessions.SessionTracker
|
||||
|
||||
// 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)
|
||||
updateSessionConfig()
|
||||
// Only do once in case the user overrides the default panichandler, and
|
||||
// configures multiple times.
|
||||
panicHandlerOnce.Do(Config.PanicHandler)
|
||||
}
|
||||
|
||||
// StartSession creates new context from the context.Context instance with
|
||||
// Bugsnag session data attached. Will start the session tracker if not already
|
||||
// started
|
||||
func StartSession(ctx context.Context) context.Context {
|
||||
sessionTrackerOnce.Do(startSessionTracking)
|
||||
return sessionTracker.StartSession(ctx)
|
||||
}
|
||||
|
||||
// Notify sends an error.Error to Bugsnag along with the current stack trace.
|
||||
// If at all possible, it is recommended to pass in a context.Context, e.g.
|
||||
// from a http.Request or bugsnag.StartSession() as Bugsnag will be able to
|
||||
// extract additional information in some cases. 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. For a detailed list of the information
|
||||
// that can be extracted, see
|
||||
// https://docs.bugsnag.com/platforms/go/reporting-handled-errors/
|
||||
func Notify(err error, rawData ...interface{}) error {
|
||||
if e := checkForEmptyError(err); e != nil {
|
||||
return e
|
||||
}
|
||||
// Stripping one stackframe to not include this function in the stacktrace
|
||||
// for a manual notification.
|
||||
skipFrames := 1
|
||||
return defaultNotifier.Notify(errors.New(err, skipFrames), 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.
|
||||
// Although it's not strictly enforced, it's highly recommended to pass a
|
||||
// context.Context object that has at one-point been returned from
|
||||
// bugsnag.StartSession. Doing so ensures your stability score remains accurate,
|
||||
// and future versions of Bugsnag may extract more useful information from this
|
||||
// context.
|
||||
// The rawData is used to send extra information along with any
|
||||
// panics that are handled this way.
|
||||
// Usage:
|
||||
// go func() {
|
||||
// ctx := bugsnag.StartSession(context.Background())
|
||||
// defer bugsnag.AutoNotify(ctx)
|
||||
// // (possibly crashy code)
|
||||
// }()
|
||||
// See also: bugsnag.Recover()
|
||||
func AutoNotify(rawData ...interface{}) {
|
||||
if err := recover(); err != nil {
|
||||
severity := defaultNotifier.getDefaultSeverity(rawData, SeverityError)
|
||||
state := HandledState{SeverityReasonHandledPanic, severity, true, ""}
|
||||
rawData = append([]interface{}{state}, rawData...)
|
||||
// We strip the following stackframes as they don't add much info
|
||||
// - runtime/$arch - e.g. runtime/asm_amd64.s#call32
|
||||
// - runtime/panic.go#gopanic
|
||||
// Panics have their own stacktrace, so no stripping of the current stack
|
||||
skipFrames := 2
|
||||
defaultNotifier.NotifySync(errors.New(err, skipFrames), true, rawData...)
|
||||
sessionTracker.FlushSessions()
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Recover logs a panic on a goroutine and then recovers.
|
||||
// Although it's not strictly enforced, it's highly recommended to pass a
|
||||
// context.Context object that has at one-point been returned from
|
||||
// bugsnag.StartSession. Doing so ensures your stability score remains accurate,
|
||||
// and future versions of Bugsnag may extract more useful information from this
|
||||
// context.
|
||||
// The rawData is used to send extra information along with
|
||||
// any panics that are handled this way
|
||||
// Usage:
|
||||
// go func() {
|
||||
// ctx := bugsnag.StartSession(context.Background())
|
||||
// defer bugsnag.Recover(ctx)
|
||||
// // (possibly crashy code)
|
||||
// }()
|
||||
// If you wish that any panics caught by the call to Recover shall affect your
|
||||
// stability score (it does not by default):
|
||||
// go func() {
|
||||
// ctx := bugsnag.StartSession(context.Background())
|
||||
// defer bugsnag.Recover(ctx, bugsnag.HandledState{Unhandled: true})
|
||||
// // (possibly crashy code)
|
||||
// }()
|
||||
// See also: bugsnag.AutoNotify()
|
||||
func Recover(rawData ...interface{}) {
|
||||
if err := recover(); err != nil {
|
||||
severity := defaultNotifier.getDefaultSeverity(rawData, SeverityWarning)
|
||||
state := HandledState{SeverityReasonHandledPanic, severity, false, ""}
|
||||
rawData = append([]interface{}{state}, rawData...)
|
||||
// We strip the following stackframes as they don't add much info
|
||||
// - runtime/$arch - e.g. runtime/asm_amd64.s#call32
|
||||
// - runtime/panic.go#gopanic
|
||||
// Panics have their own stacktrace, so no stripping of the current stack
|
||||
skipFrames := 2
|
||||
defaultNotifier.Notify(errors.New(err, skipFrames), 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) {
|
||||
request := r
|
||||
|
||||
// Record a session if auto notify session is enabled
|
||||
ctx := r.Context()
|
||||
if Config.IsAutoCaptureSessions() {
|
||||
ctx = StartSession(ctx)
|
||||
}
|
||||
ctx = AttachRequestData(ctx, request)
|
||||
request = r.WithContext(ctx)
|
||||
defer notifier.AutoNotify(ctx, request)
|
||||
h.ServeHTTP(w, request)
|
||||
})
|
||||
}
|
||||
|
||||
// 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) {
|
||||
request := r
|
||||
// Record a session if auto notify session is enabled
|
||||
ctx := request.Context()
|
||||
if notifier.Config.IsAutoCaptureSessions() {
|
||||
ctx = StartSession(ctx)
|
||||
}
|
||||
ctx = AttachRequestData(ctx, request)
|
||||
request = request.WithContext(ctx)
|
||||
defer notifier.AutoNotify(ctx)
|
||||
h(w, request)
|
||||
}
|
||||
}
|
||||
|
||||
// checkForEmptyError checks if the given error (to be reported to Bugsnag) is
|
||||
// nil. If it is, then log an error message and return another error wrapping
|
||||
// this error message.
|
||||
func checkForEmptyError(err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
msg := "attempted to notify Bugsnag without supplying an error. Bugsnag not notified"
|
||||
Config.Logger.Printf("ERROR: " + msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Set up builtin middlewarez
|
||||
OnBeforeNotify(httpRequestMiddleware)
|
||||
|
||||
// Default configuration
|
||||
sourceRoot := ""
|
||||
if gopath := os.Getenv("GOPATH"); len(gopath) > 0 {
|
||||
sourceRoot = filepath.Join(gopath, "src") + "/"
|
||||
} else {
|
||||
sourceRoot = filepath.Join(runtime.GOROOT(), "src") + "/"
|
||||
}
|
||||
Config.update(&Configuration{
|
||||
APIKey: "",
|
||||
Endpoints: Endpoints{
|
||||
Notify: "https://notify.bugsnag.com",
|
||||
Sessions: "https://sessions.bugsnag.com",
|
||||
},
|
||||
Hostname: device.GetHostname(),
|
||||
AppType: "",
|
||||
AppVersion: "",
|
||||
AutoCaptureSessions: true,
|
||||
ReleaseStage: "",
|
||||
ParamsFilters: []string{"password", "secret", "authorization", "cookie", "access_token"},
|
||||
SourceRoot: sourceRoot,
|
||||
ProjectPackages: []string{"main*"},
|
||||
NotifyReleaseStages: nil,
|
||||
Logger: log.New(os.Stdout, log.Prefix(), log.Flags()),
|
||||
PanicHandler: defaultPanicHandler,
|
||||
Transport: http.DefaultTransport,
|
||||
|
||||
flushSessionsOnRepanic: true,
|
||||
})
|
||||
updateSessionConfig()
|
||||
}
|
||||
|
||||
func startSessionTracking() {
|
||||
if sessionTracker == nil {
|
||||
updateSessionConfig()
|
||||
sessionTracker = sessions.NewSessionTracker(&sessionTrackingConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSessionConfig() {
|
||||
sessionTrackingConfig.Update(&sessions.SessionTrackingConfiguration{
|
||||
APIKey: Config.APIKey,
|
||||
AutoCaptureSessions: Config.AutoCaptureSessions,
|
||||
Endpoint: Config.Endpoints.Sessions,
|
||||
Version: VERSION,
|
||||
PublishInterval: DefaultSessionPublishInterval,
|
||||
Transport: Config.Transport,
|
||||
ReleaseStage: Config.ReleaseStage,
|
||||
Hostname: Config.Hostname,
|
||||
AppType: Config.AppType,
|
||||
AppVersion: Config.AppVersion,
|
||||
NotifyReleaseStages: Config.NotifyReleaseStages,
|
||||
Logger: Config.Logger,
|
||||
})
|
||||
}
|
264
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/configuration.go
generated
vendored
264
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/configuration.go
generated
vendored
@ -1,264 +0,0 @@
|
||||
package bugsnag
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Endpoints hold the HTTP endpoints of the notifier.
|
||||
type Endpoints struct {
|
||||
Sessions string
|
||||
Notify string
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Deprecated: Use Endpoints (with an 's') instead.
|
||||
// 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
|
||||
// Endpoints define the HTTP endpoints that the notifier should notify
|
||||
// about crashes and sessions. These default to notify.bugsnag.com for
|
||||
// error reports and sessions.bugsnag.com for sessions.
|
||||
// If you are using bugsnag on-premise you will have to set these to your
|
||||
// Event Server and Session Server endpoints. If the notify endpoint is set
|
||||
// but the sessions endpoint is not, session tracking will be disabled
|
||||
// automatically to avoid leaking session information outside of your
|
||||
// server configuration, and a warning will be logged.
|
||||
Endpoints Endpoints
|
||||
|
||||
// The current release stage. This defaults to "production" and is used to
|
||||
// filter errors in the Bugsnag dashboard.
|
||||
ReleaseStage string
|
||||
// A specialized type of the application, such as the worker queue or web
|
||||
// framework used, like "rails", "mailman", or "celery"
|
||||
AppType 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
|
||||
|
||||
// AutoCaptureSessions can be set to false to disable automatic session
|
||||
// tracking. If you want control over what is deemed a session, you can
|
||||
// switch off automatic session tracking with this configuration, and call
|
||||
// bugsnag.StartSession() when appropriate for your application. See the
|
||||
// official docs for instructions and examples of associating handled
|
||||
// errors with sessions and ensuring error rate accuracy on the Bugsnag
|
||||
// dashboard. This will default to true, but is stored as an interface to enable
|
||||
// us to detect when this option has not been set.
|
||||
AutoCaptureSessions interface{}
|
||||
|
||||
// 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
|
||||
|
||||
// The SourceRoot is the directory where the application is built, and the
|
||||
// assumed prefix of lines on the stacktrace originating in the parent
|
||||
// application. When set, the prefix is trimmed from callstack file names
|
||||
// before ProjectPackages for better readability and to better group errors
|
||||
// on the Bugsnag dashboard. The default value is $GOPATH/src or $GOROOT/src
|
||||
// if $GOPATH is unset. At runtime, $GOROOT is the root used during the Go
|
||||
// build.
|
||||
SourceRoot string
|
||||
|
||||
// Any meta-data that matches these filters will be marked as [FILTERED]
|
||||
// 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 interface {
|
||||
Printf(format string, v ...interface{}) // limited to the functions used
|
||||
}
|
||||
// The http Transport to use, defaults to the default http Transport. This
|
||||
// can be configured if you are in an environment
|
||||
// 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
|
||||
// Whether the notifier should send all sessions recorded so far to Bugsnag
|
||||
// when repanicking to ensure that no session information is lost in a
|
||||
// fatal crash.
|
||||
flushSessionsOnRepanic 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.Hostname != "" {
|
||||
config.Hostname = other.Hostname
|
||||
}
|
||||
if other.AppType != "" {
|
||||
config.AppType = other.AppType
|
||||
}
|
||||
if other.AppVersion != "" {
|
||||
config.AppVersion = other.AppVersion
|
||||
}
|
||||
if other.SourceRoot != "" {
|
||||
config.SourceRoot = other.SourceRoot
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
if other.AutoCaptureSessions != nil {
|
||||
config.AutoCaptureSessions = other.AutoCaptureSessions
|
||||
}
|
||||
config.updateEndpoints(other.Endpoint, &other.Endpoints)
|
||||
return config
|
||||
}
|
||||
|
||||
// IsAutoCaptureSessions identifies whether or not the notifier should
|
||||
// automatically capture sessions as requests come in. It's a convenience
|
||||
// wrapper that allows automatic session capturing to be enabled by default.
|
||||
func (config *Configuration) IsAutoCaptureSessions() bool {
|
||||
if config.AutoCaptureSessions == nil {
|
||||
return true // enabled by default
|
||||
}
|
||||
if val, ok := config.AutoCaptureSessions.(bool); ok {
|
||||
return val
|
||||
}
|
||||
// It has been configured to *something* (although not a valid value)
|
||||
// assume the user wanted to disable this option.
|
||||
return false
|
||||
}
|
||||
|
||||
func (config *Configuration) updateEndpoints(endpoint string, endpoints *Endpoints) {
|
||||
|
||||
if endpoint != "" {
|
||||
config.Logger.Printf("WARNING: the 'Endpoint' Bugsnag configuration parameter is deprecated in favor of 'Endpoints'")
|
||||
config.Endpoints.Notify = endpoint
|
||||
config.Endpoints.Sessions = ""
|
||||
}
|
||||
if endpoints.Notify != "" {
|
||||
config.Endpoints.Notify = endpoints.Notify
|
||||
if endpoints.Sessions == "" {
|
||||
config.Logger.Printf("WARNING: Bugsnag notify endpoint configured without also configuring the sessions endpoint. No sessions will be recorded")
|
||||
config.Endpoints.Sessions = ""
|
||||
}
|
||||
}
|
||||
if endpoints.Sessions != "" {
|
||||
if endpoints.Notify == "" {
|
||||
panic("FATAL: Bugsnag sessions endpoint configured without also changing the notify endpoint. Bugsnag cannot identify where to report errors")
|
||||
}
|
||||
config.Endpoints.Sessions = endpoints.Sessions
|
||||
}
|
||||
}
|
||||
|
||||
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 d, f := filepath.Split(p); f == "**" {
|
||||
if strings.HasPrefix(pkg, d) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if match, _ := filepath.Match(p, pkg); match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (config *Configuration) stripProjectPackages(file string) string {
|
||||
trimmedFile := file
|
||||
if strings.HasPrefix(trimmedFile, config.SourceRoot) {
|
||||
trimmedFile = strings.TrimPrefix(trimmedFile, config.SourceRoot)
|
||||
}
|
||||
for _, p := range config.ProjectPackages {
|
||||
if len(p) > 2 && p[len(p)-2] == '/' && p[len(p)-1] == '*' {
|
||||
p = p[:len(p)-1]
|
||||
} else if p[len(p)-1] == '*' && p[len(p)-2] == '*' {
|
||||
p = p[:len(p)-2]
|
||||
} else {
|
||||
p = p + "/"
|
||||
}
|
||||
if strings.HasPrefix(trimmedFile, p) {
|
||||
return strings.TrimPrefix(trimmedFile, p)
|
||||
}
|
||||
}
|
||||
|
||||
return trimmedFile
|
||||
}
|
||||
|
||||
func (config *Configuration) logf(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
|
||||
}
|
||||
if config.ReleaseStage == "" {
|
||||
return true
|
||||
}
|
||||
for _, r := range config.NotifyReleaseStages {
|
||||
if r == config.ReleaseStage {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
15
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/device/hostname.go
generated
vendored
15
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/device/hostname.go
generated
vendored
@ -1,15 +0,0 @@
|
||||
package device
|
||||
|
||||
import "os"
|
||||
|
||||
var hostname string
|
||||
|
||||
// GetHostname returns the hostname of the current device. Caches the hostname
|
||||
// between calls to ensure this is performant. Returns a blank string in case
|
||||
// that the hostname cannot be identified.
|
||||
func GetHostname() string {
|
||||
if hostname == "" {
|
||||
hostname, _ = os.Hostname()
|
||||
}
|
||||
return hostname
|
||||
}
|
50
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/device/runtimeversions.go
generated
vendored
50
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/device/runtimeversions.go
generated
vendored
@ -1,50 +0,0 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Cached runtime versions that can be updated globally by framework
|
||||
// integrations through AddVersion.
|
||||
var versions *RuntimeVersions
|
||||
|
||||
// RuntimeVersions define the various versions of Go and any framework that may
|
||||
// be in use.
|
||||
// As a user of the notifier you're unlikely to need to modify this struct.
|
||||
// As such, the authors reserve the right to introduce breaking changes to the
|
||||
// properties in this struct. In particular the framework versions are liable
|
||||
// to change in new versions of the notifier in minor/patch versions.
|
||||
type RuntimeVersions struct {
|
||||
Go string `json:"go"`
|
||||
|
||||
Gin string `json:"gin,omitempty"`
|
||||
Martini string `json:"martini,omitempty"`
|
||||
Negroni string `json:"negroni,omitempty"`
|
||||
Revel string `json:"revel,omitempty"`
|
||||
}
|
||||
|
||||
// GetRuntimeVersions retrieves the recorded runtime versions in a goroutine-safe manner.
|
||||
func GetRuntimeVersions() *RuntimeVersions {
|
||||
if versions == nil {
|
||||
versions = &RuntimeVersions{Go: runtime.Version()}
|
||||
}
|
||||
return versions
|
||||
}
|
||||
|
||||
// AddVersion permits a framework to register its version, assuming it's one of
|
||||
// the officially supported frameworks.
|
||||
func AddVersion(framework, version string) {
|
||||
if versions == nil {
|
||||
versions = &RuntimeVersions{Go: runtime.Version()}
|
||||
}
|
||||
switch framework {
|
||||
case "Martini":
|
||||
versions.Martini = version
|
||||
case "Gin":
|
||||
versions.Gin = version
|
||||
case "Negroni":
|
||||
versions.Negroni = version
|
||||
case "Revel":
|
||||
versions.Revel = version
|
||||
}
|
||||
}
|
69
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/doc.go
generated
vendored
69
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/doc.go
generated
vendored
@ -1,69 +0,0 @@
|
||||
/*
|
||||
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, 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,
|
||||
AppType, 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
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/errors/README.md
generated
vendored
6
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/errors/README.md
generated
vendored
@ -1,6 +0,0 @@
|
||||
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.
|
195
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/errors/error.go
generated
vendored
195
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/errors/error.go
generated
vendored
@ -1,195 +0,0 @@
|
||||
// Package errors provides errors that have stack-traces.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"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
|
||||
Cause *Error
|
||||
stack []uintptr
|
||||
frames []StackFrame
|
||||
}
|
||||
|
||||
// ErrorWithCallers allows passing in error objects that
|
||||
// also have caller information attached.
|
||||
type ErrorWithCallers interface {
|
||||
Error() string
|
||||
Callers() []uintptr
|
||||
}
|
||||
|
||||
// ErrorWithStackFrames allows the stack to be rebuilt from the stack frames, thus
|
||||
// allowing to use the Error type when the program counter is not available.
|
||||
type ErrorWithStackFrames interface {
|
||||
Error() string
|
||||
StackFrames() []StackFrame
|
||||
}
|
||||
|
||||
type errorWithStack interface {
|
||||
StackTrace() errors.StackTrace
|
||||
Error() string
|
||||
}
|
||||
|
||||
type errorWithCause interface {
|
||||
Unwrap() error
|
||||
}
|
||||
|
||||
// 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 ErrorWithCallers:
|
||||
return &Error{
|
||||
Err: e,
|
||||
stack: e.Callers(),
|
||||
Cause: unwrapCause(e),
|
||||
}
|
||||
case errorWithStack:
|
||||
trace := e.StackTrace()
|
||||
stack := make([]uintptr, len(trace))
|
||||
for i, ptr := range trace {
|
||||
stack[i] = uintptr(ptr) - 1
|
||||
}
|
||||
return &Error{
|
||||
Err: e,
|
||||
Cause: unwrapCause(e),
|
||||
stack: stack,
|
||||
}
|
||||
case ErrorWithStackFrames:
|
||||
stack := make([]uintptr, len(e.StackFrames()))
|
||||
for i, frame := range e.StackFrames() {
|
||||
stack[i] = frame.ProgramCounter
|
||||
}
|
||||
return &Error{
|
||||
Err: e,
|
||||
Cause: unwrapCause(e),
|
||||
stack: stack,
|
||||
frames: e.StackFrames(),
|
||||
}
|
||||
case error:
|
||||
err = e
|
||||
default:
|
||||
err = fmt.Errorf("%v", e)
|
||||
}
|
||||
|
||||
stack := make([]uintptr, MaxStackDepth)
|
||||
length := runtime.Callers(2+skip, stack[:])
|
||||
return &Error{
|
||||
Err: err,
|
||||
Cause: unwrapCause(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()
|
||||
}
|
||||
|
||||
// Callers returns the raw stack frames as returned by runtime.Callers()
|
||||
func (err *Error) Callers() []uintptr {
|
||||
return err.stack[:]
|
||||
}
|
||||
|
||||
// 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 {
|
||||
callers := runtime.CallersFrames(err.stack)
|
||||
err.frames = make([]StackFrame, 0, len(err.stack))
|
||||
for frame, more := callers.Next(); more; frame, more = callers.Next() {
|
||||
if frame.Func == nil {
|
||||
// Ignore fully inlined functions
|
||||
continue
|
||||
}
|
||||
pkg, name := packageAndName(frame.Func)
|
||||
err.frames = append(err.frames, StackFrame{
|
||||
function: frame.Func,
|
||||
File: frame.File,
|
||||
LineNumber: frame.Line,
|
||||
Name: name,
|
||||
Package: pkg,
|
||||
ProgramCounter: frame.PC,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return err.frames
|
||||
}
|
||||
|
||||
// TypeName returns the type this error. e.g. *errors.stringError.
|
||||
func (err *Error) TypeName() string {
|
||||
if p, ok := err.Err.(uncaughtPanic); ok {
|
||||
return p.typeName
|
||||
}
|
||||
if name := reflect.TypeOf(err.Err).String(); len(name) > 0 {
|
||||
return name
|
||||
}
|
||||
return "error"
|
||||
}
|
||||
|
||||
func unwrapCause(err interface{}) *Error {
|
||||
if causer, ok := err.(errorWithCause); ok {
|
||||
cause := causer.Unwrap()
|
||||
if cause == nil {
|
||||
return nil
|
||||
} else if hasStack(cause) { // avoid generating a (duplicate) stack from the current frame
|
||||
return New(cause, 0)
|
||||
} else {
|
||||
return &Error{
|
||||
Err: cause,
|
||||
Cause: unwrapCause(cause),
|
||||
stack: []uintptr{},
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasStack(err error) bool {
|
||||
if _, ok := err.(errorWithStack); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := err.(ErrorWithStackFrames); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := err.(ErrorWithCallers); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
137
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/errors/parse_panic.go
generated
vendored
137
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/errors/parse_panic.go
generated
vendored
@ -1,137 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type uncaughtPanic struct {
|
||||
typeName string
|
||||
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")
|
||||
prefixes := []string{"panic:", "fatal error:"}
|
||||
|
||||
state := "start"
|
||||
|
||||
var message string
|
||||
var typeName string
|
||||
var stack []StackFrame
|
||||
|
||||
for i := 0; i < len(lines); i++ {
|
||||
line := lines[i]
|
||||
|
||||
if state == "start" {
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
message = strings.TrimSpace(strings.TrimPrefix(line, prefix))
|
||||
typeName = prefix[:len(prefix) - 1]
|
||||
state = "seek"
|
||||
break
|
||||
}
|
||||
}
|
||||
if state == "start" {
|
||||
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 == "" || strings.HasPrefix(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{typeName, 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
|
||||
}
|
95
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/errors/stackframe.go
generated
vendored
95
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/errors/stackframe.go
generated
vendored
@ -1,95 +0,0 @@
|
||||
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
|
||||
function *runtime.Func
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return frame.function
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
248
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/event.go
generated
vendored
248
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/event.go
generated
vendored
@ -1,248 +0,0 @@
|
||||
package bugsnag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"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"`
|
||||
}
|
||||
|
||||
// ErrorClass overrides the error class in Bugsnag.
|
||||
// This struct enables you to group errors as you like.
|
||||
type ErrorClass struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type SeverityReason string
|
||||
|
||||
const (
|
||||
SeverityReasonCallbackSpecified SeverityReason = "userCallbackSetSeverity"
|
||||
SeverityReasonHandledError = "handledError"
|
||||
SeverityReasonHandledPanic = "handledPanic"
|
||||
SeverityReasonUnhandledError = "unhandledError"
|
||||
SeverityReasonUnhandledMiddlewareError = "unhandledErrorMiddleware"
|
||||
SeverityReasonUnhandledPanic = "unhandledPanic"
|
||||
SeverityReasonUserSpecified = "userSpecifiedSeverity"
|
||||
)
|
||||
|
||||
type HandledState struct {
|
||||
SeverityReason SeverityReason
|
||||
OriginalSeverity severity
|
||||
Unhandled bool
|
||||
Framework string
|
||||
}
|
||||
|
||||
// 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
|
||||
// Ctx is the context of the session the event occurred in. This allows Bugsnag to associate the event with the session.
|
||||
Ctx context.Context
|
||||
// Request is the request information that populates the Request tab in the dashboard.
|
||||
Request *RequestJSON
|
||||
// The reason for the severity and original value
|
||||
handledState HandledState
|
||||
// True if the event was caused by an automatic event
|
||||
Unhandled bool
|
||||
}
|
||||
|
||||
func newEvent(rawData []interface{}, notifier *Notifier) (*Event, *Configuration) {
|
||||
config := notifier.Config
|
||||
event := &Event{
|
||||
RawData: append(notifier.RawData, rawData...),
|
||||
Severity: SeverityWarning,
|
||||
MetaData: make(MetaData),
|
||||
handledState: HandledState{
|
||||
SeverityReason: SeverityReasonHandledError,
|
||||
OriginalSeverity: SeverityWarning,
|
||||
Unhandled: false,
|
||||
Framework: "",
|
||||
},
|
||||
Unhandled: false,
|
||||
}
|
||||
|
||||
var err *errors.Error
|
||||
var callbacks []func(*Event)
|
||||
|
||||
for _, datum := range event.RawData {
|
||||
switch datum := datum.(type) {
|
||||
|
||||
case error, errors.Error:
|
||||
err = errors.New(datum.(error), 1)
|
||||
event.Error = err
|
||||
// Only assign automatically if not explicitly set through ErrorClass already
|
||||
if event.ErrorClass == "" {
|
||||
event.ErrorClass = err.TypeName()
|
||||
}
|
||||
event.Message = err.Error()
|
||||
event.Stacktrace = make([]StackFrame, len(err.StackFrames()))
|
||||
|
||||
case bool:
|
||||
config = config.merge(&Configuration{Synchronous: bool(datum)})
|
||||
|
||||
case severity:
|
||||
event.Severity = datum
|
||||
event.handledState.OriginalSeverity = datum
|
||||
event.handledState.SeverityReason = SeverityReasonUserSpecified
|
||||
|
||||
case Context:
|
||||
event.Context = datum.String
|
||||
|
||||
case context.Context:
|
||||
populateEventWithContext(datum, event)
|
||||
|
||||
case *http.Request:
|
||||
populateEventWithRequest(datum, event)
|
||||
|
||||
case Configuration:
|
||||
config = config.merge(&datum)
|
||||
|
||||
case MetaData:
|
||||
event.MetaData.Update(datum)
|
||||
|
||||
case User:
|
||||
event.User = &datum
|
||||
|
||||
case ErrorClass:
|
||||
event.ErrorClass = datum.Name
|
||||
|
||||
case HandledState:
|
||||
event.handledState = datum
|
||||
event.Severity = datum.OriginalSeverity
|
||||
event.Unhandled = datum.Unhandled
|
||||
case func(*Event):
|
||||
callbacks = append(callbacks, datum)
|
||||
}
|
||||
}
|
||||
|
||||
event.Stacktrace = generateStacktrace(err, config)
|
||||
|
||||
for _, callback := range callbacks {
|
||||
callback(event)
|
||||
if event.Severity != event.handledState.OriginalSeverity {
|
||||
event.handledState.SeverityReason = SeverityReasonCallbackSpecified
|
||||
}
|
||||
}
|
||||
|
||||
return event, config
|
||||
}
|
||||
|
||||
func generateStacktrace(err *errors.Error, config *Configuration) []StackFrame {
|
||||
stack := make([]StackFrame, len(err.StackFrames()))
|
||||
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)
|
||||
}
|
||||
|
||||
stack[i] = StackFrame{
|
||||
Method: frame.Name,
|
||||
File: file,
|
||||
LineNumber: frame.LineNumber,
|
||||
InProject: inProject,
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
func populateEventWithContext(ctx context.Context, event *Event) {
|
||||
event.Ctx = ctx
|
||||
reqJSON, req := extractRequestInfo(ctx)
|
||||
if event.Request == nil {
|
||||
event.Request = reqJSON
|
||||
}
|
||||
populateEventWithRequest(req, event)
|
||||
|
||||
}
|
||||
|
||||
func populateEventWithRequest(req *http.Request, event *Event) {
|
||||
if req == nil {
|
||||
return
|
||||
}
|
||||
|
||||
event.Request = extractRequestInfoFromReq(req)
|
||||
|
||||
if event.Context == "" {
|
||||
event.Context = req.URL.Path
|
||||
}
|
||||
|
||||
// Default user.id to IP so that the count of users affected works.
|
||||
if event.User == nil {
|
||||
ip := req.RemoteAddr
|
||||
if idx := strings.LastIndex(ip, ":"); idx != -1 {
|
||||
ip = ip[:idx]
|
||||
}
|
||||
event.User = &User{Id: ip}
|
||||
}
|
||||
}
|
14
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/headers/prefixed.go
generated
vendored
14
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/headers/prefixed.go
generated
vendored
@ -1,14 +0,0 @@
|
||||
package headers
|
||||
|
||||
import "time"
|
||||
|
||||
//PrefixedHeaders returns a map of Content-Type and the 'Bugsnag-' headers for
|
||||
//API key, payload version, and the time at which the request is being sent.
|
||||
func PrefixedHeaders(apiKey, payloadVersion string) map[string]string {
|
||||
return map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Bugsnag-Api-Key": apiKey,
|
||||
"Bugsnag-Payload-Version": payloadVersion,
|
||||
"Bugsnag-Sent-At": time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
}
|
43
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/json_tags.go
generated
vendored
43
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/json_tags.go
generated
vendored
@ -1,43 +0,0 @@
|
||||
// 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
|
||||
}
|
192
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/metadata.go
generated
vendored
192
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/metadata.go
generated
vendored
@ -1,192 +0,0 @@
|
||||
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)
|
||||
|
||||
if t == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
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:
|
||||
if v.IsNil() {
|
||||
return "<nil>"
|
||||
}
|
||||
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 = "[FILTERED]"
|
||||
}
|
||||
|
||||
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] = "[FILTERED]"
|
||||
} 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(key), strings.ToLower(filter)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
74
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/middleware.go
generated
vendored
74
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/middleware.go
generated
vendored
@ -1,74 +0,0 @@
|
||||
package bugsnag
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
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]
|
||||
|
||||
severity := event.Severity
|
||||
err := stack.runBeforeFilter(before, event, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if event.Severity != severity {
|
||||
event.handledState.SeverityReason = SeverityReasonCallbackSpecified
|
||||
}
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
func (stack *middlewareStack) runBeforeFilter(f beforeFunc, event *Event, config *Configuration) error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
config.logf("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 {
|
||||
event.MetaData.Update(MetaData{
|
||||
"request": {
|
||||
"params": request.URL.Query(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
151
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/notifier.go
generated
vendored
151
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/notifier.go
generated
vendored
@ -1,151 +0,0 @@
|
||||
package bugsnag
|
||||
|
||||
import (
|
||||
"github.com/bugsnag/bugsnag-go/errors"
|
||||
)
|
||||
|
||||
var publisher reportPublisher = new(defaultReportPublisher)
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// FlushSessionsOnRepanic takes a boolean that indicates whether sessions
|
||||
// should be flushed when AutoNotify repanics. In the case of a fatal panic the
|
||||
// sessions might not get sent to Bugsnag before the application shuts down.
|
||||
// Many frameworks will have their own error handler, and for these frameworks
|
||||
// there is no need to flush sessions as the application will survive the panic
|
||||
// and the sessions can be sent off later. The default value is true, so this
|
||||
// needs only be called if you wish to inform Bugsnag that there is an error
|
||||
// handler that will take care of panics that AutoNotify will re-raise.
|
||||
func (notifier *Notifier) FlushSessionsOnRepanic(shouldFlush bool) {
|
||||
notifier.Config.flushSessionsOnRepanic = shouldFlush
|
||||
}
|
||||
|
||||
// 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. Any bools in rawData overrides the
|
||||
// notifier.Config.Synchronous flag.
|
||||
func (notifier *Notifier) Notify(err error, rawData ...interface{}) (e error) {
|
||||
if e := checkForEmptyError(err); e != nil {
|
||||
return e
|
||||
}
|
||||
// Stripping one stackframe to not include this function in the stacktrace
|
||||
// for a manual notification.
|
||||
skipFrames := 1
|
||||
return notifier.NotifySync(errors.New(err, skipFrames), notifier.Config.Synchronous, rawData...)
|
||||
}
|
||||
|
||||
// NotifySync sends an error to Bugsnag. A boolean parameter specifies whether
|
||||
// to send the report in the current context (by default false, i.e.
|
||||
// asynchronous). Any other 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) NotifySync(err error, sync bool, rawData ...interface{}) error {
|
||||
if e := checkForEmptyError(err); e != nil {
|
||||
return e
|
||||
}
|
||||
// Stripping one stackframe to not include this function in the stacktrace
|
||||
// for a manual notification.
|
||||
skipFrames := 1
|
||||
event, config := newEvent(append(rawData, errors.New(err, skipFrames), sync), notifier)
|
||||
|
||||
// Never block, start throwing away errors if we have too many.
|
||||
e := middleware.Run(event, config, func() error {
|
||||
return publisher.publishReport(&payload{event, config})
|
||||
})
|
||||
|
||||
if e != nil {
|
||||
config.logf("bugsnag.Notify: %v", e)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// AutoNotify notifies Bugsnag of any panics, then repanics.
|
||||
// It sends along any rawData that gets passed in.
|
||||
// Usage:
|
||||
// go func() {
|
||||
// defer AutoNotify()
|
||||
// // (possibly crashy code)
|
||||
// }()
|
||||
func (notifier *Notifier) AutoNotify(rawData ...interface{}) {
|
||||
if err := recover(); err != nil {
|
||||
severity := notifier.getDefaultSeverity(rawData, SeverityError)
|
||||
state := HandledState{SeverityReasonHandledPanic, severity, true, ""}
|
||||
rawData = notifier.appendStateIfNeeded(rawData, state)
|
||||
// We strip the following stackframes as they don't add much
|
||||
// information but would mess with the grouping algorithm
|
||||
// { "file": "github.com/bugsnag/bugsnag-go/notifier.go", "lineNumber": 116, "method": "(*Notifier).AutoNotify" },
|
||||
// { "file": "runtime/asm_amd64.s", "lineNumber": 573, "method": "call32" },
|
||||
skipFrames := 2
|
||||
notifier.NotifySync(errors.New(err, skipFrames), true, 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 {
|
||||
severity := notifier.getDefaultSeverity(rawData, SeverityWarning)
|
||||
state := HandledState{SeverityReasonHandledPanic, severity, false, ""}
|
||||
rawData = notifier.appendStateIfNeeded(rawData, state)
|
||||
notifier.Notify(errors.New(err, 2), rawData...)
|
||||
}
|
||||
}
|
||||
|
||||
func (notifier *Notifier) dontPanic() {
|
||||
if err := recover(); err != nil {
|
||||
notifier.Config.logf("bugsnag/notifier.Notify: panic! %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get defined severity from raw data or a fallback value
|
||||
func (notifier *Notifier) getDefaultSeverity(rawData []interface{}, s severity) severity {
|
||||
allData := append(notifier.RawData, rawData...)
|
||||
for _, datum := range allData {
|
||||
if _, ok := datum.(severity); ok {
|
||||
return datum.(severity)
|
||||
}
|
||||
}
|
||||
|
||||
for _, datum := range allData {
|
||||
if _, ok := datum.(HandledState); ok {
|
||||
return datum.(HandledState).OriginalSeverity
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (notifier *Notifier) appendStateIfNeeded(rawData []interface{}, h HandledState) []interface{} {
|
||||
|
||||
for _, datum := range append(notifier.RawData, rawData...) {
|
||||
if _, ok := datum.(HandledState); ok {
|
||||
return rawData
|
||||
}
|
||||
}
|
||||
|
||||
return append(rawData, h)
|
||||
}
|
32
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/panicwrap.go
generated
vendored
32
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/panicwrap.go
generated
vendored
@ -1,32 +0,0 @@
|
||||
package bugsnag
|
||||
|
||||
import (
|
||||
"github.com/bugsnag/bugsnag-go/errors"
|
||||
"github.com/bugsnag/bugsnag-go/sessions"
|
||||
"github.com/bugsnag/panicwrap"
|
||||
)
|
||||
|
||||
// Forks and re-runs your program to add panic monitoring. This function does
|
||||
// not return on one process, instead listening on stderr of the other process,
|
||||
// which returns nil.
|
||||
//
|
||||
// Related: https://godoc.org/github.com/bugsnag/panicwrap#BasicMonitor
|
||||
func defaultPanicHandler() {
|
||||
defer defaultNotifier.dontPanic()
|
||||
ctx := sessions.SendStartupSession(&sessionTrackingConfig)
|
||||
|
||||
err := panicwrap.BasicMonitor(func(output string) {
|
||||
toNotify, err := errors.ParsePanic(output)
|
||||
|
||||
if err != nil {
|
||||
defaultNotifier.Config.logf("bugsnag.handleUncaughtPanic: %v", err)
|
||||
}
|
||||
state := HandledState{SeverityReasonUnhandledPanic, SeverityError, true, ""}
|
||||
defaultNotifier.NotifySync(toNotify, true, state, ctx)
|
||||
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
defaultNotifier.Config.logf("bugsnag.handleUncaughtPanic: %v", err)
|
||||
}
|
||||
}
|
162
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/payload.go
generated
vendored
162
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/payload.go
generated
vendored
@ -1,162 +0,0 @@
|
||||
package bugsnag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bugsnag/bugsnag-go/device"
|
||||
"github.com/bugsnag/bugsnag-go/headers"
|
||||
"github.com/bugsnag/bugsnag-go/sessions"
|
||||
)
|
||||
|
||||
const notifyPayloadVersion = "4"
|
||||
|
||||
var sessionMutex sync.Mutex
|
||||
|
||||
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: '%s'", p.APIKey)
|
||||
}
|
||||
|
||||
buf, err := p.MarshalJSON()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("bugsnag/payload.deliver: %v", err)
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
Transport: p.Transport,
|
||||
}
|
||||
req, err := http.NewRequest("POST", p.Endpoints.Notify, bytes.NewBuffer(buf))
|
||||
if err != nil {
|
||||
return fmt.Errorf("bugsnag/payload.deliver unable to create request: %v", err)
|
||||
}
|
||||
for k, v := range headers.PrefixedHeaders(p.APIKey, notifyPayloadVersion) {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
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", resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *payload) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(reportJSON{
|
||||
APIKey: p.APIKey,
|
||||
Events: []eventJSON{
|
||||
eventJSON{
|
||||
App: &appJSON{
|
||||
ReleaseStage: p.ReleaseStage,
|
||||
Type: p.AppType,
|
||||
Version: p.AppVersion,
|
||||
},
|
||||
Context: p.Context,
|
||||
Device: &deviceJSON{
|
||||
Hostname: p.Hostname,
|
||||
OsName: runtime.GOOS,
|
||||
RuntimeVersions: device.GetRuntimeVersions(),
|
||||
},
|
||||
Request: p.Request,
|
||||
Exceptions: p.exceptions(),
|
||||
GroupingHash: p.GroupingHash,
|
||||
Metadata: p.MetaData.sanitize(p.ParamsFilters),
|
||||
PayloadVersion: notifyPayloadVersion,
|
||||
Session: p.makeSession(),
|
||||
Severity: p.Severity.String,
|
||||
SeverityReason: p.severityReasonPayload(),
|
||||
Unhandled: p.Unhandled,
|
||||
User: p.User,
|
||||
},
|
||||
},
|
||||
Notifier: notifierJSON{
|
||||
Name: "Bugsnag Go",
|
||||
URL: "https://github.com/bugsnag/bugsnag-go",
|
||||
Version: VERSION,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (p *payload) makeSession() *sessionJSON {
|
||||
// If a context has not been applied to the payload then assume that no
|
||||
// session has started either
|
||||
if p.Ctx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sessionMutex.Lock()
|
||||
defer sessionMutex.Unlock()
|
||||
session := sessions.IncrementEventCountAndGetSession(p.Ctx, p.Unhandled)
|
||||
if session != nil {
|
||||
s := *session
|
||||
return &sessionJSON{
|
||||
ID: s.ID,
|
||||
StartedAt: s.StartedAt.UTC().Format(time.RFC3339),
|
||||
Events: sessions.EventCounts{
|
||||
Handled: s.EventCounts.Handled,
|
||||
Unhandled: s.EventCounts.Unhandled,
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *payload) severityReasonPayload() *severityReasonJSON {
|
||||
if reason := p.handledState.SeverityReason; reason != "" {
|
||||
json := &severityReasonJSON{
|
||||
Type: reason,
|
||||
UnhandledOverridden: p.handledState.Unhandled != p.Unhandled,
|
||||
}
|
||||
if p.handledState.Framework != "" {
|
||||
json.Attributes = make(map[string]string, 1)
|
||||
json.Attributes["framework"] = p.handledState.Framework
|
||||
}
|
||||
return json
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *payload) exceptions() []exceptionJSON {
|
||||
exceptions := []exceptionJSON{
|
||||
exceptionJSON{
|
||||
ErrorClass: p.ErrorClass,
|
||||
Message: p.Message,
|
||||
Stacktrace: p.Stacktrace,
|
||||
},
|
||||
}
|
||||
|
||||
if p.Error == nil {
|
||||
return exceptions
|
||||
}
|
||||
|
||||
cause := p.Error.Cause
|
||||
for cause != nil {
|
||||
exceptions = append(exceptions, exceptionJSON{
|
||||
ErrorClass: cause.TypeName(),
|
||||
Message: cause.Error(),
|
||||
Stacktrace: generateStacktrace(cause, p.Configuration),
|
||||
})
|
||||
cause = cause.Cause
|
||||
}
|
||||
|
||||
return exceptions
|
||||
}
|
75
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/report.go
generated
vendored
75
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/report.go
generated
vendored
@ -1,75 +0,0 @@
|
||||
package bugsnag
|
||||
|
||||
import (
|
||||
"github.com/bugsnag/bugsnag-go/device"
|
||||
"github.com/bugsnag/bugsnag-go/sessions"
|
||||
uuid "github.com/google/uuid"
|
||||
)
|
||||
|
||||
type reportJSON struct {
|
||||
APIKey string `json:"apiKey"`
|
||||
Events []eventJSON `json:"events"`
|
||||
Notifier notifierJSON `json:"notifier"`
|
||||
}
|
||||
|
||||
type notifierJSON struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type eventJSON struct {
|
||||
App *appJSON `json:"app"`
|
||||
Context string `json:"context,omitempty"`
|
||||
Device *deviceJSON `json:"device,omitempty"`
|
||||
Request *RequestJSON `json:"request,omitempty"`
|
||||
Exceptions []exceptionJSON `json:"exceptions"`
|
||||
GroupingHash string `json:"groupingHash,omitempty"`
|
||||
Metadata interface{} `json:"metaData"`
|
||||
PayloadVersion string `json:"payloadVersion"`
|
||||
Session *sessionJSON `json:"session,omitempty"`
|
||||
Severity string `json:"severity"`
|
||||
SeverityReason *severityReasonJSON `json:"severityReason,omitempty"`
|
||||
Unhandled bool `json:"unhandled"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
type sessionJSON struct {
|
||||
StartedAt string `json:"startedAt"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
Events sessions.EventCounts `json:"events"`
|
||||
}
|
||||
|
||||
type appJSON struct {
|
||||
ReleaseStage string `json:"releaseStage"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
type exceptionJSON struct {
|
||||
ErrorClass string `json:"errorClass"`
|
||||
Message string `json:"message"`
|
||||
Stacktrace []StackFrame `json:"stacktrace"`
|
||||
}
|
||||
|
||||
type severityReasonJSON struct {
|
||||
Type SeverityReason `json:"type,omitempty"`
|
||||
Attributes map[string]string `json:"attributes,omitempty"`
|
||||
UnhandledOverridden bool `json:"unhandledOverridden,omitempty"`
|
||||
}
|
||||
|
||||
type deviceJSON struct {
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
OsName string `json:"osName,omitempty"`
|
||||
|
||||
RuntimeVersions *device.RuntimeVersions `json:"runtimeVersions,omitempty"`
|
||||
}
|
||||
|
||||
// RequestJSON is the request information that populates the Request tab in the dashboard.
|
||||
type RequestJSON struct {
|
||||
ClientIP string `json:"clientIp,omitempty"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
HTTPMethod string `json:"httpMethod,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Referer string `json:"referer,omitempty"`
|
||||
}
|
27
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/report_publisher.go
generated
vendored
27
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/report_publisher.go
generated
vendored
@ -1,27 +0,0 @@
|
||||
package bugsnag
|
||||
|
||||
import "fmt"
|
||||
|
||||
type reportPublisher interface {
|
||||
publishReport(*payload) error
|
||||
}
|
||||
|
||||
type defaultReportPublisher struct{}
|
||||
|
||||
func (*defaultReportPublisher) publishReport(p *payload) error {
|
||||
p.logf("notifying bugsnag: %s", p.Message)
|
||||
if !p.notifyInReleaseStage() {
|
||||
return fmt.Errorf("not notifying in %s", p.ReleaseStage)
|
||||
}
|
||||
if p.Synchronous {
|
||||
return p.deliver()
|
||||
}
|
||||
|
||||
go func(p *payload) {
|
||||
if err := p.deliver(); err != nil {
|
||||
// Ensure that any errors are logged if they occur in a goroutine.
|
||||
p.logf("bugsnag/defaultReportPublisher.publishReport: %v", err)
|
||||
}
|
||||
}(p)
|
||||
return nil
|
||||
}
|
115
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/request_extractor.go
generated
vendored
115
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/request_extractor.go
generated
vendored
@ -1,115 +0,0 @@
|
||||
package bugsnag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const requestContextKey requestKey = iota
|
||||
|
||||
type requestKey int
|
||||
|
||||
// AttachRequestData returns a child of the given context with the request
|
||||
// object attached for later extraction by the notifier in order to
|
||||
// automatically record request data
|
||||
func AttachRequestData(ctx context.Context, r *http.Request) context.Context {
|
||||
return context.WithValue(ctx, requestContextKey, r)
|
||||
}
|
||||
|
||||
// extractRequestInfo looks for the request object that the notifier
|
||||
// automatically attaches to the context when using any of the supported
|
||||
// frameworks or bugsnag.HandlerFunc or bugsnag.Handler, and returns sub-object
|
||||
// supported by the notify API.
|
||||
func extractRequestInfo(ctx context.Context) (*RequestJSON, *http.Request) {
|
||||
if req := getRequestIfPresent(ctx); req != nil {
|
||||
return extractRequestInfoFromReq(req), req
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// extractRequestInfoFromReq extracts the request information the notify API
|
||||
// understands from the given HTTP request. Returns the sub-object supported by
|
||||
// the notify API.
|
||||
func extractRequestInfoFromReq(req *http.Request) *RequestJSON {
|
||||
return &RequestJSON{
|
||||
ClientIP: req.RemoteAddr,
|
||||
HTTPMethod: req.Method,
|
||||
URL: sanitizeURL(req),
|
||||
Referer: req.Referer(),
|
||||
Headers: parseRequestHeaders(req.Header),
|
||||
}
|
||||
}
|
||||
|
||||
// sanitizeURL will build up the URL matching the request. It will filter query parameters to remove sensitive fields.
|
||||
// The query part of the URL might appear differently (different order of parameters) if any filtering was done.
|
||||
func sanitizeURL(req *http.Request) string {
|
||||
scheme := "http"
|
||||
if req.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
rawQuery := req.URL.RawQuery
|
||||
parsedQuery, err := url.ParseQuery(req.URL.RawQuery)
|
||||
|
||||
if err != nil {
|
||||
return scheme + "://" + req.Host + req.RequestURI
|
||||
}
|
||||
|
||||
changed := false
|
||||
for key, values := range parsedQuery {
|
||||
if contains(Config.ParamsFilters, key) {
|
||||
for i := range values {
|
||||
values[i] = "BUGSNAG_URL_FILTERED"
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
rawQuery = parsedQuery.Encode()
|
||||
rawQuery = strings.Replace(rawQuery, "BUGSNAG_URL_FILTERED", "[FILTERED]", -1)
|
||||
}
|
||||
|
||||
u := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: req.Host,
|
||||
Path: req.URL.Path,
|
||||
RawQuery: rawQuery,
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func parseRequestHeaders(header map[string][]string) map[string]string {
|
||||
headers := make(map[string]string)
|
||||
for k, v := range header {
|
||||
// Headers can have multiple values, in which case we report them as csv
|
||||
if contains(Config.ParamsFilters, k) {
|
||||
headers[k] = "[FILTERED]"
|
||||
} else {
|
||||
headers[k] = strings.Join(v, ",")
|
||||
}
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func contains(slice []string, e string) bool {
|
||||
for _, s := range slice {
|
||||
if strings.Contains(strings.ToLower(e), strings.ToLower(s)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getRequestIfPresent(ctx context.Context) *http.Request {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
val := ctx.Value(requestContextKey)
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
return val.(*http.Request)
|
||||
}
|
127
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/config.go
generated
vendored
127
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/config.go
generated
vendored
@ -1,127 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SessionTrackingConfiguration defines the configuration options relevant for session tracking.
|
||||
// These are likely a subset of the global bugsnag.Configuration. Users should
|
||||
// not modify this struct directly but rather call
|
||||
// `bugsnag.Configure(bugsnag.Configuration)` which will update this configuration in return.
|
||||
type SessionTrackingConfiguration struct {
|
||||
// PublishInterval defines how often the sessions are sent off to the session server.
|
||||
PublishInterval time.Duration
|
||||
|
||||
// AutoCaptureSessions can be set to false to disable automatic session
|
||||
// tracking. If you want control over what is deemed a session, you can
|
||||
// switch off automatic session tracking with this configuration, and call
|
||||
// bugsnag.StartSession() when appropriate for your application. See the
|
||||
// official docs for instructions and examples of associating handled
|
||||
// errors with sessions and ensuring error rate accuracy on the Bugsnag
|
||||
// dashboard. This will default to true, but is stored as an interface to enable
|
||||
// us to detect when this option has not been set.
|
||||
AutoCaptureSessions interface{}
|
||||
|
||||
// APIKey defines the API key for the Bugsnag project. Same value as for reporting errors.
|
||||
APIKey string
|
||||
// Endpoint is the URI of the session server to receive session payloads.
|
||||
Endpoint string
|
||||
// Version defines the current version of the notifier.
|
||||
Version string
|
||||
|
||||
// ReleaseStage defines the release stage, e.g. "production" or "staging",
|
||||
// that this session occurred in. The release stage, in combination with
|
||||
// the app version make up the release that Bugsnag tracks.
|
||||
ReleaseStage string
|
||||
// Hostname defines the host of the server this application is running on.
|
||||
Hostname string
|
||||
// AppType defines the type of the application.
|
||||
AppType string
|
||||
// AppVersion defines the version of the application.
|
||||
AppVersion string
|
||||
// Transport defines the http.RoundTripper to be used for managing HTTP requests.
|
||||
Transport http.RoundTripper
|
||||
|
||||
// The release stages to notify about sessions in. If you set this then
|
||||
// bugsnag-go will only send sessions to Bugsnag if the release stage
|
||||
// is listed here.
|
||||
NotifyReleaseStages []string
|
||||
|
||||
// Logger is the logger that Bugsnag should log to. Uses the same defaults
|
||||
// as go's builtin logging package. This logger gets invoked when any error
|
||||
// occurs inside the library itself.
|
||||
Logger interface {
|
||||
Printf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Update modifies the values inside the receiver to match the non-default properties of the given config.
|
||||
// Existing properties will not be cleared when given empty fields.
|
||||
func (c *SessionTrackingConfiguration) Update(config *SessionTrackingConfiguration) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
if config.PublishInterval != 0 {
|
||||
c.PublishInterval = config.PublishInterval
|
||||
}
|
||||
if config.APIKey != "" {
|
||||
c.APIKey = config.APIKey
|
||||
}
|
||||
if config.Endpoint != "" {
|
||||
c.Endpoint = config.Endpoint
|
||||
}
|
||||
if config.Version != "" {
|
||||
c.Version = config.Version
|
||||
}
|
||||
if config.ReleaseStage != "" {
|
||||
c.ReleaseStage = config.ReleaseStage
|
||||
}
|
||||
if config.Hostname != "" {
|
||||
c.Hostname = config.Hostname
|
||||
}
|
||||
if config.AppType != "" {
|
||||
c.AppType = config.AppType
|
||||
}
|
||||
if config.AppVersion != "" {
|
||||
c.AppVersion = config.AppVersion
|
||||
}
|
||||
if config.Transport != nil {
|
||||
c.Transport = config.Transport
|
||||
}
|
||||
if config.Logger != nil {
|
||||
c.Logger = config.Logger
|
||||
}
|
||||
if config.NotifyReleaseStages != nil {
|
||||
c.NotifyReleaseStages = config.NotifyReleaseStages
|
||||
}
|
||||
if config.AutoCaptureSessions != nil {
|
||||
c.AutoCaptureSessions = config.AutoCaptureSessions
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SessionTrackingConfiguration) logf(fmt string, args ...interface{}) {
|
||||
if c != nil && c.Logger != nil {
|
||||
c.Logger.Printf(fmt, args...)
|
||||
} else {
|
||||
log.Printf(fmt, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// IsAutoCaptureSessions identifies whether or not the notifier should
|
||||
// automatically capture sessions as requests come in. It's a convenience
|
||||
// wrapper that allows automatic session capturing to be enabled by default.
|
||||
func (c *SessionTrackingConfiguration) IsAutoCaptureSessions() bool {
|
||||
if c.AutoCaptureSessions == nil {
|
||||
return true // enabled by default
|
||||
}
|
||||
if val, ok := c.AutoCaptureSessions.(bool); ok {
|
||||
return val
|
||||
}
|
||||
// It has been configured to *something* (although not a valid value)
|
||||
// assume the user wanted to disable this option.
|
||||
return false
|
||||
}
|
81
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/payload.go
generated
vendored
81
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/payload.go
generated
vendored
@ -1,81 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/bugsnag/bugsnag-go/device"
|
||||
)
|
||||
|
||||
// notifierPayload defines the .notifier subobject of the payload
|
||||
type notifierPayload struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// appPayload defines the .app subobject of the payload
|
||||
type appPayload struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
ReleaseStage string `json:"releaseStage,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// devicePayload defines the .device subobject of the payload
|
||||
type devicePayload struct {
|
||||
OsName string `json:"osName,omitempty"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
|
||||
RuntimeVersions *device.RuntimeVersions `json:"runtimeVersions"`
|
||||
}
|
||||
|
||||
// sessionCountsPayload defines the .sessionCounts subobject of the payload
|
||||
type sessionCountsPayload struct {
|
||||
StartedAt string `json:"startedAt"`
|
||||
SessionsStarted int `json:"sessionsStarted"`
|
||||
}
|
||||
|
||||
// sessionPayload defines the top level payload object
|
||||
type sessionPayload struct {
|
||||
Notifier *notifierPayload `json:"notifier"`
|
||||
App *appPayload `json:"app"`
|
||||
Device *devicePayload `json:"device"`
|
||||
SessionCounts []sessionCountsPayload `json:"sessionCounts"`
|
||||
}
|
||||
|
||||
// makeSessionPayload creates a sessionPayload based off of the given sessions and config
|
||||
func makeSessionPayload(sessions []*Session, config *SessionTrackingConfiguration) *sessionPayload {
|
||||
releaseStage := config.ReleaseStage
|
||||
if releaseStage == "" {
|
||||
releaseStage = "production"
|
||||
}
|
||||
hostname := config.Hostname
|
||||
if hostname == "" {
|
||||
hostname = device.GetHostname()
|
||||
}
|
||||
|
||||
return &sessionPayload{
|
||||
Notifier: ¬ifierPayload{
|
||||
Name: "Bugsnag Go",
|
||||
URL: "https://github.com/bugsnag/bugsnag-go",
|
||||
Version: config.Version,
|
||||
},
|
||||
App: &appPayload{
|
||||
Type: config.AppType,
|
||||
Version: config.AppVersion,
|
||||
ReleaseStage: releaseStage,
|
||||
},
|
||||
Device: &devicePayload{
|
||||
OsName: runtime.GOOS,
|
||||
Hostname: hostname,
|
||||
RuntimeVersions: device.GetRuntimeVersions(),
|
||||
},
|
||||
SessionCounts: []sessionCountsPayload{
|
||||
{
|
||||
//This timestamp assumes that we're sending these off once a minute
|
||||
StartedAt: sessions[0].StartedAt.UTC().Format(time.RFC3339),
|
||||
SessionsStarted: len(sessions),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
87
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/publisher.go
generated
vendored
87
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/publisher.go
generated
vendored
@ -1,87 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bugsnag/bugsnag-go/headers"
|
||||
)
|
||||
|
||||
// sessionPayloadVersion defines the current version of the payload that's
|
||||
// being sent to the session server.
|
||||
const sessionPayloadVersion = "1.0"
|
||||
|
||||
type sessionPublisher interface {
|
||||
publish(sessions []*Session) error
|
||||
}
|
||||
|
||||
type httpClient interface {
|
||||
Do(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type publisher struct {
|
||||
config *SessionTrackingConfiguration
|
||||
client httpClient
|
||||
}
|
||||
|
||||
// publish builds a payload from the given sessions and publishes them to the
|
||||
// session server. Returns any errors that happened as part of publishing.
|
||||
func (p *publisher) publish(sessions []*Session) error {
|
||||
if p.config.Endpoint == "" {
|
||||
// Session tracking is disabled, likely because the notify endpoint was
|
||||
// changed without changing the sessions endpoint
|
||||
// We've already logged a warning in this case, so no need to spam the
|
||||
// log every minute
|
||||
return nil
|
||||
}
|
||||
if apiKey := p.config.APIKey; len(apiKey) != 32 {
|
||||
return fmt.Errorf("bugsnag/sessions/publisher.publish invalid API key: '%s'", apiKey)
|
||||
}
|
||||
nrs, rs := p.config.NotifyReleaseStages, p.config.ReleaseStage
|
||||
if rs != "" && (nrs != nil && !contains(nrs, rs)) {
|
||||
// Always send sessions if the release stage is not set, but don't send any
|
||||
// sessions when notify release stages don't match the current release stage
|
||||
return nil
|
||||
}
|
||||
if len(sessions) == 0 {
|
||||
return fmt.Errorf("bugsnag/sessions/publisher.publish requested publication of 0")
|
||||
}
|
||||
p.config.mutex.Lock()
|
||||
defer p.config.mutex.Unlock()
|
||||
payload := makeSessionPayload(sessions, p.config)
|
||||
buf, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bugsnag/sessions/publisher.publish unable to marshal json: %v", err)
|
||||
}
|
||||
req, err := http.NewRequest("POST", p.config.Endpoint, bytes.NewBuffer(buf))
|
||||
if err != nil {
|
||||
return fmt.Errorf("bugsnag/sessions/publisher.publish unable to create request: %v", err)
|
||||
}
|
||||
for k, v := range headers.PrefixedHeaders(p.config.APIKey, sessionPayloadVersion) {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
res, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bugsnag/sessions/publisher.publish unable to deliver session: %v", err)
|
||||
}
|
||||
defer func(res *http.Response) {
|
||||
if err := res.Body.Close(); err != nil {
|
||||
p.config.logf("%v", err)
|
||||
}
|
||||
}(res)
|
||||
if res.StatusCode != 202 {
|
||||
return fmt.Errorf("bugsnag/session.publish expected 202 response status, got HTTP %s", res.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func contains(coll []string, e string) bool {
|
||||
for _, s := range coll {
|
||||
if s == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
29
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/session.go
generated
vendored
29
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/session.go
generated
vendored
@ -1,29 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
uuid "github.com/google/uuid"
|
||||
)
|
||||
|
||||
// EventCounts register how many handled/unhandled events have happened for
|
||||
// this session
|
||||
type EventCounts struct {
|
||||
Handled int `json:"handled"`
|
||||
Unhandled int `json:"unhandled"`
|
||||
}
|
||||
|
||||
// Session represents a start time and a unique ID that identifies the session.
|
||||
type Session struct {
|
||||
StartedAt time.Time
|
||||
ID uuid.UUID
|
||||
EventCounts *EventCounts
|
||||
}
|
||||
|
||||
func newSession() *Session {
|
||||
return &Session{
|
||||
StartedAt: time.Now(),
|
||||
ID: uuid.New(),
|
||||
EventCounts: &EventCounts{},
|
||||
}
|
||||
}
|
35
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/startup.go
generated
vendored
35
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/startup.go
generated
vendored
@ -1,35 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/bugsnag/panicwrap"
|
||||
)
|
||||
|
||||
// SendStartupSession is called by Bugsnag on startup, which will send a
|
||||
// session to Bugsnag and return a context to represent the session of the main
|
||||
// goroutine. This is the session associated with any fatal panics that are
|
||||
// caught by panicwrap.
|
||||
func SendStartupSession(config *SessionTrackingConfiguration) context.Context {
|
||||
ctx := context.Background()
|
||||
session := newSession()
|
||||
if !config.IsAutoCaptureSessions() || isApplicationProcess() {
|
||||
return ctx
|
||||
}
|
||||
publisher := &publisher{
|
||||
config: config,
|
||||
client: &http.Client{Transport: config.Transport},
|
||||
}
|
||||
go publisher.publish([]*Session{session})
|
||||
return context.WithValue(ctx, contextSessionKey, session)
|
||||
}
|
||||
|
||||
// Checks to see if this is the application process, as opposed to the process
|
||||
// that monitors for panics
|
||||
func isApplicationProcess() bool {
|
||||
// Application process is run first, and this will only have been set when
|
||||
// the monitoring process runs
|
||||
return "" == os.Getenv(panicwrap.DEFAULT_COOKIE_KEY)
|
||||
}
|
158
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/tracker.go
generated
vendored
158
src/hockeypuck/vendor/github.com/bugsnag/bugsnag-go/sessions/tracker.go
generated
vendored
@ -1,158 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
//contextSessionKey is a unique key for accessing and setting Bugsnag
|
||||
//session data on a context.Context object
|
||||
contextSessionKey ctxKey = 1
|
||||
)
|
||||
|
||||
// ctxKey is a type alias that ensures uniqueness as a context.Context key
|
||||
type ctxKey int
|
||||
|
||||
// SessionTracker exposes a method for starting sessions that are used for
|
||||
// gauging your application's health
|
||||
type SessionTracker interface {
|
||||
StartSession(context.Context) context.Context
|
||||
FlushSessions()
|
||||
}
|
||||
|
||||
type sessionTracker struct {
|
||||
sessionChannel chan *Session
|
||||
sessions []*Session
|
||||
config *SessionTrackingConfiguration
|
||||
publisher sessionPublisher
|
||||
sessionsMutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewSessionTracker creates a new SessionTracker based on the provided config,
|
||||
func NewSessionTracker(config *SessionTrackingConfiguration) SessionTracker {
|
||||
publisher := publisher{
|
||||
config: config,
|
||||
client: &http.Client{Transport: config.Transport},
|
||||
}
|
||||
st := sessionTracker{
|
||||
sessionChannel: make(chan *Session, 1),
|
||||
sessions: []*Session{},
|
||||
config: config,
|
||||
publisher: &publisher,
|
||||
}
|
||||
go st.processSessions()
|
||||
return &st
|
||||
}
|
||||
|
||||
// IncrementEventCountAndGetSession extracts a Bugsnag session from the given
|
||||
// context and increments the event count of unhandled or handled events and
|
||||
// returns the session
|
||||
func IncrementEventCountAndGetSession(ctx context.Context, unhandled bool) *Session {
|
||||
if s := ctx.Value(contextSessionKey); s != nil {
|
||||
if session, ok := s.(*Session); ok && !session.StartedAt.IsZero() {
|
||||
// It is not just getting back a default value
|
||||
ec := session.EventCounts
|
||||
if unhandled {
|
||||
ec.Unhandled++
|
||||
} else {
|
||||
ec.Handled++
|
||||
}
|
||||
return session
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sessionTracker) StartSession(ctx context.Context) context.Context {
|
||||
session := newSession()
|
||||
s.sessionChannel <- session
|
||||
return context.WithValue(ctx, contextSessionKey, session)
|
||||
}
|
||||
|
||||
func (s *sessionTracker) interval() time.Duration {
|
||||
s.config.mutex.Lock()
|
||||
defer s.config.mutex.Unlock()
|
||||
return s.config.PublishInterval
|
||||
}
|
||||
|
||||
func (s *sessionTracker) processSessions() {
|
||||
tic := time.Tick(s.interval())
|
||||
shutdown := shutdownSignals()
|
||||
for {
|
||||
select {
|
||||
case session := <-s.sessionChannel:
|
||||
s.appendSession(session)
|
||||
case <-tic:
|
||||
s.publishCollectedSessions()
|
||||
case sig := <-shutdown:
|
||||
s.flushSessionsAndRepeatSignal(shutdown, sig.(syscall.Signal))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sessionTracker) appendSession(session *Session) {
|
||||
s.sessionsMutex.Lock()
|
||||
defer s.sessionsMutex.Unlock()
|
||||
|
||||
s.sessions = append(s.sessions, session)
|
||||
}
|
||||
|
||||
func (s *sessionTracker) publishCollectedSessions() {
|
||||
s.sessionsMutex.Lock()
|
||||
defer s.sessionsMutex.Unlock()
|
||||
|
||||
oldSessions := s.sessions
|
||||
s.sessions = nil
|
||||
if len(oldSessions) > 0 {
|
||||
go func(s *sessionTracker) {
|
||||
err := s.publisher.publish(oldSessions)
|
||||
if err != nil {
|
||||
s.config.logf("%v", err)
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sessionTracker) flushSessionsAndRepeatSignal(shutdown chan<- os.Signal, sig syscall.Signal) {
|
||||
s.sessionsMutex.Lock()
|
||||
defer s.sessionsMutex.Unlock()
|
||||
|
||||
signal.Stop(shutdown)
|
||||
if len(s.sessions) > 0 {
|
||||
err := s.publisher.publish(s.sessions)
|
||||
if err != nil {
|
||||
s.config.logf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if p, err := os.FindProcess(os.Getpid()); err != nil {
|
||||
s.config.logf("%v", err)
|
||||
} else {
|
||||
p.Signal(sig)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sessionTracker) FlushSessions() {
|
||||
s.sessionsMutex.Lock()
|
||||
defer s.sessionsMutex.Unlock()
|
||||
|
||||
sessions := s.sessions
|
||||
s.sessions = nil
|
||||
if len(sessions) != 0 {
|
||||
if err := s.publisher.publish(sessions); err != nil {
|
||||
s.config.logf("%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func shutdownSignals() chan os.Signal {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
|
||||
return c
|
||||
}
|
76
src/hockeypuck/vendor/github.com/bugsnag/panicwrap/CHANGELOG.md
generated
vendored
76
src/hockeypuck/vendor/github.com/bugsnag/panicwrap/CHANGELOG.md
generated
vendored
@ -1,76 +0,0 @@
|
||||
## 1.3.4 (2021-08-10)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix build support for arm64-based macOS by updating build settings for
|
||||
`dup2()`
|
||||
|
||||
## 1.3.3 (2021-07-13)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix build support for arm64 linux by updating build settings for `dup2()`
|
||||
[Robert-André Mauchin](https://github.com/eclipseo)
|
||||
[#21](https://github.com/bugsnag/panicwrap/pull/21)
|
||||
|
||||
## 1.3.2 (2021-03-25)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Add build support from arm64-based macOS by updating build settings for
|
||||
`dup2()`
|
||||
|
||||
## 1.3.1 (2021-01-12)
|
||||
|
||||
This release removes the dependency on [osext](https://github.com/kardianos/osext)
|
||||
for everyone running go1.8+.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix windows support by removing undefined syscall
|
||||
|
||||
## 1.3.0 (2021-01-05)
|
||||
|
||||
### Features
|
||||
|
||||
* Support capturing fatal errors from concurrent map writes, nil goroutines,
|
||||
out of memory errors, stack exhaustion, and others which use a different panic
|
||||
output format.
|
||||
|
||||
## 1.2.2 (2020-12-17)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix compatibility with go1.7-1.8 by removing dependency on "math/bits" package
|
||||
|
||||
## 1.2.1 (2020-12-04)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix compatibility with solaris and friends (AIX, etc)
|
||||
[Till Wegmüller](https://github.com/Toasterson)
|
||||
[#11](https://github.com/bugsnag/panicwrap/pull/11)
|
||||
|
||||
## 1.2.0 (2017-08-08)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix bug where the program would relaunch without the panic wrapper
|
||||
[emersion](https://github.com/emersion)
|
||||
[#3](https://github.com/bugsnag/panicwrap/pull/3)
|
||||
|
||||
* Fix Solaris build
|
||||
[Brian Meyers](https://github.com/bmeyers22)
|
||||
[#4](https://github.com/bugsnag/panicwrap/pull/4)
|
||||
|
||||
## 1.1.0 (2016-01-18)
|
||||
|
||||
* Add ARM64 support
|
||||
[liusdu](https://github.com/liusdu)
|
||||
[#1](https://github.com/bugsnag/panicwrap/pull/1)
|
||||
|
||||
## 1.0.0 (2014-11-10)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Add ability to monitor a process
|
21
src/hockeypuck/vendor/github.com/bugsnag/panicwrap/LICENSE
generated
vendored
21
src/hockeypuck/vendor/github.com/bugsnag/panicwrap/LICENSE
generated
vendored
@ -1,21 +0,0 @@
|
||||
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.
|
106
src/hockeypuck/vendor/github.com/bugsnag/panicwrap/README.md
generated
vendored
106
src/hockeypuck/vendor/github.com/bugsnag/panicwrap/README.md
generated
vendored
@ -1,106 +0,0 @@
|
||||
# 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.
|
||||
|
||||
_This is a fork of [mitchellh/panicwrap](https://github.com/mitchellh/panicwrap)
|
||||
which adds support for monitoring for panics without interrupting signal
|
||||
handling on supported platforms. More information is available in the
|
||||
[documentation](https://godoc.org/github.com/bugsnag/panicwrap#BasicMonitor)._
|
||||
|
||||
## 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.
|
11
src/hockeypuck/vendor/github.com/bugsnag/panicwrap/dup2.go
generated
vendored
11
src/hockeypuck/vendor/github.com/bugsnag/panicwrap/dup2.go
generated
vendored
@ -1,11 +0,0 @@
|
||||
// +build linux,!arm64 !linux,!windows
|
||||
|
||||
package panicwrap
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func dup2(oldfd, newfd int) error {
|
||||
return syscall.Dup2(oldfd, newfd)
|
||||
}
|
11
src/hockeypuck/vendor/github.com/bugsnag/panicwrap/dup3.go
generated
vendored
11
src/hockeypuck/vendor/github.com/bugsnag/panicwrap/dup3.go
generated
vendored
@ -1,11 +0,0 @@
|
||||
//+build linux,arm64
|
||||
|
||||
package panicwrap
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func dup2(oldfd, newfd int) error {
|
||||
return syscall.Dup3(oldfd, newfd, 0)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user