Skip to content

Commit

Permalink
Merge pull request #256 from airbrake/buffalo-integration
Browse files Browse the repository at this point in the history
gobrake-buffalo middleware and example application
  • Loading branch information
chimanjain authored Mar 31, 2022
2 parents 90de7e7 + 3a59c44 commit c6c08c5
Show file tree
Hide file tree
Showing 5 changed files with 753 additions and 8 deletions.
42 changes: 42 additions & 0 deletions buffalo/buffalobrake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package buffalo

import (
"errors"
"net/http"

"github.com/airbrake/gobrake/v5"
"github.com/gobuffalo/buffalo"
)

// A Handler is a buffalo middleware that provides integration with
// Airbrake.
type Handler struct {
Notifier *gobrake.Notifier
}

// New returns a new Airbrake notifier instance. Use the Handle method to wrap
// existing buffalo handlers.
func New(notifier *gobrake.Notifier) (*Handler, error) {
if notifier == nil {
return nil, errors.New("airbrake notifier not defined")
}
h := Handler{notifier}
return &h, nil
}

// Handle works as a middleware that wraps an existing buffalo.Handler and sends route performance stats
func (h *Handler) Handle(next buffalo.Handler) buffalo.Handler {
return func(c buffalo.Context) error {
_, metric := gobrake.NewRouteMetric(c, c.Request().Method, c.Value("current_route").(buffalo.RouteInfo).Path)

err := next(c)
ws, ok := c.Response().(*buffalo.Response)
if !ok {
ws = &buffalo.Response{ResponseWriter: c.Response()}
ws.Status = http.StatusOK
}
metric.StatusCode = ws.Status
_ = h.Notifier.Routes.Notify(c, metric)
return err
}
}
39 changes: 39 additions & 0 deletions examples/buffalo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Buffalo integration

This is an example of a basic Buffalo app with Airbrake middleware that reports performance data (route stats).

## How to run Example API

Insert your project ID and project key in the `main.go` file. You can find these values on the settings page for your project.

Initialise mod file

```bash
go mod init
go mod tidy
```

Run go application

```bash
go run main.go
```

The example application provides three GET endpoints:

1. `/date` - gets the system date from the server
2. `/locations` - gets the supported locations for use with the `/weather` endpoint
3. `/weather/{locationName}` - gets the weather for a location; valid values for `locationName` can be found using the `/locations` endpoint

Use the cURL commands below to interact with the endpoints.

```bash
curl "http://localhost:3000/date"
curl "http://localhost:3000/locations"
curl "http://localhost:3000/weather/{austin/pune/santabarbara}"
```

To see how Airbrake error monitoring works, use an unsupported location, e.g. `boston`: `curl "http://localhost:3000/weather/boston"`.
After issuing this request, the service will respond with a `404 Not Found` error. Visit the Airbrake dashboard to see the error captured there.

Once you call the API endpoints, view the Airbrake errors and performance dashboards for your project.
142 changes: 142 additions & 0 deletions examples/buffalo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"strings"
"time"

"github.com/airbrake/gobrake/v5"
buffalobrake "github.com/airbrake/gobrake/v5/buffalo"
"github.com/gobuffalo/buffalo"
"github.com/gobuffalo/buffalo/render"
)

// Define models

type Date struct {
Date int64 `json:"date,omitempty"`
}

type WeatherInfo struct {
Lat float32 `json:"lat,omitempty"`
Lon float32 `json:"lon,omitempty"`
Timezone string `json:"timezone,omitempty"`
TimezoneOffset float32 `json:"timezone_offset,omitempty"`
Current interface{} `json:"current,omitempty"`
Minutely []interface{} `json:"minutely,omitempty"`
Hourly []interface{} `json:"hourly,omitempty"`
Daily []interface{} `json:"daily,omitempty"`
}

var ProjectId int64 = 999999 // Insert your Project Id here
var ProjectKey string = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // Insert your Project Key here

var notifier = gobrake.NewNotifierWithOptions(&gobrake.NotifierOptions{
ProjectId: ProjectId,
ProjectKey: ProjectKey,
Environment: "production",
})

var r *render.Engine

func main() {
app := App()
log.Fatal(app.Serve())
}

func App() *buffalo.App {
app := buffalo.New(buffalo.Options{})
airbrake, err := buffalobrake.New(notifier)
if err != nil {
log.Fatal(err)
}
app.Use(airbrake.Handle)
app.GET("/date", GetDate)
app.GET("/locations", getLocations)
app.GET("/weather/{location}", getWeather)
return app
}

func GetDate(c buffalo.Context) error {
// fmt.Println(c.Request().Response.StatusCode)
var date Date
date.Date = time.Now().Unix()
return c.Render(http.StatusOK, r.JSON(date))
}

func getLocations(c buffalo.Context) error {
availableLocations, err := locations()
if err != nil {
notifier.Notify(err, nil) // send error to airbrake
return c.Render(http.StatusNotFound, r.JSON(availableLocations))
}
return c.Render(http.StatusOK, r.JSON(availableLocations))
}

func getWeather(c buffalo.Context) error {
var weatherInfo WeatherInfo
location := strings.TrimSpace(c.Param("location")) // It lets you allow whitespaces but case-sensitive
weatherResp, err := checkWeather(location)
json.Unmarshal(weatherResp, &weatherInfo)
if err != nil {
notifier.Notify(err, nil) // send error to airbrake
return c.Render(http.StatusNotFound, r.JSON(weatherInfo))
}
return c.Render(http.StatusOK, r.JSON(weatherInfo))
}

func locations() ([]string, error) {
var locations []string
weatherInfoFile := "https://airbrake.github.io/weatherapi/locations"
client := &http.Client{}
req, err := http.NewRequest(http.MethodGet, weatherInfoFile, nil)
if err != nil {
return nil, err
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return nil, errors.New("locations not found")
}
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
json.Unmarshal(bodyBytes, &locations)
return locations, nil
}

func checkWeather(location string) ([]byte, error) {
weatherInfoFile := "https://airbrake.github.io/weatherapi/weather/" + location
client := &http.Client{}
req, err := http.NewRequest(http.MethodGet, weatherInfoFile, nil)
if err != nil {
return nil, err
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
errMsg := fmt.Sprintf("%s weather data not found", location)
return nil, errors.New(errMsg)
}
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return bodyBytes, nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/beego/beego/v2 v2.0.2
github.com/caio/go-tdigest v3.1.0+incompatible
github.com/gin-gonic/gin v1.7.7
github.com/gobuffalo/buffalo v0.18.5
github.com/gofiber/fiber/v2 v2.28.0
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/mux v1.8.0
Expand Down
Loading

0 comments on commit c6c08c5

Please sign in to comment.