Skip to content

Commit abf39f2

Browse files
authored
Add support for push-gateway and OTLP (#67)
Forced to update opentelemetry-go as well as there was no way to bring the update without also going into otel breaking changes.
1 parent cb36d75 commit abf39f2

27 files changed

+1202
-1564
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77

88
runs-on: ubuntu-latest
99
steps:
10-
- uses: actions/checkout@v3
10+
- uses: actions/checkout@v4
1111

1212
- name: Set up Go
1313
uses: actions/setup-go@v3

.github/workflows/coverage.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
continue-on-error: true
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v3
13+
- uses: actions/checkout@v4
1414
with:
1515
fetch-depth: 10
1616

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
goarch: arm64
3030
ext: ''
3131
steps:
32-
- uses: actions/checkout@v3
32+
- uses: actions/checkout@v4
3333
with:
3434
# We need all tags
3535
fetch-depth: 0

CHANGELOG.md

+27
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,35 @@ versioning](https://go.dev/doc/modules/version-numbers).
1010

1111
### Added
1212

13+
- [Prometheus collector] `Init` call now takes an optional `PushConfiguration`
14+
argument that allows to push metrics to PushGateway, _on top_ of exposing the
15+
metrics to an endpoint.
16+
Using the push gateway is very useful to automatically send metrics when a
17+
web service is autoscaled and has replicas being created or deleted during
18+
normal operations.
19+
+ the `CollectorURL` in the `PushConfiguration` is mandatory and follows the
20+
API of `prometheus/push.New`
21+
+ the `JobName` parameter is optional, and `autometrics` will do a best effort
22+
to fill the value.
23+
24+
- [OpenTelemetry collector] `Init` call now takes an optional `PushConfiguration`
25+
argument that allows to push metrics in OTLP format, _instead_ of exposing the
26+
metrics to a Promtheus-format endpoint.
27+
Using an OTLP collector is very useful to automatically send metrics when a
28+
web service is autoscaled and has replicas being created or deleted during
29+
normal operations.
30+
+ the `CollectorURL` in the `PushConfiguration` is mandatory
31+
+ the `JobName` parameter is optional, and `autometrics` will do a best effort
32+
to fill the value.
33+
1334
### Changed
1435

36+
- [All] `autometrics.Init` function (for both `prometheus` and `otel` variants)
37+
take an extra optional argument. See the `Added` section above for details.
38+
- [All] `autometrics.Init` function (for both `prometheus` and `otel` variants)
39+
now return a [CancelCauseFunc](https://pkg.go.dev/context#CancelCauseFunc)
40+
to cleanly shutdown (early or as a normal shutdown process) all metric collection.
41+
1542
### Deprecated
1643

1744
### Removed

README.md

+72-3
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,16 @@ import (
9393
And then in your main function initialize the metrics
9494

9595
``` go
96-
autometrics.Init(
96+
shutdown, err := autometrics.Init(
9797
nil,
9898
autometrics.DefBuckets,
9999
autometrics.BuildInfo{Version: "0.4.0", Commit: "anySHA", Branch: "", Service: "myApp"},
100+
nil,
100101
)
102+
if err != nil {
103+
log.Fatalf("could not initialize autometrics: %s", err)
104+
}
105+
defer shutdown(nil)
101106
```
102107

103108
Everything in `BuildInfo` is optional. It will add relevant information on the
@@ -237,10 +242,11 @@ import (
237242

238243

239244
func main() {
240-
autometrics.Init(
245+
shutdown, err := autometrics.Init(
241246
nil,
242247
autometrics.DefBuckets,
243248
autometrics.BuildInfo{Version: "0.4.0", Commit: "anySHA", Branch: "", Service: "myApp"},
249+
nil,
244250
)
245251
http.Handle("/metrics", promhttp.Handler())
246252
}
@@ -346,11 +352,12 @@ the `Init` function takes a meter name for the `otel_scope` label of the exporte
346352
metric. You can use the name of the application or its version for example
347353

348354
``` patch
349-
autometrics.Init(
355+
shutdown, err := autometrics.Init(
350356
- nil,
351357
+ "myApp/v2/prod",
352358
autometrics.DefBuckets,
353359
autometrics.BuildInfo{ Version: "2.1.37", Commit: "anySHA", Branch: "", Service: "myApp" },
360+
nil,
354361
)
355362
```
356363

@@ -361,6 +368,68 @@ metric. You can use the name of the application or its version for example
361368
+//go:generate autometrics --otel
362369
```
363370

371+
#### Push-based workflows
372+
373+
<details>
374+
<summary><i>Why would I use a push-based workflow?</i></summary>
375+
376+
If you have an auto-scaled service (with instances spinning up and down),
377+
maintaining the configuration/discovery of instances on the Prometheus side of
378+
things can be hard. Using a push-based workflow inverts the burden of
379+
configuration: all your instances generate a specific ID, and they just need to
380+
push metrics to a given URL. So the main advantages of a push-based workflow
381+
appear when the the set of machines producing metrics is dynamic:
382+
383+
- Your Prometheus configuration does not need to be dynamic anymore, it's "set
384+
and forget" again
385+
- No need to configure service discovery separately (which can be error-prone)
386+
387+
388+
It can be summarized with one sentence. <b>The monitoring stack
389+
(Prometheus/OpenTelemetry collector) does not need to know the infrastructure of
390+
application deployment; nor does the application code need to know the
391+
infrastructure of the monitoring stack. Decoupling prevents
392+
configuration-rot.</b>
393+
394+
</details>
395+
396+
If you don't want to/cannot configure your Prometheus instance to scrape the
397+
instrumented code, Autometrics provides a way to push metrics instead of relying
398+
on a polling collection process.
399+
400+
> **Note**
401+
> It is strongly advised to use the OpenTelemetry variant of Autometrics to support push-based metric
402+
collection. Prometheus push gateways make aggregation of data across multiple sources harder.
403+
404+
_How can I use a push-based workflow with Autometrics?_
405+
406+
If you have a Prometheus [push
407+
gateway](https://prometheus.io/docs/instrumenting/pushing/) or an OTLP
408+
[collector](https://opentelemetry.io/docs/collector/) setup with an accessible
409+
URL, then you can directly switch from metric polling to metric pushing by
410+
passing a non `nil` argument to `autometrics.Init` for the `pushConfiguration`:
411+
412+
``` patch
413+
shutdown, err := autometrics.Init(
414+
"myApp/v2/prod",
415+
autometrics.DefBuckets,
416+
autometrics.BuildInfo{ Version: "2.1.37", Commit: "anySHA", Branch: "", Service: "myApp" },
417+
- nil,
418+
+ &autometrics.PushConfiguration{
419+
+ CollectorURL: "https://collector.example.com",
420+
+ JobName: "instance_2", // You can leave the JobName out to let autometrics generate one
421+
+ Period: 1 * time.Second, // Period is only relevant when using OpenTelemetry implementation
422+
+ Timeout: 500 * time.Millisecond, // Timeout is only relevant when using OpenTelementry implementation
423+
+ },
424+
)
425+
```
426+
427+
> **Note**
428+
> If you do not want to setup an OTLP collector or a Prometheus push-gateway yourself, you
429+
can contact us so we can setup a managed instance of Prometheus for you. We will effectively
430+
give you collector URLs, that will work with both OpenTelemetry and Prometheus; and can be
431+
visualized easily with our explorer as well!
432+
364433
#### Git hook
365434

366435
As autometrics is a Go generator that modifies the source code when run, it

docker-compose.open-telemetry-example.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ services:
5252
context: .
5353
dockerfile: examples/otel/Dockerfile
5454
environment:
55-
AUTOMETRICS_SERVICE_NAME: open-telemetry-autometrics-demo
55+
AUTOMETRICS_SERVICE_NAME: autometrics_otel
56+
AUTOMETRICS_OTLP_URL: $AUTOMETRICS_OTLP_URL
5657
container_name: web-server-otel
5758
restart: unless-stopped
5859
expose:

docker-compose.prometheus-example.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ services:
5353
context: .
5454
dockerfile: examples/web/Dockerfile
5555
environment:
56-
AUTOMETRICS_SERVICE_NAME: prometheus-autometrics-demo
56+
AUTOMETRICS_SERVICE_NAME: autometrics_prometheus
57+
AUTOMETRICS_PUSH_GATEWAY_URL: $AUTOMETRICS_PUSH_GATEWAY_URL
5758
container_name: web-server-prom
5859
restart: unless-stopped
5960
expose:

examples/otel/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ COPY . ./
1010

1111
RUN go mod download
1212

13-
WORKDIR /app/examples/web
13+
WORKDIR /app/examples/otel
1414

1515
RUN go generate cmd/main.go
1616

examples/otel/cmd/main.go

+22-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"log"
77
"math/rand"
88
"net/http"
9+
"os"
910
"time"
1011

1112
"github.com/autometrics-dev/autometrics-go/otel/autometrics"
@@ -25,24 +26,42 @@ var (
2526
func main() {
2627
rand.Seed(time.Now().UnixNano())
2728

29+
var pushConfiguration *autometrics.PushConfiguration
30+
if os.Getenv("AUTOMETRICS_OTLP_URL") != "" {
31+
pushConfiguration = &autometrics.PushConfiguration{
32+
CollectorURL: os.Getenv("AUTOMETRICS_OTLP_URL"),
33+
// NOTE: Setting the JobName is useful when you fully control the instances that will run it.
34+
// Otherwise (auto-scaling scenarii), it's better to leave this value out, and let
35+
// autometrics generate an IP-based or Ulid-based identifier for you.
36+
// JobName: "autometrics_go_otel_example",
37+
Period: 1 * time.Second,
38+
Timeout: 500 * time.Millisecond,
39+
}
40+
}
41+
2842
// Everything in BuildInfo is optional.
2943
// You can also use any string variable whose value is
3044
// injected at build time by ldflags.
31-
autometrics.Init(
32-
"web-server",
45+
shutdown, err := autometrics.Init(
46+
"web-server-go-component",
3347
autometrics.DefBuckets,
3448
autometrics.BuildInfo{
3549
Version: Version,
3650
Commit: Commit,
3751
Branch: Branch,
3852
},
53+
pushConfiguration,
3954
)
55+
if err != nil {
56+
log.Fatalf("Failed initialization of autometrics: %s", err)
57+
}
58+
defer shutdown(nil)
4059

4160
http.HandleFunc("/", errorable(indexHandler))
4261
http.HandleFunc("/random-error", errorable(randomErrorHandler))
4362
http.Handle("/metrics", promhttp.Handler())
4463

45-
log.Println("binding on http://localhost:62086")
64+
log.Println("binding on http://0.0.0.0:62086")
4665
log.Fatal(http.ListenAndServe(":62086", nil))
4766
}
4867

examples/otel/go.mod

+28-16
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,39 @@ go 1.20
44

55
require (
66
github.com/autometrics-dev/autometrics-go v0.0.0-20230222105517-4997cc8aa1e4
7-
github.com/prometheus/client_golang v1.14.0
7+
github.com/prometheus/client_golang v1.16.0
88
)
99

1010
require (
1111
github.com/beorn7/perks v1.0.1 // indirect
12-
github.com/cespare/xxhash/v2 v2.1.2 // indirect
13-
github.com/go-logr/logr v1.2.3 // indirect
12+
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
13+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
14+
github.com/go-logr/logr v1.2.4 // indirect
1415
github.com/go-logr/stdr v1.2.2 // indirect
15-
github.com/golang/protobuf v1.5.2 // indirect
16-
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
17-
github.com/prometheus/client_model v0.3.0 // indirect
18-
github.com/prometheus/common v0.37.0 // indirect
19-
github.com/prometheus/procfs v0.8.0 // indirect
20-
go.opentelemetry.io/otel v1.14.0 // indirect
21-
go.opentelemetry.io/otel/exporters/prometheus v0.37.0 // indirect
22-
go.opentelemetry.io/otel/metric v0.37.0 // indirect
23-
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
24-
go.opentelemetry.io/otel/sdk/metric v0.37.0 // indirect
25-
go.opentelemetry.io/otel/trace v1.14.0 // indirect
26-
golang.org/x/sys v0.5.0 // indirect
27-
google.golang.org/protobuf v1.28.1 // indirect
16+
github.com/golang/protobuf v1.5.3 // indirect
17+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
18+
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
19+
github.com/oklog/ulid/v2 v2.1.0 // indirect
20+
github.com/prometheus/client_model v0.4.0 // indirect
21+
github.com/prometheus/common v0.44.0 // indirect
22+
github.com/prometheus/procfs v0.11.1 // indirect
23+
go.opentelemetry.io/otel v1.17.0 // indirect
24+
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0 // indirect
25+
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.40.0 // indirect
26+
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 // indirect
27+
go.opentelemetry.io/otel/exporters/prometheus v0.40.0 // indirect
28+
go.opentelemetry.io/otel/metric v1.17.0 // indirect
29+
go.opentelemetry.io/otel/sdk v1.17.0 // indirect
30+
go.opentelemetry.io/otel/sdk/metric v0.40.0 // indirect
31+
go.opentelemetry.io/otel/trace v1.17.0 // indirect
32+
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
33+
golang.org/x/net v0.10.0 // indirect
34+
golang.org/x/sys v0.12.0 // indirect
35+
golang.org/x/text v0.9.0 // indirect
36+
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
37+
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
38+
google.golang.org/grpc v1.57.0 // indirect
39+
google.golang.org/protobuf v1.31.0 // indirect
2840
)
2941

3042
replace github.com/autometrics-dev/autometrics-go => ../..

0 commit comments

Comments
 (0)