How to re-evaluate promhttp.Handler on database change? - go

I'm perhaps abusing promhttp.Handler() to realise the use case for my microservice to tell me the:
version
if it has database connectivity
If there is a better way to monitor my microservices, do let me know!
I'm not sure how to structure the handle in such a way that when /metrics are called, the db.Ping() is re-evaluated.
https://s.natalian.org/2019-06-02/msping.mp4
package main
import (
"log"
"net/http"
"os"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
"github.com/jmoiron/sqlx"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const version = "0.0.1"
type App struct {
Router *mux.Router
DB *sqlx.DB
}
func main() {
a := App{}
a.Initialize()
log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), a.Router))
}
func (a *App) Initialize() {
connectionString := "root:secret#tcp(localhost:3306)/rest_api_example?multiStatements=true&sql_mode=TRADITIONAL&timeout=5s"
var err error
a.DB, err = sqlx.Open("mysql", connectionString)
if err != nil {
log.Fatal(err)
}
microservicecheck := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "mscheck",
Help: "Version with DB ping check",
},
[]string{
"commit",
},
)
if a.DB.Ping() == nil {
microservicecheck.WithLabelValues(version).Set(1)
} else {
microservicecheck.WithLabelValues(version).Set(0)
}
prometheus.MustRegister(microservicecheck)
a.Router = mux.NewRouter()
a.initializeRoutes()
}
func (a *App) initializeRoutes() {
a.Router.Handle("/metrics", promhttp.Handler()).Methods("GET")
}
https://play.golang.org/p/9DdXnz77S55

You could also add a middleware hook that does a preflight routine (i.e. your ping test) before calling promhttp.Handler(). However, at collection time, I think metrics should already have been tallied; and not generated at the instance of collection. So...
Try a separate go-routine that will poll the health of the DB connection at regular intervals. This avoids any messy hooks or custom collectors:
var pingPollingFreq = 5 * time.Second // this should probably match the Prometheus scrape interval
func (a *App) Initialize() {
// ...
prometheus.MustRegister(microservicecheck)
go func() {
for {
if a.DB.Ping() == nil {
microservicecheck.WithLabelValues(version).Set(1)
} else {
microservicecheck.WithLabelValues(version).Set(0)
}
time.Sleep(pingPollingFreq)
}
}()
// ...
}

Related

Use request ID in all logger

I've some web-application server using go http and I want that each request will have context with uuid, for this I can use http request context https://golang.org/pkg/net/http/#Request.Context
we are using logrus and we initiate it in one file and use the logger instance in other files.
what I need is to print request ID in all the logs but not to add new paremeters to each log print, I want do to it once in each http request (pass the req-id) and all the logs print will have it without doing anything with it
e.g. if the id=123 so log.info("foo") will print
// id=123 foo
I've tried with the following but not sure it's the right way, please advice.
package main
import (
"context"
"errors"
log "github.com/sirupsen/logrus"
)
type someContextKey string
var (
keyA = someContextKey("a")
keyB = someContextKey("b")
)
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, keyA, "foo")
ctx = context.WithValue(ctx, keyB, "bar")
logger(ctx, nil).Info("did nothing")
err := errors.New("an error")
logger(ctx, err).Fatal("unrecoverable error")
}
func logger(ctx context.Context, err error) *log.Entry {
entry := log.WithField("component", "main")
entry = entry.WithField("ReqID", "myRequestID")
return entry
}
https://play.golang.org/p/oCW09UhTjZ5
Every time you call the logger function you are creating a new *log.Entry and writing the request ID to it again. From your question it sounded like you do not want that.
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, keyA, "foo")
ctx = context.WithValue(ctx, keyB, "bar")
lg := logger(ctx)
lg.Info("did nothing")
err := errors.New("an error")
lg.WithError(err).Fatal("unrecoverable error")
}
func logger(ctx context.Context) *log.Entry {
entry := log.WithField("component", "main")
entry = entry.WithField("ReqID", "myRequestID")
return entry
}
The downside of this is that you will have to pass the lg variable to every function this request calls and which should also log the request ID.
What we did at our company is create a thin layer around logrus that has an additional method WithRequestCtx so we could pass in the request context and it would extract the request ID itself (which we had written to the context in a middleware). If no request ID was present nothing was added to the log entry. This however did add the request ID to every log entry again as your sample code also did.
Note: our thin layer around logrus had a lot more functionality and default settings to justify the extra effort. In the long run this turned out very helpful to have one place to be able to adjust logging for all our services.
Note2: meanwhile we are in the process of replacing logrus with zerolog to be more lightweight.
Late answer but all you need to do is just call logrus.WithContext(/* your *http.Request.Context() goes here*/).... in your application and logrus will automatically add "id":"SOME-UUID" to each logs. Design is flexible for extracting more key-value from request context if you wanted to.
initialise logger
package main
import (
"path/to/logger"
"path/to/request"
)
func main() {
err := logger.Setup(logger.Config{
ContextFields: map[string]interface{}{
string(request.CtxIDKey): request.CtxIDKey,
}
})
if err != nil {
// ...
}
}
logger
package logger
import (
"github.com/sirupsen/logrus"
)
type Config struct {
Level string
ContextFields map[string]interface{}
}
func Setup(config Config) error {
lev, err := logrus.ParseLevel(config.Level)
if err != nil {
return err
}
logrus.SetLevel(lev)
return nil
}
func (c Config) Fire(e *logrus.Entry) error {
for k, v := range c.StaticFields {
e.Data[k] = v
}
if e.Context != nil {
for k, v := range c.ContextFields {
if e.Context.Value(v) != nil {
e.Data[k] = e.Context.Value(v).(string)
}
}
}
return nil
}
request
package request
import (
"context"
"net/http"
"github.com/google/uuid"
)
type ctxID string
const CtxIDKey = ctxID("id")
func ID(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), CtxIDKey, uuid.New().String())))
})
}

