Marvin Preuss
77ad55f1eb
its possible to unmarshal a PasswordFile path to a read string. this is nice for docker secret setups. also adds a GetPassword method which checks if both Password and PasswordFile are declared.
116 lines
2.4 KiB
Go
116 lines
2.4 KiB
Go
package metrics
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
vm "github.com/VictoriaMetrics/metrics"
|
|
"go.xsfx.dev/glucose_exporter/api"
|
|
"go.xsfx.dev/glucose_exporter/internal/cache"
|
|
"go.xsfx.dev/glucose_exporter/internal/config"
|
|
)
|
|
|
|
var ErrOddDataLength = errors.New("odd data length")
|
|
|
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
if err := glucose(r.Context(), w); err != nil {
|
|
slog.Error("getting glucose", "err", err)
|
|
vm.GetOrCreateCounter("errors_total").Inc()
|
|
}
|
|
|
|
vm.WritePrometheus(w, false)
|
|
}
|
|
|
|
func glucose(ctx context.Context, w io.Writer) error {
|
|
c, err := cache.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("loading cache: %w", err)
|
|
}
|
|
|
|
// No token exists or token is expired
|
|
if c.JWT == "" || time.Now().After(time.Time(c.Expires)) {
|
|
slog.Debug("needs a fresh token")
|
|
|
|
if err := token(ctx); err != nil {
|
|
return fmt.Errorf("refreshing token: %w", err)
|
|
}
|
|
}
|
|
|
|
// Loading cache again with the new token inside.
|
|
c, err = cache.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("loading cache: %w", err)
|
|
}
|
|
|
|
// Getting connections, which includes the glucose data.
|
|
resp, err := api.Connections(ctx, api.BaseURL, c.JWT)
|
|
if err != nil {
|
|
return fmt.Errorf("connections: %w", err)
|
|
}
|
|
|
|
// Storing new token to cache.
|
|
if err := cache.Save(cache.Cache{
|
|
JWT: resp.Ticket.Token,
|
|
Expires: resp.Ticket.Expires,
|
|
}); err != nil {
|
|
return fmt.Errorf("saving cache: %w", err)
|
|
}
|
|
|
|
if len(resp.Data) != 1 {
|
|
return ErrOddDataLength
|
|
}
|
|
|
|
labels := fmt.Sprintf(
|
|
`patient_id="%s",sensor_serialnumber="%s"`,
|
|
resp.Data[0].PatientID,
|
|
resp.Data[0].Sensor.Serialnumber,
|
|
)
|
|
|
|
// Writing metrics.
|
|
vm.WriteGaugeUint64(
|
|
w,
|
|
fmt.Sprintf("value_in_mg_per_dl{%s}", labels),
|
|
uint64(resp.Data[0].GlucoseMeasurement.ValueInMgPerDl),
|
|
)
|
|
|
|
vm.WriteGaugeUint64(
|
|
w,
|
|
fmt.Sprintf("trend_arrow{%s}", labels),
|
|
uint64(resp.Data[0].GlucoseMeasurement.TrendArrow),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func token(ctx context.Context) error {
|
|
pass, err := config.Cfg.GetPassword()
|
|
if err != nil {
|
|
return fmt.Errorf("getting password from config: %w", err)
|
|
}
|
|
|
|
token, err := api.Login(
|
|
ctx,
|
|
api.BaseURL,
|
|
config.Cfg.Email,
|
|
pass,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("login: %w", err)
|
|
}
|
|
|
|
slog.Debug("got token", "token", token)
|
|
|
|
if err := cache.Save(
|
|
cache.Cache{JWT: token.Data.AuthTicket.Token, Expires: token.Data.AuthTicket.Expires},
|
|
); err != nil {
|
|
return fmt.Errorf("saving cache: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|