mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-24 03:47:19 +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:
parent
02fa1e2a8d
commit
1c05d9c0b7
@ -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 `go.opentelemetry.io/otel/metric/embedded` package. (#3916)
|
||||||
- The `Version` function to `go.opentelemetry.io/otel/sdk` to return the SDK version. (#3949)
|
- 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
|
### Changed
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus"
|
package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric"
|
"go.opentelemetry.io/otel/sdk/metric"
|
||||||
@ -27,6 +29,7 @@ type config struct {
|
|||||||
withoutUnits bool
|
withoutUnits bool
|
||||||
aggregation metric.AggregationSelector
|
aggregation metric.AggregationSelector
|
||||||
disableScopeInfo bool
|
disableScopeInfo bool
|
||||||
|
namespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newConfig creates a validated config configured with options.
|
// newConfig creates a validated config configured with options.
|
||||||
@ -116,3 +119,20 @@ func WithoutScopeInfo() Option {
|
|||||||
return cfg
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -99,6 +99,36 @@ func TestNewConfig(t *testing.T) {
|
|||||||
withoutUnits: true,
|
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 {
|
for _, tt := range testCases {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -66,6 +66,7 @@ type collector struct {
|
|||||||
createTargetInfoOnce sync.Once
|
createTargetInfoOnce sync.Once
|
||||||
scopeInfos map[instrumentation.Scope]prometheus.Metric
|
scopeInfos map[instrumentation.Scope]prometheus.Metric
|
||||||
metricFamilies map[string]*dto.MetricFamily
|
metricFamilies map[string]*dto.MetricFamily
|
||||||
|
namespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
// prometheus counters MUST have a _total suffix:
|
// prometheus counters MUST have a _total suffix:
|
||||||
@ -88,6 +89,7 @@ func New(opts ...Option) (*Exporter, error) {
|
|||||||
disableScopeInfo: cfg.disableScopeInfo,
|
disableScopeInfo: cfg.disableScopeInfo,
|
||||||
scopeInfos: make(map[instrumentation.Scope]prometheus.Metric),
|
scopeInfos: make(map[instrumentation.Scope]prometheus.Metric),
|
||||||
metricFamilies: make(map[string]*dto.MetricFamily),
|
metricFamilies: make(map[string]*dto.MetricFamily),
|
||||||
|
namespace: cfg.namespace,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cfg.registerer.Register(collector); err != nil {
|
if err := cfg.registerer.Register(collector); err != nil {
|
||||||
@ -316,9 +318,12 @@ var unitSuffixes = map[string]string{
|
|||||||
"ms": "_milliseconds",
|
"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 {
|
func (c *collector) getName(m metricdata.Metrics) string {
|
||||||
name := sanitizeName(m.Name)
|
name := sanitizeName(m.Name)
|
||||||
|
if c.namespace != "" {
|
||||||
|
name = c.namespace + name
|
||||||
|
}
|
||||||
if c.withoutUnits {
|
if c.withoutUnits {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
@ -258,6 +258,26 @@ func TestPrometheusExporter(t *testing.T) {
|
|||||||
counter.Add(ctx, 1, attrs...)
|
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 {
|
for _, tc := range testCases {
|
||||||
|
9
exporters/prometheus/testdata/with_namespace.txt
vendored
Executable file
9
exporters/prometheus/testdata/with_namespace.txt
vendored
Executable 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
|
Loading…
x
Reference in New Issue
Block a user