@@ -13,6 +13,7 @@ import (
13
13
14
14
"github.com/prometheus/client_golang/prometheus"
15
15
"github.com/prometheus/client_golang/prometheus/testutil"
16
+ dto "github.com/prometheus/client_model/go"
16
17
"github.com/stretchr/testify/assert"
17
18
"github.com/stretchr/testify/require"
18
19
@@ -22,6 +23,7 @@ import (
22
23
"go.opentelemetry.io/otel/sdk/metric"
23
24
"go.opentelemetry.io/otel/sdk/resource"
24
25
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
26
+ "go.opentelemetry.io/otel/trace"
25
27
)
26
28
27
29
func TestPrometheusExporter (t * testing.T ) {
@@ -898,3 +900,118 @@ func TestShutdownExporter(t *testing.T) {
898
900
// ensure we aren't unnecessarily logging errors from the shutdown MeterProvider
899
901
require .NoError (t , handledError )
900
902
}
903
+
904
+ func TestExemplars (t * testing.T ) {
905
+ attrsOpt := otelmetric .WithAttributes (
906
+ attribute .Key ("A" ).String ("B" ),
907
+ attribute .Key ("C" ).String ("D" ),
908
+ attribute .Key ("E" ).Bool (true ),
909
+ attribute .Key ("F" ).Int (42 ),
910
+ )
911
+ for _ , tc := range []struct {
912
+ name string
913
+ recordMetrics func (ctx context.Context , meter otelmetric.Meter )
914
+ expectedExemplarValue float64
915
+ }{
916
+ {
917
+ name : "counter" ,
918
+ recordMetrics : func (ctx context.Context , meter otelmetric.Meter ) {
919
+ counter , err := meter .Float64Counter ("foo" )
920
+ require .NoError (t , err )
921
+ counter .Add (ctx , 9 , attrsOpt )
922
+ },
923
+ expectedExemplarValue : 9 ,
924
+ },
925
+ {
926
+ name : "histogram" ,
927
+ recordMetrics : func (ctx context.Context , meter otelmetric.Meter ) {
928
+ hist , err := meter .Int64Histogram ("foo" )
929
+ require .NoError (t , err )
930
+ hist .Record (ctx , 9 , attrsOpt )
931
+ },
932
+ expectedExemplarValue : 9 ,
933
+ },
934
+ } {
935
+ t .Run (tc .name , func (t * testing.T ) {
936
+ t .Setenv ("OTEL_GO_X_EXEMPLAR" , "true" )
937
+ // initialize registry exporter
938
+ ctx := context .Background ()
939
+ registry := prometheus .NewRegistry ()
940
+ exporter , err := New (WithRegisterer (registry ), WithoutTargetInfo (), WithoutScopeInfo ())
941
+ require .NoError (t , err )
942
+
943
+ // initialize resource
944
+ res , err := resource .New (ctx ,
945
+ resource .WithAttributes (semconv .ServiceName ("prometheus_test" )),
946
+ resource .WithAttributes (semconv .TelemetrySDKVersion ("latest" )),
947
+ )
948
+ require .NoError (t , err )
949
+ res , err = resource .Merge (resource .Default (), res )
950
+ require .NoError (t , err )
951
+
952
+ // initialize provider and meter
953
+ provider := metric .NewMeterProvider (
954
+ metric .WithReader (exporter ),
955
+ metric .WithResource (res ),
956
+ metric .WithView (metric .NewView (
957
+ metric.Instrument {Name : "*" },
958
+ metric.Stream {
959
+ // filter out all attributes so they are added as filtered
960
+ // attributes to the exemplar
961
+ AttributeFilter : attribute .NewAllowKeysFilter (),
962
+ },
963
+ )),
964
+ )
965
+ meter := provider .Meter ("meter" , otelmetric .WithInstrumentationVersion ("v0.1.0" ))
966
+
967
+ // Add a sampled span context so that measurements get exemplars added
968
+ sc := trace .NewSpanContext (trace.SpanContextConfig {
969
+ SpanID : trace.SpanID {0o1 },
970
+ TraceID : trace.TraceID {0o1 },
971
+ TraceFlags : trace .FlagsSampled ,
972
+ })
973
+ ctx = trace .ContextWithSpanContext (ctx , sc )
974
+ // Record a single observation with the exemplar
975
+ tc .recordMetrics (ctx , meter )
976
+
977
+ // Verify that the exemplar is present in the proto version of the
978
+ // prometheus metrics.
979
+ got , done , err := prometheus .ToTransactionalGatherer (registry ).Gather ()
980
+ defer done ()
981
+ require .NoError (t , err )
982
+
983
+ require .Len (t , got , 1 )
984
+ family := got [0 ]
985
+ require .Len (t , family .GetMetric (), 1 )
986
+ metric := family .GetMetric ()[0 ]
987
+ var exemplar * dto.Exemplar
988
+ switch family .GetType () {
989
+ case dto .MetricType_COUNTER :
990
+ exemplar = metric .GetCounter ().GetExemplar ()
991
+ case dto .MetricType_HISTOGRAM :
992
+ for _ , b := range metric .GetHistogram ().GetBucket () {
993
+ if b .GetExemplar () != nil {
994
+ exemplar = b .GetExemplar ()
995
+ continue
996
+ }
997
+ }
998
+ }
999
+ require .NotNil (t , exemplar )
1000
+ require .Equal (t , exemplar .GetValue (), tc .expectedExemplarValue )
1001
+ expectedLabels := map [string ]string {
1002
+ traceIDExemplarKey : "01000000000000000000000000000000" ,
1003
+ spanIDExemplarKey : "0100000000000000" ,
1004
+ "A" : "B" ,
1005
+ "C" : "D" ,
1006
+ "E" : "true" ,
1007
+ "F" : "42" ,
1008
+ }
1009
+ require .Equal (t , len (expectedLabels ), len (exemplar .GetLabel ()))
1010
+ for _ , label := range exemplar .GetLabel () {
1011
+ val , ok := expectedLabels [label .GetName ()]
1012
+ require .True (t , ok )
1013
+ require .Equal (t , label .GetValue (), val )
1014
+ }
1015
+ })
1016
+ }
1017
+ }
0 commit comments