How to organize interaction between web form and database in golang web app?

I'm creating small web app and I want to organize interaction between DB in main package and handlers which located in other package ("handlers"). And i want to find out can i set interaction between handlers in another package and db or it is bad practice to place handler not in the main package?
package main
import (
"handlers"
"net/http"
)
func main() {
//...
DB, err = ConnectToPostgres()
//...
http.HandleFunc("/adduser/", handlers.AddUser)
http.HandleFunc("/getinfo/", handlers.GetUserInfo)
http.ListenAndServe(":8080", nil)
}
I always use this code
package providers
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"log"
)
var SQL *gorm.DB
type Postgres struct{}
func (s Postgres) Connect() *gorm.DB {
dsn := s.getDSN()
instance, err := gorm.Open("postgres", dsn)
if err != nil {
log.Panicf("Postgres Error: %+v", err)
}
return instance
}
func (s *Postgres) getDSN() string {
c := Config.Storage.Postgres
dsn := fmt.Sprintf("user=%s", c.User)
if c.Pass != "" {
dsn += fmt.Sprintf(" password=%s", c.Pass)
}
dsn += fmt.Sprintf(" host=%s", c.Host)
dsn += fmt.Sprintf(" dbname=%s", c.Name)
if !c.SSL {
dsn += " sslmode=disable"
}
return dsn
}
func init() {
conn := Postgres{}
SQL = conn.Connect()
SQL.LogMode(false)
if Config.App.Debug {
SQL.LogMode(true)
}
}

How to avoid package dependency

