Skip to content

Commit

Permalink
Monitors errors and database with New Relic
Browse files Browse the repository at this point in the history
  • Loading branch information
cuducos committed Nov 14, 2023
1 parent a896297 commit dd8240b
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 37 deletions.
53 changes: 35 additions & 18 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import (
"time"

"github.com/cuducos/go-cnpj"
"github.com/cuducos/minha-receita/monitor"
"github.com/newrelic/go-agent/v3/integrations/logcontext-v2/logWriter"
"github.com/newrelic/go-agent/v3/newrelic"
)

const cacheMaxAge = time.Hour * 24
Expand All @@ -30,26 +33,37 @@ type errorMessage struct {
Message string `json:"message"`
}

type api struct {
db database
host string
errorLogger logWriter.LogWriter
}

// messageResponse takes a text message and a HTTP status, wraps the message into a
// JSON output and writes it together with the proper headers to a response.
func messageResponse(w http.ResponseWriter, s int, m string) {
w.WriteHeader(s)
func (app *api) messageResponse(w http.ResponseWriter, s int, m string) {
if m == "" {
w.WriteHeader(s)
if s == http.StatusInternalServerError {
app.errorLogger.Write([]byte("Internal server error without error message"))
}
return
}

b, err := json.Marshal(errorMessage{m})
if err != nil {
fmt.Fprintf(os.Stderr, "Could not wrap message in JSON: %s", m)
w.WriteHeader(http.StatusInternalServerError)
app.errorLogger.Write([]byte(fmt.Sprintf("Could not wrap message in JSON: %s", m)))
return
}

w.WriteHeader(s)
w.Header().Set("Content-type", "application/json")
w.Write(b)
}

type api struct {
db database
host string
if s == http.StatusInternalServerError {
app.errorLogger.Write(b)
}
}

func (app *api) companyHandler(w http.ResponseWriter, r *http.Request) {
Expand All @@ -65,7 +79,7 @@ func (app *api) companyHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
return
default:
messageResponse(w, http.StatusMethodNotAllowed, "Essa URL aceita apenas o método GET.")
app.messageResponse(w, http.StatusMethodNotAllowed, "Essa URL aceita apenas o método GET.")
return
}

Expand All @@ -75,13 +89,13 @@ func (app *api) companyHandler(w http.ResponseWriter, r *http.Request) {
return
}
if !cnpj.IsValid(v) {
messageResponse(w, http.StatusBadRequest, fmt.Sprintf("CNPJ %s inválido.", cnpj.Mask(v[1:])))
app.messageResponse(w, http.StatusBadRequest, fmt.Sprintf("CNPJ %s inválido.", cnpj.Mask(v[1:])))
return
}

s, err := app.db.GetCompany(cnpj.Unmask(v))
if err != nil {
messageResponse(w, http.StatusNotFound, fmt.Sprintf("CNPJ %s não encontrado.", cnpj.Mask(v)))
app.messageResponse(w, http.StatusNotFound, fmt.Sprintf("CNPJ %s não encontrado.", cnpj.Mask(v)))
return
}
w.Header().Set("Content-type", "application/json")
Expand All @@ -91,25 +105,25 @@ func (app *api) companyHandler(w http.ResponseWriter, r *http.Request) {

func (app *api) updatedHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
messageResponse(w, http.StatusMethodNotAllowed, "Essa URL aceita apenas o método GET.")
app.messageResponse(w, http.StatusMethodNotAllowed, "Essa URL aceita apenas o método GET.")
return
}
s, err := app.db.MetaRead("updated-at")
if err != nil {
messageResponse(w, http.StatusInternalServerError, "Erro buscando data de atualização.")
app.messageResponse(w, http.StatusInternalServerError, "Erro buscando data de atualização.")
return
}
if s == "" {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Cache-Control", cacheControl)
messageResponse(w, http.StatusOK, fmt.Sprintf("%s é a data de extração dos dados pela Receita Federal.", s))
app.messageResponse(w, http.StatusOK, fmt.Sprintf("%s é a data de extração dos dados pela Receita Federal.", s))
}

func (app *api) healthHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
messageResponse(w, http.StatusMethodNotAllowed, "Essa URL aceita apenas o método GET.")
app.messageResponse(w, http.StatusMethodNotAllowed, "Essa URL aceita apenas o método GET.")
return
}
w.WriteHeader(http.StatusOK)
Expand All @@ -131,12 +145,15 @@ func (app *api) allowedHostWrapper(h func(http.ResponseWriter, *http.Request)) f
}

