Compare commits
No commits in common. "master" and "v2.0.0-alpha.0" have entirely different histories.
master
...
v2.0.0-alp
1931 changed files with 43932 additions and 559321 deletions
38
.drone.yml
Normal file
38
.drone.yml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
image: dmp42/go:stable
|
||||||
|
|
||||||
|
script:
|
||||||
|
# To be spoofed back into the test image
|
||||||
|
- go get github.com/modocache/gover
|
||||||
|
|
||||||
|
- go get -t ./...
|
||||||
|
|
||||||
|
# Go fmt
|
||||||
|
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
|
||||||
|
# Go lint
|
||||||
|
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||||
|
# Go vet
|
||||||
|
- go vet ./...
|
||||||
|
# Go test
|
||||||
|
- go test -v -race -cover ./...
|
||||||
|
# Helper to concatenate reports
|
||||||
|
- gover
|
||||||
|
# Send to coverall
|
||||||
|
- goveralls -service drone.io -coverprofile=gover.coverprofile -repotoken {{COVERALLS_TOKEN}}
|
||||||
|
|
||||||
|
# Do we want these as well?
|
||||||
|
# - go get code.google.com/p/go.tools/cmd/goimports
|
||||||
|
# - test -z "$(goimports -l -w ./... | tee /dev/stderr)"
|
||||||
|
# http://labix.org/gocheck
|
||||||
|
|
||||||
|
notify:
|
||||||
|
email:
|
||||||
|
recipients:
|
||||||
|
- distribution@docker.com
|
||||||
|
|
||||||
|
slack:
|
||||||
|
team: docker
|
||||||
|
channel: "#dt"
|
||||||
|
username: mom
|
||||||
|
token: {{SLACK_TOKEN}}
|
||||||
|
on_success: true
|
||||||
|
on_failure: true
|
3
.github/CODE_OF_CONDUCT.md
vendored
3
.github/CODE_OF_CONDUCT.md
vendored
|
@ -1,3 +0,0 @@
|
||||||
## Docker Distribution Community Code of Conduct
|
|
||||||
|
|
||||||
Docker Distribution follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
|
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -22,17 +22,3 @@ _testmain.go
|
||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
*.prof
|
*.prof
|
||||||
|
|
||||||
# never checkin from the bin file (for now)
|
|
||||||
bin/*
|
|
||||||
|
|
||||||
# Test key files
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# Cover profiles
|
|
||||||
*.out
|
|
||||||
|
|
||||||
# Editor/IDE specific files.
|
|
||||||
*.sublime-project
|
|
||||||
*.sublime-workspace
|
|
||||||
.idea/*
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- structcheck
|
|
||||||
- varcheck
|
|
||||||
- staticcheck
|
|
||||||
- unconvert
|
|
||||||
- gofmt
|
|
||||||
- goimports
|
|
||||||
- golint
|
|
||||||
- ineffassign
|
|
||||||
- vet
|
|
||||||
- unused
|
|
||||||
- misspell
|
|
||||||
disable:
|
|
||||||
- errcheck
|
|
||||||
|
|
||||||
run:
|
|
||||||
deadline: 2m
|
|
||||||
skip-dirs:
|
|
||||||
- vendor
|
|
36
.mailmap
36
.mailmap
|
@ -1,32 +1,4 @@
|
||||||
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
|
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
|
||||||
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
|
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
|
||||||
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
|
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
|
||||||
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
|
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
|
||||||
Brian Bland <brian.bland@docker.com> Brian Bland <brian.t.bland@gmail.com>
|
|
||||||
Josh Hawn <josh.hawn@docker.com> Josh Hawn <jlhawn@berkeley.edu>
|
|
||||||
Richard Scothern <richard.scothern@docker.com> Richard <richard.scothern@gmail.com>
|
|
||||||
Richard Scothern <richard.scothern@docker.com> Richard Scothern <richard.scothern@gmail.com>
|
|
||||||
Andrew Meredith <andymeredith@gmail.com> Andrew Meredith <kendru@users.noreply.github.com>
|
|
||||||
harche <p.harshal@gmail.com> harche <harche@users.noreply.github.com>
|
|
||||||
Jessie Frazelle <jessie@docker.com> <jfrazelle@users.noreply.github.com>
|
|
||||||
Sharif Nassar <sharif@mrwacky.com> Sharif Nassar <mrwacky42@users.noreply.github.com>
|
|
||||||
Sven Dowideit <SvenDowideit@home.org.au> Sven Dowideit <SvenDowideit@users.noreply.github.com>
|
|
||||||
Vincent Giersch <vincent.giersch@ovh.net> Vincent Giersch <vincent@giersch.fr>
|
|
||||||
davidli <wenquan.li@hp.com> davidli <wenquan.li@hpe.com>
|
|
||||||
Omer Cohen <git@omer.io> Omer Cohen <git@omerc.net>
|
|
||||||
Eric Yang <windfarer@gmail.com> Eric Yang <Windfarer@users.noreply.github.com>
|
|
||||||
Nikita Tarasov <nikita@mygento.ru> Nikita <luckyraul@users.noreply.github.com>
|
|
||||||
Yu Wang <yuwa@microsoft.com> yuwaMSFT2 <yuwa@microsoft.com>
|
|
||||||
Yu Wang <yuwa@microsoft.com> Yu Wang (UC) <yuwa@microsoft.com>
|
|
||||||
Olivier Gambier <olivier@docker.com> dmp <dmp@loaner.local>
|
|
||||||
Olivier Gambier <olivier@docker.com> Olivier <o+github@gambier.email>
|
|
||||||
Olivier Gambier <olivier@docker.com> Olivier <dmp42@users.noreply.github.com>
|
|
||||||
Elsan Li 李楠 <elsanli@tencent.com> elsanli(李楠) <elsanli@tencent.com>
|
|
||||||
Rui Cao <ruicao@alauda.io> ruicao <ruicao@alauda.io>
|
|
||||||
Gwendolynne Barr <gwendolynne.barr@docker.com> gbarr01 <gwendolynne.barr@docker.com>
|
|
||||||
Haibing Zhou 周海兵 <zhouhaibing089@gmail.com> zhouhaibing089 <zhouhaibing089@gmail.com>
|
|
||||||
Feng Honglin <tifayuki@gmail.com> tifayuki <tifayuki@gmail.com>
|
|
||||||
Helen Xie <xieyulin821@harmonycloud.cn> Helen-xie <xieyulin821@harmonycloud.cn>
|
|
||||||
Mike Brown <brownwm@us.ibm.com> Mike Brown <mikebrow@users.noreply.github.com>
|
|
||||||
Manish Tomar <manish.tomar@docker.com> Manish Tomar <manishtomar@users.noreply.github.com>
|
|
||||||
Sakeven Jiang <jc5930@sina.cn> sakeven <jc5930@sina.cn>
|
|
56
.travis.yml
56
.travis.yml
|
@ -1,56 +0,0 @@
|
||||||
dist: bionic
|
|
||||||
sudo: required
|
|
||||||
# setup travis so that we can run containers for integration tests
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- arch: amd64
|
|
||||||
- arch: s390x
|
|
||||||
|
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- "1.14.x"
|
|
||||||
|
|
||||||
go_import_path: github.com/docker/distribution
|
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- python-minimal
|
|
||||||
|
|
||||||
|
|
||||||
env:
|
|
||||||
- TRAVIS_GOOS=linux DOCKER_BUILDTAGS="include_oss include_gcs" TRAVIS_CGO_ENABLED=1
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- uname -r
|
|
||||||
- sudo apt-get -q update
|
|
||||||
|
|
||||||
install:
|
|
||||||
- cd /tmp && go get -u github.com/vbatts/git-validation
|
|
||||||
# TODO: Add enforcement of license
|
|
||||||
# - go get -u github.com/kunalkushwaha/ltag
|
|
||||||
- cd $TRAVIS_BUILD_DIR
|
|
||||||
|
|
||||||
script:
|
|
||||||
- export GOOS=$TRAVIS_GOOS
|
|
||||||
- export CGO_ENABLED=$TRAVIS_CGO_ENABLED
|
|
||||||
- DCO_VERBOSITY=-q script/validate/dco
|
|
||||||
- GOOS=linux GO111MODULE=on script/setup/install-dev-tools
|
|
||||||
- script/validate/vendor
|
|
||||||
- go build -i .
|
|
||||||
- make check
|
|
||||||
- make build
|
|
||||||
- make binaries
|
|
||||||
# Currently takes too long
|
|
||||||
#- if [ "$GOOS" = "linux" ]; then make test-race ; fi
|
|
||||||
- if [ "$GOOS" = "linux" ]; then make coverage ; fi
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- bash <(curl -s https://codecov.io/bash) -F linux
|
|
||||||
|
|
||||||
before_deploy:
|
|
||||||
# Run tests with storage driver configurations
|
|
8
AUTHORS
Normal file
8
AUTHORS
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
|
||||||
|
Andrey Kostov <kostov.andrey@gmail.com>
|
||||||
|
Anton Tiurin <noxiouz@yandex.ru>
|
||||||
|
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||||
|
Brian Bland <brian.bland@docker.com>
|
||||||
|
Josh Hawn <josh.hawn@docker.com>
|
||||||
|
Olivier Gambier <olivier@docker.com>
|
||||||
|
Stephen J Day <stephen.day@docker.com>
|
117
BUILDING.md
117
BUILDING.md
|
@ -1,117 +0,0 @@
|
||||||
|
|
||||||
# Building the registry source
|
|
||||||
|
|
||||||
## Use-case
|
|
||||||
|
|
||||||
This is useful if you intend to actively work on the registry.
|
|
||||||
|
|
||||||
### Alternatives
|
|
||||||
|
|
||||||
Most people should use the [official Registry docker image](https://hub.docker.com/r/library/registry/).
|
|
||||||
|
|
||||||
People looking for advanced operational use cases might consider rolling their own image with a custom Dockerfile inheriting `FROM registry:2`.
|
|
||||||
|
|
||||||
OS X users who want to run natively can do so following [the instructions here](https://github.com/docker/docker.github.io/blob/master/registry/recipes/osx-setup-guide.md).
|
|
||||||
|
|
||||||
### Gotchas
|
|
||||||
|
|
||||||
You are expected to know your way around with go & git.
|
|
||||||
|
|
||||||
If you are a casual user with no development experience, and no preliminary knowledge of go, building from source is probably not a good solution for you.
|
|
||||||
|
|
||||||
## Build the development environment
|
|
||||||
|
|
||||||
The first prerequisite of properly building distribution targets is to have a Go
|
|
||||||
development environment setup. Please follow [How to Write Go Code](https://golang.org/doc/code.html)
|
|
||||||
for proper setup. If done correctly, you should have a GOROOT and GOPATH set in the
|
|
||||||
environment.
|
|
||||||
|
|
||||||
If a Go development environment is setup, one can use `go get` to install the
|
|
||||||
`registry` command from the current latest:
|
|
||||||
|
|
||||||
go get github.com/docker/distribution/cmd/registry
|
|
||||||
|
|
||||||
The above will install the source repository into the `GOPATH`.
|
|
||||||
|
|
||||||
Now create the directory for the registry data (this might require you to set permissions properly)
|
|
||||||
|
|
||||||
mkdir -p /var/lib/registry
|
|
||||||
|
|
||||||
... or alternatively `export REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/somewhere` if you want to store data into another location.
|
|
||||||
|
|
||||||
The `registry`
|
|
||||||
binary can then be run with the following:
|
|
||||||
|
|
||||||
$ $GOPATH/bin/registry --version
|
|
||||||
$GOPATH/bin/registry github.com/docker/distribution v2.0.0-alpha.1+unknown
|
|
||||||
|
|
||||||
> __NOTE:__ While you do not need to use `go get` to checkout the distribution
|
|
||||||
> project, for these build instructions to work, the project must be checked
|
|
||||||
> out in the correct location in the `GOPATH`. This should almost always be
|
|
||||||
> `$GOPATH/src/github.com/docker/distribution`.
|
|
||||||
|
|
||||||
The registry can be run with the default config using the following
|
|
||||||
incantation:
|
|
||||||
|
|
||||||
$ $GOPATH/bin/registry serve $GOPATH/src/github.com/docker/distribution/cmd/registry/config-example.yml
|
|
||||||
INFO[0000] endpoint local-5003 disabled, skipping app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
|
|
||||||
INFO[0000] endpoint local-8083 disabled, skipping app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
|
|
||||||
INFO[0000] listening on :5000 app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
|
|
||||||
INFO[0000] debug server listening localhost:5001
|
|
||||||
|
|
||||||
If it is working, one should see the above log messages.
|
|
||||||
|
|
||||||
### Repeatable Builds
|
|
||||||
|
|
||||||
For the full development experience, one should `cd` into
|
|
||||||
`$GOPATH/src/github.com/docker/distribution`. From there, the regular `go`
|
|
||||||
commands, such as `go test`, should work per package (please see
|
|
||||||
[Developing](#developing) if they don't work).
|
|
||||||
|
|
||||||
A `Makefile` has been provided as a convenience to support repeatable builds.
|
|
||||||
Please install the following into `GOPATH` for it to work:
|
|
||||||
|
|
||||||
go get github.com/golang/lint/golint
|
|
||||||
|
|
||||||
Once these commands are available in the `GOPATH`, run `make` to get a full
|
|
||||||
build:
|
|
||||||
|
|
||||||
$ make
|
|
||||||
+ clean
|
|
||||||
+ fmt
|
|
||||||
+ vet
|
|
||||||
+ lint
|
|
||||||
+ build
|
|
||||||
github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar
|
|
||||||
github.com/sirupsen/logrus
|
|
||||||
github.com/docker/libtrust
|
|
||||||
...
|
|
||||||
github.com/yvasiyarov/gorelic
|
|
||||||
github.com/docker/distribution/registry/handlers
|
|
||||||
github.com/docker/distribution/cmd/registry
|
|
||||||
+ test
|
|
||||||
...
|
|
||||||
ok github.com/docker/distribution/digest 7.875s
|
|
||||||
ok github.com/docker/distribution/manifest 0.028s
|
|
||||||
ok github.com/docker/distribution/notifications 17.322s
|
|
||||||
? github.com/docker/distribution/registry [no test files]
|
|
||||||
ok github.com/docker/distribution/registry/api/v2 0.101s
|
|
||||||
? github.com/docker/distribution/registry/auth [no test files]
|
|
||||||
ok github.com/docker/distribution/registry/auth/silly 0.011s
|
|
||||||
...
|
|
||||||
+ /Users/sday/go/src/github.com/docker/distribution/bin/registry
|
|
||||||
+ /Users/sday/go/src/github.com/docker/distribution/bin/registry-api-descriptor-template
|
|
||||||
+ binaries
|
|
||||||
|
|
||||||
The above provides a repeatable build using the contents of the vendor
|
|
||||||
directory. This includes formatting, vetting, linting, building,
|
|
||||||
testing and generating tagged binaries. We can verify this worked by running
|
|
||||||
the registry binary generated in the "./bin" directory:
|
|
||||||
|
|
||||||
$ ./bin/registry --version
|
|
||||||
./bin/registry github.com/docker/distribution v2.0.0-alpha.2-80-g16d8b2c.m
|
|
||||||
|
|
||||||
### Optional build tags
|
|
||||||
|
|
||||||
Optional [build tags](http://golang.org/pkg/go/build/) can be provided using
|
|
||||||
the environment variable `DOCKER_BUILDTAGS`.
|
|
141
CONTRIBUTING.md
141
CONTRIBUTING.md
|
@ -1,129 +1,54 @@
|
||||||
# Contributing to the registry
|
# Contributing to the registry
|
||||||
|
|
||||||
## Before reporting an issue...
|
## Are you having issues?
|
||||||
|
|
||||||
### If your problem is with...
|
Please first try any of these support forums before opening an issue:
|
||||||
|
|
||||||
- automated builds or your [Docker Hub](https://hub.docker.com/) account
|
* irc #docker on freenode (archives: [https://botbot.me/freenode/docker/])
|
||||||
- Report it to [Hub Support](https://hub.docker.com/support/)
|
* https://forums.docker.com/
|
||||||
- Distributions of Docker for desktop or Linux
|
* if your problem is with the "hub" (the website and other user-facing components), or about automated builds, then please direct your issues to https://support.docker.com
|
||||||
- Report [Mac Desktop issues](https://github.com/docker/for-mac)
|
|
||||||
- Report [Windows Desktop issues](https://github.com/docker/for-win)
|
|
||||||
- Report [Linux issues](https://github.com/docker/for-linux)
|
|
||||||
|
|
||||||
### If you...
|
## So, you found a bug?
|
||||||
|
|
||||||
- need help setting up your registry
|
First check if your problem was already reported in the issue tracker.
|
||||||
- can't figure out something
|
|
||||||
- are not sure what's going on or what your problem is
|
|
||||||
|
|
||||||
Please ask first in the #distribution channel on Docker community slack.
|
If it's already there, please refrain from adding "same here" comments - these don't add any value and are only adding useless noise. **Said comments will quite often be deleted at sight**. On the other hand, if you have any technical, relevant information to add, by all means do!
|
||||||
[Click here for an invite to Docker community slack](https://dockr.ly/slack)
|
|
||||||
|
|
||||||
### Reporting security issues
|
Your issue is not there? Then please, create a ticket.
|
||||||
|
|
||||||
The Docker maintainers take security seriously. If you discover a security
|
If possible the following guidelines should be followed:
|
||||||
issue, please bring it to their attention right away!
|
|
||||||
|
|
||||||
Please **DO NOT** file a public issue, instead send your report privately to
|
* try to come up with a minimal, simple to reproduce test-case
|
||||||
[security@docker.com](mailto:security@docker.com).
|
* try to add a title that describe succinctly the issue
|
||||||
|
* if you are running your own registry, please provide:
|
||||||
|
* registry version
|
||||||
|
* registry launch command used
|
||||||
|
* registry configuration
|
||||||
|
* registry logs
|
||||||
|
* in all cases:
|
||||||
|
* `docker version` and `docker info`
|
||||||
|
* run your docker daemon in debug mode (-D), and provide docker daemon logs
|
||||||
|
|
||||||
## Reporting an issue properly
|
## You have a patch for a known bug, or a small correction?
|
||||||
|
|
||||||
By following these simple rules you will get better and faster feedback on your issue.
|
Basic github workflow (fork, patch, make sure the tests pass, PR).
|
||||||
|
|
||||||
- search the bugtracker for an already reported issue
|
... and some simple rules to ensure quick merge:
|
||||||
|
|
||||||
### If you found an issue that describes your problem:
|
* clearly point to the issue(s) you want to fix
|
||||||
|
* when possible, prefer multiple (smaller) PRs addressing individual issues over a big one trying to address multiple issues at once
|
||||||
|
* if you need to amend your PR following comments, squash instead of adding more commits
|
||||||
|
|
||||||
- please read other user comments first, and confirm this is the same issue: a given error condition might be indicative of different problems - you may also find a workaround in the comments
|
## You want some shiny new feature to be added?
|
||||||
- please refrain from adding "same thing here" or "+1" comments
|
|
||||||
- you don't need to comment on an issue to get notified of updates: just hit the "subscribe" button
|
|
||||||
- comment if you have some new, technical and relevant information to add to the case
|
|
||||||
- __DO NOT__ comment on closed issues or merged PRs. If you think you have a related problem, open up a new issue and reference the PR or issue.
|
|
||||||
|
|
||||||
### If you have not found an existing issue that describes your problem:
|
Fork the project.
|
||||||
|
|
||||||
1. create a new issue, with a succinct title that describes your issue:
|
Create a new proposal in the folder `open-design/specs`, named `DEP_MY_AWESOME_PROPOSAL.md`, using `open-design/specs/TEMPLATE.md` as a starting point.
|
||||||
- bad title: "It doesn't work with my docker"
|
|
||||||
- good title: "Private registry push fail: 400 error with E_INVALID_DIGEST"
|
|
||||||
2. copy the output of:
|
|
||||||
- `docker version`
|
|
||||||
- `docker info`
|
|
||||||
- `docker exec <registry-container> registry --version`
|
|
||||||
3. copy the command line you used to launch your Registry
|
|
||||||
4. restart your docker daemon in debug mode (add `-D` to the daemon launch arguments)
|
|
||||||
5. reproduce your problem and get your docker daemon logs showing the error
|
|
||||||
6. if relevant, copy your registry logs that show the error
|
|
||||||
7. provide any relevant detail about your specific Registry configuration (e.g., storage backend used)
|
|
||||||
8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry
|
|
||||||
|
|
||||||
## Contributing Code
|
Then immediately submit this new file as a pull-request, in order to get early feedback.
|
||||||
|
|
||||||
Contributions should be made via pull requests. Pull requests will be reviewed
|
Eventually, you will have to update your proposal to accommodate the feedback you received.
|
||||||
by one or more maintainers or reviewers and merged when acceptable.
|
|
||||||
|
|
||||||
You should follow the basic GitHub workflow:
|
Usually, it's not advisable to start working too much on the implementation itself before the proposal receives sufficient feedback, since it can significantly altered (or rejected).
|
||||||
|
|
||||||
1. Use your own [fork](https://help.github.com/en/articles/about-forks)
|
Your implementation should then be submitted as a separate PR, that will be reviewed as well.
|
||||||
2. Create your [change](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes)
|
|
||||||
3. Test your code
|
|
||||||
4. [Commit](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages) your work, always [sign your commits](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages)
|
|
||||||
5. Push your change to your fork and create a [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork)
|
|
||||||
|
|
||||||
Refer to [containerd's contribution guide](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes)
|
|
||||||
for tips on creating a successful contribution.
|
|
||||||
|
|
||||||
## Sign your work
|
|
||||||
|
|
||||||
The sign-off is a simple line at the end of the explanation for the patch. Your
|
|
||||||
signature certifies that you wrote the patch or otherwise have the right to pass
|
|
||||||
it on as an open-source patch. The rules are pretty simple: if you can certify
|
|
||||||
the below (from [developercertificate.org](http://developercertificate.org/)):
|
|
||||||
|
|
||||||
```
|
|
||||||
Developer Certificate of Origin
|
|
||||||
Version 1.1
|
|
||||||
|
|
||||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
|
||||||
660 York Street, Suite 102,
|
|
||||||
San Francisco, CA 94110 USA
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies of this
|
|
||||||
license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Developer's Certificate of Origin 1.1
|
|
||||||
|
|
||||||
By making a contribution to this project, I certify that:
|
|
||||||
|
|
||||||
(a) The contribution was created in whole or in part by me and I
|
|
||||||
have the right to submit it under the open source license
|
|
||||||
indicated in the file; or
|
|
||||||
|
|
||||||
(b) The contribution is based upon previous work that, to the best
|
|
||||||
of my knowledge, is covered under an appropriate open source
|
|
||||||
license and I have the right under that license to submit that
|
|
||||||
work with modifications, whether created in whole or in part
|
|
||||||
by me, under the same open source license (unless I am
|
|
||||||
permitted to submit under a different license), as indicated
|
|
||||||
in the file; or
|
|
||||||
|
|
||||||
(c) The contribution was provided directly to me by some other
|
|
||||||
person who certified (a), (b) or (c) and I have not modified
|
|
||||||
it.
|
|
||||||
|
|
||||||
(d) I understand and agree that this project and the contribution
|
|
||||||
are public and that a record of the contribution (including all
|
|
||||||
personal information I submit with it, including my sign-off) is
|
|
||||||
maintained indefinitely and may be redistributed consistent with
|
|
||||||
this project or the open source license(s) involved.
|
|
||||||
```
|
|
||||||
|
|
||||||
Then you just add a line to every git commit message:
|
|
||||||
|
|
||||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
|
||||||
|
|
||||||
Use your real name (sorry, no pseudonyms or anonymous contributions.)
|
|
||||||
|
|
||||||
If you set your `user.name` and `user.email` git configs, you can sign your
|
|
||||||
commit automatically with `git commit -s`.
|
|
||||||
|
|
35
Dockerfile
35
Dockerfile
|
@ -1,31 +1,14 @@
|
||||||
ARG GO_VERSION=1.13.8
|
FROM golang
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-alpine3.11 AS build
|
COPY . /go/src/github.com/docker/distribution
|
||||||
|
|
||||||
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
|
# Fetch any dependencies to run the registry
|
||||||
ENV BUILDTAGS include_oss include_gcs
|
RUN go get github.com/docker/distribution/...
|
||||||
|
RUN go install github.com/docker/distribution/cmd/registry
|
||||||
|
|
||||||
ARG GOOS=linux
|
ENV CONFIG_PATH /etc/docker/registry/config.yml
|
||||||
ARG GOARCH=amd64
|
COPY ./cmd/registry/config.yml $CONFIG_PATH
|
||||||
ARG GOARM=6
|
|
||||||
ARG VERSION
|
|
||||||
ARG REVISION
|
|
||||||
|
|
||||||
RUN set -ex \
|
|
||||||
&& apk add --no-cache make git file
|
|
||||||
|
|
||||||
WORKDIR $DISTRIBUTION_DIR
|
|
||||||
COPY . $DISTRIBUTION_DIR
|
|
||||||
RUN CGO_ENABLED=0 make PREFIX=/go clean binaries && file ./bin/registry | grep "statically linked"
|
|
||||||
|
|
||||||
FROM alpine:3.11
|
|
||||||
|
|
||||||
RUN set -ex \
|
|
||||||
&& apk add --no-cache ca-certificates apache2-utils
|
|
||||||
|
|
||||||
COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml
|
|
||||||
COPY --from=build /go/src/github.com/docker/distribution/bin/registry /bin/registry
|
|
||||||
VOLUME ["/var/lib/registry"]
|
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
ENTRYPOINT ["registry"]
|
ENV PATH /go/bin
|
||||||
CMD ["serve", "/etc/docker/registry/config.yml"]
|
CMD registry $CONFIG_PATH
|
||||||
|
|
144
GOVERNANCE.md
144
GOVERNANCE.md
|
@ -1,144 +0,0 @@
|
||||||
# docker/distribution Project Governance
|
|
||||||
|
|
||||||
Docker distribution abides by the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
|
||||||
|
|
||||||
For specific guidance on practical contribution steps please
|
|
||||||
see our [CONTRIBUTING.md](./CONTRIBUTING.md) guide.
|
|
||||||
|
|
||||||
## Maintainership
|
|
||||||
|
|
||||||
There are different types of maintainers, with different responsibilities, but
|
|
||||||
all maintainers have 3 things in common:
|
|
||||||
|
|
||||||
1) They share responsibility in the project's success.
|
|
||||||
2) They have made a long-term, recurring time investment to improve the project.
|
|
||||||
3) They spend that time doing whatever needs to be done, not necessarily what
|
|
||||||
is the most interesting or fun.
|
|
||||||
|
|
||||||
Maintainers are often under-appreciated, because their work is harder to appreciate.
|
|
||||||
It's easy to appreciate a really cool and technically advanced feature. It's harder
|
|
||||||
to appreciate the absence of bugs, the slow but steady improvement in stability,
|
|
||||||
or the reliability of a release process. But those things distinguish a good
|
|
||||||
project from a great one.
|
|
||||||
|
|
||||||
## Reviewers
|
|
||||||
|
|
||||||
A reviewer is a core role within the project.
|
|
||||||
They share in reviewing issues and pull requests and their LGTM counts towards the
|
|
||||||
required LGTM count to merge a code change into the project.
|
|
||||||
|
|
||||||
Reviewers are part of the organization but do not have write access.
|
|
||||||
Becoming a reviewer is a core aspect in the journey to becoming a maintainer.
|
|
||||||
|
|
||||||
## Adding maintainers
|
|
||||||
|
|
||||||
Maintainers are first and foremost contributors that have shown they are
|
|
||||||
committed to the long term success of a project. Contributors wanting to become
|
|
||||||
maintainers are expected to be deeply involved in contributing code, pull
|
|
||||||
request review, and triage of issues in the project for more than three months.
|
|
||||||
|
|
||||||
Just contributing does not make you a maintainer, it is about building trust
|
|
||||||
with the current maintainers of the project and being a person that they can
|
|
||||||
depend on and trust to make decisions in the best interest of the project.
|
|
||||||
|
|
||||||
Periodically, the existing maintainers curate a list of contributors that have
|
|
||||||
shown regular activity on the project over the prior months. From this list,
|
|
||||||
maintainer candidates are selected and proposed in a pull request or a
|
|
||||||
maintainers communication channel.
|
|
||||||
|
|
||||||
After a candidate has been announced to the maintainers, the existing
|
|
||||||
maintainers are given five business days to discuss the candidate, raise
|
|
||||||
objections and cast their vote. Votes may take place on the communication
|
|
||||||
channel or via pull request comment. Candidates must be approved by at least 66%
|
|
||||||
of the current maintainers by adding their vote on the mailing list. The
|
|
||||||
reviewer role has the same process but only requires 33% of current maintainers.
|
|
||||||
Only maintainers of the repository that the candidate is proposed for are
|
|
||||||
allowed to vote.
|
|
||||||
|
|
||||||
If a candidate is approved, a maintainer will contact the candidate to invite
|
|
||||||
the candidate to open a pull request that adds the contributor to the
|
|
||||||
MAINTAINERS file. The voting process may take place inside a pull request if a
|
|
||||||
maintainer has already discussed the candidacy with the candidate and a
|
|
||||||
maintainer is willing to be a sponsor by opening the pull request. The candidate
|
|
||||||
becomes a maintainer once the pull request is merged.
|
|
||||||
|
|
||||||
## Stepping down policy
|
|
||||||
|
|
||||||
Life priorities, interests, and passions can change. If you're a maintainer but
|
|
||||||
feel you must remove yourself from the list, inform other maintainers that you
|
|
||||||
intend to step down, and if possible, help find someone to pick up your work.
|
|
||||||
At the very least, ensure your work can be continued where you left off.
|
|
||||||
|
|
||||||
After you've informed other maintainers, create a pull request to remove
|
|
||||||
yourself from the MAINTAINERS file.
|
|
||||||
|
|
||||||
## Removal of inactive maintainers
|
|
||||||
|
|
||||||
Similar to the procedure for adding new maintainers, existing maintainers can
|
|
||||||
be removed from the list if they do not show significant activity on the
|
|
||||||
project. Periodically, the maintainers review the list of maintainers and their
|
|
||||||
activity over the last three months.
|
|
||||||
|
|
||||||
If a maintainer has shown insufficient activity over this period, a neutral
|
|
||||||
person will contact the maintainer to ask if they want to continue being
|
|
||||||
a maintainer. If the maintainer decides to step down as a maintainer, they
|
|
||||||
open a pull request to be removed from the MAINTAINERS file.
|
|
||||||
|
|
||||||
If the maintainer wants to remain a maintainer, but is unable to perform the
|
|
||||||
required duties they can be removed with a vote of at least 66% of the current
|
|
||||||
maintainers. In this case, maintainers should first propose the change to
|
|
||||||
maintainers via the maintainers communication channel, then open a pull request
|
|
||||||
for voting. The voting period is five business days. The voting pull request
|
|
||||||
should not come as a surpise to any maintainer and any discussion related to
|
|
||||||
performance must not be discussed on the pull request.
|
|
||||||
|
|
||||||
## How are decisions made?
|
|
||||||
|
|
||||||
Docker distribution is an open-source project with an open design philosophy.
|
|
||||||
This means that the repository is the source of truth for EVERY aspect of the
|
|
||||||
project, including its philosophy, design, road map, and APIs. *If it's part of
|
|
||||||
the project, it's in the repo. If it's in the repo, it's part of the project.*
|
|
||||||
|
|
||||||
As a result, all decisions can be expressed as changes to the repository. An
|
|
||||||
implementation change is a change to the source code. An API change is a change
|
|
||||||
to the API specification. A philosophy change is a change to the philosophy
|
|
||||||
manifesto, and so on.
|
|
||||||
|
|
||||||
All decisions affecting distribution, big and small, follow the same 3 steps:
|
|
||||||
|
|
||||||
* Step 1: Open a pull request. Anyone can do this.
|
|
||||||
|
|
||||||
* Step 2: Discuss the pull request. Anyone can do this.
|
|
||||||
|
|
||||||
* Step 3: Merge or refuse the pull request. Who does this depends on the nature
|
|
||||||
of the pull request and which areas of the project it affects.
|
|
||||||
|
|
||||||
## Helping contributors with the DCO
|
|
||||||
|
|
||||||
The [DCO or `Sign your work`](./CONTRIBUTING.md#sign-your-work)
|
|
||||||
requirement is not intended as a roadblock or speed bump.
|
|
||||||
|
|
||||||
Some contributors are not as familiar with `git`, or have used a web
|
|
||||||
based editor, and thus asking them to `git commit --amend -s` is not the best
|
|
||||||
way forward.
|
|
||||||
|
|
||||||
In this case, maintainers can update the commits based on clause (c) of the DCO.
|
|
||||||
The most trivial way for a contributor to allow the maintainer to do this, is to
|
|
||||||
add a DCO signature in a pull requests's comment, or a maintainer can simply
|
|
||||||
note that the change is sufficiently trivial that it does not substantially
|
|
||||||
change the existing contribution - i.e., a spelling change.
|
|
||||||
|
|
||||||
When you add someone's DCO, please also add your own to keep a log.
|
|
||||||
|
|
||||||
## I'm a maintainer. Should I make pull requests too?
|
|
||||||
|
|
||||||
Yes. Nobody should ever push to master directly. All changes should be
|
|
||||||
made through a pull request.
|
|
||||||
|
|
||||||
## Conflict Resolution
|
|
||||||
|
|
||||||
If you have a technical dispute that you feel has reached an impasse with a
|
|
||||||
subset of the community, any contributor may open an issue, specifically
|
|
||||||
calling for a resolution vote of the current core maintainers to resolve the
|
|
||||||
dispute. The same voting quorums required (2/3) for adding and removing
|
|
||||||
maintainers will apply to conflict resolution.
|
|
91
Godeps/Godeps.json
generated
Normal file
91
Godeps/Godeps.json
generated
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/distribution",
|
||||||
|
"GoVersion": "go1.4",
|
||||||
|
"Packages": [
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "code.google.com/p/go-uuid/uuid",
|
||||||
|
"Comment": "null-12",
|
||||||
|
"Rev": "7dda39b2e7d5e265014674c5af696ba4186679e9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Sirupsen/logrus",
|
||||||
|
"Comment": "v0.6.1-8-gcc09837",
|
||||||
|
"Rev": "cc09837bcd512ffe6bb2e3f635bed138c4cd6bc8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/bugsnag/bugsnag-go",
|
||||||
|
"Comment": "v1.0.2-5-gb1d1530",
|
||||||
|
"Rev": "b1d153021fcd90ca3f080db36bec96dc690fb274"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/bugsnag/osext",
|
||||||
|
"Rev": "0dd3f918b21bec95ace9dc86c7e70266cfc5c702"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/bugsnag/panicwrap",
|
||||||
|
"Rev": "e5f9854865b9778a45169fc249e99e338d4d6f27"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/crowdmob/goamz/aws",
|
||||||
|
"Rev": "962cedbbde5e1af59fb0b4ab681c848e61676941"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/crowdmob/goamz/cloudfront",
|
||||||
|
"Rev": "962cedbbde5e1af59fb0b4ab681c848e61676941"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/crowdmob/goamz/s3",
|
||||||
|
"Rev": "962cedbbde5e1af59fb0b4ab681c848e61676941"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/pkg/tarsum",
|
||||||
|
"Comment": "v1.4.1-330-g3fbf723",
|
||||||
|
"Rev": "3fbf723e81fa2696daa95847ccdcacddba6484da"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar",
|
||||||
|
"Comment": "v1.4.1-330-g3fbf723",
|
||||||
|
"Rev": "3fbf723e81fa2696daa95847ccdcacddba6484da"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/libtrust",
|
||||||
|
"Rev": "c54fbb67c1f1e68d7d6f8d2ad7c9360404616a41"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/context",
|
||||||
|
"Rev": "14f550f51af52180c2eefed15e5fd18d63c0a64a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/handlers",
|
||||||
|
"Rev": "0e84b7d810c16aed432217e330206be156bafae0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/mux",
|
||||||
|
"Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/yvasiyarov/go-metrics",
|
||||||
|
"Rev": "57bccd1ccd43f94bb17fdd8bf3007059b802f85e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/yvasiyarov/gorelic",
|
||||||
|
"Comment": "v0.0.6-8-ga9bba5b",
|
||||||
|
"Rev": "a9bba5b9ab508a086f9a12b8c51fab68478e2128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/yvasiyarov/newrelic_platform_go",
|
||||||
|
"Rev": "b21fdbd4370f3717f3bbd2bf41c223bc273068e6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/check.v1",
|
||||||
|
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/yaml.v2",
|
||||||
|
"Rev": "71e7ede9d48a2e096f6d5d0516c763513a471bd1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/pkg
|
||||||
|
/bin
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2011 Google Inc. All rights reserved.
|
Copyright (c) 2009 Google Inc. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
84
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/dce.go
generated
vendored
Normal file
84
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/dce.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Domain represents a Version 2 domain
|
||||||
|
type Domain byte
|
||||||
|
|
||||||
|
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||||
|
const (
|
||||||
|
Person = Domain(0)
|
||||||
|
Group = Domain(1)
|
||||||
|
Org = Domain(2)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||||
|
//
|
||||||
|
// The domain should be one of Person, Group or Org.
|
||||||
|
// On a POSIX system the id should be the users UID for the Person
|
||||||
|
// domain and the users GID for the Group. The meaning of id for
|
||||||
|
// the domain Org or on non-POSIX systems is site defined.
|
||||||
|
//
|
||||||
|
// For a given domain/id pair the same token may be returned for up to
|
||||||
|
// 7 minutes and 10 seconds.
|
||||||
|
func NewDCESecurity(domain Domain, id uint32) UUID {
|
||||||
|
uuid := NewUUID()
|
||||||
|
if uuid != nil {
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||||
|
uuid[9] = byte(domain)
|
||||||
|
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||||
|
}
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||||
|
// domain with the id returned by os.Getuid.
|
||||||
|
//
|
||||||
|
// NewDCEPerson(Person, uint32(os.Getuid()))
|
||||||
|
func NewDCEPerson() UUID {
|
||||||
|
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||||
|
// domain with the id returned by os.Getgid.
|
||||||
|
//
|
||||||
|
// NewDCEGroup(Group, uint32(os.Getgid()))
|
||||||
|
func NewDCEGroup() UUID {
|
||||||
|
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain returns the domain for a Version 2 UUID or false.
|
||||||
|
func (uuid UUID) Domain() (Domain, bool) {
|
||||||
|
if v, _ := uuid.Version(); v != 2 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return Domain(uuid[9]), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Id returns the id for a Version 2 UUID or false.
|
||||||
|
func (uuid UUID) Id() (uint32, bool) {
|
||||||
|
if v, _ := uuid.Version(); v != 2 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint32(uuid[0:4]), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Domain) String() string {
|
||||||
|
switch d {
|
||||||
|
case Person:
|
||||||
|
return "Person"
|
||||||
|
case Group:
|
||||||
|
return "Group"
|
||||||
|
case Org:
|
||||||
|
return "Org"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Domain%d", int(d))
|
||||||
|
}
|
8
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/doc.go
generated
vendored
Normal file
8
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// The uuid package generates and inspects UUIDs.
|
||||||
|
//
|
||||||
|
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
|
||||||
|
package uuid
|
53
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/hash.go
generated
vendored
Normal file
53
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/hash.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Well known Name Space IDs and UUIDs
|
||||||
|
var (
|
||||||
|
NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
||||||
|
NIL = Parse("00000000-0000-0000-0000-000000000000")
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHash returns a new UUID dervied from the hash of space concatenated with
|
||||||
|
// data generated by h. The hash should be at least 16 byte in length. The
|
||||||
|
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||||
|
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||||
|
// NewMD5 and NewSHA1.
|
||||||
|
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||||
|
h.Reset()
|
||||||
|
h.Write(space)
|
||||||
|
h.Write([]byte(data))
|
||||||
|
s := h.Sum(nil)
|
||||||
|
uuid := make([]byte, 16)
|
||||||
|
copy(uuid, s)
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||||
|
// supplied name space and data.
|
||||||
|
//
|
||||||
|
// NewHash(md5.New(), space, data, 3)
|
||||||
|
func NewMD5(space UUID, data []byte) UUID {
|
||||||
|
return NewHash(md5.New(), space, data, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||||
|
// supplied name space and data.
|
||||||
|
//
|
||||||
|
// NewHash(sha1.New(), space, data, 5)
|
||||||
|
func NewSHA1(space UUID, data []byte) UUID {
|
||||||
|
return NewHash(sha1.New(), space, data, 5)
|
||||||
|
}
|
101
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/node.go
generated
vendored
Normal file
101
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/node.go
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
var (
|
||||||
|
interfaces []net.Interface // cached list of interfaces
|
||||||
|
ifname string // name of interface being used
|
||||||
|
nodeID []byte // hardware for version 1 UUIDs
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeInterface returns the name of the interface from which the NodeID was
|
||||||
|
// derived. The interface "user" is returned if the NodeID was set by
|
||||||
|
// SetNodeID.
|
||||||
|
func NodeInterface() string {
|
||||||
|
return ifname
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||||
|
// If name is "" then the first usable interface found will be used or a random
|
||||||
|
// Node ID will be generated. If a named interface cannot be found then false
|
||||||
|
// is returned.
|
||||||
|
//
|
||||||
|
// SetNodeInterface never fails when name is "".
|
||||||
|
func SetNodeInterface(name string) bool {
|
||||||
|
if interfaces == nil {
|
||||||
|
var err error
|
||||||
|
interfaces, err = net.Interfaces()
|
||||||
|
if err != nil && name != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ifs := range interfaces {
|
||||||
|
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||||
|
if setNodeID(ifs.HardwareAddr) {
|
||||||
|
ifname = ifs.Name
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found no interfaces with a valid hardware address. If name
|
||||||
|
// does not specify a specific interface generate a random Node ID
|
||||||
|
// (section 4.1.6)
|
||||||
|
if name == "" {
|
||||||
|
if nodeID == nil {
|
||||||
|
nodeID = make([]byte, 6)
|
||||||
|
}
|
||||||
|
randomBits(nodeID)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||||
|
// if not already set.
|
||||||
|
func NodeID() []byte {
|
||||||
|
if nodeID == nil {
|
||||||
|
SetNodeInterface("")
|
||||||
|
}
|
||||||
|
nid := make([]byte, 6)
|
||||||
|
copy(nid, nodeID)
|
||||||
|
return nid
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||||
|
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||||
|
// Node ID is not set.
|
||||||
|
func SetNodeID(id []byte) bool {
|
||||||
|
if setNodeID(id) {
|
||||||
|
ifname = "user"
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func setNodeID(id []byte) bool {
|
||||||
|
if len(id) < 6 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if nodeID == nil {
|
||||||
|
nodeID = make([]byte, 6)
|
||||||
|
}
|
||||||
|
copy(nodeID, id)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||||
|
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) NodeID() []byte {
|
||||||
|
if len(uuid) != 16 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
node := make([]byte, 6)
|
||||||
|
copy(node, uuid[10:])
|
||||||
|
return node
|
||||||
|
}
|
132
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/time.go
generated
vendored
Normal file
132
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/time.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
// Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||||
|
// 1582.
|
||||||
|
type Time int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||||
|
unix = 2440587 // Julian day of 1 Jan 1970
|
||||||
|
epoch = unix - lillian // Days between epochs
|
||||||
|
g1582 = epoch * 86400 // seconds between epochs
|
||||||
|
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
lasttime uint64 // last time we returned
|
||||||
|
clock_seq uint16 // clock sequence for this run
|
||||||
|
|
||||||
|
timeNow = time.Now // for testing
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||||
|
// epoch of 1 Jan 1970.
|
||||||
|
func (t Time) UnixTime() (sec, nsec int64) {
|
||||||
|
sec = int64(t - g1582ns100)
|
||||||
|
nsec = (sec % 10000000) * 100
|
||||||
|
sec /= 10000000
|
||||||
|
return sec, nsec
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||||
|
// adjusts the clock sequence as needed. An error is returned if the current
|
||||||
|
// time cannot be determined.
|
||||||
|
func GetTime() (Time, error) {
|
||||||
|
defer mu.Unlock()
|
||||||
|
mu.Lock()
|
||||||
|
return getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTime() (Time, error) {
|
||||||
|
t := timeNow()
|
||||||
|
|
||||||
|
// If we don't have a clock sequence already, set one.
|
||||||
|
if clock_seq == 0 {
|
||||||
|
setClockSequence(-1)
|
||||||
|
}
|
||||||
|
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||||
|
|
||||||
|
// If time has gone backwards with this clock sequence then we
|
||||||
|
// increment the clock sequence
|
||||||
|
if now <= lasttime {
|
||||||
|
clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
|
||||||
|
}
|
||||||
|
lasttime = now
|
||||||
|
return Time(now), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockSequence returns the current clock sequence, generating one if not
|
||||||
|
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||||
|
//
|
||||||
|
// The uuid package does not use global static storage for the clock sequence or
|
||||||
|
// the last time a UUID was generated. Unless SetClockSequence a new random
|
||||||
|
// clock sequence is generated the first time a clock sequence is requested by
|
||||||
|
// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
|
||||||
|
// for
|
||||||
|
func ClockSequence() int {
|
||||||
|
defer mu.Unlock()
|
||||||
|
mu.Lock()
|
||||||
|
return clockSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
func clockSequence() int {
|
||||||
|
if clock_seq == 0 {
|
||||||
|
setClockSequence(-1)
|
||||||
|
}
|
||||||
|
return int(clock_seq & 0x3fff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||||
|
// -1 causes a new sequence to be generated.
|
||||||
|
func SetClockSequence(seq int) {
|
||||||
|
defer mu.Unlock()
|
||||||
|
mu.Lock()
|
||||||
|
setClockSequence(seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setClockSequence(seq int) {
|
||||||
|
if seq == -1 {
|
||||||
|
var b [2]byte
|
||||||
|
randomBits(b[:]) // clock sequence
|
||||||
|
seq = int(b[0])<<8 | int(b[1])
|
||||||
|
}
|
||||||
|
old_seq := clock_seq
|
||||||
|
clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||||
|
if old_seq != clock_seq {
|
||||||
|
lasttime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||||
|
// uuid. It returns false if uuid is not valid. The time is only well defined
|
||||||
|
// for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) Time() (Time, bool) {
|
||||||
|
if len(uuid) != 16 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||||
|
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||||
|
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||||
|
return Time(time), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockSequence returns the clock sequence encoded in uuid. It returns false
|
||||||
|
// if uuid is not valid. The clock sequence is only well defined for version 1
|
||||||
|
// and 2 UUIDs.
|
||||||
|
func (uuid UUID) ClockSequence() (int, bool) {
|
||||||
|
if len(uuid) != 16 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
|
||||||
|
}
|
43
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/util.go
generated
vendored
Normal file
43
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/util.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// randomBits completely fills slice b with random data.
|
||||||
|
func randomBits(b []byte) {
|
||||||
|
if _, err := io.ReadFull(rander, b); err != nil {
|
||||||
|
panic(err.Error()) // rand should never fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||||
|
var xvalues = []byte{
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
// xtob converts the the first two hex bytes of x into a byte.
|
||||||
|
func xtob(x string) (byte, bool) {
|
||||||
|
b1 := xvalues[x[0]]
|
||||||
|
b2 := xvalues[x[1]]
|
||||||
|
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||||
|
}
|
163
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid.go
generated
vendored
Normal file
163
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid.go
generated
vendored
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||||
|
// 4122.
|
||||||
|
type UUID []byte
|
||||||
|
|
||||||
|
// A Version represents a UUIDs version.
|
||||||
|
type Version byte
|
||||||
|
|
||||||
|
// A Variant represents a UUIDs variant.
|
||||||
|
type Variant byte
|
||||||
|
|
||||||
|
// Constants returned by Variant.
|
||||||
|
const (
|
||||||
|
Invalid = Variant(iota) // Invalid UUID
|
||||||
|
RFC4122 // The variant specified in RFC4122
|
||||||
|
Reserved // Reserved, NCS backward compatibility.
|
||||||
|
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||||
|
Future // Reserved for future definition.
|
||||||
|
)
|
||||||
|
|
||||||
|
var rander = rand.Reader // random function
|
||||||
|
|
||||||
|
// New returns a new random (version 4) UUID as a string. It is a convenience
|
||||||
|
// function for NewRandom().String().
|
||||||
|
func New() string {
|
||||||
|
return NewRandom().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse decodes s into a UUID or returns nil. Both the UUID form of
|
||||||
|
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
|
||||||
|
func Parse(s string) UUID {
|
||||||
|
if len(s) == 36+9 {
|
||||||
|
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s = s[9:]
|
||||||
|
} else if len(s) != 36 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
uuid := make([]byte, 16)
|
||||||
|
for i, x := range []int{
|
||||||
|
0, 2, 4, 6,
|
||||||
|
9, 11,
|
||||||
|
14, 16,
|
||||||
|
19, 21,
|
||||||
|
24, 26, 28, 30, 32, 34} {
|
||||||
|
if v, ok := xtob(s[x:]); !ok {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
uuid[i] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if uuid1 and uuid2 are equal.
|
||||||
|
func Equal(uuid1, uuid2 UUID) bool {
|
||||||
|
return bytes.Equal(uuid1, uuid2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
// , or "" if uuid is invalid.
|
||||||
|
func (uuid UUID) String() string {
|
||||||
|
if uuid == nil || len(uuid) != 16 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b := []byte(uuid)
|
||||||
|
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
|
||||||
|
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// URN returns the RFC 2141 URN form of uuid,
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||||
|
func (uuid UUID) URN() string {
|
||||||
|
if uuid == nil || len(uuid) != 16 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b := []byte(uuid)
|
||||||
|
return fmt.Sprintf("urn:uuid:%08x-%04x-%04x-%04x-%012x",
|
||||||
|
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant returns the variant encoded in uuid. It returns Invalid if
|
||||||
|
// uuid is invalid.
|
||||||
|
func (uuid UUID) Variant() Variant {
|
||||||
|
if len(uuid) != 16 {
|
||||||
|
return Invalid
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case (uuid[8] & 0xc0) == 0x80:
|
||||||
|
return RFC4122
|
||||||
|
case (uuid[8] & 0xe0) == 0xc0:
|
||||||
|
return Microsoft
|
||||||
|
case (uuid[8] & 0xe0) == 0xe0:
|
||||||
|
return Future
|
||||||
|
default:
|
||||||
|
return Reserved
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the verison of uuid. It returns false if uuid is not
|
||||||
|
// valid.
|
||||||
|
func (uuid UUID) Version() (Version, bool) {
|
||||||
|
if len(uuid) != 16 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return Version(uuid[6] >> 4), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
if v > 15 {
|
||||||
|
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("VERSION_%d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Variant) String() string {
|
||||||
|
switch v {
|
||||||
|
case RFC4122:
|
||||||
|
return "RFC4122"
|
||||||
|
case Reserved:
|
||||||
|
return "Reserved"
|
||||||
|
case Microsoft:
|
||||||
|
return "Microsoft"
|
||||||
|
case Future:
|
||||||
|
return "Future"
|
||||||
|
case Invalid:
|
||||||
|
return "Invalid"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("BadVariant%d", int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRand sets the random number generator to r, which implents io.Reader.
|
||||||
|
// If r.Read returns an error when the package requests random data then
|
||||||
|
// a panic will be issued.
|
||||||
|
//
|
||||||
|
// Calling SetRand with nil sets the random number generator to the default
|
||||||
|
// generator.
|
||||||
|
func SetRand(r io.Reader) {
|
||||||
|
if r == nil {
|
||||||
|
rander = rand.Reader
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rander = r
|
||||||
|
}
|
390
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid_test.go
generated
vendored
Normal file
390
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/uuid_test.go
generated
vendored
Normal file
|
@ -0,0 +1,390 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
in string
|
||||||
|
version Version
|
||||||
|
variant Variant
|
||||||
|
isuuid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var tests = []test{
|
||||||
|
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true},
|
||||||
|
|
||||||
|
{"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true},
|
||||||
|
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true},
|
||||||
|
{"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true},
|
||||||
|
{"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true},
|
||||||
|
{"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true},
|
||||||
|
{"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true},
|
||||||
|
|
||||||
|
{"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||||
|
{"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||||
|
{"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false},
|
||||||
|
{"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false},
|
||||||
|
{"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false},
|
||||||
|
{"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
var constants = []struct {
|
||||||
|
c interface{}
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{Person, "Person"},
|
||||||
|
{Group, "Group"},
|
||||||
|
{Org, "Org"},
|
||||||
|
{Invalid, "Invalid"},
|
||||||
|
{RFC4122, "RFC4122"},
|
||||||
|
{Reserved, "Reserved"},
|
||||||
|
{Microsoft, "Microsoft"},
|
||||||
|
{Future, "Future"},
|
||||||
|
{Domain(17), "Domain17"},
|
||||||
|
{Variant(42), "BadVariant42"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTest(t *testing.T, in string, tt test) {
|
||||||
|
uuid := Parse(in)
|
||||||
|
if ok := (uuid != nil); ok != tt.isuuid {
|
||||||
|
t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid)
|
||||||
|
}
|
||||||
|
if uuid == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := uuid.Variant(); v != tt.variant {
|
||||||
|
t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant)
|
||||||
|
}
|
||||||
|
if v, _ := uuid.Version(); v != tt.version {
|
||||||
|
t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUUID(t *testing.T) {
|
||||||
|
for _, tt := range tests {
|
||||||
|
testTest(t, tt.in, tt)
|
||||||
|
testTest(t, strings.ToUpper(tt.in), tt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConstants(t *testing.T) {
|
||||||
|
for x, tt := range constants {
|
||||||
|
v, ok := tt.c.(fmt.Stringer)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%x: %v: not a stringer", x, v)
|
||||||
|
} else if s := v.String(); s != tt.name {
|
||||||
|
v, _ := tt.c.(int)
|
||||||
|
t.Errorf("%x: Constant %T:%d gives %q, expected %q\n", x, tt.c, v, s, tt.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRandomUUID(t *testing.T) {
|
||||||
|
m := make(map[string]bool)
|
||||||
|
for x := 1; x < 32; x++ {
|
||||||
|
uuid := NewRandom()
|
||||||
|
s := uuid.String()
|
||||||
|
if m[s] {
|
||||||
|
t.Errorf("NewRandom returned duplicated UUID %s\n", s)
|
||||||
|
}
|
||||||
|
m[s] = true
|
||||||
|
if v, _ := uuid.Version(); v != 4 {
|
||||||
|
t.Errorf("Random UUID of version %s\n", v)
|
||||||
|
}
|
||||||
|
if uuid.Variant() != RFC4122 {
|
||||||
|
t.Errorf("Random UUID is variant %d\n", uuid.Variant())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
m := make(map[string]bool)
|
||||||
|
for x := 1; x < 32; x++ {
|
||||||
|
s := New()
|
||||||
|
if m[s] {
|
||||||
|
t.Errorf("New returned duplicated UUID %s\n", s)
|
||||||
|
}
|
||||||
|
m[s] = true
|
||||||
|
uuid := Parse(s)
|
||||||
|
if uuid == nil {
|
||||||
|
t.Errorf("New returned %q which does not decode\n", s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v, _ := uuid.Version(); v != 4 {
|
||||||
|
t.Errorf("Random UUID of version %s\n", v)
|
||||||
|
}
|
||||||
|
if uuid.Variant() != RFC4122 {
|
||||||
|
t.Errorf("Random UUID is variant %d\n", uuid.Variant())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clockSeq(t *testing.T, uuid UUID) int {
|
||||||
|
seq, ok := uuid.ClockSequence()
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s: invalid clock sequence\n", uuid)
|
||||||
|
}
|
||||||
|
return seq
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClockSeq(t *testing.T) {
|
||||||
|
// Fake time.Now for this test to return a monotonically advancing time; restore it at end.
|
||||||
|
defer func(orig func() time.Time) { timeNow = orig }(timeNow)
|
||||||
|
monTime := time.Now()
|
||||||
|
timeNow = func() time.Time {
|
||||||
|
monTime = monTime.Add(1 * time.Second)
|
||||||
|
return monTime
|
||||||
|
}
|
||||||
|
|
||||||
|
SetClockSequence(-1)
|
||||||
|
uuid1 := NewUUID()
|
||||||
|
uuid2 := NewUUID()
|
||||||
|
|
||||||
|
if clockSeq(t, uuid1) != clockSeq(t, uuid2) {
|
||||||
|
t.Errorf("clock sequence %d != %d\n", clockSeq(t, uuid1), clockSeq(t, uuid2))
|
||||||
|
}
|
||||||
|
|
||||||
|
SetClockSequence(-1)
|
||||||
|
uuid2 = NewUUID()
|
||||||
|
|
||||||
|
// Just on the very off chance we generated the same sequence
|
||||||
|
// two times we try again.
|
||||||
|
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
|
||||||
|
SetClockSequence(-1)
|
||||||
|
uuid2 = NewUUID()
|
||||||
|
}
|
||||||
|
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
|
||||||
|
t.Errorf("Duplicate clock sequence %d\n", clockSeq(t, uuid1))
|
||||||
|
}
|
||||||
|
|
||||||
|
SetClockSequence(0x1234)
|
||||||
|
uuid1 = NewUUID()
|
||||||
|
if seq := clockSeq(t, uuid1); seq != 0x1234 {
|
||||||
|
t.Errorf("%s: expected seq 0x1234 got 0x%04x\n", uuid1, seq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoding(t *testing.T) {
|
||||||
|
text := "7d444840-9dc0-11d1-b245-5ffdce74fad2"
|
||||||
|
urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2"
|
||||||
|
data := UUID{
|
||||||
|
0x7d, 0x44, 0x48, 0x40,
|
||||||
|
0x9d, 0xc0,
|
||||||
|
0x11, 0xd1,
|
||||||
|
0xb2, 0x45,
|
||||||
|
0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2,
|
||||||
|
}
|
||||||
|
if v := data.String(); v != text {
|
||||||
|
t.Errorf("%x: encoded to %s, expected %s\n", data, v, text)
|
||||||
|
}
|
||||||
|
if v := data.URN(); v != urn {
|
||||||
|
t.Errorf("%x: urn is %s, expected %s\n", data, v, urn)
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid := Parse(text)
|
||||||
|
if !Equal(uuid, data) {
|
||||||
|
t.Errorf("%s: decoded to %s, expected %s\n", text, uuid, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion1(t *testing.T) {
|
||||||
|
uuid1 := NewUUID()
|
||||||
|
uuid2 := NewUUID()
|
||||||
|
|
||||||
|
if Equal(uuid1, uuid2) {
|
||||||
|
t.Errorf("%s:duplicate uuid\n", uuid1)
|
||||||
|
}
|
||||||
|
if v, _ := uuid1.Version(); v != 1 {
|
||||||
|
t.Errorf("%s: version %s expected 1\n", uuid1, v)
|
||||||
|
}
|
||||||
|
if v, _ := uuid2.Version(); v != 1 {
|
||||||
|
t.Errorf("%s: version %s expected 1\n", uuid2, v)
|
||||||
|
}
|
||||||
|
n1 := uuid1.NodeID()
|
||||||
|
n2 := uuid2.NodeID()
|
||||||
|
if !bytes.Equal(n1, n2) {
|
||||||
|
t.Errorf("Different nodes %x != %x\n", n1, n2)
|
||||||
|
}
|
||||||
|
t1, ok := uuid1.Time()
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: invalid time\n", uuid1)
|
||||||
|
}
|
||||||
|
t2, ok := uuid2.Time()
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: invalid time\n", uuid2)
|
||||||
|
}
|
||||||
|
q1, ok := uuid1.ClockSequence()
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: invalid clock sequence\n", uuid1)
|
||||||
|
}
|
||||||
|
q2, ok := uuid2.ClockSequence()
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: invalid clock sequence", uuid2)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case t1 == t2 && q1 == q2:
|
||||||
|
t.Errorf("time stopped\n")
|
||||||
|
case t1 > t2 && q1 == q2:
|
||||||
|
t.Errorf("time reversed\n")
|
||||||
|
case t1 < t2 && q1 != q2:
|
||||||
|
t.Errorf("clock sequence chaned unexpectedly\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeAndTime(t *testing.T) {
|
||||||
|
// Time is February 5, 1998 12:30:23.136364800 AM GMT
|
||||||
|
|
||||||
|
uuid := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2")
|
||||||
|
node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2}
|
||||||
|
|
||||||
|
ts, ok := uuid.Time()
|
||||||
|
if ok {
|
||||||
|
c := time.Unix(ts.UnixTime())
|
||||||
|
want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC)
|
||||||
|
if !c.Equal(want) {
|
||||||
|
t.Errorf("Got time %v, want %v", c, want)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("%s: bad time\n", uuid)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(node, uuid.NodeID()) {
|
||||||
|
t.Errorf("Expected node %v got %v\n", node, uuid.NodeID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMD5(t *testing.T) {
|
||||||
|
uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String()
|
||||||
|
want := "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
||||||
|
if uuid != want {
|
||||||
|
t.Errorf("MD5: got %q expected %q\n", uuid, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSHA1(t *testing.T) {
|
||||||
|
uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String()
|
||||||
|
want := "886313e1-3b8a-5372-9b90-0c9aee199e5d"
|
||||||
|
if uuid != want {
|
||||||
|
t.Errorf("SHA1: got %q expected %q\n", uuid, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeID(t *testing.T) {
|
||||||
|
nid := []byte{1, 2, 3, 4, 5, 6}
|
||||||
|
SetNodeInterface("")
|
||||||
|
s := NodeInterface()
|
||||||
|
if s == "" || s == "user" {
|
||||||
|
t.Errorf("NodeInterface %q after SetInteface\n", s)
|
||||||
|
}
|
||||||
|
node1 := NodeID()
|
||||||
|
if node1 == nil {
|
||||||
|
t.Errorf("NodeID nil after SetNodeInterface\n", s)
|
||||||
|
}
|
||||||
|
SetNodeID(nid)
|
||||||
|
s = NodeInterface()
|
||||||
|
if s != "user" {
|
||||||
|
t.Errorf("Expected NodeInterface %q got %q\n", "user", s)
|
||||||
|
}
|
||||||
|
node2 := NodeID()
|
||||||
|
if node2 == nil {
|
||||||
|
t.Errorf("NodeID nil after SetNodeID\n", s)
|
||||||
|
}
|
||||||
|
if bytes.Equal(node1, node2) {
|
||||||
|
t.Errorf("NodeID not changed after SetNodeID\n", s)
|
||||||
|
} else if !bytes.Equal(nid, node2) {
|
||||||
|
t.Errorf("NodeID is %x, expected %x\n", node2, nid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) {
|
||||||
|
if uuid == nil {
|
||||||
|
t.Errorf("%s failed\n", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v, _ := uuid.Version(); v != 2 {
|
||||||
|
t.Errorf("%s: %s: expected version 2, got %s\n", name, uuid, v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v, ok := uuid.Domain(); !ok || v != domain {
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: %d: Domain failed\n", name, uuid)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%s: %s: expected domain %d, got %d\n", name, uuid, domain, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := uuid.Id(); !ok || v != id {
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: %d: Id failed\n", name, uuid)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%s: %s: expected id %d, got %d\n", name, uuid, id, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDCE(t *testing.T) {
|
||||||
|
testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678)
|
||||||
|
testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid()))
|
||||||
|
testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type badRand struct{}
|
||||||
|
|
||||||
|
func (r badRand) Read(buf []byte) (int, error) {
|
||||||
|
for i, _ := range buf {
|
||||||
|
buf[i] = byte(i)
|
||||||
|
}
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadRand(t *testing.T) {
|
||||||
|
SetRand(badRand{})
|
||||||
|
uuid1 := New()
|
||||||
|
uuid2 := New()
|
||||||
|
if uuid1 != uuid2 {
|
||||||
|
t.Errorf("execpted duplicates, got %q and %q\n", uuid1, uuid2)
|
||||||
|
}
|
||||||
|
SetRand(nil)
|
||||||
|
uuid1 = New()
|
||||||
|
uuid2 = New()
|
||||||
|
if uuid1 == uuid2 {
|
||||||
|
t.Errorf("unexecpted duplicates, got %q\n", uuid1)
|
||||||
|
}
|
||||||
|
}
|
41
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version1.go
generated
vendored
Normal file
41
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version1.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||||
|
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||||
|
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||||
|
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||||
|
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||||
|
// return the current NewUUID returns nil.
|
||||||
|
func NewUUID() UUID {
|
||||||
|
if nodeID == nil {
|
||||||
|
SetNodeInterface("")
|
||||||
|
}
|
||||||
|
|
||||||
|
now, err := GetTime()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid := make([]byte, 16)
|
||||||
|
|
||||||
|
time_low := uint32(now & 0xffffffff)
|
||||||
|
time_mid := uint16((now >> 32) & 0xffff)
|
||||||
|
time_hi := uint16((now >> 48) & 0x0fff)
|
||||||
|
time_hi |= 0x1000 // Version 1
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(uuid[0:], time_low)
|
||||||
|
binary.BigEndian.PutUint16(uuid[4:], time_mid)
|
||||||
|
binary.BigEndian.PutUint16(uuid[6:], time_hi)
|
||||||
|
binary.BigEndian.PutUint16(uuid[8:], clock_seq)
|
||||||
|
copy(uuid[10:], nodeID)
|
||||||
|
|
||||||
|
return uuid
|
||||||
|
}
|
25
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version4.go
generated
vendored
Normal file
25
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/version4.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
// Random returns a Random (Version 4) UUID or panics.
|
||||||
|
//
|
||||||
|
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||||
|
// package.
|
||||||
|
//
|
||||||
|
// A note about uniqueness derived from from the UUID Wikipedia entry:
|
||||||
|
//
|
||||||
|
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||||
|
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||||
|
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||||
|
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||||
|
// year and having one duplicate.
|
||||||
|
func NewRandom() UUID {
|
||||||
|
uuid := make([]byte, 16)
|
||||||
|
randomBits([]byte(uuid))
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||||
|
return uuid
|
||||||
|
}
|
|
@ -1,2 +1 @@
|
||||||
logrus
|
logrus
|
||||||
vendor
|
|
10
Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.2
|
||||||
|
- 1.3
|
||||||
|
- tip
|
||||||
|
install:
|
||||||
|
- go get github.com/stretchr/testify
|
||||||
|
- go get github.com/stvp/go-udp-testing
|
||||||
|
- go get github.com/tobi/airbrake-go
|
||||||
|
- go get github.com/getsentry/raven-go
|
349
Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md
generated
vendored
Normal file
349
Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md
generated
vendored
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/Sirupsen/logrus)
|
||||||
|
|
||||||
|
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||||
|
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
||||||
|
yet stable (pre 1.0), the core API is unlikely change much but please version
|
||||||
|
control your Logrus to make sure you aren't fetching latest `master` on every
|
||||||
|
build.**
|
||||||
|
|
||||||
|
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||||
|
plain text):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
|
||||||
|
or Splunk:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||||
|
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||||
|
|
||||||
|
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||||
|
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||||
|
|
||||||
|
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||||
|
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||||
|
|
||||||
|
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||||
|
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||||
|
|
||||||
|
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||||
|
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||||
|
```
|
||||||
|
|
||||||
|
With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not
|
||||||
|
attached, the output is compatible with the
|
||||||
|
[l2met](http://r.32k.io/l2met-introduction) format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10
|
||||||
|
time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122
|
||||||
|
time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10
|
||||||
|
time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9
|
||||||
|
time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
}).Info("A walrus appears")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||||
|
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
|
||||||
|
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||||
|
want:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Log as JSON instead of the default ASCII formatter.
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
|
||||||
|
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||||
|
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||||
|
log.AddHook(&logrus_airbrake.AirbrakeHook{})
|
||||||
|
|
||||||
|
// Output to stderr instead of stdout, could also be a file.
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
|
||||||
|
// Only log the warning severity or above.
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For more advanced usage such as logging to multiple locations from the same
|
||||||
|
application, you can also create an instance of the `logrus` Logger:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a new instance of the logger. You can have any number of instances.
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// The API for setting attributes is a little different than the package level
|
||||||
|
// exported logger. See Godoc.
|
||||||
|
log.Out = os.Stderr
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fields
|
||||||
|
|
||||||
|
Logrus encourages careful, structured logging though logging fields instead of
|
||||||
|
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||||
|
to send event %s to topic %s with key %d")`, you should log the much more
|
||||||
|
discoverable:
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"event": event,
|
||||||
|
"topic": topic,
|
||||||
|
"key": key,
|
||||||
|
}).Fatal("Failed to send event")
|
||||||
|
```
|
||||||
|
|
||||||
|
We've found this API forces you to think about logging in a way that produces
|
||||||
|
much more useful logging messages. We've been in countless situations where just
|
||||||
|
a single added field to a log statement that was already there would've saved us
|
||||||
|
hours. The `WithFields` call is optional.
|
||||||
|
|
||||||
|
In general, with Logrus using any of the `printf`-family functions should be
|
||||||
|
seen as a hint you should add a field, however, you can still use the
|
||||||
|
`printf`-family functions with Logrus.
|
||||||
|
|
||||||
|
#### Hooks
|
||||||
|
|
||||||
|
You can add hooks for logging levels. For example to send errors to an exception
|
||||||
|
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||||
|
multiple places simultaneously, e.g. syslog.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Not the real implementation of the Airbrake hook. Just a simple sample.
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.AddHook(new(AirbrakeHook))
|
||||||
|
}
|
||||||
|
|
||||||
|
type AirbrakeHook struct{}
|
||||||
|
|
||||||
|
// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains
|
||||||
|
// the fields for the entry. See the Fields section of the README.
|
||||||
|
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
||||||
|
err := airbrake.Notify(entry.Data["error"].(error))
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"source": "airbrake",
|
||||||
|
"endpoint": airbrake.Endpoint,
|
||||||
|
}).Info("Failed to send error to Airbrake")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Levels()` returns a slice of `Levels` the hook is fired for.
|
||||||
|
func (hook *AirbrakeHook) Levels() []log.Level {
|
||||||
|
return []log.Level{
|
||||||
|
log.ErrorLevel,
|
||||||
|
log.FatalLevel,
|
||||||
|
log.PanicLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Logrus comes with built-in hooks. Add those, or your custom hook, in `init`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/syslog"
|
||||||
|
"log/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.AddHook(new(logrus_airbrake.AirbrakeHook))
|
||||||
|
|
||||||
|
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to connect to local syslog daemon")
|
||||||
|
} else {
|
||||||
|
log.AddHook(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
|
||||||
|
Send errors to an exception tracking service compatible with the Airbrake API.
|
||||||
|
Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
|
||||||
|
|
||||||
|
* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
|
||||||
|
Send errors to the Papertrail hosted logging service via UDP.
|
||||||
|
|
||||||
|
* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
|
||||||
|
Send errors to remote syslog server.
|
||||||
|
Uses standard library `log/syslog` behind the scenes.
|
||||||
|
|
||||||
|
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
|
||||||
|
Send errors to a channel in hipchat.
|
||||||
|
|
||||||
|
#### Level logging
|
||||||
|
|
||||||
|
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.Debug("Useful debugging information.")
|
||||||
|
log.Info("Something noteworthy happened!")
|
||||||
|
log.Warn("You should probably take a look at this.")
|
||||||
|
log.Error("Something failed but I'm not quitting.")
|
||||||
|
// Calls os.Exit(1) after logging
|
||||||
|
log.Fatal("Bye.")
|
||||||
|
// Calls panic() after logging
|
||||||
|
log.Panic("I'm bailing.")
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set the logging level on a `Logger`, then it will only log entries with
|
||||||
|
that severity or anything above it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
```
|
||||||
|
|
||||||
|
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||||
|
environment if your application has that.
|
||||||
|
|
||||||
|
#### Entries
|
||||||
|
|
||||||
|
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||||
|
automatically added to all logging events:
|
||||||
|
|
||||||
|
1. `time`. The timestamp when the entry was created.
|
||||||
|
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||||
|
the `AddFields` call. E.g. `Failed to send event.`
|
||||||
|
3. `level`. The logging level. E.g. `info`.
|
||||||
|
|
||||||
|
#### Environments
|
||||||
|
|
||||||
|
Logrus has no notion of environment.
|
||||||
|
|
||||||
|
If you wish for hooks and formatters to only be used in specific environments,
|
||||||
|
you should handle that yourself. For example, if your application has a global
|
||||||
|
variable `Environment`, which is a string representation of the environment you
|
||||||
|
could do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// do something here to set environment depending on an environment variable
|
||||||
|
// or command-line flag
|
||||||
|
if Environment == "production" {
|
||||||
|
log.SetFormatter(logrus.JSONFormatter)
|
||||||
|
} else {
|
||||||
|
// The TextFormatter is default, you don't actually have to do this.
|
||||||
|
log.SetFormatter(logrus.TextFormatter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration is how `logrus` was intended to be used, but JSON in
|
||||||
|
production is mostly only useful if you do log aggregation with tools like
|
||||||
|
Splunk or Logstash.
|
||||||
|
|
||||||
|
#### Formatters
|
||||||
|
|
||||||
|
The built-in logging formatters are:
|
||||||
|
|
||||||
|
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||||
|
without colors.
|
||||||
|
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||||
|
field to `true`. To force no colored output even if there is a TTY set the
|
||||||
|
`DisableColors` field to `true`
|
||||||
|
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||||
|
|
||||||
|
Third party logging formatters:
|
||||||
|
|
||||||
|
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||||
|
|
||||||
|
You can define your formatter by implementing the `Formatter` interface,
|
||||||
|
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||||
|
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||||
|
default ones (see Entries section above):
|
||||||
|
|
||||||
|
```go
|
||||||
|
type MyJSONFormatter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetFormatter(new(MyJSONFormatter))
|
||||||
|
|
||||||
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
// Note this doesn't include Time, Level and Message which are available on
|
||||||
|
// the Entry. Consult `godoc` on information about those fields or read the
|
||||||
|
// source of the official loggers.
|
||||||
|
serialized, err := json.Marshal(entry.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||||
|
}
|
||||||
|
return append(serialized, '\n'), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Rotation
|
||||||
|
|
||||||
|
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||||
|
external program (like `logrotated(8)`) that can compress and delete old log
|
||||||
|
entries. It should not be a feature of the application-level logger.
|
||||||
|
|
||||||
|
|
||||||
|
[godoc]: https://godoc.org/github.com/Sirupsen/logrus
|
248
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go
generated
vendored
Normal file
248
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go
generated
vendored
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||||
|
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
||||||
|
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
||||||
|
// passed around as much as you wish to avoid field duplication.
|
||||||
|
type Entry struct {
|
||||||
|
Logger *Logger
|
||||||
|
|
||||||
|
// Contains all the fields set by the user.
|
||||||
|
Data Fields
|
||||||
|
|
||||||
|
// Time at which the log entry was created
|
||||||
|
Time time.Time
|
||||||
|
|
||||||
|
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Level Level
|
||||||
|
|
||||||
|
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntry(logger *Logger) *Entry {
|
||||||
|
return &Entry{
|
||||||
|
Logger: logger,
|
||||||
|
// Default is three fields, give a little extra room
|
||||||
|
Data: make(Fields, 5),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a reader for the entry, which is a proxy to the formatter.
|
||||||
|
func (entry *Entry) Reader() (*bytes.Buffer, error) {
|
||||||
|
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||||
|
return bytes.NewBuffer(serialized), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the string representation from the reader and ultimately the
|
||||||
|
// formatter.
|
||||||
|
func (entry *Entry) String() (string, error) {
|
||||||
|
reader, err := entry.Reader()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a single field to the Entry.
|
||||||
|
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||||
|
return entry.WithFields(Fields{key: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a map of fields to the Entry.
|
||||||
|
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
|
data := Fields{}
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range fields {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: data}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) log(level Level, msg string) {
|
||||||
|
entry.Time = time.Now()
|
||||||
|
entry.Level = level
|
||||||
|
entry.Message = msg
|
||||||
|
|
||||||
|
if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := entry.Reader()
|
||||||
|
if err != nil {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
defer entry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
_, err = io.Copy(entry.Logger.Out, reader)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// To avoid Entry#log() returning a value that only would make sense for
|
||||||
|
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||||
|
// directly here.
|
||||||
|
if level <= PanicLevel {
|
||||||
|
panic(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Debug(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Print(args ...interface{}) {
|
||||||
|
entry.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Info(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warn(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Error(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatal(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panic(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
panic(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Printf family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.Debug(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.Info(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||||
|
entry.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.Warn(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||||
|
entry.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.Error(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.Fatal(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.Panic(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Println family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Debugln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= DebugLevel {
|
||||||
|
entry.Debug(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infoln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= InfoLevel {
|
||||||
|
entry.Info(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Println(args ...interface{}) {
|
||||||
|
entry.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= WarnLevel {
|
||||||
|
entry.Warn(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningln(args ...interface{}) {
|
||||||
|
entry.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= ErrorLevel {
|
||||||
|
entry.Error(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= FatalLevel {
|
||||||
|
entry.Fatal(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicln(args ...interface{}) {
|
||||||
|
if entry.Logger.Level >= PanicLevel {
|
||||||
|
entry.Panic(entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||||
|
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||||
|
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||||
|
// string allocation, we do the simplest thing.
|
||||||
|
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||||
|
msg := fmt.Sprintln(args...)
|
||||||
|
return msg[:len(msg)-1]
|
||||||
|
}
|
53
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEntryPanicln(t *testing.T) {
|
||||||
|
errBoom := fmt.Errorf("boom time")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
p := recover()
|
||||||
|
assert.NotNil(t, p)
|
||||||
|
|
||||||
|
switch pVal := p.(type) {
|
||||||
|
case *Entry:
|
||||||
|
assert.Equal(t, "kaboom", pVal.Message)
|
||||||
|
assert.Equal(t, errBoom, pVal.Data["err"])
|
||||||
|
default:
|
||||||
|
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &bytes.Buffer{}
|
||||||
|
entry := NewEntry(logger)
|
||||||
|
entry.WithField("err", errBoom).Panicln("kaboom")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEntryPanicf(t *testing.T) {
|
||||||
|
errBoom := fmt.Errorf("boom again")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
p := recover()
|
||||||
|
assert.NotNil(t, p)
|
||||||
|
|
||||||
|
switch pVal := p.(type) {
|
||||||
|
case *Entry:
|
||||||
|
assert.Equal(t, "kaboom true", pVal.Message)
|
||||||
|
assert.Equal(t, errBoom, pVal.Data["err"])
|
||||||
|
default:
|
||||||
|
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &bytes.Buffer{}
|
||||||
|
entry := NewEntry(logger)
|
||||||
|
entry.WithField("err", errBoom).Panicf("kaboom %v", true)
|
||||||
|
}
|
40
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go
generated
vendored
Normal file
40
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Formatter = new(logrus.JSONFormatter)
|
||||||
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"err": err,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "orca",
|
||||||
|
"size": 9009,
|
||||||
|
}).Panic("It's over 9000!")
|
||||||
|
}
|
35
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
Normal file
35
Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/airbrake"
|
||||||
|
"github.com/tobi/airbrake-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Formatter = new(logrus.TextFormatter) // default
|
||||||
|
log.Hooks.Add(new(logrus_airbrake.AirbrakeHook))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml"
|
||||||
|
airbrake.ApiKey = "whatever"
|
||||||
|
airbrake.Environment = "production"
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -11,54 +9,37 @@ var (
|
||||||
std = New()
|
std = New()
|
||||||
)
|
)
|
||||||
|
|
||||||
func StandardLogger() *Logger {
|
|
||||||
return std
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOutput sets the standard logger output.
|
// SetOutput sets the standard logger output.
|
||||||
func SetOutput(out io.Writer) {
|
func SetOutput(out io.Writer) {
|
||||||
std.SetOutput(out)
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Out = out
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFormatter sets the standard logger formatter.
|
// SetFormatter sets the standard logger formatter.
|
||||||
func SetFormatter(formatter Formatter) {
|
func SetFormatter(formatter Formatter) {
|
||||||
std.SetFormatter(formatter)
|
std.mu.Lock()
|
||||||
}
|
defer std.mu.Unlock()
|
||||||
|
std.Formatter = formatter
|
||||||
// SetReportCaller sets whether the standard logger will include the calling
|
|
||||||
// method as a field.
|
|
||||||
func SetReportCaller(include bool) {
|
|
||||||
std.SetReportCaller(include)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLevel sets the standard logger level.
|
// SetLevel sets the standard logger level.
|
||||||
func SetLevel(level Level) {
|
func SetLevel(level Level) {
|
||||||
std.SetLevel(level)
|
std.mu.Lock()
|
||||||
|
defer std.mu.Unlock()
|
||||||
|
std.Level = level
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLevel returns the standard logger level.
|
// GetLevel returns the standard logger level.
|
||||||
func GetLevel() Level {
|
func GetLevel() Level {
|
||||||
return std.GetLevel()
|
return std.Level
|
||||||
}
|
|
||||||
|
|
||||||
// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
|
|
||||||
func IsLevelEnabled(level Level) bool {
|
|
||||||
return std.IsLevelEnabled(level)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddHook adds a hook to the standard logger hooks.
|
// AddHook adds a hook to the standard logger hooks.
|
||||||
func AddHook(hook Hook) {
|
func AddHook(hook Hook) {
|
||||||
std.AddHook(hook)
|
std.mu.Lock()
|
||||||
}
|
defer std.mu.Unlock()
|
||||||
|
std.Hooks.Add(hook)
|
||||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
|
||||||
func WithError(err error) *Entry {
|
|
||||||
return std.WithField(ErrorKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithContext creates an entry from the standard logger and adds a context to it.
|
|
||||||
func WithContext(ctx context.Context) *Entry {
|
|
||||||
return std.WithContext(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithField creates an entry from the standard logger and adds a field to
|
// WithField creates an entry from the standard logger and adds a field to
|
||||||
|
@ -80,20 +61,6 @@ func WithFields(fields Fields) *Entry {
|
||||||
return std.WithFields(fields)
|
return std.WithFields(fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTime creates an entry from the standard logger and overrides the time of
|
|
||||||
// logs generated with it.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithTime(t time.Time) *Entry {
|
|
||||||
return std.WithTime(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace logs a message at level Trace on the standard logger.
|
|
||||||
func Trace(args ...interface{}) {
|
|
||||||
std.Trace(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug logs a message at level Debug on the standard logger.
|
// Debug logs a message at level Debug on the standard logger.
|
||||||
func Debug(args ...interface{}) {
|
func Debug(args ...interface{}) {
|
||||||
std.Debug(args...)
|
std.Debug(args...)
|
||||||
|
@ -129,16 +96,11 @@ func Panic(args ...interface{}) {
|
||||||
std.Panic(args...)
|
std.Panic(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
// Fatal logs a message at level Fatal on the standard logger.
|
||||||
func Fatal(args ...interface{}) {
|
func Fatal(args ...interface{}) {
|
||||||
std.Fatal(args...)
|
std.Fatal(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tracef logs a message at level Trace on the standard logger.
|
|
||||||
func Tracef(format string, args ...interface{}) {
|
|
||||||
std.Tracef(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf logs a message at level Debug on the standard logger.
|
// Debugf logs a message at level Debug on the standard logger.
|
||||||
func Debugf(format string, args ...interface{}) {
|
func Debugf(format string, args ...interface{}) {
|
||||||
std.Debugf(format, args...)
|
std.Debugf(format, args...)
|
||||||
|
@ -174,16 +136,11 @@ func Panicf(format string, args ...interface{}) {
|
||||||
std.Panicf(format, args...)
|
std.Panicf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
// Fatalf logs a message at level Fatal on the standard logger.
|
||||||
func Fatalf(format string, args ...interface{}) {
|
func Fatalf(format string, args ...interface{}) {
|
||||||
std.Fatalf(format, args...)
|
std.Fatalf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traceln logs a message at level Trace on the standard logger.
|
|
||||||
func Traceln(args ...interface{}) {
|
|
||||||
std.Traceln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugln logs a message at level Debug on the standard logger.
|
// Debugln logs a message at level Debug on the standard logger.
|
||||||
func Debugln(args ...interface{}) {
|
func Debugln(args ...interface{}) {
|
||||||
std.Debugln(args...)
|
std.Debugln(args...)
|
||||||
|
@ -219,7 +176,7 @@ func Panicln(args ...interface{}) {
|
||||||
std.Panicln(args...)
|
std.Panicln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
// Fatalln logs a message at level Fatal on the standard logger.
|
||||||
func Fatalln(args ...interface{}) {
|
func Fatalln(args ...interface{}) {
|
||||||
std.Fatalln(args...)
|
std.Fatalln(args...)
|
||||||
}
|
}
|
44
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go
generated
vendored
Normal file
44
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
|
// `Entry`. It exposes all the fields, including the default ones:
|
||||||
|
//
|
||||||
|
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||||
|
// * `entry.Data["time"]`. The timestamp.
|
||||||
|
// * `entry.Data["level"]. The level the entry was logged at.
|
||||||
|
//
|
||||||
|
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||||
|
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||||
|
// logged to `logger.Out`.
|
||||||
|
type Formatter interface {
|
||||||
|
Format(*Entry) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||||
|
// dumping it. If this code wasn't there doing:
|
||||||
|
//
|
||||||
|
// logrus.WithField("level", 1).Info("hello")
|
||||||
|
//
|
||||||
|
// Would just silently drop the user provided level. Instead with this code
|
||||||
|
// it'll logged as:
|
||||||
|
//
|
||||||
|
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||||
|
//
|
||||||
|
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||||
|
// avoid code duplication between the two default formatters.
|
||||||
|
func prefixFieldClashes(entry *Entry) {
|
||||||
|
_, ok := entry.Data["time"]
|
||||||
|
if ok {
|
||||||
|
entry.Data["fields.time"] = entry.Data["time"]
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = entry.Data["msg"]
|
||||||
|
if ok {
|
||||||
|
entry.Data["fields.msg"] = entry.Data["msg"]
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = entry.Data["level"]
|
||||||
|
if ok {
|
||||||
|
entry.Data["fields.level"] = entry.Data["level"]
|
||||||
|
}
|
||||||
|
}
|
88
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go
generated
vendored
Normal file
88
Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// smallFields is a small size data set for benchmarking
|
||||||
|
var smallFields = Fields{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qux",
|
||||||
|
"one": "two",
|
||||||
|
"three": "four",
|
||||||
|
}
|
||||||
|
|
||||||
|
// largeFields is a large size data set for benchmarking
|
||||||
|
var largeFields = Fields{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qux",
|
||||||
|
"one": "two",
|
||||||
|
"three": "four",
|
||||||
|
"five": "six",
|
||||||
|
"seven": "eight",
|
||||||
|
"nine": "ten",
|
||||||
|
"eleven": "twelve",
|
||||||
|
"thirteen": "fourteen",
|
||||||
|
"fifteen": "sixteen",
|
||||||
|
"seventeen": "eighteen",
|
||||||
|
"nineteen": "twenty",
|
||||||
|
"a": "b",
|
||||||
|
"c": "d",
|
||||||
|
"e": "f",
|
||||||
|
"g": "h",
|
||||||
|
"i": "j",
|
||||||
|
"k": "l",
|
||||||
|
"m": "n",
|
||||||
|
"o": "p",
|
||||||
|
"q": "r",
|
||||||
|
"s": "t",
|
||||||
|
"u": "v",
|
||||||
|
"w": "x",
|
||||||
|
"y": "z",
|
||||||
|
"this": "will",
|
||||||
|
"make": "thirty",
|
||||||
|
"entries": "yeah",
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmallTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmallJSONFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &JSONFormatter{}, smallFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeJSONFormatter(b *testing.B) {
|
||||||
|
doBenchmark(b, &JSONFormatter{}, largeFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
|
||||||
|
entry := &Entry{
|
||||||
|
Time: time.Time{},
|
||||||
|
Level: InfoLevel,
|
||||||
|
Message: "message",
|
||||||
|
Data: fields,
|
||||||
|
}
|
||||||
|
var d []byte
|
||||||
|
var err error
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
d, err = formatter.Format(entry)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(d)))
|
||||||
|
}
|
||||||
|
}
|
122
Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go
generated
vendored
Normal file
122
Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestHook struct {
|
||||||
|
Fired bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *TestHook) Fire(entry *Entry) error {
|
||||||
|
hook.Fired = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *TestHook) Levels() []Level {
|
||||||
|
return []Level{
|
||||||
|
DebugLevel,
|
||||||
|
InfoLevel,
|
||||||
|
WarnLevel,
|
||||||
|
ErrorLevel,
|
||||||
|
FatalLevel,
|
||||||
|
PanicLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHookFires(t *testing.T) {
|
||||||
|
hook := new(TestHook)
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
assert.Equal(t, hook.Fired, false)
|
||||||
|
|
||||||
|
log.Print("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, hook.Fired, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModifyHook struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *ModifyHook) Fire(entry *Entry) error {
|
||||||
|
entry.Data["wow"] = "whale"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *ModifyHook) Levels() []Level {
|
||||||
|
return []Level{
|
||||||
|
DebugLevel,
|
||||||
|
InfoLevel,
|
||||||
|
WarnLevel,
|
||||||
|
ErrorLevel,
|
||||||
|
FatalLevel,
|
||||||
|
PanicLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHookCanModifyEntry(t *testing.T) {
|
||||||
|
hook := new(ModifyHook)
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
log.WithField("wow", "elephant").Print("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["wow"], "whale")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCanFireMultipleHooks(t *testing.T) {
|
||||||
|
hook1 := new(ModifyHook)
|
||||||
|
hook2 := new(TestHook)
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Hooks.Add(hook1)
|
||||||
|
log.Hooks.Add(hook2)
|
||||||
|
|
||||||
|
log.WithField("wow", "elephant").Print("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["wow"], "whale")
|
||||||
|
assert.Equal(t, hook2.Fired, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorHook struct {
|
||||||
|
Fired bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *ErrorHook) Fire(entry *Entry) error {
|
||||||
|
hook.Fired = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *ErrorHook) Levels() []Level {
|
||||||
|
return []Level{
|
||||||
|
ErrorLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
|
||||||
|
hook := new(ErrorHook)
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
log.Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, hook.Fired, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorHookShouldFireOnError(t *testing.T) {
|
||||||
|
hook := new(ErrorHook)
|
||||||
|
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
log.Error("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, hook.Fired, true)
|
||||||
|
})
|
||||||
|
}
|
|
@ -11,11 +11,11 @@ type Hook interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal type for storing the hooks on a logger instance.
|
// Internal type for storing the hooks on a logger instance.
|
||||||
type LevelHooks map[Level][]Hook
|
type levelHooks map[Level][]Hook
|
||||||
|
|
||||||
// Add a hook to an instance of logger. This is called with
|
// Add a hook to an instance of logger. This is called with
|
||||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||||
func (hooks LevelHooks) Add(hook Hook) {
|
func (hooks levelHooks) Add(hook Hook) {
|
||||||
for _, level := range hook.Levels() {
|
for _, level := range hook.Levels() {
|
||||||
hooks[level] = append(hooks[level], hook)
|
hooks[level] = append(hooks[level], hook)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ func (hooks LevelHooks) Add(hook Hook) {
|
||||||
|
|
||||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||||
// appropriate hooks for a log entry.
|
// appropriate hooks for a log entry.
|
||||||
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
func (hooks levelHooks) Fire(level Level, entry *Entry) error {
|
||||||
for _, hook := range hooks[level] {
|
for _, hook := range hooks[level] {
|
||||||
if err := hook.Fire(entry); err != nil {
|
if err := hook.Fire(entry); err != nil {
|
||||||
return err
|
return err
|
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package logrus_airbrake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/tobi/airbrake-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AirbrakeHook to send exceptions to an exception-tracking service compatible
|
||||||
|
// with the Airbrake API. You must set:
|
||||||
|
// * airbrake.Endpoint
|
||||||
|
// * airbrake.ApiKey
|
||||||
|
// * airbrake.Environment (only sends exceptions when set to "production")
|
||||||
|
//
|
||||||
|
// Before using this hook, to send an error. Entries that trigger an Error,
|
||||||
|
// Fatal or Panic should now include an "error" field to send to Airbrake.
|
||||||
|
type AirbrakeHook struct{}
|
||||||
|
|
||||||
|
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
|
||||||
|
if entry.Data["error"] == nil {
|
||||||
|
entry.Logger.WithFields(logrus.Fields{
|
||||||
|
"source": "airbrake",
|
||||||
|
"endpoint": airbrake.Endpoint,
|
||||||
|
}).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err, ok := entry.Data["error"].(error)
|
||||||
|
if !ok {
|
||||||
|
entry.Logger.WithFields(logrus.Fields{
|
||||||
|
"source": "airbrake",
|
||||||
|
"endpoint": airbrake.Endpoint,
|
||||||
|
}).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
airErr := airbrake.Notify(err)
|
||||||
|
if airErr != nil {
|
||||||
|
entry.Logger.WithFields(logrus.Fields{
|
||||||
|
"source": "airbrake",
|
||||||
|
"endpoint": airbrake.Endpoint,
|
||||||
|
"error": airErr,
|
||||||
|
}).Warn("Failed to send error to Airbrake")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *AirbrakeHook) Levels() []logrus.Level {
|
||||||
|
return []logrus.Level{
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.PanicLevel,
|
||||||
|
}
|
||||||
|
}
|
28
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Papertrail Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
|
||||||
|
|
||||||
|
[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts).
|
||||||
|
|
||||||
|
In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`.
|
||||||
|
|
||||||
|
For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"log/syslog"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/papertrail"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log := logrus.New()
|
||||||
|
hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package logrus_papertrail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
format = "Jan 2 15:04:05"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PapertrailHook to send logs to a logging service compatible with the Papertrail API.
|
||||||
|
type PapertrailHook struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
AppName string
|
||||||
|
UDPConn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPapertrailHook creates a hook to be added to an instance of logger.
|
||||||
|
func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) {
|
||||||
|
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port))
|
||||||
|
return &PapertrailHook{host, port, appName, conn}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire is called when a log event is fired.
|
||||||
|
func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
|
||||||
|
date := time.Now().Format(format)
|
||||||
|
payload := fmt.Sprintf("<22> %s %s: [%s] %s", date, hook.AppName, entry.Level, entry.Message)
|
||||||
|
|
||||||
|
bytesWritten, err := hook.UDPConn.Write([]byte(payload))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Levels returns the available logging levels.
|
||||||
|
func (hook *PapertrailHook) Levels() []logrus.Level {
|
||||||
|
return []logrus.Level{
|
||||||
|
logrus.PanicLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
logrus.WarnLevel,
|
||||||
|
logrus.InfoLevel,
|
||||||
|
logrus.DebugLevel,
|
||||||
|
}
|
||||||
|
}
|
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package logrus_papertrail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/stvp/go-udp-testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWritingToUDP(t *testing.T) {
|
||||||
|
port := 16661
|
||||||
|
udp.SetAddr(fmt.Sprintf(":%d", port))
|
||||||
|
|
||||||
|
hook, err := NewPapertrailHook("localhost", port, "test")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to connect to local UDP server.")
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logrus.New()
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
|
||||||
|
udp.ShouldReceive(t, "foo", func() {
|
||||||
|
log.Info("foo")
|
||||||
|
})
|
||||||
|
}
|
61
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# Sentry Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
|
||||||
|
|
||||||
|
[Sentry](https://getsentry.com) provides both self-hosted and hosted
|
||||||
|
solutions for exception tracking.
|
||||||
|
Both client and server are
|
||||||
|
[open source](https://github.com/getsentry/sentry).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Every sentry application defined on the server gets a different
|
||||||
|
[DSN](https://www.getsentry.com/docs/). In the example below replace
|
||||||
|
`YOUR_DSN` with the one created for your application.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/sentry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log := logrus.New()
|
||||||
|
hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{
|
||||||
|
logrus.PanicLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Special fields
|
||||||
|
|
||||||
|
Some logrus fields have a special meaning in this hook,
|
||||||
|
these are server_name and logger.
|
||||||
|
When logs are sent to sentry these fields are treated differently.
|
||||||
|
- server_name (also known as hostname) is the name of the server which
|
||||||
|
is logging the event (hostname.example.com)
|
||||||
|
- logger is the part of the application which is logging the event.
|
||||||
|
In go this usually means setting it to the name of the package.
|
||||||
|
|
||||||
|
## Timeout
|
||||||
|
|
||||||
|
`Timeout` is the time the sentry hook will wait for a response
|
||||||
|
from the sentry server.
|
||||||
|
|
||||||
|
If this time elapses with no response from
|
||||||
|
the server an error will be returned.
|
||||||
|
|
||||||
|
If `Timeout` is set to 0 the SentryHook will not wait for a reply
|
||||||
|
and will assume a correct delivery.
|
||||||
|
|
||||||
|
The SentryHook has a default timeout of `100 milliseconds` when created
|
||||||
|
with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field:
|
||||||
|
|
||||||
|
```go
|
||||||
|
hook, _ := logrus_sentry.NewSentryHook(...)
|
||||||
|
hook.Timeout = 20*time.Seconds
|
||||||
|
```
|
100
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go
generated
vendored
Normal file
100
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go
generated
vendored
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package logrus_sentry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
severityMap = map[logrus.Level]raven.Severity{
|
||||||
|
logrus.DebugLevel: raven.DEBUG,
|
||||||
|
logrus.InfoLevel: raven.INFO,
|
||||||
|
logrus.WarnLevel: raven.WARNING,
|
||||||
|
logrus.ErrorLevel: raven.ERROR,
|
||||||
|
logrus.FatalLevel: raven.FATAL,
|
||||||
|
logrus.PanicLevel: raven.FATAL,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func getAndDel(d logrus.Fields, key string) (string, bool) {
|
||||||
|
var (
|
||||||
|
ok bool
|
||||||
|
v interface{}
|
||||||
|
val string
|
||||||
|
)
|
||||||
|
if v, ok = d[key]; !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok = v.(string); !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
delete(d, key)
|
||||||
|
return val, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SentryHook delivers logs to a sentry server.
|
||||||
|
type SentryHook struct {
|
||||||
|
// Timeout sets the time to wait for a delivery error from the sentry server.
|
||||||
|
// If this is set to zero the server will not wait for any response and will
|
||||||
|
// consider the message correctly sent
|
||||||
|
Timeout time.Duration
|
||||||
|
|
||||||
|
client *raven.Client
|
||||||
|
levels []logrus.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSentryHook creates a hook to be added to an instance of logger
|
||||||
|
// and initializes the raven client.
|
||||||
|
// This method sets the timeout to 100 milliseconds.
|
||||||
|
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
|
||||||
|
client, err := raven.NewClient(DSN, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &SentryHook{100 * time.Millisecond, client, levels}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when an event should be sent to sentry
|
||||||
|
// Special fields that sentry uses to give more information to the server
|
||||||
|
// are extracted from entry.Data (if they are found)
|
||||||
|
// These fields are: logger and server_name
|
||||||
|
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
|
||||||
|
packet := &raven.Packet{
|
||||||
|
Message: entry.Message,
|
||||||
|
Timestamp: raven.Timestamp(entry.Time),
|
||||||
|
Level: severityMap[entry.Level],
|
||||||
|
Platform: "go",
|
||||||
|
}
|
||||||
|
|
||||||
|
d := entry.Data
|
||||||
|
|
||||||
|
if logger, ok := getAndDel(d, "logger"); ok {
|
||||||
|
packet.Logger = logger
|
||||||
|
}
|
||||||
|
if serverName, ok := getAndDel(d, "server_name"); ok {
|
||||||
|
packet.ServerName = serverName
|
||||||
|
}
|
||||||
|
packet.Extra = map[string]interface{}(d)
|
||||||
|
|
||||||
|
_, errCh := hook.client.Capture(packet, nil)
|
||||||
|
timeout := hook.Timeout
|
||||||
|
if timeout != 0 {
|
||||||
|
timeoutCh := time.After(timeout)
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
return err
|
||||||
|
case <-timeoutCh:
|
||||||
|
return fmt.Errorf("no response from sentry server in %s", timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Levels returns the available logging levels.
|
||||||
|
func (hook *SentryHook) Levels() []logrus.Level {
|
||||||
|
return hook.levels
|
||||||
|
}
|
97
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go
generated
vendored
Normal file
97
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package logrus_sentry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
message = "error message"
|
||||||
|
server_name = "testserver.internal"
|
||||||
|
logger_name = "test.logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getTestLogger() *logrus.Logger {
|
||||||
|
l := logrus.New()
|
||||||
|
l.Out = ioutil.Discard
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTestDSN(t *testing.T, tf func(string, <-chan *raven.Packet)) {
|
||||||
|
pch := make(chan *raven.Packet, 1)
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
defer req.Body.Close()
|
||||||
|
d := json.NewDecoder(req.Body)
|
||||||
|
p := &raven.Packet{}
|
||||||
|
err := d.Decode(p)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
pch <- p
|
||||||
|
}))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
fragments := strings.SplitN(s.URL, "://", 2)
|
||||||
|
dsn := fmt.Sprintf(
|
||||||
|
"%s://public:secret@%s/sentry/project-id",
|
||||||
|
fragments[0],
|
||||||
|
fragments[1],
|
||||||
|
)
|
||||||
|
tf(dsn, pch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpecialFields(t *testing.T) {
|
||||||
|
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
|
||||||
|
logger := getTestLogger()
|
||||||
|
|
||||||
|
hook, err := NewSentryHook(dsn, []logrus.Level{
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
logger.WithFields(logrus.Fields{
|
||||||
|
"server_name": server_name,
|
||||||
|
"logger": logger_name,
|
||||||
|
}).Error(message)
|
||||||
|
|
||||||
|
packet := <-pch
|
||||||
|
if packet.Logger != logger_name {
|
||||||
|
t.Errorf("logger should have been %s, was %s", logger_name, packet.Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.ServerName != server_name {
|
||||||
|
t.Errorf("server_name should have been %s, was %s", server_name, packet.ServerName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSentryHandler(t *testing.T) {
|
||||||
|
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
|
||||||
|
logger := getTestLogger()
|
||||||
|
hook, err := NewSentryHook(dsn, []logrus.Level{
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
|
||||||
|
logger.Error(message)
|
||||||
|
packet := <-pch
|
||||||
|
if packet.Message != message {
|
||||||
|
t.Errorf("message should have been %s, was %s", message, packet.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"log/syslog"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/Sirupsen/logrus/hooks/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log := logrus.New()
|
||||||
|
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
59
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package logrus_syslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"log/syslog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SyslogHook to send logs via syslog.
|
||||||
|
type SyslogHook struct {
|
||||||
|
Writer *syslog.Writer
|
||||||
|
SyslogNetwork string
|
||||||
|
SyslogRaddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a hook to be added to an instance of logger. This is called with
|
||||||
|
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
|
||||||
|
// `if err == nil { log.Hooks.Add(hook) }`
|
||||||
|
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
|
||||||
|
w, err := syslog.Dial(network, raddr, priority, tag)
|
||||||
|
return &SyslogHook{w, network, raddr}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
|
||||||
|
line, err := entry.String()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch entry.Level {
|
||||||
|
case logrus.PanicLevel:
|
||||||
|
return hook.Writer.Crit(line)
|
||||||
|
case logrus.FatalLevel:
|
||||||
|
return hook.Writer.Crit(line)
|
||||||
|
case logrus.ErrorLevel:
|
||||||
|
return hook.Writer.Err(line)
|
||||||
|
case logrus.WarnLevel:
|
||||||
|
return hook.Writer.Warning(line)
|
||||||
|
case logrus.InfoLevel:
|
||||||
|
return hook.Writer.Info(line)
|
||||||
|
case logrus.DebugLevel:
|
||||||
|
return hook.Writer.Debug(line)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hook *SyslogHook) Levels() []logrus.Level {
|
||||||
|
return []logrus.Level{
|
||||||
|
logrus.PanicLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
logrus.WarnLevel,
|
||||||
|
logrus.InfoLevel,
|
||||||
|
logrus.DebugLevel,
|
||||||
|
}
|
||||||
|
}
|
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package logrus_syslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"log/syslog"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLocalhostAddAndPrint(t *testing.T) {
|
||||||
|
log := logrus.New()
|
||||||
|
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to connect to local syslog.")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Hooks.Add(hook)
|
||||||
|
|
||||||
|
for _, level := range hook.Levels() {
|
||||||
|
if len(log.Hooks[level]) != 1 {
|
||||||
|
t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Congratulations!")
|
||||||
|
}
|
22
Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JSONFormatter struct{}
|
||||||
|
|
||||||
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
prefixFieldClashes(entry)
|
||||||
|
entry.Data["time"] = entry.Time.Format(time.RFC3339)
|
||||||
|
entry.Data["msg"] = entry.Message
|
||||||
|
entry.Data["level"] = entry.Level.String()
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(entry.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||||
|
}
|
||||||
|
return append(serialized, '\n'), nil
|
||||||
|
}
|
161
Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go
generated
vendored
Normal file
161
Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go
generated
vendored
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||||
|
// file, or leave it default which is `os.Stdout`. You can also set this to
|
||||||
|
// something more adventorous, such as logging to Kafka.
|
||||||
|
Out io.Writer
|
||||||
|
// Hooks for the logger instance. These allow firing events based on logging
|
||||||
|
// levels and log entries. For example, to send errors to an error tracking
|
||||||
|
// service, log to StatsD or dump the core on fatal errors.
|
||||||
|
Hooks levelHooks
|
||||||
|
// All log entries pass through the formatter before logged to Out. The
|
||||||
|
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||||
|
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||||
|
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||||
|
// own that implements the `Formatter` interface, see the `README` or included
|
||||||
|
// formatters for examples.
|
||||||
|
Formatter Formatter
|
||||||
|
// The logging level the logger should log at. This is typically (and defaults
|
||||||
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
|
// logged. `logrus.Debug` is useful in
|
||||||
|
Level Level
|
||||||
|
// Used to sync writing to the log.
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||||
|
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||||
|
// instantiate your own:
|
||||||
|
//
|
||||||
|
// var log = &Logger{
|
||||||
|
// Out: os.Stderr,
|
||||||
|
// Formatter: new(JSONFormatter),
|
||||||
|
// Hooks: make(levelHooks),
|
||||||
|
// Level: logrus.Debug,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// It's recommended to make this a global instance called `log`.
|
||||||
|
func New() *Logger {
|
||||||
|
return &Logger{
|
||||||
|
Out: os.Stdout,
|
||||||
|
Formatter: new(TextFormatter),
|
||||||
|
Hooks: make(levelHooks),
|
||||||
|
Level: InfoLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a field to the log entry, note that you it doesn't log until you call
|
||||||
|
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
|
||||||
|
// Ff you want multiple fields, use `WithFields`.
|
||||||
|
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||||
|
return NewEntry(logger).WithField(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||||
|
// each `Field`.
|
||||||
|
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||||
|
return NewEntry(logger).WithFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Debugf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Fatalf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
|
NewEntry(logger).Panicf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debug(args ...interface{}) {
|
||||||
|
NewEntry(logger).Debug(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Info(args ...interface{}) {
|
||||||
|
NewEntry(logger).Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Print(args ...interface{}) {
|
||||||
|
NewEntry(logger).Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warn(args ...interface{}) {
|
||||||
|
NewEntry(logger).Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warning(args ...interface{}) {
|
||||||
|
NewEntry(logger).Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Error(args ...interface{}) {
|
||||||
|
NewEntry(logger).Error(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
|
NewEntry(logger).Fatal(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panic(args ...interface{}) {
|
||||||
|
NewEntry(logger).Panic(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Debugln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infoln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Println(args ...interface{}) {
|
||||||
|
NewEntry(logger).Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Errorln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Fatalln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
|
NewEntry(logger).Panicln(args...)
|
||||||
|
}
|
94
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go
generated
vendored
Normal file
94
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fields type, used to pass to `WithFields`.
|
||||||
|
type Fields map[string]interface{}
|
||||||
|
|
||||||
|
// Level type
|
||||||
|
type Level uint8
|
||||||
|
|
||||||
|
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||||
|
func (level Level) String() string {
|
||||||
|
switch level {
|
||||||
|
case DebugLevel:
|
||||||
|
return "debug"
|
||||||
|
case InfoLevel:
|
||||||
|
return "info"
|
||||||
|
case WarnLevel:
|
||||||
|
return "warning"
|
||||||
|
case ErrorLevel:
|
||||||
|
return "error"
|
||||||
|
case FatalLevel:
|
||||||
|
return "fatal"
|
||||||
|
case PanicLevel:
|
||||||
|
return "panic"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||||
|
func ParseLevel(lvl string) (Level, error) {
|
||||||
|
switch lvl {
|
||||||
|
case "panic":
|
||||||
|
return PanicLevel, nil
|
||||||
|
case "fatal":
|
||||||
|
return FatalLevel, nil
|
||||||
|
case "error":
|
||||||
|
return ErrorLevel, nil
|
||||||
|
case "warn", "warning":
|
||||||
|
return WarnLevel, nil
|
||||||
|
case "info":
|
||||||
|
return InfoLevel, nil
|
||||||
|
case "debug":
|
||||||
|
return DebugLevel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var l Level
|
||||||
|
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are the different logging levels. You can set the logging level to log
|
||||||
|
// on your instance of logger, obtained with `logrus.New()`.
|
||||||
|
const (
|
||||||
|
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||||
|
// message passed to Debug, Info, ...
|
||||||
|
PanicLevel Level = iota
|
||||||
|
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
||||||
|
// logging level is set to Panic.
|
||||||
|
FatalLevel
|
||||||
|
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||||
|
// Commonly used for hooks to send errors to an error tracking service.
|
||||||
|
ErrorLevel
|
||||||
|
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||||
|
WarnLevel
|
||||||
|
// InfoLevel level. General operational entries about what's going on inside the
|
||||||
|
// application.
|
||||||
|
InfoLevel
|
||||||
|
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||||
|
DebugLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||||
|
var _ StdLogger = &log.Logger{}
|
||||||
|
|
||||||
|
// StdLogger is what your logrus-enabled library should take, that way
|
||||||
|
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||||
|
// interface, this is the closest we get, unfortunately.
|
||||||
|
type StdLogger interface {
|
||||||
|
Print(...interface{})
|
||||||
|
Printf(string, ...interface{})
|
||||||
|
Println(...interface{})
|
||||||
|
|
||||||
|
Fatal(...interface{})
|
||||||
|
Fatalf(string, ...interface{})
|
||||||
|
Fatalln(...interface{})
|
||||||
|
|
||||||
|
Panic(...interface{})
|
||||||
|
Panicf(string, ...interface{})
|
||||||
|
Panicln(...interface{})
|
||||||
|
}
|
247
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go
generated
vendored
Normal file
247
Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go
generated
vendored
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
var fields Fields
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = new(JSONFormatter)
|
||||||
|
|
||||||
|
log(logger)
|
||||||
|
|
||||||
|
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assertions(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = &TextFormatter{
|
||||||
|
DisableColors: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
log(logger)
|
||||||
|
|
||||||
|
fields := make(map[string]string)
|
||||||
|
for _, kv := range strings.Split(buffer.String(), " ") {
|
||||||
|
if !strings.Contains(kv, "=") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kvArr := strings.Split(kv, "=")
|
||||||
|
key := strings.TrimSpace(kvArr[0])
|
||||||
|
val, err := strconv.Unquote(kvArr[1])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
fields[key] = val
|
||||||
|
}
|
||||||
|
assertions(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrint(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Print("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfo(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarn(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Warn("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["level"], "warning")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln("test", "test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test test")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln("test", 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test 10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln(10, 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "10 10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Infoln(10, 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "10 10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Info("test", 10)
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test10")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.Info("test", "test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "testtest")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
var fields Fields
|
||||||
|
|
||||||
|
logger := New()
|
||||||
|
logger.Out = &buffer
|
||||||
|
logger.Formatter = new(JSONFormatter)
|
||||||
|
|
||||||
|
localLog := logger.WithFields(Fields{
|
||||||
|
"key1": "value1",
|
||||||
|
})
|
||||||
|
|
||||||
|
localLog.WithField("key2", "value2").Info("test")
|
||||||
|
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "value2", fields["key2"])
|
||||||
|
assert.Equal(t, "value1", fields["key1"])
|
||||||
|
|
||||||
|
buffer = bytes.Buffer{}
|
||||||
|
fields = Fields{}
|
||||||
|
localLog.Info("test")
|
||||||
|
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, ok := fields["key2"]
|
||||||
|
assert.Equal(t, false, ok)
|
||||||
|
assert.Equal(t, "value1", fields["key1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("msg", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("msg", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["fields.msg"], "hello")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("time", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["fields.time"], "hello")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("level", 1).Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
assert.Equal(t, fields["fields.level"], 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
|
||||||
|
LogAndAssertText(t, func(log *Logger) {
|
||||||
|
ll := log.WithField("herp", "derp")
|
||||||
|
ll.Info("hello")
|
||||||
|
ll.Info("bye")
|
||||||
|
}, func(fields map[string]string) {
|
||||||
|
for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} {
|
||||||
|
if _, ok := fields[fieldName]; ok {
|
||||||
|
t.Fatalf("should not have prefixed %q: %v", fieldName, fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertLevelToString(t *testing.T) {
|
||||||
|
assert.Equal(t, "debug", DebugLevel.String())
|
||||||
|
assert.Equal(t, "info", InfoLevel.String())
|
||||||
|
assert.Equal(t, "warning", WarnLevel.String())
|
||||||
|
assert.Equal(t, "error", ErrorLevel.String())
|
||||||
|
assert.Equal(t, "fatal", FatalLevel.String())
|
||||||
|
assert.Equal(t, "panic", PanicLevel.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLevel(t *testing.T) {
|
||||||
|
l, err := ParseLevel("panic")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, PanicLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("fatal")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, FatalLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("error")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, ErrorLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("warn")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, WarnLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("warning")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, WarnLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("info")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, InfoLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("debug")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, DebugLevel, l)
|
||||||
|
|
||||||
|
l, err = ParseLevel("invalid")
|
||||||
|
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
|
||||||
|
}
|
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
|
||||||
|
*/
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint32
|
||||||
|
Oflag uint32
|
||||||
|
Cflag uint32
|
||||||
|
Lflag uint32
|
||||||
|
Cc [20]uint8
|
||||||
|
Ispeed uint32
|
||||||
|
Ospeed uint32
|
||||||
|
}
|
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TCGETS
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
21
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux,!appengine darwin freebsd
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal() bool {
|
||||||
|
fd := syscall.Stdout
|
||||||
|
var termios Termios
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return err == 0
|
||||||
|
}
|
27
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Based on ssh/terminal:
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
var (
|
||||||
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal() bool {
|
||||||
|
fd := syscall.Stdout
|
||||||
|
var st uint32
|
||||||
|
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
return r != 0 && e == 0
|
||||||
|
}
|
95
Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nocolor = 0
|
||||||
|
red = 31
|
||||||
|
green = 32
|
||||||
|
yellow = 33
|
||||||
|
blue = 34
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
baseTimestamp time.Time
|
||||||
|
isTerminal bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
baseTimestamp = time.Now()
|
||||||
|
isTerminal = IsTerminal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func miniTS() int {
|
||||||
|
return int(time.Since(baseTimestamp) / time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextFormatter struct {
|
||||||
|
// Set to true to bypass checking for a TTY before outputting colors.
|
||||||
|
ForceColors bool
|
||||||
|
DisableColors bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
|
||||||
|
var keys []string
|
||||||
|
for k := range entry.Data {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
|
||||||
|
prefixFieldClashes(entry)
|
||||||
|
|
||||||
|
isColored := (f.ForceColors || isTerminal) && !f.DisableColors
|
||||||
|
|
||||||
|
if isColored {
|
||||||
|
printColored(b, entry, keys)
|
||||||
|
} else {
|
||||||
|
f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
|
||||||
|
f.appendKeyValue(b, "level", entry.Level.String())
|
||||||
|
f.appendKeyValue(b, "msg", entry.Message)
|
||||||
|
for _, key := range keys {
|
||||||
|
f.appendKeyValue(b, key, entry.Data[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteByte('\n')
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
|
||||||
|
var levelColor int
|
||||||
|
switch entry.Level {
|
||||||
|
case WarnLevel:
|
||||||
|
levelColor = yellow
|
||||||
|
case ErrorLevel, FatalLevel, PanicLevel:
|
||||||
|
levelColor = red
|
||||||
|
default:
|
||||||
|
levelColor = blue
|
||||||
|
}
|
||||||
|
|
||||||
|
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||||
|
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
|
||||||
|
for _, k := range keys {
|
||||||
|
v := entry.Data[k]
|
||||||
|
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) {
|
||||||
|
switch value.(type) {
|
||||||
|
case string, error:
|
||||||
|
fmt.Fprintf(b, "%v=%q ", key, value)
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(b, "%v=%v ", key, value)
|
||||||
|
}
|
||||||
|
}
|
461
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/bugsnag_test.go
generated
vendored
Normal file
461
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/bugsnag_test.go
generated
vendored
Normal file
|
@ -0,0 +1,461 @@
|
||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bitly/go-simplejson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigure(t *testing.T) {
|
||||||
|
Configure(Configuration{
|
||||||
|
APIKey: testAPIKey,
|
||||||
|
})
|
||||||
|
|
||||||
|
if Config.APIKey != testAPIKey {
|
||||||
|
t.Errorf("Setting APIKey didn't work")
|
||||||
|
}
|
||||||
|
|
||||||
|
if New().Config.APIKey != testAPIKey {
|
||||||
|
t.Errorf("Setting APIKey didn't work for new notifiers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var postedJSON = make(chan []byte, 10)
|
||||||
|
var testOnce sync.Once
|
||||||
|
var testEndpoint string
|
||||||
|
var testAPIKey = "166f5ad3590596f9aa8d601ea89af845"
|
||||||
|
|
||||||
|
func startTestServer() {
|
||||||
|
testOnce.Do(func() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
postedJSON <- body
|
||||||
|
})
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
testEndpoint = "http://" + l.Addr().String() + "/"
|
||||||
|
|
||||||
|
go http.Serve(l, mux)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type _recurse struct {
|
||||||
|
*_recurse
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
startTestServer()
|
||||||
|
|
||||||
|
recurse := _recurse{}
|
||||||
|
recurse._recurse = &recurse
|
||||||
|
|
||||||
|
OnBeforeNotify(func(event *Event, config *Configuration) error {
|
||||||
|
if event.Context == "testing" {
|
||||||
|
event.GroupingHash = "lol"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
Notify(fmt.Errorf("hello world"),
|
||||||
|
Configuration{
|
||||||
|
APIKey: testAPIKey,
|
||||||
|
Endpoint: testEndpoint,
|
||||||
|
ReleaseStage: "test",
|
||||||
|
AppVersion: "1.2.3",
|
||||||
|
Hostname: "web1",
|
||||||
|
ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"},
|
||||||
|
},
|
||||||
|
User{Id: "123", Name: "Conrad", Email: "me@cirw.in"},
|
||||||
|
Context{"testing"},
|
||||||
|
MetaData{"test": {
|
||||||
|
"password": "sneaky",
|
||||||
|
"value": "able",
|
||||||
|
"broken": complex(1, 2),
|
||||||
|
"recurse": recurse,
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
|
||||||
|
json, err := simplejson.NewJson(<-postedJSON)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if json.Get("apiKey").MustString() != testAPIKey {
|
||||||
|
t.Errorf("Wrong api key in payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
if json.GetPath("notifier", "name").MustString() != "Bugsnag Go" {
|
||||||
|
t.Errorf("Wrong notifier name in payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
event := json.Get("events").GetIndex(0)
|
||||||
|
|
||||||
|
for k, value := range map[string]string{
|
||||||
|
"payloadVersion": "2",
|
||||||
|
"severity": "warning",
|
||||||
|
"context": "testing",
|
||||||
|
"groupingHash": "lol",
|
||||||
|
"app.releaseStage": "test",
|
||||||
|
"app.version": "1.2.3",
|
||||||
|
"device.hostname": "web1",
|
||||||
|
"user.id": "123",
|
||||||
|
"user.name": "Conrad",
|
||||||
|
"user.email": "me@cirw.in",
|
||||||
|
"metaData.test.password": "[REDACTED]",
|
||||||
|
"metaData.test.value": "able",
|
||||||
|
"metaData.test.broken": "[complex128]",
|
||||||
|
"metaData.test.recurse._recurse": "[RECURSION]",
|
||||||
|
} {
|
||||||
|
key := strings.Split(k, ".")
|
||||||
|
if event.GetPath(key...).MustString() != value {
|
||||||
|
t.Errorf("Wrong %v: %v != %v", key, event.GetPath(key...).MustString(), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exception := event.Get("exceptions").GetIndex(0)
|
||||||
|
|
||||||
|
if exception.Get("message").MustString() != "hello world" {
|
||||||
|
t.Errorf("Wrong message in payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exception.Get("errorClass").MustString() != "*errors.errorString" {
|
||||||
|
t.Errorf("Wrong errorClass in payload: %v", exception.Get("errorClass").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
frame0 := exception.Get("stacktrace").GetIndex(0)
|
||||||
|
if frame0.Get("file").MustString() != "bugsnag_test.go" ||
|
||||||
|
frame0.Get("method").MustString() != "TestNotify" ||
|
||||||
|
frame0.Get("inProject").MustBool() != true ||
|
||||||
|
frame0.Get("lineNumber").MustInt() == 0 {
|
||||||
|
t.Errorf("Wrong frame0")
|
||||||
|
}
|
||||||
|
|
||||||
|
frame1 := exception.Get("stacktrace").GetIndex(1)
|
||||||
|
|
||||||
|
if frame1.Get("file").MustString() != "testing/testing.go" ||
|
||||||
|
frame1.Get("method").MustString() != "tRunner" ||
|
||||||
|
frame1.Get("inProject").MustBool() != false ||
|
||||||
|
frame1.Get("lineNumber").MustInt() == 0 {
|
||||||
|
t.Errorf("Wrong frame1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func crashyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c := make(chan int)
|
||||||
|
close(c)
|
||||||
|
c <- 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCrashyServer(rawData ...interface{}) (net.Listener, error) {
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", crashyHandler)
|
||||||
|
srv := http.Server{
|
||||||
|
Addr: l.Addr().String(),
|
||||||
|
Handler: Handler(mux, rawData...),
|
||||||
|
ErrorLog: log.New(ioutil.Discard, log.Prefix(), 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
go srv.Serve(l)
|
||||||
|
return l, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandler(t *testing.T) {
|
||||||
|
startTestServer()
|
||||||
|
|
||||||
|
l, err := runCrashyServer(Configuration{
|
||||||
|
APIKey: testAPIKey,
|
||||||
|
Endpoint: testEndpoint,
|
||||||
|
ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"},
|
||||||
|
Logger: log.New(ioutil.Discard, log.Prefix(), log.Flags()),
|
||||||
|
}, SeverityInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
http.Get("http://" + l.Addr().String() + "/ok?foo=bar")
|
||||||
|
l.Close()
|
||||||
|
|
||||||
|
json, err := simplejson.NewJson(<-postedJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if json.Get("apiKey").MustString() != testAPIKey {
|
||||||
|
t.Errorf("Wrong api key in payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
if json.GetPath("notifier", "name").MustString() != "Bugsnag Go" {
|
||||||
|
t.Errorf("Wrong notifier name in payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
event := json.Get("events").GetIndex(0)
|
||||||
|
|
||||||
|
for k, value := range map[string]string{
|
||||||
|
"payloadVersion": "2",
|
||||||
|
"severity": "info",
|
||||||
|
"user.id": "127.0.0.1",
|
||||||
|
"metaData.Request.Url": "http://" + l.Addr().String() + "/ok?foo=bar",
|
||||||
|
"metaData.Request.Method": "GET",
|
||||||
|
} {
|
||||||
|
key := strings.Split(k, ".")
|
||||||
|
if event.GetPath(key...).MustString() != value {
|
||||||
|
t.Errorf("Wrong %v: %v != %v", key, event.GetPath(key...).MustString(), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.GetPath("metaData", "Request", "Params", "foo").GetIndex(0).MustString() != "bar" {
|
||||||
|
t.Errorf("missing GET params in request metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.GetPath("metaData", "Headers", "Accept-Encoding").GetIndex(0).MustString() != "gzip" {
|
||||||
|
t.Errorf("missing GET params in request metadata: %v", event.GetPath("metaData", "Headers"))
|
||||||
|
}
|
||||||
|
|
||||||
|
exception := event.Get("exceptions").GetIndex(0)
|
||||||
|
|
||||||
|
if exception.Get("message").MustString() != "runtime error: send on closed channel" {
|
||||||
|
t.Errorf("Wrong message in payload: %v", exception.Get("message").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if exception.Get("errorClass").MustString() != "runtime.errorCString" {
|
||||||
|
t.Errorf("Wrong errorClass in payload: %v", exception.Get("errorClass").MustString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:CI these are probably dependent on go version.
|
||||||
|
frame0 := exception.Get("stacktrace").GetIndex(0)
|
||||||
|
if frame0.Get("file").MustString() != "runtime/panic.c" ||
|
||||||
|
frame0.Get("method").MustString() != "panicstring" ||
|
||||||
|
frame0.Get("inProject").MustBool() != false ||
|
||||||
|
frame0.Get("lineNumber").MustInt() == 0 {
|
||||||
|
t.Errorf("Wrong frame0: %v", frame0)
|
||||||
|
}
|
||||||
|
|
||||||
|
frame3 := exception.Get("stacktrace").GetIndex(3)
|
||||||
|
|
||||||
|
if frame3.Get("file").MustString() != "bugsnag_test.go" ||
|
||||||
|
frame3.Get("method").MustString() != "crashyHandler" ||
|
||||||
|
frame3.Get("inProject").MustBool() != true ||
|
||||||
|
frame3.Get("lineNumber").MustInt() == 0 {
|
||||||
|
t.Errorf("Wrong frame3: %v", frame3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAutoNotify(t *testing.T) {
|
||||||
|
|
||||||
|
var panicked interface{}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
panicked = recover()
|
||||||
|
}()
|
||||||
|
defer AutoNotify(Configuration{Endpoint: testEndpoint, APIKey: testAPIKey})
|
||||||
|
|
||||||
|
panic("eggs")
|
||||||
|
}()
|
||||||
|
|
||||||
|
if panicked.(string) != "eggs" {
|
||||||
|
t.Errorf("didn't re-panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := simplejson.NewJson(<-postedJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
event := json.Get("events").GetIndex(0)
|
||||||
|
|
||||||
|
if event.Get("severity").MustString() != "error" {
|
||||||
|
t.Errorf("severity should be error")
|
||||||
|
}
|
||||||
|
exception := event.Get("exceptions").GetIndex(0)
|
||||||
|
|
||||||
|
if exception.Get("message").MustString() != "eggs" {
|
||||||
|
t.Errorf("caught wrong panic")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecover(t *testing.T) {
|
||||||
|
var panicked interface{}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
panicked = recover()
|
||||||
|
}()
|
||||||
|
defer Recover(Configuration{Endpoint: testEndpoint, APIKey: testAPIKey})
|
||||||
|
|
||||||
|
panic("ham")
|
||||||
|
}()
|
||||||
|
|
||||||
|
if panicked != nil {
|
||||||
|
t.Errorf("re-panick'd")
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := simplejson.NewJson(<-postedJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
event := json.Get("events").GetIndex(0)
|
||||||
|
|
||||||
|
if event.Get("severity").MustString() != "warning" {
|
||||||
|
t.Errorf("severity should be warning")
|
||||||
|
}
|
||||||
|
exception := event.Get("exceptions").GetIndex(0)
|
||||||
|
|
||||||
|
if exception.Get("message").MustString() != "ham" {
|
||||||
|
t.Errorf("caught wrong panic")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var createAccount = handleGet
|
||||||
|
|
||||||
|
type _job struct {
|
||||||
|
Name string
|
||||||
|
Process func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAutoNotify() interface{} {
|
||||||
|
return func(w http.ResponseWriter, request *http.Request) {
|
||||||
|
defer AutoNotify(request, Context{"createAccount"})
|
||||||
|
|
||||||
|
createAccount(w, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRecover(job _job) {
|
||||||
|
go func() {
|
||||||
|
defer Recover(Context{job.Name}, SeverityWarning)
|
||||||
|
|
||||||
|
job.Process()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleConfigure() {
|
||||||
|
Configure(Configuration{
|
||||||
|
APIKey: "YOUR_API_KEY_HERE",
|
||||||
|
|
||||||
|
ReleaseStage: "production",
|
||||||
|
|
||||||
|
// See Configuration{} for other fields
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleHandler() {
|
||||||
|
// Set up your http handlers as usual
|
||||||
|
http.HandleFunc("/", handleGet)
|
||||||
|
|
||||||
|
// use bugsnag.Handler(nil) to wrap the default http handlers
|
||||||
|
// so that Bugsnag is automatically notified about panics.
|
||||||
|
http.ListenAndServe(":1234", Handler(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleHandler_customServer() {
|
||||||
|
// If you're using a custom server, set the handlers explicitly.
|
||||||
|
http.HandleFunc("/", handleGet)
|
||||||
|
|
||||||
|
srv := http.Server{
|
||||||
|
Addr: ":1234",
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
// use bugsnag.Handler(nil) to wrap the default http handlers
|
||||||
|
// so that Bugsnag is automatically notified about panics.
|
||||||
|
Handler: Handler(nil),
|
||||||
|
}
|
||||||
|
srv.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleHandler_customHandlers() {
|
||||||
|
// If you're using custom handlers, wrap the handlers explicitly.
|
||||||
|
handler := http.NewServeMux()
|
||||||
|
http.HandleFunc("/", handleGet)
|
||||||
|
// use bugsnag.Handler(handler) to wrap the handlers so that Bugsnag is
|
||||||
|
// automatically notified about panics
|
||||||
|
http.ListenAndServe(":1234", Handler(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNotify() {
|
||||||
|
_, err := net.Listen("tcp", ":80")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
Notify(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNotify_details(userID string) {
|
||||||
|
_, err := net.Listen("tcp", ":80")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
Notify(err,
|
||||||
|
// show as low-severity
|
||||||
|
SeverityInfo,
|
||||||
|
// set the context
|
||||||
|
Context{"createlistener"},
|
||||||
|
// pass the user id in to count users affected.
|
||||||
|
User{Id: userID},
|
||||||
|
// custom meta-data tab
|
||||||
|
MetaData{
|
||||||
|
"Listen": {
|
||||||
|
"Protocol": "tcp",
|
||||||
|
"Port": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Job struct {
|
||||||
|
Retry bool
|
||||||
|
UserId string
|
||||||
|
UserEmail string
|
||||||
|
Name string
|
||||||
|
Params map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleOnBeforeNotify() {
|
||||||
|
OnBeforeNotify(func(event *Event, config *Configuration) error {
|
||||||
|
|
||||||
|
// Search all the RawData for any *Job pointers that we're passed in
|
||||||
|
// to bugsnag.Notify() and friends.
|
||||||
|
for _, datum := range event.RawData {
|
||||||
|
if job, ok := datum.(*Job); ok {
|
||||||
|
// don't notify bugsnag about errors in retries
|
||||||
|
if job.Retry {
|
||||||
|
return fmt.Errorf("bugsnag middleware: not notifying about job retry")
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the job as a tab on Bugsnag.com
|
||||||
|
event.MetaData.AddStruct("Job", job)
|
||||||
|
|
||||||
|
// set the user correctly
|
||||||
|
event.User = &User{Id: job.UserId, Email: job.UserEmail}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue notifying as normal
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
58
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/configuration_test.go
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/configuration_test.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNotifyReleaseStages(t *testing.T) {
|
||||||
|
|
||||||
|
var testCases = []struct {
|
||||||
|
stage string
|
||||||
|
configured []string
|
||||||
|
notify bool
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
stage: "production",
|
||||||
|
notify: true,
|
||||||
|
msg: "Should notify in all release stages by default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stage: "production",
|
||||||
|
configured: []string{"development", "production"},
|
||||||
|
notify: true,
|
||||||
|
msg: "Failed to notify in configured release stage",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stage: "staging",
|
||||||
|
configured: []string{"development", "production"},
|
||||||
|
notify: false,
|
||||||
|
msg: "Failed to prevent notification in excluded release stage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
Configure(Configuration{ReleaseStage: testCase.stage, NotifyReleaseStages: testCase.configured})
|
||||||
|
|
||||||
|
if Config.notifyInReleaseStage() != testCase.notify {
|
||||||
|
t.Error(testCase.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProjectPackages(t *testing.T) {
|
||||||
|
Configure(Configuration{ProjectPackages: []string{"main", "github.com/ConradIrwin/*"}})
|
||||||
|
if !Config.isProjectPackage("main") {
|
||||||
|
t.Error("literal project package doesn't work")
|
||||||
|
}
|
||||||
|
if !Config.isProjectPackage("github.com/ConradIrwin/foo") {
|
||||||
|
t.Error("wildcard project package doesn't work")
|
||||||
|
}
|
||||||
|
if Config.isProjectPackage("runtime") {
|
||||||
|
t.Error("wrong packges being marked in project")
|
||||||
|
}
|
||||||
|
if Config.isProjectPackage("github.com/ConradIrwin/foo/bar") {
|
||||||
|
t.Error("wrong packges being marked in project")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
117
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/error_test.go
generated
vendored
Normal file
117
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/error_test.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"runtime/debug"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStackFormatMatches(t *testing.T) {
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != 'a' {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := [][]byte{Errorf("hi").Stack(), debug.Stack()}
|
||||||
|
|
||||||
|
// Ignore the first line (as it contains the PC of the .Stack() call)
|
||||||
|
bs[0] = bytes.SplitN(bs[0], []byte("\n"), 2)[1]
|
||||||
|
bs[1] = bytes.SplitN(bs[1], []byte("\n"), 2)[1]
|
||||||
|
|
||||||
|
if bytes.Compare(bs[0], bs[1]) != 0 {
|
||||||
|
t.Errorf("Stack didn't match")
|
||||||
|
t.Errorf("%s", bs[0])
|
||||||
|
t.Errorf("%s", bs[1])
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
a()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipWorks(t *testing.T) {
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != 'a' {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := [][]byte{New("hi", 2).Stack(), debug.Stack()}
|
||||||
|
|
||||||
|
// should skip four lines of debug.Stack()
|
||||||
|
bs[1] = bytes.SplitN(bs[1], []byte("\n"), 5)[4]
|
||||||
|
|
||||||
|
if bytes.Compare(bs[0], bs[1]) != 0 {
|
||||||
|
t.Errorf("Stack didn't match")
|
||||||
|
t.Errorf("%s", bs[0])
|
||||||
|
t.Errorf("%s", bs[1])
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
a()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewError(t *testing.T) {
|
||||||
|
|
||||||
|
e := func() error {
|
||||||
|
return New("hi", 1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if e.Error() != "hi" {
|
||||||
|
t.Errorf("Constructor with a string failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if New(fmt.Errorf("yo"), 0).Error() != "yo" {
|
||||||
|
t.Errorf("Constructor with an error failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if New(e, 0) != e {
|
||||||
|
t.Errorf("Constructor with an Error failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if New(nil, 0).Error() != "<nil>" {
|
||||||
|
t.Errorf("Constructor with nil failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleErrorf(x int) (int, error) {
|
||||||
|
if x%2 == 1 {
|
||||||
|
return 0, Errorf("can only halve even numbers, got %d", x)
|
||||||
|
}
|
||||||
|
return x / 2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewError() (error, error) {
|
||||||
|
// Wrap io.EOF with the current stack-trace and return it
|
||||||
|
return nil, New(io.EOF, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewError_skip() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
// skip 1 frame (the deferred function) and then return the wrapped err
|
||||||
|
err = New(err, 1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleError_Stack(err Error) {
|
||||||
|
fmt.Printf("Error: %s\n%s", err.Error(), err.Stack())
|
||||||
|
}
|
||||||
|
|
||||||
|
func a() error {
|
||||||
|
b(5)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func b(i int) {
|
||||||
|
c()
|
||||||
|
}
|
||||||
|
|
||||||
|
func c() {
|
||||||
|
panic('a')
|
||||||
|
}
|
142
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/parse_panic_test.go
generated
vendored
Normal file
142
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/errors/parse_panic_test.go
generated
vendored
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var createdBy = `panic: hello!
|
||||||
|
|
||||||
|
goroutine 54 [running]:
|
||||||
|
runtime.panic(0x35ce40, 0xc208039db0)
|
||||||
|
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
|
||||||
|
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
|
||||||
|
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
|
||||||
|
created by github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.App.Index
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:14 +0x3e
|
||||||
|
|
||||||
|
goroutine 16 [IO wait]:
|
||||||
|
net.runtime_pollWait(0x911c30, 0x72, 0x0)
|
||||||
|
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
|
||||||
|
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
|
||||||
|
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
|
||||||
|
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
|
||||||
|
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
|
||||||
|
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
|
||||||
|
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
|
||||||
|
github.com/revel/revel.Run(0xe6d9)
|
||||||
|
/0/go/src/github.com/revel/revel/server.go:113 +0x926
|
||||||
|
main.main()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
|
||||||
|
`
|
||||||
|
|
||||||
|
var normalSplit = `panic: hello!
|
||||||
|
|
||||||
|
goroutine 54 [running]:
|
||||||
|
runtime.panic(0x35ce40, 0xc208039db0)
|
||||||
|
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
|
||||||
|
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
|
||||||
|
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
|
||||||
|
|
||||||
|
goroutine 16 [IO wait]:
|
||||||
|
net.runtime_pollWait(0x911c30, 0x72, 0x0)
|
||||||
|
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
|
||||||
|
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
|
||||||
|
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
|
||||||
|
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
|
||||||
|
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
|
||||||
|
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
|
||||||
|
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
|
||||||
|
github.com/revel/revel.Run(0xe6d9)
|
||||||
|
/0/go/src/github.com/revel/revel/server.go:113 +0x926
|
||||||
|
main.main()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
|
||||||
|
`
|
||||||
|
|
||||||
|
var lastGoroutine = `panic: hello!
|
||||||
|
|
||||||
|
goroutine 16 [IO wait]:
|
||||||
|
net.runtime_pollWait(0x911c30, 0x72, 0x0)
|
||||||
|
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
|
||||||
|
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
|
||||||
|
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
|
||||||
|
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
|
||||||
|
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
|
||||||
|
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
|
||||||
|
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
|
||||||
|
github.com/revel/revel.Run(0xe6d9)
|
||||||
|
/0/go/src/github.com/revel/revel/server.go:113 +0x926
|
||||||
|
main.main()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
|
||||||
|
|
||||||
|
goroutine 54 [running]:
|
||||||
|
runtime.panic(0x35ce40, 0xc208039db0)
|
||||||
|
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
|
||||||
|
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
|
||||||
|
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
|
||||||
|
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
|
||||||
|
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
|
||||||
|
`
|
||||||
|
|
||||||
|
var result = []StackFrame{
|
||||||
|
StackFrame{File: "/0/c/go/src/pkg/runtime/panic.c", LineNumber: 279, Name: "panic", Package: "runtime"},
|
||||||
|
StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 13, Name: "func.001", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers"},
|
||||||
|
StackFrame{File: "/0/c/go/src/pkg/net/http/server.go", LineNumber: 1698, Name: "(*Server).Serve", Package: "net/http"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultCreatedBy = append(result,
|
||||||
|
StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 14, Name: "App.Index", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers", ProgramCounter: 0x0})
|
||||||
|
|
||||||
|
func TestParsePanic(t *testing.T) {
|
||||||
|
|
||||||
|
todo := map[string]string{
|
||||||
|
"createdBy": createdBy,
|
||||||
|
"normalSplit": normalSplit,
|
||||||
|
"lastGoroutine": lastGoroutine,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range todo {
|
||||||
|
Err, err := ParsePanic(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Err.TypeName() != "panic" {
|
||||||
|
t.Errorf("Wrong type: %s", Err.TypeName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if Err.Error() != "hello!" {
|
||||||
|
t.Errorf("Wrong message: %s", Err.TypeName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if Err.StackFrames()[0].Func() != nil {
|
||||||
|
t.Errorf("Somehow managed to find a func...")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := result
|
||||||
|
if key == "createdBy" {
|
||||||
|
result = resultCreatedBy
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(Err.StackFrames(), result) {
|
||||||
|
t.Errorf("Wrong stack for %s: %#v", key, Err.StackFrames())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
182
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/metadata_test.go
generated
vendored
Normal file
182
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/metadata_test.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/bugsnag/bugsnag-go/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _account struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Plan struct {
|
||||||
|
Premium bool
|
||||||
|
}
|
||||||
|
Password string
|
||||||
|
secret string
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmptyEmail string `json:"emptyemail,omitempty"`
|
||||||
|
NotEmptyEmail string `json:"not_empty_email,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type _broken struct {
|
||||||
|
Me *_broken
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
var account = _account{}
|
||||||
|
var notifier = New(Configuration{})
|
||||||
|
|
||||||
|
func TestMetaDataAdd(t *testing.T) {
|
||||||
|
m := MetaData{
|
||||||
|
"one": {
|
||||||
|
"key": "value",
|
||||||
|
"override": false,
|
||||||
|
}}
|
||||||
|
|
||||||
|
m.Add("one", "override", true)
|
||||||
|
m.Add("one", "new", "key")
|
||||||
|
m.Add("new", "tab", account)
|
||||||
|
|
||||||
|
m.AddStruct("lol", "not really a struct")
|
||||||
|
m.AddStruct("account", account)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(m, MetaData{
|
||||||
|
"one": {
|
||||||
|
"key": "value",
|
||||||
|
"override": true,
|
||||||
|
"new": "key",
|
||||||
|
},
|
||||||
|
"new": {
|
||||||
|
"tab": account,
|
||||||
|
},
|
||||||
|
"Extra data": {
|
||||||
|
"lol": "not really a struct",
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"ID": "",
|
||||||
|
"Name": "",
|
||||||
|
"Plan": map[string]interface{}{
|
||||||
|
"Premium": false,
|
||||||
|
},
|
||||||
|
"Password": "",
|
||||||
|
"email": "",
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
t.Errorf("metadata.Add didn't work: %#v", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetaDataUpdate(t *testing.T) {
|
||||||
|
|
||||||
|
m := MetaData{
|
||||||
|
"one": {
|
||||||
|
"key": "value",
|
||||||
|
"override": false,
|
||||||
|
}}
|
||||||
|
|
||||||
|
m.Update(MetaData{
|
||||||
|
"one": {
|
||||||
|
"override": true,
|
||||||
|
"new": "key",
|
||||||
|
},
|
||||||
|
"new": {
|
||||||
|
"tab": account,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(m, MetaData{
|
||||||
|
"one": {
|
||||||
|
"key": "value",
|
||||||
|
"override": true,
|
||||||
|
"new": "key",
|
||||||
|
},
|
||||||
|
"new": {
|
||||||
|
"tab": account,
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
t.Errorf("metadata.Update didn't work: %#v", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetaDataSanitize(t *testing.T) {
|
||||||
|
|
||||||
|
var broken = _broken{}
|
||||||
|
broken.Me = &broken
|
||||||
|
broken.Data = "ohai"
|
||||||
|
account.Name = "test"
|
||||||
|
account.ID = "test"
|
||||||
|
account.secret = "hush"
|
||||||
|
account.Email = "example@example.com"
|
||||||
|
account.EmptyEmail = ""
|
||||||
|
account.NotEmptyEmail = "not_empty_email@example.com"
|
||||||
|
|
||||||
|
m := MetaData{
|
||||||
|
"one": {
|
||||||
|
"bool": true,
|
||||||
|
"int": 7,
|
||||||
|
"float": 7.1,
|
||||||
|
"complex": complex(1, 1),
|
||||||
|
"func": func() {},
|
||||||
|
"unsafe": unsafe.Pointer(broken.Me),
|
||||||
|
"string": "string",
|
||||||
|
"password": "secret",
|
||||||
|
"array": []hash{{
|
||||||
|
"creditcard": "1234567812345678",
|
||||||
|
"broken": broken,
|
||||||
|
}},
|
||||||
|
"broken": broken,
|
||||||
|
"account": account,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
n := m.sanitize([]string{"password", "creditcard"})
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(n, map[string]interface{}{
|
||||||
|
"one": map[string]interface{}{
|
||||||
|
"bool": true,
|
||||||
|
"int": 7,
|
||||||
|
"float": 7.1,
|
||||||
|
"complex": "[complex128]",
|
||||||
|
"string": "string",
|
||||||
|
"unsafe": "[unsafe.Pointer]",
|
||||||
|
"func": "[func()]",
|
||||||
|
"password": "[REDACTED]",
|
||||||
|
"array": []interface{}{map[string]interface{}{
|
||||||
|
"creditcard": "[REDACTED]",
|
||||||
|
"broken": map[string]interface{}{
|
||||||
|
"Me": "[RECURSION]",
|
||||||
|
"Data": "ohai",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"broken": map[string]interface{}{
|
||||||
|
"Me": "[RECURSION]",
|
||||||
|
"Data": "ohai",
|
||||||
|
},
|
||||||
|
"account": map[string]interface{}{
|
||||||
|
"ID": "test",
|
||||||
|
"Name": "test",
|
||||||
|
"Plan": map[string]interface{}{
|
||||||
|
"Premium": false,
|
||||||
|
},
|
||||||
|
"Password": "[REDACTED]",
|
||||||
|
"email": "example@example.com",
|
||||||
|
"not_empty_email": "not_empty_email@example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
t.Errorf("metadata.Sanitize didn't work: %#v", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleMetaData() {
|
||||||
|
notifier.Notify(errors.Errorf("hi world"),
|
||||||
|
MetaData{"Account": {
|
||||||
|
"id": account.ID,
|
||||||
|
"name": account.Name,
|
||||||
|
"paying?": account.Plan.Premium,
|
||||||
|
}})
|
||||||
|
}
|
88
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/middleware_test.go
generated
vendored
Normal file
88
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/middleware_test.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMiddlewareOrder(t *testing.T) {
|
||||||
|
|
||||||
|
result := make([]int, 0, 7)
|
||||||
|
stack := middlewareStack{}
|
||||||
|
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
|
||||||
|
result = append(result, 2)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
|
||||||
|
result = append(result, 1)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
|
||||||
|
result = append(result, 0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
stack.Run(nil, nil, func() error {
|
||||||
|
result = append(result, 3)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(result, []int{0, 1, 2, 3}) {
|
||||||
|
t.Errorf("unexpected middleware order %v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBeforeNotifyReturnErr(t *testing.T) {
|
||||||
|
|
||||||
|
stack := middlewareStack{}
|
||||||
|
err := fmt.Errorf("test")
|
||||||
|
|
||||||
|
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
called := false
|
||||||
|
|
||||||
|
e := stack.Run(nil, nil, func() error {
|
||||||
|
called = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if e != err {
|
||||||
|
t.Errorf("Middleware didn't return the error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if called == true {
|
||||||
|
t.Errorf("Notify was called when BeforeNotify returned False")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBeforeNotifyPanic(t *testing.T) {
|
||||||
|
|
||||||
|
stack := middlewareStack{}
|
||||||
|
|
||||||
|
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
|
||||||
|
panic("oops")
|
||||||
|
})
|
||||||
|
|
||||||
|
called := false
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
|
||||||
|
stack.Run(nil, &Configuration{Logger: log.New(b, log.Prefix(), 0)}, func() error {
|
||||||
|
called = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
logged := b.String()
|
||||||
|
|
||||||
|
if logged != "bugsnag/middleware: unexpected panic: oops\n" {
|
||||||
|
t.Errorf("Logged: %s", logged)
|
||||||
|
}
|
||||||
|
|
||||||
|
if called == false {
|
||||||
|
t.Errorf("Notify was not called when BeforeNotify panicked")
|
||||||
|
}
|
||||||
|
}
|
79
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/panicwrap_test.go
generated
vendored
Normal file
79
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/panicwrap_test.go
generated
vendored
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bitly/go-simplejson"
|
||||||
|
"github.com/mitchellh/osext"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPanicHandler(t *testing.T) {
|
||||||
|
startTestServer()
|
||||||
|
|
||||||
|
exePath, err := osext.Executable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the same trick as panicwrap() to re-run ourselves.
|
||||||
|
// In the init() block below, we will then panic.
|
||||||
|
cmd := exec.Command(exePath, os.Args[1:]...)
|
||||||
|
cmd.Env = append(os.Environ(), "BUGSNAG_API_KEY="+testAPIKey, "BUGSNAG_ENDPOINT="+testEndpoint, "please_panic=please_panic")
|
||||||
|
|
||||||
|
if err = cmd.Start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cmd.Wait(); err.Error() != "exit status 2" {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := simplejson.NewJson(<-postedJSON)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
event := json.Get("events").GetIndex(0)
|
||||||
|
|
||||||
|
if event.Get("severity").MustString() != "error" {
|
||||||
|
t.Errorf("severity should be error")
|
||||||
|
}
|
||||||
|
exception := event.Get("exceptions").GetIndex(0)
|
||||||
|
|
||||||
|
if exception.Get("message").MustString() != "ruh roh" {
|
||||||
|
t.Errorf("caught wrong panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exception.Get("errorClass").MustString() != "panic" {
|
||||||
|
t.Errorf("caught wrong panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
frame := exception.Get("stacktrace").GetIndex(1)
|
||||||
|
|
||||||
|
// Yeah, we just caught a panic from the init() function below and sent it to the server running above (mindblown)
|
||||||
|
if frame.Get("inProject").MustBool() != true ||
|
||||||
|
frame.Get("file").MustString() != "panicwrap_test.go" ||
|
||||||
|
frame.Get("method").MustString() != "panick" ||
|
||||||
|
frame.Get("lineNumber").MustInt() == 0 {
|
||||||
|
t.Errorf("stack trace seemed wrong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if os.Getenv("please_panic") != "" {
|
||||||
|
Configure(Configuration{APIKey: os.Getenv("BUGSNAG_API_KEY"), Endpoint: os.Getenv("BUGSNAG_ENDPOINT"), ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"}})
|
||||||
|
go func() {
|
||||||
|
panick()
|
||||||
|
}()
|
||||||
|
// Plenty of time to crash, it shouldn't need any of it.
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func panick() {
|
||||||
|
panic("ruh roh")
|
||||||
|
}
|
60
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/revel/bugsnagrevel.go
generated
vendored
Normal file
60
Godeps/_workspace/src/github.com/bugsnag/bugsnag-go/revel/bugsnagrevel.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Package bugsnagrevel adds Bugsnag to revel.
|
||||||
|
// It lets you pass *revel.Controller into bugsnag.Notify(),
|
||||||
|
// and provides a Filter to catch errors.
|
||||||
|
package bugsnagrevel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bugsnag/bugsnag-go"
|
||||||
|
"github.com/revel/revel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
|
// Filter should be added to the filter chain just after the PanicFilter.
|
||||||
|
// It sends errors to Bugsnag automatically. Configuration is read out of
|
||||||
|
// conf/app.conf, you should set bugsnag.apikey, and can also set
|
||||||
|
// bugsnag.endpoint, bugsnag.releasestage, bugsnag.appversion,
|
||||||
|
// bugsnag.projectroot, bugsnag.projectpackages if needed.
|
||||||
|
func Filter(c *revel.Controller, fc []revel.Filter) {
|
||||||
|
defer bugsnag.AutoNotify(c)
|
||||||
|
fc[0](c, fc[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add support to bugsnag for reading data out of *revel.Controllers
|
||||||
|
func middleware(event *bugsnag.Event, config *bugsnag.Configuration) error {
|
||||||
|
for _, datum := range event.RawData {
|
||||||
|
if controller, ok := datum.(*revel.Controller); ok {
|
||||||
|
// make the request visible to the builtin HttpMIddleware
|
||||||
|
event.RawData = append(event.RawData, controller.Request.Request)
|
||||||
|
event.Context = controller.Action
|
||||||
|
event.MetaData.AddStruct("Session", controller.Session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
revel.OnAppStart(func() {
|
||||||
|
bugsnag.OnBeforeNotify(middleware)
|
||||||
|
|
||||||
|
var projectPackages []string
|
||||||
|
if packages, ok := revel.Config.String("bugsnag.projectpackages"); ok {
|
||||||
|
projectPackages = strings.Split(packages, ",")
|
||||||
|
} else {
|
||||||
|
projectPackages = []string{revel.ImportPath + "/app/*", revel.ImportPath + "/app"}
|
||||||
|
}
|
||||||
|
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
APIKey: revel.Config.StringDefault("bugsnag.apikey", ""),
|
||||||
|
Endpoint: revel.Config.StringDefault("bugsnag.endpoint", ""),
|
||||||
|
AppVersion: revel.Config.StringDefault("bugsnag.appversion", ""),
|
||||||
|
ReleaseStage: revel.Config.StringDefault("bugsnag.releasestage", revel.RunMode),
|
||||||
|
ProjectPackages: projectPackages,
|
||||||
|
Logger: revel.ERROR,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
79
Godeps/_workspace/src/github.com/bugsnag/osext/osext_test.go
generated
vendored
Normal file
79
Godeps/_workspace/src/github.com/bugsnag/osext/osext_test.go
generated
vendored
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin linux freebsd netbsd windows
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
oexec "os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH"
|
||||||
|
|
||||||
|
func TestExecPath(t *testing.T) {
|
||||||
|
ep, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExecPath failed: %v", err)
|
||||||
|
}
|
||||||
|
// we want fn to be of the form "dir/prog"
|
||||||
|
dir := filepath.Dir(filepath.Dir(ep))
|
||||||
|
fn, err := filepath.Rel(dir, ep)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("filepath.Rel: %v", err)
|
||||||
|
}
|
||||||
|
cmd := &oexec.Cmd{}
|
||||||
|
// make child start with a relative program path
|
||||||
|
cmd.Dir = dir
|
||||||
|
cmd.Path = fn
|
||||||
|
// forge argv[0] for child, so that we can verify we could correctly
|
||||||
|
// get real path of the executable without influenced by argv[0].
|
||||||
|
cmd.Args = []string{"-", "-test.run=XXXX"}
|
||||||
|
cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)}
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("exec(self) failed: %v", err)
|
||||||
|
}
|
||||||
|
outs := string(out)
|
||||||
|
if !filepath.IsAbs(outs) {
|
||||||
|
t.Fatalf("Child returned %q, want an absolute path", out)
|
||||||
|
}
|
||||||
|
if !sameFile(outs, ep) {
|
||||||
|
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameFile(fn1, fn2 string) bool {
|
||||||
|
fi1, err := os.Stat(fn1)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fi2, err := os.Stat(fn2)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return os.SameFile(fi1, fi2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if e := os.Getenv(execPath_EnvVar); e != "" {
|
||||||
|
// first chdir to another path
|
||||||
|
dir := "/"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
dir = filepath.VolumeName(".")
|
||||||
|
}
|
||||||
|
os.Chdir(dir)
|
||||||
|
if ep, err := Executable(); err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(os.Stderr, ep)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/bugsnag/osext"
|
"github.com/bugsnag/osext"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func monitor(c *WrapConfig) (int, error) {
|
func monitor(c *WrapConfig) (int, error) {
|
||||||
|
@ -53,7 +54,7 @@ func monitor(c *WrapConfig) (int, error) {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = dup2(int(write.Fd()), int(os.Stderr.Fd()))
|
err = syscall.Dup2(int(write.Fd()), int(os.Stderr.Fd()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
360
Godeps/_workspace/src/github.com/bugsnag/panicwrap/panicwrap_test.go
generated
vendored
Normal file
360
Godeps/_workspace/src/github.com/bugsnag/panicwrap/panicwrap_test.go
generated
vendored
Normal file
|
@ -0,0 +1,360 @@
|
||||||
|
package panicwrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func helperProcess(s ...string) *exec.Cmd {
|
||||||
|
cs := []string{"-test.run=TestHelperProcess", "--"}
|
||||||
|
cs = append(cs, s...)
|
||||||
|
env := []string{
|
||||||
|
"GO_WANT_HELPER_PROCESS=1",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(os.Args[0], cs...)
|
||||||
|
cmd.Env = append(env, os.Environ()...)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is executed by `helperProcess` in a separate process in order to
|
||||||
|
// provider a proper sub-process environment to test some of our functionality.
|
||||||
|
func TestHelperProcess(*testing.T) {
|
||||||
|
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the arguments to our helper, which are the arguments past
|
||||||
|
// the "--" in the command line.
|
||||||
|
args := os.Args
|
||||||
|
for len(args) > 0 {
|
||||||
|
if args[0] == "--" {
|
||||||
|
args = args[1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
args = args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "No command\n")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
panicHandler := func(s string) {
|
||||||
|
fmt.Fprintf(os.Stdout, "wrapped: %d", len(s))
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, args := args[0], args[1:]
|
||||||
|
switch cmd {
|
||||||
|
case "no-panic-ordered-output":
|
||||||
|
exitStatus, err := BasicWrap(panicHandler)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus < 0 {
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
os.Stdout.Write([]byte("a"))
|
||||||
|
os.Stderr.Write([]byte("b"))
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
case "no-panic-output":
|
||||||
|
fmt.Fprint(os.Stdout, "i am output")
|
||||||
|
fmt.Fprint(os.Stderr, "stderr out")
|
||||||
|
os.Exit(0)
|
||||||
|
case "panic-boundary":
|
||||||
|
exitStatus, err := BasicWrap(panicHandler)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus < 0 {
|
||||||
|
// Simulate a panic but on two boundaries...
|
||||||
|
fmt.Fprint(os.Stderr, "pan")
|
||||||
|
os.Stderr.Sync()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
fmt.Fprint(os.Stderr, "ic: oh crap")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
case "panic-long":
|
||||||
|
exitStatus, err := BasicWrap(panicHandler)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus < 0 {
|
||||||
|
// Make a fake panic by faking the header and adding a
|
||||||
|
// bunch of garbage.
|
||||||
|
fmt.Fprint(os.Stderr, "panic: foo\n\n")
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
fmt.Fprint(os.Stderr, "foobarbaz")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep so that it dumps the previous data
|
||||||
|
//time.Sleep(1 * time.Millisecond)
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// Make a real panic
|
||||||
|
panic("I AM REAL!")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
case "panic":
|
||||||
|
hidePanic := false
|
||||||
|
if args[0] == "hide" {
|
||||||
|
hidePanic = true
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &WrapConfig{
|
||||||
|
Handler: panicHandler,
|
||||||
|
HidePanic: hidePanic,
|
||||||
|
}
|
||||||
|
|
||||||
|
exitStatus, err := Wrap(config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus < 0 {
|
||||||
|
panic("uh oh")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
case "wrapped":
|
||||||
|
child := false
|
||||||
|
if len(args) > 0 && args[0] == "child" {
|
||||||
|
child = true
|
||||||
|
}
|
||||||
|
config := &WrapConfig{
|
||||||
|
Handler: panicHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
exitStatus, err := Wrap(config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus < 0 {
|
||||||
|
if child {
|
||||||
|
fmt.Printf("%v", Wrapped(config))
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !child {
|
||||||
|
fmt.Printf("%v", Wrapped(config))
|
||||||
|
}
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
case "panic-monitor":
|
||||||
|
|
||||||
|
config := &WrapConfig{
|
||||||
|
Handler: panicHandler,
|
||||||
|
HidePanic: true,
|
||||||
|
Monitor: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
exitStatus, err := Wrap(config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus != -1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("uh oh")
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "Unknown command: %q\n", cmd)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicWrap_Output(t *testing.T) {
|
||||||
|
stderr := new(bytes.Buffer)
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("no-panic-output")
|
||||||
|
p.Stdout = stdout
|
||||||
|
p.Stderr = stderr
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "i am output") {
|
||||||
|
t.Fatalf("didn't forward: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stderr.String(), "stderr out") {
|
||||||
|
t.Fatalf("didn't forward: %#v", stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO(mitchellh): This property would be nice to gain.
|
||||||
|
func TestPanicWrap_Output_Order(t *testing.T) {
|
||||||
|
output := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("no-panic-ordered-output")
|
||||||
|
p.Stdout = output
|
||||||
|
p.Stderr = output
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedBuf := new(bytes.Buffer)
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
expectedBuf.WriteString("ab")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(output.String())
|
||||||
|
expected := strings.TrimSpace(expectedBuf.String())
|
||||||
|
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestPanicWrap_panicHide(t *testing.T) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
stderr := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("panic", "hide")
|
||||||
|
p.Stdout = stdout
|
||||||
|
p.Stderr = stderr
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "wrapped:") {
|
||||||
|
t.Fatalf("didn't wrap: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(stderr.String(), "panic:") {
|
||||||
|
t.Fatalf("shouldn't have panic: %#v", stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicWrap_panicShow(t *testing.T) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
stderr := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("panic", "show")
|
||||||
|
p.Stdout = stdout
|
||||||
|
p.Stderr = stderr
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "wrapped:") {
|
||||||
|
t.Fatalf("didn't wrap: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stderr.String(), "panic:") {
|
||||||
|
t.Fatalf("should have panic: %#v", stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicWrap_panicLong(t *testing.T) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("panic-long")
|
||||||
|
p.Stdout = stdout
|
||||||
|
p.Stderr = new(bytes.Buffer)
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "wrapped:") {
|
||||||
|
t.Fatalf("didn't wrap: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicWrap_panicBoundary(t *testing.T) {
|
||||||
|
// TODO(mitchellh): panics are currently lost on boundaries
|
||||||
|
t.SkipNow()
|
||||||
|
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("panic-boundary")
|
||||||
|
p.Stdout = stdout
|
||||||
|
//p.Stderr = new(bytes.Buffer)
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "wrapped: 1015") {
|
||||||
|
t.Fatalf("didn't wrap: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicWrap_monitor(t *testing.T) {
|
||||||
|
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("panic-monitor")
|
||||||
|
p.Stdout = stdout
|
||||||
|
//p.Stderr = new(bytes.Buffer)
|
||||||
|
if err := p.Run(); err == nil || err.Error() != "exit status 2" {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "wrapped:") {
|
||||||
|
t.Fatalf("didn't wrap: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapped(t *testing.T) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("wrapped", "child")
|
||||||
|
p.Stdout = stdout
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "true") {
|
||||||
|
t.Fatalf("bad: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapped_parent(t *testing.T) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
p := helperProcess("wrapped")
|
||||||
|
p.Stdout = stdout
|
||||||
|
if err := p.Run(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(stdout.String(), "false") {
|
||||||
|
t.Fatalf("bad: %#v", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,12 @@
|
||||||
package util
|
package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AttemptStrategy is reused from the goamz package
|
|
||||||
|
|
||||||
// AttemptStrategy represents a strategy for waiting for an action
|
// AttemptStrategy represents a strategy for waiting for an action
|
||||||
// to complete successfully. This is an internal type used by the
|
// to complete successfully. This is an internal type used by the
|
||||||
// implementation of other packages.
|
// implementation of other goamz packages.
|
||||||
type AttemptStrategy struct {
|
type AttemptStrategy struct {
|
||||||
Total time.Duration // total duration of attempt.
|
Total time.Duration // total duration of attempt.
|
||||||
Delay time.Duration // interval between each try in the burst.
|
Delay time.Duration // interval between each try in the burst.
|
57
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/attempt_test.go
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/attempt_test.go
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package aws_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/crowdmob/goamz/aws"
|
||||||
|
"gopkg.in/check.v1"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (S) TestAttemptTiming(c *check.C) {
|
||||||
|
testAttempt := aws.AttemptStrategy{
|
||||||
|
Total: 0.25e9,
|
||||||
|
Delay: 0.1e9,
|
||||||
|
}
|
||||||
|
want := []time.Duration{0, 0.1e9, 0.2e9, 0.2e9}
|
||||||
|
got := make([]time.Duration, 0, len(want)) // avoid allocation when testing timing
|
||||||
|
t0 := time.Now()
|
||||||
|
for a := testAttempt.Start(); a.Next(); {
|
||||||
|
got = append(got, time.Now().Sub(t0))
|
||||||
|
}
|
||||||
|
got = append(got, time.Now().Sub(t0))
|
||||||
|
c.Assert(got, check.HasLen, len(want))
|
||||||
|
const margin = 0.01e9
|
||||||
|
for i, got := range want {
|
||||||
|
lo := want[i] - margin
|
||||||
|
hi := want[i] + margin
|
||||||
|
if got < lo || got > hi {
|
||||||
|
c.Errorf("attempt %d want %g got %g", i, want[i].Seconds(), got.Seconds())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (S) TestAttemptNextHasNext(c *check.C) {
|
||||||
|
a := aws.AttemptStrategy{}.Start()
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.Next(), check.Equals, false)
|
||||||
|
|
||||||
|
a = aws.AttemptStrategy{}.Start()
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.HasNext(), check.Equals, false)
|
||||||
|
c.Assert(a.Next(), check.Equals, false)
|
||||||
|
|
||||||
|
a = aws.AttemptStrategy{Total: 2e8}.Start()
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.HasNext(), check.Equals, true)
|
||||||
|
time.Sleep(2e8)
|
||||||
|
c.Assert(a.HasNext(), check.Equals, true)
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.Next(), check.Equals, false)
|
||||||
|
|
||||||
|
a = aws.AttemptStrategy{Total: 1e8, Min: 2}.Start()
|
||||||
|
time.Sleep(1e8)
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.HasNext(), check.Equals, true)
|
||||||
|
c.Assert(a.Next(), check.Equals, true)
|
||||||
|
c.Assert(a.HasNext(), check.Equals, false)
|
||||||
|
c.Assert(a.Next(), check.Equals, false)
|
||||||
|
}
|
624
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/aws.go
generated
vendored
Normal file
624
Godeps/_workspace/src/github.com/crowdmob/goamz/aws/aws.go
generated
vendored
Normal file
|
@ -0,0 +1,624 @@
|
||||||
|
//
|
||||||
|
// goamz - Go packages to interact with the Amazon Web Services.
|
||||||
|
//
|
||||||
|
// https://wiki.ubuntu.com/goamz
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 Canonical Ltd.
|
||||||
|
//
|
||||||
|
// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
|
||||||
|
//
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Regular expressions for INI files
|
||||||
|
var (
|
||||||
|
iniSectionRegexp = regexp.MustCompile(`^\s*\[([^\[\]]+)\]\s*$`)
|
||||||
|
iniSettingRegexp = regexp.MustCompile(`^\s*(.+?)\s*=\s*(.*\S)\s*$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines the valid signers
|
||||||
|
const (
|
||||||
|
V2Signature = iota
|
||||||
|
V4Signature = iota
|
||||||
|
Route53Signature = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines the service endpoint and correct Signer implementation to use
|
||||||
|
// to sign requests for this endpoint
|
||||||
|
type ServiceInfo struct {
|
||||||
|
Endpoint string
|
||||||
|
Signer uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region defines the URLs where AWS services may be accessed.
|
||||||
|
//
|
||||||
|
// See http://goo.gl/d8BP1 for more details.
|
||||||
|
type Region struct {
|
||||||
|
Name string // the canonical name of this region.
|
||||||
|
EC2Endpoint string
|
||||||
|
S3Endpoint string
|
||||||
|
S3BucketEndpoint string // Not needed by AWS S3. Use ${bucket} for bucket name.
|
||||||
|
S3LocationConstraint bool // true if this region requires a LocationConstraint declaration.
|
||||||
|
S3LowercaseBucket bool // true if the region requires bucket names to be lower case.
|
||||||
|
SDBEndpoint string
|
||||||
|
SNSEndpoint string
|
||||||
|
SQSEndpoint string
|
||||||
|
SESEndpoint string
|
||||||
|
IAMEndpoint string
|
||||||
|
ELBEndpoint string
|
||||||
|
DynamoDBEndpoint string
|
||||||
|
CloudWatchServicepoint ServiceInfo
|
||||||
|
AutoScalingEndpoint string
|
||||||
|
RDSEndpoint ServiceInfo
|
||||||
|
KinesisEndpoint string
|
||||||
|
STSEndpoint string
|
||||||
|
CloudFormationEndpoint string
|
||||||
|
ElastiCacheEndpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
var Regions = map[string]Region{
|
||||||
|
APNortheast.Name: APNortheast,
|
||||||
|
APSoutheast.Name: APSoutheast,
|
||||||
|
APSoutheast2.Name: APSoutheast2,
|
||||||
|
EUCentral.Name: EUCentral,
|
||||||
|
EUWest.Name: EUWest,
|
||||||
|
USEast.Name: USEast,
|
||||||
|
USWest.Name: USWest,
|
||||||
|
USWest2.Name: USWest2,
|
||||||
|
USGovWest.Name: USGovWest,
|
||||||
|
SAEast.Name: SAEast,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Designates a signer interface suitable for signing AWS requests, params
|
||||||
|
// should be appropriately encoded for the request before signing.
|
||||||
|
//
|
||||||
|
// A signer should be initialized with Auth and the appropriate endpoint.
|
||||||
|
type Signer interface {
|
||||||
|
Sign(method, path string, params map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An AWS Service interface with the API to query the AWS service
|
||||||
|
//
|
||||||
|
// Supplied as an easy way to mock out service calls during testing.
|
||||||
|
type AWSService interface {
|
||||||
|
// Queries the AWS service at a given method/path with the params and
|
||||||
|
// returns an http.Response and error
|
||||||
|
Query(method, path string, params map[string]string) (*http.Response, error)
|
||||||
|
// Builds an error given an XML payload in the http.Response, can be used
|
||||||
|
// to process an error if the status code is not 200 for example.
|
||||||
|
BuildError(r *http.Response) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements a Server Query/Post API to easily query AWS services and build
|
||||||
|
// errors when desired
|
||||||
|
type Service struct {
|
||||||
|
service ServiceInfo
|
||||||
|
signer Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a base set of params for an action
|
||||||
|
func MakeParams(action string) map[string]string {
|
||||||
|
params := make(map[string]string)
|
||||||
|
params["Action"] = action
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new AWS server to handle making requests
|
||||||
|
func NewService(auth Auth, service ServiceInfo) (s *Service, err error) {
|
||||||
|
var signer Signer
|
||||||
|
switch service.Signer {
|
||||||
|
case V2Signature:
|
||||||
|
signer, err = NewV2Signer(auth, service)
|
||||||
|
// case V4Signature:
|
||||||
|
// signer, err = NewV4Signer(auth, service, Regions["eu-west-1"])
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Unsupported signer for service")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s = &Service{service: service, signer: signer}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Query(method, path string, params map[string]string) (resp *http.Response, err error) {
|
||||||
|
params["Timestamp"] = time.Now().UTC().Format(time.RFC3339)
|
||||||
|
u, err := url.Parse(s.service.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u.Path = path
|
||||||
|
|
||||||
|
s.signer.Sign(method, path, params)
|
||||||
|
if method == "GET" {
|
||||||
|
u.RawQuery = multimap(params).Encode()
|
||||||
|
resp, err = http.Get(u.String())
|
||||||
|
} else if method == "POST" {
|
||||||
|
resp, err = http.PostForm(u.String(), multimap(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) BuildError(r *http.Response) error {
|
||||||
|
errors := ErrorResponse{}
|
||||||
|
xml.NewDecoder(r.Body).Decode(&errors)
|
||||||
|
var err Error
|
||||||
|
err = errors.Errors
|
||||||
|
err.RequestId = errors.RequestId
|
||||||
|
err.StatusCode = r.StatusCode
|
||||||
|
if err.Message == "" {
|
||||||
|
err.Message = r.Status
|
||||||
|
}
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceError interface {
|
||||||
|
error
|
||||||
|
ErrorCode() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Errors Error `xml:"Error"`
|
||||||
|
RequestId string // A unique ID for tracking the request
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
StatusCode int
|
||||||
|
Type string
|
||||||
|
Code string
|
||||||
|
Message string
|
||||||
|
RequestId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *Error) Error() string {
|
||||||
|
return fmt.Sprintf("Type: %s, Code: %s, Message: %s",
|
||||||
|
err.Type, err.Code, err.Message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *Error) ErrorCode() string {
|
||||||
|
return err.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
AccessKey, SecretKey string
|
||||||
|
token string
|
||||||
|
expiration time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) Token() string {
|
||||||
|
if a.token == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if time.Since(a.expiration) >= -30*time.Second { //in an ideal world this should be zero assuming the instance is synching it's clock
|
||||||
|
*a, _ = GetAuth("", "", "", time.Time{})
|
||||||
|
}
|
||||||
|
return a.token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) Expiration() time.Time {
|
||||||
|
return a.expiration
|
||||||
|
}
|
||||||
|
|
||||||
|
// To be used with other APIs that return auth credentials such as STS
|
||||||
|
func NewAuth(accessKey, secretKey, token string, expiration time.Time) *Auth {
|
||||||
|
return &Auth{
|
||||||
|
AccessKey: accessKey,
|
||||||
|
SecretKey: secretKey,
|
||||||
|
token: token,
|
||||||
|
expiration: expiration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseMetadata
|
||||||
|
type ResponseMetadata struct {
|
||||||
|
RequestId string // A unique ID for tracking the request
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseResponse struct {
|
||||||
|
ResponseMetadata ResponseMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
var unreserved = make([]bool, 128)
|
||||||
|
var hex = "0123456789ABCDEF"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// RFC3986
|
||||||
|
u := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890-_.~"
|
||||||
|
for _, c := range u {
|
||||||
|
unreserved[c] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func multimap(p map[string]string) url.Values {
|
||||||
|
q := make(url.Values, len(p))
|
||||||
|
for k, v := range p {
|
||||||
|
q[k] = []string{v}
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
type credentials struct {
|
||||||
|
Code string
|
||||||
|
LastUpdated string
|
||||||
|
Type string
|
||||||
|
AccessKeyId string
|
||||||
|
SecretAccessKey string
|
||||||
|
Token string
|
||||||
|
Expiration string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMetaData retrieves instance metadata about the current machine.
|
||||||
|
//
|
||||||
|
// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html for more details.
|
||||||
|
func GetMetaData(path string) (contents []byte, err error) {
|
||||||
|
c := http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Dial: func(netw, addr string) (net.Conn, error) {
|
||||||
|
deadline := time.Now().Add(5 * time.Second)
|
||||||
|
c, err := net.DialTimeout(netw, addr, time.Second*2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.SetDeadline(deadline)
|
||||||
|
return c, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
url := "http://169.254.169.254/latest/meta-data/" + path
|
||||||
|
|
||||||
|
resp, err := c.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
err = fmt.Errorf("Code %d returned for url %s", resp.StatusCode, url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return []byte(body), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRegion(regionName string) (region Region) {
|
||||||
|
region = Regions[regionName]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInstanceCredentials creates an Auth based on the instance's role credentials.
|
||||||
|
// If the running instance is not in EC2 or does not have a valid IAM role, an error will be returned.
|
||||||
|
// For more info about setting up IAM roles, see http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
|
||||||
|
func GetInstanceCredentials() (cred credentials, err error) {
|
||||||
|
credentialPath := "iam/security-credentials/"
|
||||||
|
|
||||||
|
// Get the instance role
|
||||||
|
role, err := GetMetaData(credentialPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the instance role credentials
|
||||||
|
credentialJSON, err := GetMetaData(credentialPath + string(role))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(credentialJSON), &cred)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuth creates an Auth based on either passed in credentials,
|
||||||
|
// environment information or instance based role credentials.
|
||||||
|
func GetAuth(accessKey string, secretKey, token string, expiration time.Time) (auth Auth, err error) {
|
||||||
|
// First try passed in credentials
|
||||||
|
if accessKey != "" && secretKey != "" {
|
||||||
|
return Auth{accessKey, secretKey, token, expiration}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next try to get auth from the environment
|
||||||
|
auth, err = EnvAuth()
|
||||||
|
if err == nil {
|
||||||
|
// Found auth, return
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next try getting auth from the instance role
|
||||||
|
cred, err := GetInstanceCredentials()
|
||||||
|
if err == nil {
|
||||||
|
// Found auth, return
|
||||||
|
auth.AccessKey = cred.AccessKeyId
|
||||||
|
auth.SecretKey = cred.SecretAccessKey
|
||||||
|
auth.token = cred.Token
|
||||||
|
exptdate, err := time.Parse("2006-01-02T15:04:05Z", cred.Expiration)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Error Parsing expiration date: cred.Expiration :%s , error: %s \n", cred.Expiration, err)
|
||||||
|
}
|
||||||
|
auth.expiration = exptdate
|
||||||
|
return auth, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next try getting auth from the credentials file
|
||||||
|
auth, err = CredentialFileAuth("", "", time.Minute*5)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//err = errors.New("No valid AWS authentication found")
|
||||||
|
err = fmt.Errorf("No valid AWS authentication found: %s", err)
|
||||||
|
return auth, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnvAuth creates an Auth based on environment information.
|
||||||
|
// The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment
|
||||||
|
// variables are used.
|
||||||
|
func EnvAuth() (auth Auth, err error) {
|
||||||
|
auth.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
|
||||||
|
if auth.AccessKey == "" {
|
||||||
|
auth.AccessKey = os.Getenv("AWS_ACCESS_KEY")
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
|
||||||
|
if auth.SecretKey == "" {
|
||||||
|
auth.SecretKey = os.Getenv("AWS_SECRET_KEY")
|
||||||
|
}
|
||||||
|
if auth.AccessKey == "" {
|
||||||
|
err = errors.New("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
|
||||||
|
}
|
||||||
|
if auth.SecretKey == "" {
|
||||||
|
err = errors.New("AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CredentialFileAuth creates and Auth based on a credentials file. The file
|
||||||
|
// contains various authentication profiles for use with AWS.
|
||||||
|
//
|
||||||
|
// The credentials file, which is used by other AWS SDKs, is documented at
|
||||||
|
// http://blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs
|
||||||
|
func CredentialFileAuth(filePath string, profile string, expiration time.Duration) (auth Auth, err error) {
|
||||||
|
if profile == "" {
|
||||||
|
profile = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
if filePath == "" {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return auth, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath = path.Join(u.HomeDir, ".aws", "credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the file, then parse the INI
|
||||||
|
contents, err := ioutil.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles := parseINI(string(contents))
|
||||||
|
profileData, ok := profiles[profile]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("The credentials file did not contain the profile")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keyId, ok := profileData["aws_access_key_id"]
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("The credentials file did not contain required attribute aws_access_key_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secretKey, ok := profileData["aws_secret_access_key"]
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("The credentials file did not contain required attribute aws_secret_access_key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.AccessKey = keyId
|
||||||
|
auth.SecretKey = secretKey
|
||||||
|
|
||||||
|
if token, ok := profileData["aws_session_token"]; ok {
|
||||||
|
auth.token = token
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.expiration = time.Now().Add(expiration)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseINI takes the contents of a credentials file and returns a map, whose keys
|
||||||
|
// are the various profiles, and whose values are maps of the settings for the
|
||||||
|
// profiles
|
||||||
|
func parseINI(fileContents string) map[string]map[string]string {
|
||||||
|
profiles := make(map[string]map[string]string)
|
||||||
|
|
||||||
|
lines := strings.Split(fileContents, "\n")
|
||||||
|
|
||||||
|
var currentSection map[string]string
|
||||||
|
for _, line := range lines {
|
||||||
|
// remove comments, which start with a semi-colon
|
||||||
|
if split := strings.Split(line, ";"); len(split) > 1 {
|
||||||
|
line = split[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the line is the start of a profile.
|
||||||
|
//
|
||||||
|
// for example:
|
||||||
|
// [default]
|
||||||
|
//
|
||||||
|
// otherwise, check for the proper setting
|
||||||
|
// property=value
|
||||||
|
if sectMatch := iniSectionRegexp.FindStringSubmatch(line); len(sectMatch) == 2 {
|
||||||
|
currentSection = make(map[string]string)
|
||||||
|
profiles[sectMatch[1]] = currentSection
|
||||||
|
} else if setMatch := iniSettingRegexp.FindStringSubmatch(line); len(setMatch) == 3 && currentSection != nil {
|
||||||
|
currentSection[setMatch[1]] = setMatch[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode takes a string and URI-encodes it in a way suitable
|
||||||
|
// to be used in AWS signatures.
|
||||||
|
func Encode(s string) string {
|
||||||
|
encode := false
|
||||||
|
for i := 0; i != len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c > 127 || !unreserved[c] {
|
||||||
|
encode = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !encode {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
e := make([]byte, len(s)*3)
|
||||||
|
ei := 0
|
||||||
|
for i := 0; i != len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c > 127 || !unreserved[c] {
|
||||||
|
e[ei] = '%'
|
||||||
|
e[ei+1] = hex[c>>4]
|
||||||
|
e[ei+2] = hex[c&0xF]
|
||||||
|
ei += 3
|
||||||
|
} else {
|
||||||
|
e[ei] = c
|
||||||
|
ei += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(e[:ei])
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialTimeout(network, addr string) (net.Conn, error) {
|
||||||
|
return net.DialTimeout(network, addr, time.Duration(2*time.Second))
|
||||||
|
}
|
||||||
|
|
||||||
|
func AvailabilityZone() string {
|
||||||
|
transport := http.Transport{Dial: dialTimeout}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("http://169.254.169.254/latest/meta-data/placement/availability-zone")
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstanceRegion() string {
|
||||||
|
az := AvailabilityZone()
|
||||||
|
if az == "unknown" {
|
||||||
|
return az
|
||||||
|
} else {
|
||||||
|
region := az[:len(az)-1]
|
||||||
|
return region
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstanceId() string {
|
||||||
|
transport := http.Transport{Dial: dialTimeout}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("http://169.254.169.254/latest/meta-data/instance-id")
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstanceType() string {
|
||||||
|
transport := http.Transport{Dial: dialTimeout}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("http://169.254.169.254/latest/meta-data/instance-type")
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
} else {
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServerLocalIp() string {
|
||||||
|
transport := http.Transport{Dial: dialTimeout}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("http://169.254.169.254/latest/meta-data/local-ipv4")
|
||||||
|
if err != nil {
|
||||||
|
return "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServerPublicIp() string {
|
||||||
|
transport := http.Transport{Dial: dialTimeout}
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
resp, err := client.Get("http://169.254.169.254/latest/meta-data/public-ipv4")
|
||||||
|
if err != nil {
|
||||||
|
return "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue