1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-01-10 00:29:12 +02:00

prometheus: add WithNamespace option to prefix metrics (#3970)

* prometheus: add WithNamespace option to prefix metrics

* sanitize namespace, add config test

* Update CHANGELOG.md

Co-authored-by: Robert Pająk <pellared@hotmail.com>

---------

Co-authored-by: Robert Pająk <pellared@hotmail.com>
This commit is contained in:
Gustavo Paiva 2023-04-06 15:44:13 -03:00 committed by GitHub
parent 02fa1e2a8d
commit 1c05d9c0b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 1 deletions

View File

@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- The `go.opentelemetry.io/otel/metric/embedded` package. (#3916)
- The `Version` function to `go.opentelemetry.io/otel/sdk` to return the SDK version. (#3949)
- Add a `WithNamespace` option to `go.opentelemetry.io/otel/exporters/prometheus` to allow users to prefix metrics with a namespace. (#3970)
### Changed

View File

@ -15,6 +15,8 @@
package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus"
import (
"strings"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/sdk/metric"
@ -27,6 +29,7 @@ type config struct {
withoutUnits bool
aggregation metric.AggregationSelector
disableScopeInfo bool
namespace string
}
// newConfig creates a validated config configured with options.
@ -116,3 +119,20 @@ func WithoutScopeInfo() Option {
return cfg
})
}
// WithNamespace configures the Exporter to prefix metric with the given namespace.
// Metadata metrics such as target_info and otel_scope_info are not prefixed since these
// have special behavior based on their name.
func WithNamespace(ns string) Option {
return optionFunc(func(cfg config) config {
ns = sanitizeName(ns)
if !strings.HasSuffix(ns, "_") {
// namespace and metric names should be separated with an underscore,
// adds a trailing underscore if there is not one already.
ns = ns + "_"
}
cfg.namespace = ns
return cfg
})
}

View File

@ -99,6 +99,36 @@ func TestNewConfig(t *testing.T) {
withoutUnits: true,
},
},
{
name: "with namespace",
options: []Option{
WithNamespace("test"),
},
wantConfig: config{
registerer: prometheus.DefaultRegisterer,
namespace: "test_",
},
},
{
name: "with namespace with trailing underscore",
options: []Option{
WithNamespace("test_"),
},
wantConfig: config{
registerer: prometheus.DefaultRegisterer,
namespace: "test_",
},
},
{
name: "with unsanitized namespace",
options: []Option{
WithNamespace("test/"),
},
wantConfig: config{
registerer: prometheus.DefaultRegisterer,
namespace: "test_",
},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {

View File

@ -66,6 +66,7 @@ type collector struct {
createTargetInfoOnce sync.Once
scopeInfos map[instrumentation.Scope]prometheus.Metric
metricFamilies map[string]*dto.MetricFamily
namespace string
}
// prometheus counters MUST have a _total suffix:
@ -88,6 +89,7 @@ func New(opts ...Option) (*Exporter, error) {
disableScopeInfo: cfg.disableScopeInfo,
scopeInfos: make(map[instrumentation.Scope]prometheus.Metric),
metricFamilies: make(map[string]*dto.MetricFamily),
namespace: cfg.namespace,
}
if err := cfg.registerer.Register(collector); err != nil {
@ -316,9 +318,12 @@ var unitSuffixes = map[string]string{
"ms": "_milliseconds",
}
// getName returns the sanitized name, including unit suffix.
// getName returns the sanitized name, prefixed with the namespace and suffixed with unit.
func (c *collector) getName(m metricdata.Metrics) string {
name := sanitizeName(m.Name)
if c.namespace != "" {
name = c.namespace + name
}
if c.withoutUnits {
return name
}

View File

@ -258,6 +258,26 @@ func TestPrometheusExporter(t *testing.T) {
counter.Add(ctx, 1, attrs...)
},
},
{
name: "with namespace",
expectedFile: "testdata/with_namespace.txt",
options: []Option{
WithNamespace("test"),
},
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
attrs := []attribute.KeyValue{
attribute.Key("A").String("B"),
attribute.Key("C").String("D"),
attribute.Key("E").Bool(true),
attribute.Key("F").Int(42),
}
counter, err := meter.Float64Counter("foo", instrument.WithDescription("a simple counter"))
require.NoError(t, err)
counter.Add(ctx, 5, attrs...)
counter.Add(ctx, 10.3, attrs...)
counter.Add(ctx, 9, attrs...)
},
},
}
for _, tc := range testCases {

View File

@ -0,0 +1,9 @@
# HELP test_foo_total a simple counter
# TYPE test_foo_total counter
test_foo_total{A="B",C="D",E="true",F="42",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 24.3
# HELP otel_scope_info Instrumentation Scope metadata
# TYPE otel_scope_info gauge
otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1
# HELP target_info Target metadata
# TYPE target_info gauge
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1