// Serve spins up the HTTP server.
func Serve(db database, p, n string) {
func Serve(db database, p string, nr *newrelic.Application) {
if !strings.HasPrefix(p, ":") {
p = ":" + p
}
nr := newRelicApp(n)
app := api{db: db, host: os.Getenv("ALLOWED_HOST")}
app := api{
db: db,
host: os.Getenv("ALLOWED_HOST"),
errorLogger: logWriter.New(os.Stderr, nr),
}
for _, r := range []struct {
path string
handler func(http.ResponseWriter, *http.Request)
Expand All @@ -145,7 +162,7 @@ func Serve(db database, p, n string) {
{"/updated", app.updatedHandler},
{"/healthz", app.healthHandler},
} {
http.HandleFunc(newRelicHandle(nr, r.path, app.allowedHostWrapper(r.handler)))
http.HandleFunc(monitor.NewRelicHandle(nr, r.path, app.allowedHostWrapper(r.handler)))
}
log.Output(1, fmt.Sprintf("Serving at http://0.0.0.0%s", p))
log.Fatal(http.ListenAndServe(p, nil))
Expand Down
19 changes: 14 additions & 5 deletions cmd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (

"github.com/cuducos/minha-receita/api"
"github.com/cuducos/minha-receita/db"
"github.com/cuducos/minha-receita/monitor"
"github.com/newrelic/go-agent/v3/newrelic"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -41,7 +43,17 @@ var apiCmd = &cobra.Command{
if err != nil {
return err
}
pg, err := db.NewPostgreSQL(u, postgresSchema)
if newRelic == "" {
newRelic = os.Getenv("NEW_RELIC_LICENSE_KEY")
}
var nr *newrelic.Application
if newRelic != "" {
nr, err = monitor.NewRelicApp(newRelic)
if err != nil {
return err
}
}
pg, err := db.NewPostgreSQL(u, postgresSchema, nr)
if err != nil {
return err
}
Expand All @@ -52,10 +64,7 @@ var apiCmd = &cobra.Command{
if port == "" {
port = defaultPort
}
if newRelic == "" {
newRelic = os.Getenv("NEW_RELIC_LICENSE_KEY")
}
api.Serve(&pg, port, newRelic)
api.Serve(&pg, port, nr)
return nil
},
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ var createCmd = &cobra.Command{
if err != nil {
return err
}
pg, err := db.NewPostgreSQL(u, postgresSchema)
pg, err := db.NewPostgreSQL(u, postgresSchema, nil)
if err != nil {
return err
}
Expand All @@ -83,7 +83,7 @@ var dropCmd = &cobra.Command{
if err != nil {
return err
}
pg, err := db.NewPostgreSQL(u, postgresSchema)
pg, err := db.NewPostgreSQL(u, postgresSchema, nil)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var transformCmd = &cobra.Command{
if err != nil {
return err
}
pg, err := db.NewPostgreSQL(u, postgresSchema)
pg, err := db.NewPostgreSQL(u, postgresSchema, nil)
if err != nil {
return err
}
Expand Down
24 changes: 21 additions & 3 deletions db/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (

"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/newrelic/go-agent/v3/integrations/nrpgx5"
"github.com/newrelic/go-agent/v3/newrelic"
)

const (
Expand All @@ -31,6 +33,7 @@ var sql embed.FS
// PostgreSQL database interface.
type PostgreSQL struct {
pool *pgxpool.Pool
newRelic *newrelic.Application
uri string
schema string
sql map[string]string
Expand Down Expand Up @@ -125,7 +128,15 @@ func (p *PostgreSQL) GetCompany(id string) (string, error) {
if err != nil {
return "", fmt.Errorf("error converting cnpj %s to integer: %w", id, err)
}
rows, err := p.pool.Query(context.Background(), p.sql["get"], n)

ctx := context.Background()
if p.newRelic != nil {
txn := p.newRelic.StartTransaction("GetCompany")
ctx = newrelic.NewContext(ctx, txn)
defer txn.End()
}

rows, err := p.pool.Query(ctx, p.sql["get"], n)
if err != nil {
return "", fmt.Errorf("error looking for cnpj %d: %w", n, err)
}
Expand Down Expand Up @@ -179,8 +190,15 @@ func (p *PostgreSQL) MetaRead(k string) (string, error) {
}

// NewPostgreSQL creates a new PostgreSQL connection and ping it to make sure it works.
func NewPostgreSQL(uri, schema string) (PostgreSQL, error) {
conn, err := pgxpool.New(context.Background(), uri)
func NewPostgreSQL(uri, schema string, nr *newrelic.Application) (PostgreSQL, error) {
cfg, err := pgxpool.ParseConfig(uri)
if err != nil {
return PostgreSQL{}, fmt.Errorf("could not create database config: %w", err)
}
if nr != nil {
cfg.ConnConfig.Tracer = nrpgx5.NewTracer()
}
conn, err := pgxpool.NewWithConfig(context.Background(), cfg)
if err != nil {
return PostgreSQL{}, fmt.Errorf("could not connect to the database: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion db/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestPostgresDB(t *testing.T) {
t.Errorf("expected a posgres uri at TEST_DATABASE_URL, found nothing")
return
}
pg, err := NewPostgreSQL(u, "public")
pg, err := NewPostgreSQL(u, "public", nil)
if err != nil {
t.Errorf("expected no error connecting to postgres, got %s", err)
return
Expand Down
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ require (
github.com/dgraph-io/badger/v4 v4.2.0
github.com/jackc/pgx/v5 v5.5.0
github.com/newrelic/go-agent/v3 v3.27.0
github.com/newrelic/go-agent/v3/integrations/logcontext-v2/logWriter v1.0.1
github.com/newrelic/go-agent/v3/integrations/nrpgx5 v1.2.0
github.com/schollz/progressbar/v3 v3.14.1
github.com/spf13/cobra v1.8.0
golang.org/x/text v0.14.0
Expand All @@ -26,11 +28,15 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/flatbuffers v23.3.3+incompatible // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v4 v4.18.1 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/klauspost/compress v1.16.4 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrwriter v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/spf13/pflag v1.0.5 // indirect
Expand Down
Loading

0 comments on commit dd8240b

Please sign in to comment.