You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-09-16 09:26:25 +02:00
prometheus: Add support for setting Translation Strategy config option (#7111)
As per the specification: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/prometheus.md#configuration This is part of a broader effort to unify the behavior of all the touch points between open telemetry metrics and prometheus: https://github.com/prometheus/prometheus/issues/16542 Fixes https://github.com/open-telemetry/opentelemetry-go/issues/6668 --------- Signed-off-by: Owen Williams <owen.williams@grafana.com> Co-authored-by: Robert Pająk <pellared@hotmail.com>
This commit is contained in:
@@ -51,6 +51,7 @@ The next release will require at least [Go 1.24].
|
||||
See the [migration documentation](./semconv/v1.36.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.34.0.`(#7032)
|
||||
- Add experimental self-observability span and batch span processor metrics in `go.opentelemetry.io/otel/sdk/trace`.
|
||||
Check the `go.opentelemetry.io/otel/sdk/trace/internal/x` package documentation for more information. (#7027, #6393, #7209)
|
||||
- Add support for configuring Prometheus name translation using `WithTranslationStrategy` option in `go.opentelemetry.io/otel/exporters/prometheus`. The current default translation strategy when UTF-8 mode is enabled is `NoUTF8EscapingWithSuffixes`, but a future release will change the default strategy to `UnderscoreEscapingWithSuffixes` for compliance with the specification. (#7111)
|
||||
- Add native histogram exemplar support in `go.opentelemetry.io/otel/exporters/prometheus`. (#6772)
|
||||
- Add experimental self-observability log metrics in `go.opentelemetry.io/otel/sdk/log`.
|
||||
Check the `go.opentelemetry.io/otel/sdk/log/internal/x` package documentation for more information. (#7121)
|
||||
@@ -71,10 +72,11 @@ The next release will require at least [Go 1.24].
|
||||
### Deprecated
|
||||
|
||||
- Deprecate support for `OTEL_GO_X_CARDINALITY_LIMIT` environment variable in `go.opentelemetry.io/otel/sdk/metric`. Use `WithCardinalityLimit` option instead. (#7166)
|
||||
- Deprecate `WithoutUnits` and `WithoutCounterSuffixes` options, preferring `WithTranslationStrategy` instead. (#7111)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix `go.opentelemetry.io/otel/exporters/prometheus` to deduplicate suffixes if already present in metric name when UTF8 is enabled. (#7088)
|
||||
- Fix `go.opentelemetry.io/otel/exporters/prometheus` to not append a suffix if it's already present in metric name. (#7088)
|
||||
- `SetBody` method of `Record` in `go.opentelemetry.io/otel/sdk/log` now deduplicates key-value collections (`log.Value` of `log.KindMap` from `go.opentelemetry.io/otel/log`). (#7002)
|
||||
- Fix the `go.opentelemetry.io/otel/exporters/stdout/stdouttrace` self-observability component type and name. (#7195)
|
||||
- Fix partial export count metric in `go.opentelemetry.io/otel/exporters/stdout/stdouttrace`. (#7199)
|
||||
|
@@ -7,6 +7,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/otlptranslator"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
@@ -17,6 +19,7 @@ import (
|
||||
type config struct {
|
||||
registerer prometheus.Registerer
|
||||
disableTargetInfo bool
|
||||
translationStrategy otlptranslator.TranslationStrategyOption
|
||||
withoutUnits bool
|
||||
withoutCounterSuffixes bool
|
||||
readerOpts []metric.ManualReaderOption
|
||||
@@ -25,9 +28,9 @@ type config struct {
|
||||
resourceAttributesFilter attribute.Filter
|
||||
}
|
||||
|
||||
var logDeprecatedLegacyScheme = sync.OnceFunc(func() {
|
||||
var logTemporaryDefault = sync.OnceFunc(func() {
|
||||
global.Warn(
|
||||
"prometheus exporter legacy scheme deprecated: support for the legacy NameValidationScheme will be removed in a future release",
|
||||
"The default Prometheus naming translation strategy is planned to be changed from otlptranslator.NoUTF8EscapingWithSuffixes to otlptranslator.UnderscoreEscapingWithSuffixes in a future release. Add prometheus.WithTranslationStrategy(otlptranslator.NoUTF8EscapingWithSuffixes) to preserve the existing behavior, or prometheus.WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes) to opt into the future default behavior.",
|
||||
)
|
||||
})
|
||||
|
||||
@@ -38,6 +41,30 @@ func newConfig(opts ...Option) config {
|
||||
cfg = opt.apply(cfg)
|
||||
}
|
||||
|
||||
if cfg.translationStrategy == "" {
|
||||
// If no translation strategy was specified, deduce one based on the global
|
||||
// NameValidationScheme. NOTE: this logic will change in the future, always
|
||||
// defaulting to UnderscoreEscapingWithSuffixes
|
||||
|
||||
//nolint:staticcheck // NameValidationScheme is deprecated but we still need it for now.
|
||||
if model.NameValidationScheme == model.UTF8Validation {
|
||||
logTemporaryDefault()
|
||||
cfg.translationStrategy = otlptranslator.NoUTF8EscapingWithSuffixes
|
||||
} else {
|
||||
cfg.translationStrategy = otlptranslator.UnderscoreEscapingWithSuffixes
|
||||
}
|
||||
} else {
|
||||
// Note, if the translation strategy implies that suffixes should be added,
|
||||
// the user can still use WithoutUnits and WithoutCounterSuffixes to
|
||||
// explicitly disable specific suffixes. We do not override their preference
|
||||
// in this case. However if the chosen strategy disables suffixes, we should
|
||||
// forcibly disable all of them.
|
||||
if !cfg.translationStrategy.ShouldAddSuffixes() {
|
||||
cfg.withoutCounterSuffixes = true
|
||||
cfg.withoutUnits = true
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.registerer == nil {
|
||||
cfg.registerer = prometheus.DefaultRegisterer
|
||||
}
|
||||
@@ -95,6 +122,30 @@ func WithoutTargetInfo() Option {
|
||||
})
|
||||
}
|
||||
|
||||
// WithTranslationStrategy provides a standardized way to define how metric and
|
||||
// label names should be handled during translation to Prometheus format. See:
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.48.0/specification/metrics/sdk_exporters/prometheus.md#configuration.
|
||||
// The recommended approach is to use either
|
||||
// [otlptranslator.UnderscoreEscapingWithSuffixes] for full Prometheus-style
|
||||
// compatibility or [otlptranslator.NoTranslation] for OpenTelemetry-style names.
|
||||
//
|
||||
// By default, if the NameValidationScheme variable in
|
||||
// [github.com/prometheus/common/model] is "legacy", the default strategy is
|
||||
// [otlptranslator.UnderscoreEscapingWithSuffixes]. If the validation scheme is
|
||||
// "utf8", then currently the default Strategy is
|
||||
// [otlptranslator.NoUTF8EscapingWithSuffixes].
|
||||
//
|
||||
// Notice: It is planned that a future release of this SDK will change the
|
||||
// default to always be [otlptranslator.UnderscoreEscapingWithSuffixes] in all
|
||||
// circumstances. Users wanting a different translation strategy should specify
|
||||
// it explicitly.
|
||||
func WithTranslationStrategy(strategy otlptranslator.TranslationStrategyOption) Option {
|
||||
return optionFunc(func(cfg config) config {
|
||||
cfg.translationStrategy = strategy
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
// WithoutUnits disables exporter's addition of unit suffixes to metric names,
|
||||
// and will also prevent unit comments from being added in OpenMetrics once
|
||||
// unit comments are supported.
|
||||
@@ -103,6 +154,12 @@ func WithoutTargetInfo() Option {
|
||||
// conventions. For example, the counter metric request.duration, with unit
|
||||
// milliseconds would become request_duration_milliseconds_total.
|
||||
// With this option set, the name would instead be request_duration_total.
|
||||
//
|
||||
// Can be used in conjunction with [WithTranslationStrategy] to disable unit
|
||||
// suffixes in strategies that would otherwise add suffixes, but this behavior
|
||||
// is not recommended and may be removed in a future release.
|
||||
//
|
||||
// Deprecated: Use [WithTranslationStrategy] instead.
|
||||
func WithoutUnits() Option {
|
||||
return optionFunc(func(cfg config) config {
|
||||
cfg.withoutUnits = true
|
||||
@@ -110,12 +167,19 @@ func WithoutUnits() Option {
|
||||
})
|
||||
}
|
||||
|
||||
// WithoutCounterSuffixes disables exporter's addition _total suffixes on counters.
|
||||
// WithoutCounterSuffixes disables exporter's addition _total suffixes on
|
||||
// counters.
|
||||
//
|
||||
// By default, metric names include a _total suffix to follow Prometheus naming
|
||||
// conventions. For example, the counter metric happy.people would become
|
||||
// happy_people_total. With this option set, the name would instead be
|
||||
// happy_people.
|
||||
//
|
||||
// Can be used in conjunction with [WithTranslationStrategy] to disable counter
|
||||
// suffixes in strategies that would otherwise add suffixes, but this behavior
|
||||
// is not recommended and may be removed in a future release.
|
||||
//
|
||||
// Deprecated: Use [WithTranslationStrategy] instead.
|
||||
func WithoutCounterSuffixes() Option {
|
||||
return optionFunc(func(cfg config) config {
|
||||
cfg.withoutCounterSuffixes = true
|
||||
@@ -132,9 +196,11 @@ func WithoutScopeInfo() Option {
|
||||
})
|
||||
}
|
||||
|
||||
// WithNamespace configures the Exporter to prefix metric with the given namespace.
|
||||
// Metadata metrics such as target_info are not prefixed since these
|
||||
// have special behavior based on their name.
|
||||
// WithNamespace configures the Exporter to prefix metric with the given
|
||||
// namespace. Metadata metrics such as target_info are not prefixed since these
|
||||
// have special behavior based on their name. Namespaces will be prepended even
|
||||
// if [otlptranslator.NoTranslation] is set as a translation strategy. If the provided namespace
|
||||
// is empty, nothing will be prepended to metric names.
|
||||
func WithNamespace(ns string) Option {
|
||||
return optionFunc(func(cfg config) config {
|
||||
cfg.namespace = ns
|
||||
|
@@ -8,6 +8,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/otlptranslator"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
@@ -21,15 +23,17 @@ func TestNewConfig(t *testing.T) {
|
||||
producer := &noopProducer{}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
options []Option
|
||||
wantConfig config
|
||||
name string
|
||||
options []Option
|
||||
wantConfig config
|
||||
legacyValidation bool
|
||||
}{
|
||||
{
|
||||
name: "Default",
|
||||
options: nil,
|
||||
wantConfig: config{
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -38,7 +42,8 @@ func TestNewConfig(t *testing.T) {
|
||||
WithRegisterer(registry),
|
||||
},
|
||||
wantConfig: config{
|
||||
registerer: registry,
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: registry,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -47,8 +52,9 @@ func TestNewConfig(t *testing.T) {
|
||||
WithAggregationSelector(aggregationSelector),
|
||||
},
|
||||
wantConfig: config{
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
readerOpts: []metric.ManualReaderOption{metric.WithAggregationSelector(aggregationSelector)},
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
readerOpts: []metric.ManualReaderOption{metric.WithAggregationSelector(aggregationSelector)},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -57,8 +63,9 @@ func TestNewConfig(t *testing.T) {
|
||||
WithProducer(producer),
|
||||
},
|
||||
wantConfig: config{
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
readerOpts: []metric.ManualReaderOption{metric.WithProducer(producer)},
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
readerOpts: []metric.ManualReaderOption{metric.WithProducer(producer)},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -70,7 +77,8 @@ func TestNewConfig(t *testing.T) {
|
||||
},
|
||||
|
||||
wantConfig: config{
|
||||
registerer: registry,
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: registry,
|
||||
readerOpts: []metric.ManualReaderOption{
|
||||
metric.WithAggregationSelector(aggregationSelector),
|
||||
metric.WithProducer(producer),
|
||||
@@ -83,7 +91,8 @@ func TestNewConfig(t *testing.T) {
|
||||
WithRegisterer(nil),
|
||||
},
|
||||
wantConfig: config{
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -92,8 +101,42 @@ func TestNewConfig(t *testing.T) {
|
||||
WithoutTargetInfo(),
|
||||
},
|
||||
wantConfig: config{
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
disableTargetInfo: true,
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
disableTargetInfo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "legacy validation mode default",
|
||||
options: []Option{},
|
||||
legacyValidation: true,
|
||||
wantConfig: config{
|
||||
translationStrategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "legacy validation mode, unit suffixes disabled",
|
||||
options: []Option{
|
||||
WithoutUnits(),
|
||||
},
|
||||
legacyValidation: true,
|
||||
wantConfig: config{
|
||||
translationStrategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
withoutUnits: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "legacy validation mode, counter suffixes disabled",
|
||||
options: []Option{
|
||||
WithoutCounterSuffixes(),
|
||||
},
|
||||
legacyValidation: true,
|
||||
wantConfig: config{
|
||||
translationStrategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
withoutCounterSuffixes: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -102,8 +145,45 @@ func TestNewConfig(t *testing.T) {
|
||||
WithoutUnits(),
|
||||
},
|
||||
wantConfig: config{
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
withoutUnits: true,
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
withoutUnits: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NoTranslation implies no suffixes",
|
||||
options: []Option{
|
||||
WithTranslationStrategy(otlptranslator.NoTranslation),
|
||||
},
|
||||
wantConfig: config{
|
||||
translationStrategy: otlptranslator.NoTranslation,
|
||||
withoutUnits: true,
|
||||
withoutCounterSuffixes: true,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "translation strategy does not override unit suffixes disabled",
|
||||
options: []Option{
|
||||
WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes),
|
||||
WithoutUnits(),
|
||||
},
|
||||
wantConfig: config{
|
||||
translationStrategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
withoutUnits: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "translation strategy does not override counter suffixes disabled",
|
||||
options: []Option{
|
||||
WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes),
|
||||
WithoutCounterSuffixes(),
|
||||
},
|
||||
wantConfig: config{
|
||||
translationStrategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
withoutCounterSuffixes: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -112,8 +192,9 @@ func TestNewConfig(t *testing.T) {
|
||||
WithNamespace("test"),
|
||||
},
|
||||
wantConfig: config{
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
namespace: "test",
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
namespace: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -122,8 +203,9 @@ func TestNewConfig(t *testing.T) {
|
||||
WithNamespace("test"),
|
||||
},
|
||||
wantConfig: config{
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
namespace: "test",
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
namespace: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -132,13 +214,21 @@ func TestNewConfig(t *testing.T) {
|
||||
WithNamespace("test/"),
|
||||
},
|
||||
wantConfig: config{
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
namespace: "test/",
|
||||
translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes,
|
||||
registerer: prometheus.DefaultRegisterer,
|
||||
namespace: "test/",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.legacyValidation {
|
||||
//nolint:staticcheck
|
||||
model.NameValidationScheme = model.LegacyValidation
|
||||
} else {
|
||||
//nolint:staticcheck
|
||||
model.NameValidationScheme = model.UTF8Validation
|
||||
}
|
||||
cfg := newConfig(tt.options...)
|
||||
// only check the length of readerOpts, since they are not comparable
|
||||
assert.Len(t, cfg.readerOpts, len(tt.wantConfig.readerOpts))
|
||||
|
@@ -15,7 +15,6 @@ import (
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/otlptranslator"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
@@ -104,12 +103,18 @@ func New(opts ...Option) (*Exporter, error) {
|
||||
// TODO (#3244): Enable some way to configure the reader, but not change temporality.
|
||||
reader := metric.NewManualReader(cfg.readerOpts...)
|
||||
|
||||
utf8Allowed := model.NameValidationScheme == model.UTF8Validation // nolint:staticcheck // We need this check to keep supporting the legacy scheme.
|
||||
if !utf8Allowed {
|
||||
// Only sanitize if prometheus does not support UTF-8.
|
||||
logDeprecatedLegacyScheme()
|
||||
labelNamer := otlptranslator.LabelNamer{UTF8Allowed: !cfg.translationStrategy.ShouldEscape()}
|
||||
escapedNamespace := cfg.namespace
|
||||
if escapedNamespace != "" {
|
||||
var err error
|
||||
// If the namespace needs to be escaped, do that now when creating the new
|
||||
// Collector object. The escaping is not persisted in the Config itself.
|
||||
escapedNamespace, err = labelNamer.Build(escapedNamespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
labelNamer := otlptranslator.LabelNamer{UTF8Allowed: utf8Allowed}
|
||||
|
||||
collector := &collector{
|
||||
reader: reader,
|
||||
disableTargetInfo: cfg.disableTargetInfo,
|
||||
@@ -117,18 +122,11 @@ func New(opts ...Option) (*Exporter, error) {
|
||||
withoutCounterSuffixes: cfg.withoutCounterSuffixes,
|
||||
disableScopeInfo: cfg.disableScopeInfo,
|
||||
metricFamilies: make(map[string]*dto.MetricFamily),
|
||||
namespace: labelNamer.Build(cfg.namespace),
|
||||
namespace: escapedNamespace,
|
||||
resourceAttributesFilter: cfg.resourceAttributesFilter,
|
||||
metricNamer: otlptranslator.MetricNamer{
|
||||
Namespace: cfg.namespace,
|
||||
// We decide whether to pass type and unit to the netricNamer based
|
||||
// on whether units or counter suffixes are enabled, and keep this
|
||||
// always enabled.
|
||||
WithMetricSuffixes: true,
|
||||
UTF8Allowed: utf8Allowed,
|
||||
},
|
||||
unitNamer: otlptranslator.UnitNamer{UTF8Allowed: utf8Allowed},
|
||||
labelNamer: labelNamer,
|
||||
metricNamer: otlptranslator.NewMetricNamer(escapedNamespace, cfg.translationStrategy),
|
||||
unitNamer: otlptranslator.UnitNamer{UTF8Allowed: !cfg.translationStrategy.ShouldEscape()},
|
||||
labelNamer: labelNamer,
|
||||
}
|
||||
|
||||
if err := cfg.registerer.Register(collector); err != nil {
|
||||
@@ -197,7 +195,11 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
}
|
||||
|
||||
if c.resourceAttributesFilter != nil && len(c.resourceKeyVals.keys) == 0 {
|
||||
c.createResourceAttributes(metrics.Resource)
|
||||
err := c.createResourceAttributes(metrics.Resource)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, scopeMetrics := range metrics.ScopeMetrics {
|
||||
@@ -211,7 +213,11 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
kv.keys = append(kv.keys, scopeNameLabel, scopeVersionLabel, scopeSchemaLabel)
|
||||
kv.vals = append(kv.vals, scopeMetrics.Scope.Name, scopeMetrics.Scope.Version, scopeMetrics.Scope.SchemaURL)
|
||||
|
||||
attrKeys, attrVals := getAttrs(scopeMetrics.Scope.Attributes, c.labelNamer)
|
||||
attrKeys, attrVals, err := getAttrs(scopeMetrics.Scope.Attributes, c.labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
continue
|
||||
}
|
||||
for i := range attrKeys {
|
||||
attrKeys[i] = scopeLabelPrefix + attrKeys[i]
|
||||
}
|
||||
@@ -227,7 +233,13 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
if typ == nil {
|
||||
continue
|
||||
}
|
||||
name := c.getName(m)
|
||||
name, err := c.getName(m)
|
||||
if err != nil {
|
||||
// TODO(#7066): Handle this error better. It's not clear this can be
|
||||
// reached, bad metric names should / will be caught at creation time.
|
||||
otel.Handle(err)
|
||||
continue
|
||||
}
|
||||
|
||||
drop, help := c.validateMetrics(name, m.Description, typ)
|
||||
if drop {
|
||||
@@ -322,7 +334,11 @@ func addExponentialHistogramMetric[N int64 | float64](
|
||||
labelNamer otlptranslator.LabelNamer,
|
||||
) {
|
||||
for _, dp := range histogram.DataPoints {
|
||||
keys, values := getAttrs(dp.Attributes, labelNamer)
|
||||
keys, values, err := getAttrs(dp.Attributes, labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
continue
|
||||
}
|
||||
keys = append(keys, kv.keys...)
|
||||
values = append(values, kv.vals...)
|
||||
|
||||
@@ -396,7 +412,11 @@ func addHistogramMetric[N int64 | float64](
|
||||
labelNamer otlptranslator.LabelNamer,
|
||||
) {
|
||||
for _, dp := range histogram.DataPoints {
|
||||
keys, values := getAttrs(dp.Attributes, labelNamer)
|
||||
keys, values, err := getAttrs(dp.Attributes, labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
continue
|
||||
}
|
||||
keys = append(keys, kv.keys...)
|
||||
values = append(values, kv.vals...)
|
||||
|
||||
@@ -432,7 +452,11 @@ func addSumMetric[N int64 | float64](
|
||||
}
|
||||
|
||||
for _, dp := range sum.DataPoints {
|
||||
keys, values := getAttrs(dp.Attributes, labelNamer)
|
||||
keys, values, err := getAttrs(dp.Attributes, labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
continue
|
||||
}
|
||||
keys = append(keys, kv.keys...)
|
||||
values = append(values, kv.vals...)
|
||||
|
||||
@@ -460,7 +484,11 @@ func addGaugeMetric[N int64 | float64](
|
||||
labelNamer otlptranslator.LabelNamer,
|
||||
) {
|
||||
for _, dp := range gauge.DataPoints {
|
||||
keys, values := getAttrs(dp.Attributes, labelNamer)
|
||||
keys, values, err := getAttrs(dp.Attributes, labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
continue
|
||||
}
|
||||
keys = append(keys, kv.keys...)
|
||||
values = append(values, kv.vals...)
|
||||
|
||||
@@ -476,7 +504,7 @@ func addGaugeMetric[N int64 | float64](
|
||||
|
||||
// getAttrs converts the attribute.Set to two lists of matching Prometheus-style
|
||||
// keys and values.
|
||||
func getAttrs(attrs attribute.Set, labelNamer otlptranslator.LabelNamer) ([]string, []string) {
|
||||
func getAttrs(attrs attribute.Set, labelNamer otlptranslator.LabelNamer) ([]string, []string, error) {
|
||||
keys := make([]string, 0, attrs.Len())
|
||||
values := make([]string, 0, attrs.Len())
|
||||
itr := attrs.Iter()
|
||||
@@ -494,7 +522,11 @@ func getAttrs(attrs attribute.Set, labelNamer otlptranslator.LabelNamer) ([]stri
|
||||
keysMap := make(map[string][]string)
|
||||
for itr.Next() {
|
||||
kv := itr.Attribute()
|
||||
key := labelNamer.Build(string(kv.Key))
|
||||
key, err := labelNamer.Build(string(kv.Key))
|
||||
if err != nil {
|
||||
// TODO(#7066) Handle this error better.
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, ok := keysMap[key]; !ok {
|
||||
keysMap[key] = []string{kv.Value.Emit()}
|
||||
} else {
|
||||
@@ -508,17 +540,21 @@ func getAttrs(attrs attribute.Set, labelNamer otlptranslator.LabelNamer) ([]stri
|
||||
values = append(values, strings.Join(vals, ";"))
|
||||
}
|
||||
}
|
||||
return keys, values
|
||||
return keys, values, nil
|
||||
}
|
||||
|
||||
func (c *collector) createInfoMetric(name, description string, res *resource.Resource) (prometheus.Metric, error) {
|
||||
keys, values := getAttrs(*res.Set(), c.labelNamer)
|
||||
keys, values, err := getAttrs(*res.Set(), c.labelNamer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
desc := prometheus.NewDesc(name, description, keys, nil)
|
||||
return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), values...)
|
||||
}
|
||||
|
||||
// getName returns the sanitized name, prefixed with the namespace and suffixed with unit.
|
||||
func (c *collector) getName(m metricdata.Metrics) string {
|
||||
// getName returns the sanitized name, translated according to the selected
|
||||
// TranslationStrategy and namespace option.
|
||||
func (c *collector) getName(m metricdata.Metrics) (string, error) {
|
||||
translatorMetric := otlptranslator.Metric{
|
||||
Name: m.Name,
|
||||
Type: c.namingMetricType(m),
|
||||
@@ -580,13 +616,18 @@ func (c *collector) namingMetricType(m metricdata.Metrics) otlptranslator.Metric
|
||||
return otlptranslator.MetricTypeUnknown
|
||||
}
|
||||
|
||||
func (c *collector) createResourceAttributes(res *resource.Resource) {
|
||||
func (c *collector) createResourceAttributes(res *resource.Resource) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
resourceAttrs, _ := res.Set().Filter(c.resourceAttributesFilter)
|
||||
resourceKeys, resourceValues := getAttrs(resourceAttrs, c.labelNamer)
|
||||
resourceKeys, resourceValues, err := getAttrs(resourceAttrs, c.labelNamer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.resourceKeyVals = keyVals{keys: resourceKeys, vals: resourceValues}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *collector) validateMetrics(name, description string, metricType *dto.MetricType) (drop bool, help string) {
|
||||
@@ -637,7 +678,11 @@ func addExemplars[N int64 | float64](
|
||||
}
|
||||
promExemplars := make([]prometheus.Exemplar, len(exemplars))
|
||||
for i, exemplar := range exemplars {
|
||||
labels := attributesToLabels(exemplar.FilteredAttributes, labelNamer)
|
||||
labels, err := attributesToLabels(exemplar.FilteredAttributes, labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
return m
|
||||
}
|
||||
// Overwrite any existing trace ID or span ID attributes
|
||||
labels[otlptranslator.ExemplarTraceIDKey] = hex.EncodeToString(exemplar.TraceID)
|
||||
labels[otlptranslator.ExemplarSpanIDKey] = hex.EncodeToString(exemplar.SpanID)
|
||||
@@ -657,10 +702,14 @@ func addExemplars[N int64 | float64](
|
||||
return metricWithExemplar
|
||||
}
|
||||
|
||||
func attributesToLabels(attrs []attribute.KeyValue, labelNamer otlptranslator.LabelNamer) prometheus.Labels {
|
||||
func attributesToLabels(attrs []attribute.KeyValue, labelNamer otlptranslator.LabelNamer) (prometheus.Labels, error) {
|
||||
labels := make(map[string]string)
|
||||
for _, attr := range attrs {
|
||||
labels[labelNamer.Build(string(attr.Key))] = attr.Value.Emit()
|
||||
name, err := labelNamer.Build(string(attr.Key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
labels[name] = attr.Value.Emit()
|
||||
}
|
||||
return labels
|
||||
return labels, nil
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ package prometheus
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/otlptranslator"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -38,13 +38,13 @@ func TestPrometheusExporter(t *testing.T) {
|
||||
recordMetrics func(ctx context.Context, meter otelmetric.Meter)
|
||||
options []Option
|
||||
expectedFile string
|
||||
disableUTF8 bool
|
||||
strategy otlptranslator.TranslationStrategyOption
|
||||
checkMetricFamilies func(t testing.TB, dtos []*dto.MetricFamily)
|
||||
}{
|
||||
{
|
||||
name: "counter",
|
||||
expectedFile: "testdata/counter.txt",
|
||||
disableUTF8: true,
|
||||
strategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
||||
opt := otelmetric.WithAttributes(
|
||||
attribute.Key("A").String("B"),
|
||||
@@ -70,11 +70,15 @@ func TestPrometheusExporter(t *testing.T) {
|
||||
)
|
||||
counter.Add(ctx, 5, otelmetric.WithAttributeSet(attrs2))
|
||||
},
|
||||
options: []Option{
|
||||
WithNamespace("my.dotted.namespace"),
|
||||
WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "counter that already has the unit suffix",
|
||||
expectedFile: "testdata/counter_noutf8_with_unit_suffix.txt",
|
||||
disableUTF8: true,
|
||||
strategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
||||
opt := otelmetric.WithAttributes(
|
||||
attribute.Key("A").String("B"),
|
||||
@@ -112,7 +116,7 @@ func TestPrometheusExporter(t *testing.T) {
|
||||
attribute.Key("F").Int(42),
|
||||
)
|
||||
counter, err := meter.Float64Counter(
|
||||
"foo",
|
||||
"foo.dotted",
|
||||
otelmetric.WithDescription("a simple counter"),
|
||||
otelmetric.WithUnit("madeup"),
|
||||
)
|
||||
@@ -162,7 +166,7 @@ func TestPrometheusExporter(t *testing.T) {
|
||||
{
|
||||
name: "counter that already has a total suffix",
|
||||
expectedFile: "testdata/counter.txt",
|
||||
disableUTF8: true,
|
||||
strategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
||||
opt := otelmetric.WithAttributes(
|
||||
attribute.Key("A").String("B"),
|
||||
@@ -188,6 +192,10 @@ func TestPrometheusExporter(t *testing.T) {
|
||||
)
|
||||
counter.Add(ctx, 5, otelmetric.WithAttributeSet(attrs2))
|
||||
},
|
||||
options: []Option{
|
||||
WithNamespace("my.dotted.namespace"),
|
||||
WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "counter with suffixes disabled",
|
||||
@@ -303,7 +311,7 @@ func TestPrometheusExporter(t *testing.T) {
|
||||
{
|
||||
name: "sanitized attributes to labels",
|
||||
expectedFile: "testdata/sanitized_labels.txt",
|
||||
disableUTF8: true,
|
||||
strategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
options: []Option{WithoutUnits()},
|
||||
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
||||
opt := otelmetric.WithAttributes(
|
||||
@@ -521,6 +529,43 @@ func TestPrometheusExporter(t *testing.T) {
|
||||
{
|
||||
name: "counter utf-8",
|
||||
expectedFile: "testdata/counter_utf8.txt",
|
||||
options: []Option{
|
||||
WithNamespace("my.dotted.namespace"),
|
||||
WithTranslationStrategy(otlptranslator.NoUTF8EscapingWithSuffixes),
|
||||
},
|
||||
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
||||
opt := otelmetric.WithAttributes(
|
||||
attribute.Key("A.G").String("B"),
|
||||
attribute.Key("C.H").String("D"),
|
||||
attribute.Key("E.I").Bool(true),
|
||||
attribute.Key("F.J").Int(42),
|
||||
)
|
||||
counter, err := meter.Float64Counter(
|
||||
"foo.things",
|
||||
otelmetric.WithDescription("a simple counter"),
|
||||
otelmetric.WithUnit("s"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
counter.Add(ctx, 5, opt)
|
||||
counter.Add(ctx, 10.3, opt)
|
||||
counter.Add(ctx, 9, opt)
|
||||
|
||||
attrs2 := attribute.NewSet(
|
||||
attribute.Key("A.G").String("D"),
|
||||
attribute.Key("C.H").String("B"),
|
||||
attribute.Key("E.I").Bool(true),
|
||||
attribute.Key("F.J").Int(42),
|
||||
)
|
||||
counter.Add(ctx, 5, otelmetric.WithAttributeSet(attrs2))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "counter utf-8 notranslation",
|
||||
expectedFile: "testdata/counter_utf8_notranslation.txt",
|
||||
strategy: otlptranslator.NoTranslation,
|
||||
options: []Option{
|
||||
WithNamespace("my.dotted.namespace"),
|
||||
},
|
||||
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
||||
opt := otelmetric.WithAttributes(
|
||||
attribute.Key("A.G").String("B"),
|
||||
@@ -587,16 +632,10 @@ func TestPrometheusExporter(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.disableUTF8 {
|
||||
model.NameValidationScheme = model.LegacyValidation // nolint:staticcheck // We need this check to keep supporting the legacy scheme.
|
||||
defer func() {
|
||||
// Reset to defaults
|
||||
model.NameValidationScheme = model.UTF8Validation // nolint:staticcheck // We need this check to keep supporting the legacy scheme.
|
||||
}()
|
||||
}
|
||||
ctx := context.Background()
|
||||
registry := prometheus.NewRegistry()
|
||||
exporter, err := New(append(tc.options, WithRegisterer(registry))...)
|
||||
opts := append(tc.options, WithRegisterer(registry), WithTranslationStrategy(tc.strategy))
|
||||
exporter, err := New(opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
var res *resource.Resource
|
||||
@@ -663,7 +702,10 @@ func TestPrometheusExporter(t *testing.T) {
|
||||
func TestMultiScopes(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
registry := prometheus.NewRegistry()
|
||||
exporter, err := New(WithRegisterer(registry))
|
||||
exporter, err := New(
|
||||
WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes),
|
||||
WithRegisterer(registry),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := resource.New(ctx,
|
||||
@@ -932,7 +974,15 @@ func TestDuplicateMetrics(t *testing.T) {
|
||||
// initialize registry exporter
|
||||
ctx := context.Background()
|
||||
registry := prometheus.NewRegistry()
|
||||
exporter, err := New(append(tc.options, WithRegisterer(registry))...)
|
||||
// This test does not set the Translation Strategy, so it defaults to
|
||||
// UnderscoreEscapingWithSuffixes.
|
||||
opts := append(
|
||||
[]Option{
|
||||
WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes),
|
||||
},
|
||||
tc.options...,
|
||||
)
|
||||
exporter, err := New(append(opts, WithRegisterer(registry))...)
|
||||
require.NoError(t, err)
|
||||
|
||||
// initialize resource
|
||||
@@ -1062,8 +1112,7 @@ func TestExemplars(t *testing.T) {
|
||||
recordMetrics func(ctx context.Context, meter otelmetric.Meter)
|
||||
expectedExemplarValue float64
|
||||
expectedLabels map[string]string
|
||||
escapingScheme model.EscapingScheme
|
||||
validationScheme model.ValidationScheme
|
||||
strategy otlptranslator.TranslationStrategyOption
|
||||
}{
|
||||
{
|
||||
name: "escaped counter",
|
||||
@@ -1074,8 +1123,7 @@ func TestExemplars(t *testing.T) {
|
||||
},
|
||||
expectedExemplarValue: 9,
|
||||
expectedLabels: expectedEscapedLabels,
|
||||
escapingScheme: model.UnderscoreEscaping,
|
||||
validationScheme: model.LegacyValidation,
|
||||
strategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
},
|
||||
{
|
||||
name: "escaped histogram",
|
||||
@@ -1086,8 +1134,7 @@ func TestExemplars(t *testing.T) {
|
||||
},
|
||||
expectedExemplarValue: 9,
|
||||
expectedLabels: expectedEscapedLabels,
|
||||
escapingScheme: model.UnderscoreEscaping,
|
||||
validationScheme: model.LegacyValidation,
|
||||
strategy: otlptranslator.UnderscoreEscapingWithSuffixes,
|
||||
},
|
||||
{
|
||||
name: "non-escaped counter",
|
||||
@@ -1098,8 +1145,7 @@ func TestExemplars(t *testing.T) {
|
||||
},
|
||||
expectedExemplarValue: 9,
|
||||
expectedLabels: expectedNonEscapedLabels,
|
||||
escapingScheme: model.NoEscaping,
|
||||
validationScheme: model.UTF8Validation,
|
||||
strategy: otlptranslator.NoTranslation,
|
||||
},
|
||||
{
|
||||
name: "non-escaped histogram",
|
||||
@@ -1110,8 +1156,7 @@ func TestExemplars(t *testing.T) {
|
||||
},
|
||||
expectedExemplarValue: 9,
|
||||
expectedLabels: expectedNonEscapedLabels,
|
||||
escapingScheme: model.NoEscaping,
|
||||
validationScheme: model.UTF8Validation,
|
||||
strategy: otlptranslator.NoTranslation,
|
||||
},
|
||||
{
|
||||
name: "exponential histogram",
|
||||
@@ -1122,24 +1167,19 @@ func TestExemplars(t *testing.T) {
|
||||
},
|
||||
expectedExemplarValue: 9,
|
||||
expectedLabels: expectedNonEscapedLabels,
|
||||
escapingScheme: model.NoEscaping,
|
||||
validationScheme: model.UTF8Validation,
|
||||
strategy: otlptranslator.NoTranslation,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
originalEscapingScheme := model.NameEscapingScheme
|
||||
originalValidationScheme := model.NameValidationScheme // nolint:staticcheck // We need this check to keep supporting the legacy scheme.
|
||||
model.NameEscapingScheme = tc.escapingScheme
|
||||
model.NameValidationScheme = tc.validationScheme // nolint:staticcheck // We need this check to keep supporting the legacy scheme.
|
||||
// Restore original value after the test is complete
|
||||
defer func() {
|
||||
model.NameEscapingScheme = originalEscapingScheme
|
||||
model.NameValidationScheme = originalValidationScheme // nolint:staticcheck // We need this check to keep supporting the legacy scheme.
|
||||
}()
|
||||
// initialize registry exporter
|
||||
ctx := context.Background()
|
||||
registry := prometheus.NewRegistry()
|
||||
exporter, err := New(WithRegisterer(registry), WithoutTargetInfo(), WithoutScopeInfo())
|
||||
exporter, err := New(
|
||||
WithRegisterer(registry),
|
||||
WithoutTargetInfo(),
|
||||
WithoutScopeInfo(),
|
||||
WithTranslationStrategy(tc.strategy),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// initialize resource
|
||||
@@ -1704,3 +1744,154 @@ func TestDownscaleExponentialBucketEdgeCases(t *testing.T) {
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestEscapingErrorHandling increases test coverage by exercising some error
|
||||
// conditions.
|
||||
func TestEscapingErrorHandling(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
namespace string
|
||||
counterName string
|
||||
customScopeAttrs []attribute.KeyValue
|
||||
customResourceAttrs []attribute.KeyValue
|
||||
labelName string
|
||||
expectNewErr string
|
||||
expectMetricErr string
|
||||
checkMetricFamilies func(t testing.TB, dtos []*dto.MetricFamily)
|
||||
}{
|
||||
{
|
||||
name: "simple happy path",
|
||||
counterName: "foo",
|
||||
checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) {
|
||||
require.Len(t, mfs, 2)
|
||||
for _, mf := range mfs {
|
||||
if mf.GetName() == "target_info" {
|
||||
continue
|
||||
}
|
||||
require.Equal(t, "foo_seconds_total", mf.GetName())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bad namespace",
|
||||
namespace: "$%^&",
|
||||
counterName: "foo",
|
||||
expectNewErr: `normalization for label name "$%^&" resulted in invalid name "____"`,
|
||||
},
|
||||
{
|
||||
name: "good namespace, names should be escaped",
|
||||
namespace: "my-strange-namespace",
|
||||
counterName: "foo",
|
||||
labelName: "bar",
|
||||
checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) {
|
||||
for _, mf := range mfs {
|
||||
if mf.GetName() == "target_info" {
|
||||
continue
|
||||
}
|
||||
require.Contains(t, mf.GetName(), "my_strange_namespace")
|
||||
require.NotContains(t, mf.GetName(), "my-strange-namespace")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bad resource attribute",
|
||||
counterName: "foo",
|
||||
customResourceAttrs: []attribute.KeyValue{
|
||||
attribute.Key("$%^&").String("B"),
|
||||
},
|
||||
checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) {
|
||||
require.Empty(t, mfs)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bad scope metric attribute",
|
||||
counterName: "foo",
|
||||
customScopeAttrs: []attribute.KeyValue{
|
||||
attribute.Key("$%^&").String("B"),
|
||||
},
|
||||
checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) {
|
||||
require.Len(t, mfs, 1)
|
||||
require.Equal(t, "target_info", mfs[0].GetName())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bad translated metric name",
|
||||
counterName: "$%^&",
|
||||
expectMetricErr: `invalid instrument name: $%^&: must start with a letter`,
|
||||
},
|
||||
{
|
||||
// label names are not translated and therefore not checked until
|
||||
// collection time, and there is no place to catch and return this error.
|
||||
// Instead we drop the metric.
|
||||
name: "bad translated label name",
|
||||
counterName: "foo",
|
||||
labelName: "$%^&",
|
||||
checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) {
|
||||
require.Len(t, mfs, 1)
|
||||
require.Equal(t, "target_info", mfs[0].GetName())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
sc := trace.NewSpanContext(trace.SpanContextConfig{
|
||||
SpanID: trace.SpanID{0o1},
|
||||
TraceID: trace.TraceID{0o1},
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
})
|
||||
ctx = trace.ContextWithSpanContext(ctx, sc)
|
||||
|
||||
exporter, err := New(
|
||||
WithRegisterer(registry),
|
||||
WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes),
|
||||
WithNamespace(tc.namespace),
|
||||
WithResourceAsConstantLabels(attribute.NewDenyKeysFilter()),
|
||||
)
|
||||
if tc.expectNewErr != "" {
|
||||
require.ErrorContains(t, err, tc.expectNewErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := resource.New(ctx,
|
||||
resource.WithAttributes(semconv.ServiceName("prometheus_test")),
|
||||
resource.WithAttributes(semconv.TelemetrySDKVersion("latest")),
|
||||
resource.WithAttributes(tc.customResourceAttrs...),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
provider := metric.NewMeterProvider(
|
||||
metric.WithReader(exporter),
|
||||
metric.WithResource(res),
|
||||
)
|
||||
|
||||
fooCounter, err := provider.Meter(
|
||||
"meterfoo",
|
||||
otelmetric.WithInstrumentationVersion("v0.1.0"),
|
||||
otelmetric.WithInstrumentationAttributes(tc.customScopeAttrs...),
|
||||
).
|
||||
Int64Counter(
|
||||
tc.counterName,
|
||||
otelmetric.WithUnit("s"),
|
||||
otelmetric.WithDescription(fmt.Sprintf(`meter %q counter`, tc.counterName)))
|
||||
if tc.expectMetricErr != "" {
|
||||
require.ErrorContains(t, err, tc.expectMetricErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
var opts []otelmetric.AddOption
|
||||
if tc.labelName != "" {
|
||||
opts = append(opts, otelmetric.WithAttributes(attribute.String(tc.labelName, "foo")))
|
||||
}
|
||||
fooCounter.Add(ctx, 100, opts...)
|
||||
got, err := registry.Gather()
|
||||
require.NoError(t, err)
|
||||
if tc.checkMetricFamilies != nil {
|
||||
tc.checkMetricFamilies(t, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ require (
|
||||
github.com/prometheus/client_golang v1.23.0
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/prometheus/common v0.65.0
|
||||
github.com/prometheus/otlptranslator v0.0.1
|
||||
github.com/prometheus/otlptranslator v0.0.2
|
||||
github.com/stretchr/testify v1.11.0
|
||||
go.opentelemetry.io/otel v1.37.0
|
||||
go.opentelemetry.io/otel/metric v1.37.0
|
||||
|
@@ -31,8 +31,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/otlptranslator v0.0.1 h1:C4HDe7bEfM6O/+9cz7l5Y12sWL9BN/QsMb9Le4/WHmA=
|
||||
github.com/prometheus/otlptranslator v0.0.1/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
|
||||
github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ=
|
||||
github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
|
@@ -4,4 +4,4 @@ bar_bytes_total{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version
|
||||
bar_bytes_total{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -4,4 +4,4 @@ bar_bytes_total{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version
|
||||
bar_bytes_total{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -38,4 +38,4 @@ bar_bytes_sum{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_ver
|
||||
bar_bytes_count{A="B",otel_scope_name="mb",otel_scope_schema_url="",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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -38,4 +38,4 @@ bar_bytes_sum{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_ver
|
||||
bar_bytes_count{A="B",otel_scope_name="mb",otel_scope_schema_url="",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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -4,4 +4,4 @@ bar_bytes{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1
|
||||
bar_bytes{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -4,4 +4,4 @@ bar_bytes{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1
|
||||
bar_bytes{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -3,4 +3,4 @@
|
||||
foo_total{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="foo"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -3,4 +3,4 @@
|
||||
foo_total{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="foo"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -3,4 +3,4 @@
|
||||
foo_bytes{A="B",otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -20,4 +20,4 @@ foo_bytes_sum{A="B",otel_scope_name="ma",otel_scope_schema_url="",otel_scope_ver
|
||||
foo_bytes_count{A="B",otel_scope_name="ma",otel_scope_schema_url="",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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -4,4 +4,4 @@ bar_total{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1
|
||||
bar_total{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -38,4 +38,4 @@ bar_sum{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="
|
||||
bar_count{A="B",otel_scope_name="mb",otel_scope_schema_url="",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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -4,4 +4,4 @@ bar{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1.0",ty
|
||||
bar{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
8
exporters/prometheus/testdata/counter.txt
vendored
8
exporters/prometheus/testdata/counter.txt
vendored
@@ -1,7 +1,7 @@
|
||||
# HELP foo_seconds_total a simple counter
|
||||
# TYPE foo_seconds_total counter
|
||||
foo_seconds_total{A="B",C="D",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3
|
||||
foo_seconds_total{A="D",C="B",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5
|
||||
# HELP my_dotted_namespace_foo_seconds_total a simple counter
|
||||
# TYPE my_dotted_namespace_foo_seconds_total counter
|
||||
my_dotted_namespace_foo_seconds_total{A="B",C="D",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3
|
||||
my_dotted_namespace_foo_seconds_total{A="D",C="B",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5
|
||||
# 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# HELP "foo.things_seconds_total" a simple counter
|
||||
# TYPE "foo.things_seconds_total" counter
|
||||
{"foo.things_seconds_total","A.G"="B","C.H"="D","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3
|
||||
{"foo.things_seconds_total","A.G"="D","C.H"="B","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5
|
||||
# HELP "my.dotted.namespace_foo.things_seconds_total" a simple counter
|
||||
# TYPE "my.dotted.namespace_foo.things_seconds_total" counter
|
||||
{"my.dotted.namespace_foo.things_seconds_total","A.G"="B","C.H"="D","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3
|
||||
{"my.dotted.namespace_foo.things_seconds_total","A.G"="D","C.H"="B","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5
|
||||
# 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
|
||||
|
7
exporters/prometheus/testdata/counter_utf8_notranslation.txt
vendored
Executable file
7
exporters/prometheus/testdata/counter_utf8_notranslation.txt
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
# HELP "my.dotted.namespace_foo.things" a simple counter
|
||||
# TYPE "my.dotted.namespace_foo.things" counter
|
||||
{"my.dotted.namespace_foo.things","A.G"="B","C.H"="D","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3
|
||||
{"my.dotted.namespace_foo.things","A.G"="D","C.H"="B","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5
|
||||
# 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
|
@@ -1,7 +1,7 @@
|
||||
# HELP "foo_madeup_total" a simple counter
|
||||
# TYPE "foo_madeup_total" counter
|
||||
{"foo_madeup_total",A="B",C="D",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3
|
||||
{"foo_madeup_total",A="D",C="B",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5
|
||||
# HELP "foo.dotted_madeup_total" a simple counter
|
||||
# TYPE "foo.dotted_madeup_total" counter
|
||||
{"foo.dotted_madeup_total",A="B",C="D",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3
|
||||
{"foo.dotted_madeup_total",A="D",C="B",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5
|
||||
# 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
|
||||
|
@@ -4,4 +4,4 @@
|
||||
{"foo.seconds_total",A="D",C="B",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -6,4 +6,4 @@ bar_seconds_total{otel_scope_name="meterbar",otel_scope_schema_url="",otel_scope
|
||||
foo_seconds_total{otel_scope_name="meterfoo",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="foo"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -4,4 +4,4 @@ foo_bytes_total{A="B",otel_scope_name="ma",otel_scope_schema_url="",otel_scope_v
|
||||
foo_bytes_total{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -38,4 +38,4 @@ foo_bytes_sum{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_ver
|
||||
foo_bytes_count{A="B",otel_scope_name="mb",otel_scope_schema_url="",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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
@@ -4,4 +4,4 @@ foo_bytes{A="B",otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version
|
||||
foo_bytes{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 100
|
||||
# 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
|
||||
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
|
||||
|
Reference in New Issue
Block a user