1
0
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:
Owen Williams
2025-08-27 13:56:14 -04:00
committed by GitHub
parent 3342341f15
commit d0cab8666b
29 changed files with 541 additions and 136 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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))

View File

@@ -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
}

View File

@@ -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)
}
})
}
}

View File

@@ -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

View File

@@ -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=

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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