Skip to content

Commit

Permalink
feat(fxmetrics): Provided module (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
ekkinox committed Jan 12, 2024
1 parent 61ae829 commit 40945da
Show file tree
Hide file tree
Showing 18 changed files with 839 additions and 8 deletions.
1 change: 1 addition & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
- "fxconfig"
- "fxgenerate"
- "fxlog"
- "fxmetrics"
- "fxtrace"
steps:
- name: Checkout
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/fxmetrics-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: "fxmetrics-ci"

on:
push:
branches:
- "feat**"
- "fix**"
- "hotfix**"
- "chore**"
paths:
- "fxmetrics/**.go"
- "fxmetrics/go.mod"
- "fxmetrics/go.sum"
pull_request:
types:
- opened
- synchronize
- reopened
branches:
- main
paths:
- "fxmetrics/**.go"
- "fxmetrics/go.mod"
- "fxmetrics/go.sum"

jobs:
ci:
uses: ./.github/workflows/common-ci.yml
secrets: inherit
with:
module: "fxmetrics"
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ Yokai is using [Fx](https://github.com/uber-go/fx) for its plugin system.

Yokai's `Fx modules` are the plugins for your Yokai application.

| Fx Module | Description |
|--------------------------|------------------------------------|
| [fxconfig](fxconfig) | Fx module for [config](config) |
| [fxgenerate](fxgenerate) | Fx module for [generate](generate) |
| [fxlog](fxlog) | Fx module for [log](log) |
| [fxtrace](fxtrace) | Fx module for [trace](trace) |
| Fx Module | Description |
|--------------------------|-------------------------------------------------------------------------|
| [fxconfig](fxconfig) | Fx module for [config](config) |
| [fxgenerate](fxgenerate) | Fx module for [generate](generate) |
| [fxlog](fxlog) | Fx module for [log](log) |
| [fxmetrics](fxmetrics) | Fx module for [prometheus](https://github.com/prometheus/client_golang) |
| [fxtrace](fxtrace) | Fx module for [trace](trace) |

They can also be used in any [Fx](https://github.com/uber-go/fx) based Go application.

Expand Down
64 changes: 64 additions & 0 deletions fxmetrics/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
run:
timeout: 5m
concurrency: 8

linters:
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- containedctx
- contextcheck
- decorder
- dogsled
- dupl
- durationcheck
- errcheck
- errchkjson
- errname
- errorlint
- forbidigo
- forcetypeassert
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- godox
- gofmt
- goheader
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- gosimple
- govet
- grouper
- importas
- ineffassign
- interfacebloat
- logrlint
- maintidx
- makezero
- misspell
- nestif
- nilerr
- nilnil
- nlreturn
- nolintlint
- nosprintfhostport
- prealloc
- predeclared
- promlinter
- reassign
- staticcheck
- tenv
- thelper
- tparallel
- typecheck
- unconvert
- unparam
- unused
- usestdlibvars
- whitespace
206 changes: 206 additions & 0 deletions fxmetrics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Fx Metrics Module

[![ci](https://github.com/ankorstore/yokai/actions/workflows/fxmetrics-ci.yml/badge.svg)](https://github.com/ankorstore/yokai/actions/workflows/fxmetrics-ci.yml)
[![go report](https://goreportcard.com/badge/github.com/ankorstore/yokai/fxmetrics)](https://goreportcard.com/report/github.com/ankorstore/yokai/fxmetrics)
[![codecov](https://codecov.io/gh/ankorstore/yokai/graph/badge.svg?token=ghUBlFsjhR&flag=fxmetrics)](https://app.codecov.io/gh/ankorstore/yokai/tree/main/fxmetrics)
[![Deps](https://img.shields.io/badge/osi-deps-blue)](https://deps.dev/go/github.com%2Fankorstore%2Fyokai%2Ffxmetrics)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/ankorstore/yokai/fxmetrics)](https://pkg.go.dev/github.com/ankorstore/yokai/fxmetrics)

> [Fx](https://uber-go.github.io/fx/) module for [prometheus](https://github.com/prometheus/client_golang).
<!-- TOC -->
* [Installation](#installation)
* [Documentation](#documentation)
* [Dependencies](#dependencies)
* [Loading](#loading)
* [Registration](#registration)
* [Override](#override)
* [Testing](#testing)
<!-- TOC -->

## Installation

```shell
go get github.com/ankorstore/yokai/fxmetrics
```

## Documentation

### Dependencies

This module is intended to be used alongside:

- the [fxconfig](https://github.com/ankorstore/yokai/tree/main/fxconfig) module
- the [fxlog](https://github.com/ankorstore/yokai/tree/main/fxlog) module

### Loading

To load the module in your Fx application:

```go
package main

import (
"github.com/ankorstore/yokai/fxconfig"
"github.com/ankorstore/yokai/fxlog"
"github.com/ankorstore/yokai/fxmetrics"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/fx"
)

func main() {
fx.New(
fxconfig.FxConfigModule, // load the module dependencies
fxlog.FxLogModule,
fxmetrics.FxMetricsModule, // load the module
fx.Invoke(func(registry *prometheus.Registry) { // invoke the metrics registry
// ...
}),
).Run()
}
```

### Registration

This module provides the possibility to register your metrics [collectors](https://github.com/prometheus/client_golang/blob/main/prometheus/collector.go) in a common `*prometheus.Registry` via `AsMetricsCollector()`:

```go
package main

import (
"github.com/ankorstore/yokai/fxconfig"
"github.com/ankorstore/yokai/fxlog"
"github.com/ankorstore/yokai/fxmetrics"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/fx"
)

var SomeCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "some_total",
Help: "some help",
})

func main() {
fx.New(
fxconfig.FxConfigModule, // load the module dependencies
fxlog.FxLogModule,
fxmetrics.FxMetricsModule, // load the module
fx.Options(
fxmetrics.AsMetricsCollector(SomeCounter), // register the counter
),
fx.Invoke(func() {
SomeCounter.Inc() // manipulate the counter
}),
).Run()
}
```

**Important**: even if convenient, it's recommended to **NOT** use the [promauto](https://github.com/prometheus/client_golang/tree/main/prometheus/promauto) way of registering metrics,
but to use instead `fxmetrics.AsMetricsCollector()`, as `promauto` uses a global registry that leads to data race
conditions in testing.

Also, if you want to register several collectors at once, you can use `fxmetrics.AsMetricsCollectors()`

### Override

By default, the `*prometheus.Registry` is created by the [DefaultMetricsRegistryFactory](factory.go).

If needed, you can provide your own factory and override the module:

```go
package main

import (
"github.com/ankorstore/yokai/fxconfig"
"github.com/ankorstore/yokai/fxlog"
"github.com/ankorstore/yokai/fxmetrics"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/fx"
)

type CustomMetricsRegistryFactory struct{}

func NewCustomMetricsRegistryFactory() fxmetrics.MetricsRegistryFactory {
return &CustomMetricsRegistryFactory{}
}

func (f *CustomMetricsRegistryFactory) Create() (*prometheus.Registry, error) {
return prometheus.NewPedanticRegistry(), nil
}

func main() {
fx.New(
fxconfig.FxConfigModule, // load the module dependencies
fxlog.FxLogModule,
fxmetrics.FxMetricsModule, // load the module
fx.Decorate(NewCustomMetricsRegistryFactory), // override the module with a custom factory
fx.Invoke(func(registry *prometheus.Registry) { // invoke the custom registry
// ...
}),
).Run()
}
```

### Testing

This module provides the possibility to easily test your metrics with the prometheus package [testutil](https://github.com/prometheus/client_golang/tree/main/prometheus/testutil) helpers.

```go
package main_test

import (
"strings"
"testing"

"github.com/ankorstore/yokai/fxconfig"
"github.com/ankorstore/yokai/fxlog"
"github.com/ankorstore/yokai/fxmetrics"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"
"go.uber.org/fx"
"go.uber.org/fx/fxtest"
)

var SomeCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "some_total",
Help: "some help",
})

func TestSomeCounter(t *testing.T) {
t.Setenv("APP_CONFIG_PATH", "testdata/config")

var registry *prometheus.Registry

fxtest.New(
t,
fx.NopLogger,
fxconfig.FxConfigModule,
fxlog.FxLogModule,
fxmetrics.FxMetricsModule,
fx.Options(
fxmetrics.AsMetricsCollector(SomeCounter),
),
fx.Invoke(func() {
SomeCounter.Add(9)
}),
fx.Populate(&registry),
).RequireStart().RequireStop()

// metric assertions
expectedHelp := `
# HELP some_total some help
# TYPE some_total counter
`
expectedMetric := `
some_total 9
`

err := testutil.GatherAndCompare(
registry,
strings.NewReader(expectedHelp+expectedMetric),
"some_total",
)
assert.NoError(t, err)
}
```
23 changes: 23 additions & 0 deletions fxmetrics/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package fxmetrics

import (
"github.com/prometheus/client_golang/prometheus"
)

// MetricsRegistryFactory is the interface for [prometheus.Registry] factories.
type MetricsRegistryFactory interface {
Create() (*prometheus.Registry, error)
}

// DefaultMetricsRegistryFactory is the default [MetricsRegistryFactory] implementation.
type DefaultMetricsRegistryFactory struct{}

// NewDefaultMetricsRegistryFactory returns a [DefaultMetricsRegistryFactory], implementing [MetricsRegistryFactory].
func NewDefaultMetricsRegistryFactory() MetricsRegistryFactory {
return &DefaultMetricsRegistryFactory{}
}

// Create returns a new [prometheus.Registry].
func (f *DefaultMetricsRegistryFactory) Create() (*prometheus.Registry, error) {
return prometheus.NewRegistry(), nil
}
29 changes: 29 additions & 0 deletions fxmetrics/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package fxmetrics_test

import (
"testing"

"github.com/ankorstore/yokai/fxmetrics"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
)

func TestDefaultMetricsRegistryFactory(t *testing.T) {
t.Parallel()

factory := fxmetrics.NewDefaultMetricsRegistryFactory()

assert.IsType(t, &fxmetrics.DefaultMetricsRegistryFactory{}, factory)
assert.Implements(t, (*fxmetrics.MetricsRegistryFactory)(nil), factory)
}

func TestCreate(t *testing.T) {
t.Parallel()

factory := fxmetrics.NewDefaultMetricsRegistryFactory()

registry, err := factory.Create()
assert.NoError(t, err)

assert.IsType(t, &prometheus.Registry{}, registry)
}
Loading

0 comments on commit 40945da

Please sign in to comment.