Skip to content

Commit 7c33c91

Browse files
committed
Init
0 parents  commit 7c33c91

File tree

8 files changed

+358
-0
lines changed

8 files changed

+358
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea/*
2+
go.sum

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 SpiralScout
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# RoadRunner Sentry integration
2+
3+
Sentry events handler for RoadRunner.
4+
5+
## Installation
6+
7+
### QuickBuild
8+
9+
Add to `.build.json` package `github.com/UPDG/roadrunner-sentry` and register it as `rr.Container.Register(sentry.ID, &sentry.Service{})`
10+
11+
After it build RR using QuickBuild.
12+
13+
Example of final file:
14+
```json
15+
{
16+
"packages": [
17+
"github.com/spiral/roadrunner/service/env",
18+
"github.com/spiral/roadrunner/service/http",
19+
"github.com/spiral/roadrunner/service/rpc",
20+
"github.com/spiral/roadrunner/service/static",
21+
"github.com/UPDG/roadrunner-sentry"
22+
],
23+
"commands": [
24+
"github.com/spiral/roadrunner/cmd/rr/http"
25+
],
26+
"register": [
27+
"rr.Container.Register(env.ID, &env.Service{})",
28+
"rr.Container.Register(rpc.ID, &rpc.Service{})",
29+
"rr.Container.Register(http.ID, &http.Service{})",
30+
"rr.Container.Register(static.ID, &static.Service{})",
31+
"rr.Container.Register(sentry.ID, &sentry.Service{})"
32+
]
33+
}
34+
```
35+
36+
### Manual
37+
38+
1. Add dependency by running `go get github.com/UPDG/roadrunner-sentry`
39+
40+
2. Add to `cms/rr/main.go` import `github.com/UPDG/roadrunner-sentry`
41+
42+
3. Add to `cms/rr/main.go` line `rr.Container.Register(sentry.ID, &sentry.Service{})` after `rr.Container.Register(http.ID, &http.Service{})`
43+
44+
Final file should look like this:
45+
```go
46+
package main
47+
48+
import (
49+
"github.com/sirupsen/logrus"
50+
rr "github.com/spiral/roadrunner/cmd/rr/cmd"
51+
52+
// services (plugins)
53+
"github.com/spiral/roadrunner/service/env"
54+
"github.com/spiral/roadrunner/service/http"
55+
"github.com/spiral/roadrunner/service/rpc"
56+
"github.com/spiral/roadrunner/service/static"
57+
"github.com/UPDG/roadrunner-sentry"
58+
59+
// additional commands and debug handlers
60+
_ "github.com/spiral/roadrunner/cmd/rr/http"
61+
)
62+
63+
func main() {
64+
rr.Container.Register(env.ID, &env.Service{})
65+
rr.Container.Register(rpc.ID, &rpc.Service{})
66+
rr.Container.Register(http.ID, &http.Service{})
67+
rr.Container.Register(static.ID, &static.Service{})
68+
rr.Container.Register(sentry.ID, &sentry.Service{})
69+
70+
rr.Logger.Formatter = &logrus.TextFormatter{ForceColors: true}
71+
72+
// you can register additional commands using cmd.CLI
73+
rr.Execute()
74+
}
75+
```
76+
77+
## Configuration
78+
79+
Add your RoadRunner config (`.rr.yaml` by default) this lines:
80+
81+
```yaml
82+
sentry:
83+
DSN: <your senry DSN>
84+
```

config.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package sentry
2+
3+
import (
4+
"fmt"
5+
"github.com/asaskevich/govalidator"
6+
"github.com/spiral/roadrunner/service"
7+
)
8+
9+
type Config struct {
10+
// Contains Sentry DSN.
11+
DSN string
12+
}
13+
14+
// Hydrate must populate Config values using given Config source. Must return error if Config is not valid.
15+
func (c *Config) Hydrate(cfg service.Config) error {
16+
if err := cfg.Unmarshal(c); err != nil {
17+
return err
18+
}
19+
return c.Valid()
20+
}
21+
22+
func (c *Config) Valid() error {
23+
if !govalidator.IsRequestURL(c.DSN) {
24+
return fmt.Errorf("sentry DSN in not URL")
25+
}
26+
return nil
27+
}

config_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package sentry
2+
3+
import (
4+
"encoding/json"
5+
"github.com/spiral/roadrunner/service"
6+
"github.com/stretchr/testify/assert"
7+
"testing"
8+
)
9+
10+
type mockCfg struct{ cfg string }
11+
12+
func (cfg *mockCfg) Get(name string) service.Config { return nil }
13+
func (cfg *mockCfg) Unmarshal(out interface{}) error { return json.Unmarshal([]byte(cfg.cfg), out) }
14+
15+
func Test_Config_Hydrate(t *testing.T) {
16+
cfg := &mockCfg{`{"DSN": "https://u:[email protected]/sentry/1"}`}
17+
c := &Config{}
18+
19+
assert.NoError(t, c.Hydrate(cfg))
20+
}
21+
22+
func Test_Config_Hydrate_Error(t *testing.T) {
23+
cfg := &mockCfg{`{"enable": true,"DSN": https://u:[email protected]/sentry/1"}`}
24+
c := &Config{}
25+
26+
assert.Error(t, c.Hydrate(cfg))
27+
}
28+
29+
func Test_Config_Valid(t *testing.T) {
30+
assert.NoError(t, (&Config{DSN: "https://u:[email protected]/sentry/1"}).Valid())
31+
assert.Error(t, (&Config{DSN: "//u:[email protected]/sentry/1"}).Valid())
32+
assert.Error(t, (&Config{DSN: "[email protected]"}).Valid())
33+
}

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/UPDG/roadrunner-sentry
2+
3+
require (
4+
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf
5+
github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448 // indirect
6+
github.com/getsentry/raven-go v0.2.0
7+
github.com/pkg/errors v0.8.1 // indirect
8+
github.com/sirupsen/logrus v1.1.1
9+
github.com/spiral/roadrunner v1.3.1
10+
github.com/stretchr/testify v1.3.0
11+
)

service.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package sentry
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"github.com/getsentry/raven-go"
7+
"github.com/spiral/roadrunner"
8+
rr "github.com/spiral/roadrunner/cmd/rr/cmd"
9+
rrhttp "github.com/spiral/roadrunner/service/http"
10+
"net/http"
11+
)
12+
13+
const ID = "sentry"
14+
15+
type Service struct {
16+
}
17+
18+
func (s *Service) Init(cfg *Config) (bool, error) {
19+
err := raven.SetDSN(cfg.DSN)
20+
if err != nil {
21+
return false, err
22+
}
23+
svc, _ := rr.Container.Get(rrhttp.ID)
24+
if svc, ok := svc.(*rrhttp.Service); ok {
25+
svc.AddListener(systemListener)
26+
svc.AddListener(httpListener)
27+
}
28+
return true, nil
29+
}
30+
31+
// listener listens to http events and generates nice looking output.
32+
func httpListener(event int, ctx interface{}) {
33+
// http events
34+
switch event {
35+
case rrhttp.EventError:
36+
e := ctx.(*rrhttp.ErrorEvent)
37+
38+
buf := new(bytes.Buffer)
39+
buf.ReadFrom(e.Request.Body)
40+
body := buf.String()
41+
42+
meta := map[string]string{
43+
"event": "EventError",
44+
"code": "500",
45+
"method": e.Request.Method,
46+
"site": e.Request.Host,
47+
"uri": uri(e.Request),
48+
"requestBody": body,
49+
}
50+
raven.CaptureErrorAndWait(e.Error, meta)
51+
return
52+
}
53+
}
54+
55+
func systemListener(event int, ctx interface{}) {
56+
switch event {
57+
case roadrunner.EventWorkerError:
58+
e := ctx.(roadrunner.WorkerError)
59+
60+
meta := map[string]string{
61+
"event": "EventWorkerError",
62+
}
63+
raven.CaptureErrorAndWait(e.Caused, meta)
64+
return
65+
case roadrunner.EventWorkerDead:
66+
e := ctx.(roadrunner.WorkerError)
67+
68+
meta := map[string]string{
69+
"event": "EventWorkerDead",
70+
}
71+
raven.CaptureErrorAndWait(e.Caused, meta)
72+
return
73+
case roadrunner.EventPoolError:
74+
e := ctx.(roadrunner.WorkerError)
75+
76+
meta := map[string]string{
77+
"event": "EventPoolError",
78+
}
79+
raven.CaptureErrorAndWait(e.Caused, meta)
80+
return
81+
case roadrunner.EventWorkerKill:
82+
e := ctx.(roadrunner.WorkerError)
83+
84+
meta := map[string]string{
85+
"event": "EventWorkerKill",
86+
}
87+
raven.CaptureErrorAndWait(e.Caused, meta)
88+
return
89+
}
90+
}
91+
92+
func uri(r *http.Request) string {
93+
if r.TLS != nil {
94+
return fmt.Sprintf("https://%s%s", r.Host, r.URL.String())
95+
}
96+
97+
return fmt.Sprintf("http://%s%s", r.Host, r.URL.String())
98+
}

service_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package sentry
2+
3+
import (
4+
"crypto/tls"
5+
"encoding/json"
6+
"github.com/sirupsen/logrus"
7+
"github.com/sirupsen/logrus/hooks/test"
8+
"github.com/spiral/roadrunner/service"
9+
"github.com/stretchr/testify/assert"
10+
"net/http"
11+
"net/url"
12+
"testing"
13+
)
14+
15+
type testCfg struct {
16+
sentryCgf string
17+
target string
18+
}
19+
20+
func (cfg *testCfg) Get(name string) service.Config {
21+
if name == ID {
22+
return &testCfg{target: cfg.sentryCgf}
23+
}
24+
25+
return nil
26+
}
27+
func (cfg *testCfg) Unmarshal(out interface{}) error {
28+
return json.Unmarshal([]byte(cfg.target), out)
29+
}
30+
31+
func Test_Service_Init_NoProject(t *testing.T) {
32+
logger, _ := test.NewNullLogger()
33+
logger.SetLevel(logrus.DebugLevel)
34+
35+
c := service.NewContainer(logger)
36+
c.Register(ID, &Service{})
37+
38+
assert.Error(t, c.Init(&testCfg{sentryCgf: `{"DSN":"https://u:[email protected]/sentry/"}`}))
39+
}
40+
41+
func Test_Service_Init_NoUser(t *testing.T) {
42+
logger, _ := test.NewNullLogger()
43+
logger.SetLevel(logrus.DebugLevel)
44+
45+
c := service.NewContainer(logger)
46+
c.Register(ID, &Service{})
47+
48+
assert.Error(t, c.Init(&testCfg{sentryCgf: `{"DSN":"https://example.com/sentry"}`}))
49+
}
50+
51+
func Test_Service_Init_AllGood(t *testing.T) {
52+
logger, _ := test.NewNullLogger()
53+
logger.SetLevel(logrus.DebugLevel)
54+
55+
c := service.NewContainer(logger)
56+
c.Register(ID, &Service{})
57+
58+
assert.NoError(t, c.Init(&testCfg{sentryCgf: `{"DSN":"https://u:[email protected]/sentry/1"}`}))
59+
}
60+
61+
func TestService_Uri(t *testing.T) {
62+
testUrl, _ := url.Parse("/sentry/1")
63+
assert.Equal(t, "https://example.com/sentry/1", uri(&http.Request{
64+
TLS: &tls.ConnectionState{},
65+
Host: "example.com",
66+
URL: testUrl,
67+
}))
68+
assert.NotEqual(t, "http://example.com/sentry/1", uri(&http.Request{
69+
TLS: &tls.ConnectionState{},
70+
Host: "example.com",
71+
URL: testUrl,
72+
}))
73+
74+
assert.Equal(t, "http://example.com/sentry/1", uri(&http.Request{
75+
Host: "example.com",
76+
URL: testUrl,
77+
}))
78+
assert.NotEqual(t, "https://example.com/sentry/1", uri(&http.Request{
79+
Host: "example.com",
80+
URL: testUrl,
81+
}))
82+
}

0 commit comments

Comments
 (0)