I have a project consisting of 4 parts:
A gateway (gateway/gateway.go) it's a package that knows how to talk with an application server and open this connection channel.
A runner (runner/runner.go) it's the main (go build -o runner/runner runner/runner.go) It loads and execute Modules (using reflect I run functions from the module)!
A framework (framework/framework.go) Implements many functionalities calling the gateway.
Modules (aka Plugins in Go) (modules/sample.go) (go build -buildmode plugin -o modules/sample.so ./modules/sample.go) Using the framework, does customer logic! When init I export the reflect.Value of struct, then runner can run methods of this struct.
I want the runner instantiate the gateway and the framework obtain this instance without create a dependency between runner/framework.
Why? To avoid Go error 'plugin was built with a different version of package' when runner loads the module!
If I update the runner (with the framework changed), I will invalidate old modules.
I already do that using 2 ways I don't like:
Using context, but all functions from module and framework need receive a parameter context, then framework extract the gateway.
Just let the framework instantiate gateway, but then the runner cannot use gateway.
There have been a lot of headaches with Go plugins, especially the plugin compiler version must exactly match the program's compiler version. But the example works.
runner/runner.go
package main
import (
"context"
"fmt"
"os"
"plugin"
"reflect"
"../gateway"
"../dep"
)
var a *gateway.Gateway
func main() {
myModule := os.Args[1]
if _, err := plugin.Open(myModule); err != nil {
os.Exit(1)
}
mod, err := dep.NewModule()
if err != nil {
os.Exit(1)
}
a = gateway.NewGW()
ctx := context.WithValue(context.Background(), "gateway", a)
modreflect, err := mod.Init(ctx, dep.Config{})
if err != nil {
os.Exit(1)
}
if !modreflect.IsValid() {
os.Exit(1)
}
modnode, err := mod.Start(ctx)
if err != nil {
os.Exit(1002)
}
for {
if len(modnode) <= 0 {
break
}
modnoderefl := modreflect.MethodByName(modnode)
if !modnoderefl.IsValid() {
break
}
result := modnoderefl.Call([]reflect.Value{reflect.ValueOf(ctx)})
if len(result) != 2 {
break
}
modnode = result[0].String()
}
mod.End(ctx)
}
gateway/gateway.go
package gateway
type Gateway struct {}
fun NewGW() *Gateway {
a := Gateway{}
return &a
}
dep/dep.go
package dep
import (
"errors"
"context"
"reflect"
)
// Config is a configuration provider.
type Config map[string]interface{}
// Module is the interface implementated by types that
// register themselves as modular plug-ins.
type Module interface {
Init(ctx context.Context, config Config) (reflect.Value,error)
Start(ctx context.Context) (string,error)
End(ctx context.Context) error
}
var themod = []func() Module{}
func RegisterModule(ctor func() Module) {
themod = append(themod, ctor)
}
func NewModule() (Module, error) {
if len(themod) == 0 {
return nil, errors.New("Module not registered")
}
return themod[0](), nil
}
framework/framework.go
package framework
import (
"fmt"
"context"
"../gateway"
)
type PlayFileInput struct {
Path string
}
func Play(ctx context.Context, p PlayFileInput) error {
if a := ctx.Value("gateway"); a != nil {
if a.(*gateway.Gateway) != nil {
_, err := a.(*gateway.Gateway).Exec("Playback", p.Path)
return err
}
}
return nil
}
modules/sample.go
package main
import "C"
import (
"context"
"fmt"
"os"
"reflect"
"../dep"
"../framework"
)
type MyModuleImpl struct {}
func init() {
dep.RegisterModule(func() dep.Module {
return &MyModuleImpl{}
})
}
func (m *MyModuleImpl) Init(ctx context.Context, config dep.Config) (reflect.Value, error) {
return reflect.ValueOf(m), nil
}
func (m *MyModuleImpl) Start(ctx context.Context) (string,error) {
return "Menu_1",nil
}
func (n *MyModuleImpl)Menu_1(ctx context.Context) (string, error) {
framework.Play(ctx, framework.PlayFileInput{Path: "welcome.wav"})
return "Menu_2",nil
}
func (n *MyModuleImpl)Menu_2(ctx context.Context) (string, error) {
return "Menu_3", nil
}
// ....
// ....
func (m *MyModuleImpl) End(ctx context.Context) error {
return nil
}

How to update metric values in prometheus exporter (golang)

