Skip to content

Commit a43ffb6

Browse files
gagbomellowagain
andauthored
OpenTelemetry support (#27)
* Refactor autometrics to separate prometheus The initializer, and instrumentation functions of Prometheus have been separated into an additional package so that support for open telemetry can be added more transparently. Also added support for detecting and reusing the local names of autometrics package. Also also make the generator error if it uses non-default objective targets or latency targets, to avoid letting users have wrong targets in their files * Force Go 1.20 for tests It seems that the tests are dependent on a breaking change in gofmt * Refactor context to be global per am invocation This allows to add a flag to allow, or not, custom latencies in the SLOs * Document git-hook and -custom-latency flag * Tweak comment generation Doc comments do not accept Level-2 headings Add a less alarming sub text to documentation fences * Refactor: ask for single import in client code This is done through adding a lot of interfaces and copy pasting to allow the "descendant" packages (prom and otel implementations) to build and return "parent" structures (autometrics.Context) --------- Co-authored-by: Mari <[email protected]>
1 parent eebca20 commit a43ffb6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3802
-492
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Set up Go
1313
uses: actions/setup-go@v3
1414
with:
15-
go-version: "1.18"
15+
go-version: "1.20"
1616
check-latest: true
1717
cache: true
1818

README.md

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,52 @@ A fully working use-case and example of library usage is available in the
2020
There is a one-time setup phase to prime the code for autometrics. Once this
2121
phase is accomplished, only calling `go generate` is necessary.
2222

23+
### Install the go generator.
24+
25+
The generator is the binary in cmd/autometrics, so the easiest way to get it is
26+
to install it through go:
27+
28+
```console
29+
go install github.com/autometrics-dev/autometrics-go/cmd/autometrics@latest
30+
```
31+
32+
In order to have `autometrics` visible then, make sure that the directory
33+
`$GOBIN` (or the default `$GOPATH/bin`) is in your `$PATH`:
34+
35+
``` console
36+
$ echo "$PATH" | grep -q "${GOBIN:-$GOPATH/bin}" && echo "GOBIN in PATH" || echo "GOBIN not in PATH, please add it"
37+
GOBIN in PATH
38+
```
39+
40+
### Import the libraries and initialize the metrics
41+
42+
In the main entrypoint of your program, you need to both add package
43+
44+
``` go
45+
import (
46+
am "github.com/autometrics-dev/autometrics-go/pkg/autometrics/prometheus"
47+
)
48+
```
49+
50+
And then in your main function initialize the metrics
51+
52+
``` go
53+
am.Init(nil, am.DefBuckets)
54+
```
55+
56+
> **Warning**
57+
> If you want to enable alerting from Autometrics, you **MUST**
58+
have the `--latency-ms` values to match the values given in your buckets. The
59+
values in the buckets are given in _seconds_. By default, the generator will
60+
error and tell you the valid default values if they don't match.
61+
If the default values do not match you use case, you can change the buckets in
62+
the init call, and add a `-custom-latency` argument to the `//go:generate` invocation.
63+
64+
```patch
65+
-//go:generate autometrics
66+
+//go:generate autometrics -custom-latency
67+
```
68+
2369
### Add cookies in your code
2470

2571
Given a starting function like:
@@ -34,7 +80,6 @@ func RouteHandler(args interface{}) error {
3480
The manual changes you need to do are:
3581

3682
```go
37-
// Somewhere in your file, probably at the bottom
3883
//go:generate autometrics
3984

4085
//autometrics:doc
@@ -50,6 +95,12 @@ value you return for function you want to instrument.
5095

5196
### Generate the documentation and instrumentation code
5297

98+
Install the go generator using `go install` as usual:
99+
100+
``` console
101+
go install https://github.com/autometrics-dev/autometrics-go/cmd/autometrics
102+
```
103+
53104
Once you've done this, the `autometrics` generator takes care of the rest, and you can
54105
simply call `go generate` with an optional environment variable:
55106

@@ -77,13 +128,13 @@ For Prometheus the shortest way is to add the handler code in your main entrypoi
77128

78129
``` go
79130
import (
80-
"github.com/autometrics-dev/autometrics-go/pkg/autometrics"
131+
am "github.com/autometrics-dev/autometrics-go/pkg/autometrics/prometheus"
81132
"github.com/prometheus/client_golang/prometheus/promhttp"
82133
)
83134

84135

85136
func main() {
86-
autometrics.Init(nil, autometrics.DefBuckets)
137+
am.Init(nil, am.DefBuckets)
87138
http.Handle("/metrics", promhttp.Handler())
88139
}
89140
```
@@ -114,6 +165,52 @@ The valid arguments for alert generation are:
114165
- `--latency-target` : latency target for the threshold, between 0 and 100 (so X%
115166
of calls must last less than `latency-ms` milliseconds). You must specify both
116167
latency options, or none.
168+
169+
> **Warning**
170+
> The generator will error out if you use targets that are not
171+
supported by the bundled [Alerting rules file](./configs/autometrics.rules.yml).
172+
Support for custom target is planned but not present at the moment
173+
174+
## (OPTIONAL) OpenTelemetry Support
175+
176+
Autometrics supports using OpenTelemetry with a prometheus exporter instead of using
177+
Prometheus to publish the metrics. The changes you need to make are:
178+
179+
- change where the `amImpl` import points to
180+
```patch
181+
import (
182+
- am "github.com/autometrics-dev/autometrics-go/pkg/autometrics/prometheus"
183+
+ am "github.com/autometrics-dev/autometrics-go/pkg/autometrics/otel"
184+
)
185+
```
186+
- change the call to `amImpl.Init` to the new signature: instead of a registry,
187+
the `Init` function takes a meter name for the `otel_scope` label of the exported
188+
metric. You can use the name of the application or its version for example
189+
190+
``` patch
191+
- am.Init(nil, am.DefBuckets)
192+
+ am.Init("myApp/v2/prod", am.DefBuckets)
193+
```
194+
195+
- add the `-otel` flag to the `//go:generate` directive
196+
197+
```patch
198+
-//go:generate autometrics
199+
+//go:generate autometrics -otel
200+
```
201+
202+
## (OPTIONAL) Git hook
203+
204+
As autometrics is a Go generator that modifies the source code when run, it
205+
might be interesting to set up `go generate ./...` to run in a git pre-commit
206+
hook so that you never forget to run it if you change the source code.
207+
208+
If you use a tool like [pre-commit](https://pre-commit.com/), see their
209+
documentation about how to add a hook that will run `go generate ./...`.
210+
211+
Otherwise, a simple example has been added in the [configs folder](./configs/pre-commit)
212+
as an example. You can copy this file in your copy of your project's repository, within
213+
`.git/hooks` and make sure that the file is executable.
117214

118215
## Status
119216

@@ -125,8 +222,10 @@ the current status
125222
The first version of the library has _not_ been written by Go experts. Any comment or
126223
code suggestion as Pull Request is more than welcome!
127224

128-
### Metrics system
225+
### Support for custom alerting rules generation
129226

130-
For the time being only Prometheus metrics are supported, but the code has been
131-
written with the possibility to have other systems, like OpenTelemetry,
132-
integrated in the same way.
227+
The alerting system for SLOs that Autometrics uses is based on
228+
[Sloth](https://github.com/slok/sloth), and it has native Go types for
229+
marshalling/unmarshalling rules, so it should be possible to provide an extra
230+
binary in this repository, that only takes care of generating a new [rules
231+
file](./configs/autometrics.rules.yml) with custom objectives.

cmd/autometrics/doc.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Autometrics runs as Go generator and updates a source file to add usage queries and metric collection to annotated functions.
2+
//
3+
// As a Go generator, it relies on the environment variables `GOFILE` and
4+
// `GOPACKAGE` to find the target file to edit.
5+
//
6+
// By default, `autometrics` generates metric collection code for usage with the
7+
// [Prometheus client library]. If you want to use [OpenTelemetry metrics]
8+
// instead (with a prometheus exporter for the metrics), pass the `-otel` flag
9+
// to the invocation.
10+
//
11+
// By default, when activating Service Level Objectives (SLOs) `autometrics`
12+
// does not allow to use latency targets that are outside the default latencies
13+
// defined in [autometrics.DefBuckets]. If you want to use custom latencies for
14+
// your latency SLOs, pass the `-custom-latency` flag to the invocation.
15+
//
16+
// By default, the generated links in the documentation point to a Prometheus
17+
// instance at http://localhost:9090. You can use the environment variable
18+
// `AM_PROMETHEUS_URL` to change the base URL in the documentation links.
19+
//
20+
// [Prometheus client library]: https://github.com/prometheus/client_golang
21+
// [OpenTelemetry metrics]: https://opentelemetry.io/docs/instrumentation/go/
22+
package main

cmd/autometrics/main.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,48 @@ import (
44
"log"
55
"os"
66

7-
"github.com/autometrics-dev/autometrics-go/internal/doc"
7+
internal "github.com/autometrics-dev/autometrics-go/internal/autometrics"
88
"github.com/autometrics-dev/autometrics-go/internal/generate"
9+
"github.com/autometrics-dev/autometrics-go/pkg/autometrics"
910
)
1011

11-
const prometheusAddressEnvironmentVariable = "AM_PROMETHEUS_URL"
12+
const (
13+
prometheusAddressEnvironmentVariable = "AM_PROMETHEUS_URL"
14+
useOtelFlag = "-otel"
15+
allowCustomLatencies = "-custom-latency"
16+
DefaultPrometheusInstanceUrl = "http://localhost:9090/"
17+
)
1218

1319
func main() {
1420
fileName := os.Getenv("GOFILE")
1521
moduleName := os.Getenv("GOPACKAGE")
22+
args := os.Args
1623

1724
prometheusUrl, envVarExists := os.LookupEnv(prometheusAddressEnvironmentVariable)
1825
if !envVarExists {
19-
prometheusUrl = doc.DefaultPrometheusInstanceUrl
26+
prometheusUrl = DefaultPrometheusInstanceUrl
27+
}
28+
29+
implementation := autometrics.PROMETHEUS
30+
if contains(args, useOtelFlag) {
31+
implementation = autometrics.OTEL
2032
}
2133

22-
promGenerator := doc.NewPrometheusDoc(prometheusUrl)
34+
ctx, err := internal.NewGeneratorContext(implementation, prometheusUrl, contains(args, allowCustomLatencies))
35+
if err != nil {
36+
log.Fatalf("error initialising autometrics context: %s", err)
37+
}
2338

24-
if err := generate.TransformFile(fileName, moduleName, promGenerator); err != nil {
39+
if err := generate.TransformFile(ctx, fileName, moduleName); err != nil {
2540
log.Fatalf("error transforming %s: %s", fileName, err)
2641
}
2742
}
43+
44+
func contains[T comparable](s []T, e T) bool {
45+
for _, v := range s {
46+
if v == e {
47+
return true
48+
}
49+
}
50+
return false
51+
}

configs/pre-commit

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
3+
# This pre-commit hook runs "go generate over all .go files that are staged"
4+
5+
STAGED_GO_FILES=$(git diff --cached --name-only -- '*.go')
6+
7+
if [[ $STAGED_GO_FILES == "" ]]; then
8+
echo "No Go Files to check"
9+
else
10+
for file in $STAGED_GO_FILES; do
11+
go generate $file
12+
git add $file
13+
done
14+
fi

examples/otel/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Local build of autometrics-dev/autometrics-go/cmd/autometrics
2+
/autometrics
3+
4+
# Local build of the example server
5+
/web-server
6+
7+
# Local file that holds Slack secret webhook URL for alerts
8+
slack_url.txt

examples/otel/Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
FROM golang:1.20-alpine
2+
MAINTAINER Fiberplane <[email protected]>
3+
4+
# Cannot really build the demo image from
5+
# the examples subfolder because of
6+
# relative imports shenanigans that go out of build context (i.e. upwards)
7+
#
8+
# Use
9+
# GOOS=linux GOARCH=amd64 go build -o web-server ./cmd/main.go
10+
#
11+
# To build the web-server app
12+
13+
COPY web-server /
14+
15+
EXPOSE 62086
16+
17+
CMD [ "/web-server" ]

examples/otel/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# OpenTelemetry example
2+
3+
This is simply an OpenTelemetry version of the [web](../web) example so most of
4+
the README there also applies here.
5+
6+
The only difference is that the metrics implementation used is OpenTelemetry
7+
with a Prometheus exporter instead of using a Prometheus only client crate.
8+
9+
You can notice the 3 differences that are mentionned in the top-level README:
10+
- The amImpl import has been changed to `otel`
11+
- The autometrics call in the Go generator has the `-otel` flag
12+
- The `amImpl.Init` call uses a different first argument, with the name of the
13+
OpenTelemetry scope to use

examples/otel/alertmanager.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
global:
2+
# Also possible to use the URL directly
3+
# Ex: `slack_api_url: 'https://slack.com/...'`
4+
slack_api_url_file: '/etc/alertmanager/slack_url'
5+
6+
route:
7+
receiver: 'slack-notifications'
8+
group_by: [sloth_service, sloth_slo, objective_name]
9+
10+
receivers:
11+
- name: 'slack-notifications'
12+
slack_configs:
13+
# Channel is ignored when using a webhook. The webhook URL encodes the
14+
# channel the alerts will be posted to.
15+
- channel: '#alerts'
16+
title: "{{ range .Alerts }}{{ .Annotations.summary }}\n{{ end }}"
17+
text: "{{ range .Alerts }}{{ .Annotations.description }}\n{{ end }}"

0 commit comments

Comments
 (0)