I'm starting to write an own prometheus exporter using golang.
I think I got the basics but I don't know what to do exactly to get the value of the metric up to date. Using Set only does it once.
It is not changing on runtime.
What I have so far:
package main
import (
"log"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"time"
"io/ioutil"
"github.com/tidwall/gjson"
"strconv"
)
var (
sidekiqProcessed = setGaugeMetric("sidekiq_processed", "Sidekiq Processed", "lable", "lablevalue")
)
func setGaugeMetric(name string, help string, label string, labelvalue string) (prometheusGauge prometheus.Gauge) {
var (
gaugeMetric = prometheus.NewGauge(prometheus.GaugeOpts{
Name: name,
Help: help,
ConstLabels: prometheus.Labels{label: labelvalue},
})
)
return gaugeMetric
}
func getSidekiqProcessed() (sidekiq float64) {
body := getContent("http://example.com/sidekiq/stats")
processed := gjson.Get(body, "sidekiq.processed")
conv, err := strconv.ParseFloat(processed.String(), 64)
if err != nil {
log.Fatal(err)
}
return conv
}
func getContent(url string) (body string) {
httpClient := &http.Client{Timeout: 10 * time.Second}
res, err := httpClient.Get(url)
if err != nil {
log.Fatal(err)
}
content, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
return string(content)
}
func init() {
prometheus.MustRegister(sidekiqProcessed)
}
func main() {
sidekiqProcessed.Set(getSidekiqProcessed())
// The Handler function provides a default handler to expose metrics
// via an HTTP server. "/metrics" is the usual endpoint for that.
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":8080", nil))
}
Read something about Collector but have no clue how to implement it.
Can somebody help me to complete/correct my code so that the value of the metric also updates at runtime?
Here is an example of custom collector (from https://www.robustperception.io/setting-a-prometheus-counter):
package main
import "github.com/prometheus/client_golang/prometheus"
type MyCollector struct {
counterDesc *prometheus.Desc
}
func (c *MyCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.counterDesc
}
func (c *MyCollector) Collect(ch chan<- prometheus.Metric) {
value := 1.0 // Your code to fetch the counter value goes here.
ch <- prometheus.MustNewConstMetric(
c.counterDesc,
prometheus.CounterValue,
value,
)
}
func NewMyCollector() *MyCollector {
return &MyCollector{
counterDesc: prometheus.NewDesc("my_counter_total", "Help string", nil, nil),
}
}
// To hook in the collector: prometheus.MustRegister(NewMyCollector())
You probably want to implement a collector instead, and run the http request when the Prometheus server scrapes. See the best practices.
When implementing the collector for your exporter, you should never use the usual direct instrumentation approach and then update the metrics on each scrape.
Rather create new metrics each time. In Go this is done with MustNewConstMetric in your Update() method. For Python see https://github.com/prometheus/client_python#custom-collectors and for Java generate a List in your collect method, see StandardExports.java for an example.
The github.com/prometheus/client_golang library may be non-trivial to use when writing Prometheus exporters in Go. Try https://pkg.go.dev/github.com/VictoriaMetrics/metrics library instead. It is much easier to use in general. See the following code as an example, which allows dynamically updating sidekiq_processed metric with the given label:
import (
"fmt"
"github.com/VictoriaMetrics/metrics"
)
// UpdateSidekiqProcessed updates `sidekiq_processed{label="<labelValue>"}` metric to the given value
func UpdateSidekiqProcessed(labelValue string, value float64) {
metricName := fmt.Sprintf("sidekiq_processed{label=%q}", labelValue)
metrics.GetOrCreateFloatCounter(metricName).Set(value)
}

How to test functions that implement gorilla context

I wrote a functions that save data into redis database server. The challenge is that I want to test these functions and do not know how to test it.
I just start somehow with
Functions
package sessrage
/*
* Save data into redis database. In the common case,
* the data will be only valid during a request. Use
* hash datatype in redis.
*/
import (
"../context"
"github.com/garyburd/redigo/redis"
"net/http"
)
const (
protocol string = "tcp"
port string = ":6379"
)
func connectAndCloseRedis(connectCall func(con redis.Conn)) {
c, err := redis.Dial("tcp", ":6379")
defer c.Close()
if err != nil {
panic(err.Error())
}
connectCall(c)
}
func PostSessionData(r *http.Request, key, value string) {
go connectAndCloseRedis(func(con redis.Conn) {
sessionId := context.Get(r, context.JwtId).(string)
con.Do("HMSET", sessionId, key, value)
})
}
func GetSessionData(r *http.Request, key string) interface{} {
var result interface{}
sessionId := context.Get(r, context.JwtId).(string)
reply, _ := redis.Values(c.Do("HMGET", sessionId, key))
redis.Scan(reply, &result)
return result
}
and the test file
package sessrage
import (
//"fmt"
"../context"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
"time"
)
var server *httptest.Server
var glrw http.ResponseWriter
var glr *http.Request
func init() {
server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
glrw = rw
glr = r
context.Set(glr, context.JwtId, "TestId")
}))
}
func TestPostAndGetSession(t *testing.T) {
Convey("POST and GET data on redis.", t, func() {
PostSessionData(glr, "key1", "value1")
time.Sleep(time.Second * 10)
v := GetSessionData(glr, "key1")
assert.Equal(t, "value1", v)
})
}
when I try to run the test I've got
an't load package: ......./sessrage.go:10:2: local import "../context" in non-local package
and the context package looks like
package context
import (
"github.com/gorilla/context"
"net/http"
)
type contextKey int
const (
LanguageId contextKey = iota
JwtId
)
func Get(r *http.Request, key interface{}) interface{} {
return context.Get(r, key)
}
func Set(r *http.Request, key, val interface{}) {
context.Set(r, key, val)
}
What do I wrong?
That is the first time, I am testing code in conjunction with http. It seems to be very hard to test.
There are a few issues:
Don't use relative import paths.
Use a pool instead of dialing redis on every action.
The call to sessionId := context.Get(r, context.JwtId).(string) in the PostSessionData anonymous function can fail if the mux or something higher in the call chain clears the Gorilla context before the goroutine runs. Do this instead:
func PostSessionData(r *http.Request, key, value string) {
c := pool.Get()
defer c.Close()
sessionId := context.Get(r, context.JwtId).(string)
if err := c.Do("HMSET", sessionId, key, value); err != nil {
// handle error
}
}

Resources