@@ -654,15 +654,42 @@ private static object GetStateForInstrument(object instrument)
654654 return observableResult ;
655655 }
656656
657- var createInstrumentDelegate = ( Func < Meter , string , string , string , object > ) _createInstrumentDelegates . GetOrAdd ( instrumentType , CreateBridgedInstrumentDelegate ) ;
657+ var createInstrumentDelegate = ( Func < Meter , string , string , string , IEnumerable < KeyValuePair < string , object > > , object > ) _createInstrumentDelegates . GetOrAdd ( instrumentType , CreateBridgedInstrumentDelegate ) ;
658658
659659 if ( createInstrumentDelegate == null )
660660 {
661661 return null ;
662662 }
663663
664- // TODO: Tags are not avaiilable on older instruments, need a way to dynamically get them for newer versions
665- var result = createInstrumentDelegate . Invoke ( meter , dynamicInstrument . Name , dynamicInstrument . Unit , dynamicInstrument . Description ) ;
664+ // Try to get Tags from the instrument if available (DiagnosticSource 8.0+)
665+ IEnumerable < KeyValuePair < string , object > > instrumentTags = null ;
666+ var tagsProperty = instrumentType . GetProperty ( "Tags" ) ;
667+ if ( tagsProperty != null )
668+ {
669+ try
670+ {
671+ var tagsValue = tagsProperty . GetValue ( instrument ) ;
672+ if ( tagsValue != null )
673+ {
674+ var tagsList = new List < KeyValuePair < string , object > > ( ) ;
675+ foreach ( var tag in ( dynamic ) tagsValue )
676+ {
677+ tagsList . Add ( new KeyValuePair < string , object > ( tag . Key , tag . Value ?? string . Empty ) ) ;
678+ }
679+ if ( tagsList . Count > 0 )
680+ {
681+ instrumentTags = tagsList ;
682+ Log . Finest ( $ "Bridged { tagsList . Count } tags from instrument '{ dynamicInstrument . Name } '") ;
683+ }
684+ }
685+ }
686+ catch ( Exception ex )
687+ {
688+ Log . Debug ( ex , $ "Failed to bridge Tags from instrument '{ dynamicInstrument . Name } '. Continuing without tags.") ;
689+ }
690+ }
691+
692+ var result = createInstrumentDelegate . Invoke ( meter , dynamicInstrument . Name , dynamicInstrument . Unit , dynamicInstrument . Description , instrumentTags ) ;
666693 return result ;
667694
668695 Meter CreateBridgedMeterFromInstrument ( string _ )
@@ -856,7 +883,7 @@ private static Func<object, Measurement<T>> GetMethodToBridgeMeasurement<T>(Type
856883 return lambda . Compile ( ) ;
857884 }
858885
859- private static Func < Meter , string , string , string , object > CreateBridgedInstrumentDelegate ( Type originalInstrumentType )
886+ private static Func < Meter , string , string , string , IEnumerable < KeyValuePair < string , object > > , object > CreateBridgedInstrumentDelegate ( Type originalInstrumentType )
860887 {
861888 var genericType = originalInstrumentType . GetGenericArguments ( ) . FirstOrDefault ( ) ;
862889 if ( genericType == null )
@@ -878,7 +905,6 @@ private static Func<Meter, string, string, string, object> CreateBridgedInstrume
878905 return null ;
879906 }
880907
881- // TODO: If we upgrade .NET or see test failures related to this reflection, revisit this logic.
882908 // Reflection logic for Meter.Create* methods:
883909 // 1. Try to match the latest public OTel signature (4 parameters: name, unit, description, tags)
884910 // 2. Fallback to 3-parameter version (name, unit, description) for backward compatibility
@@ -899,8 +925,8 @@ private static Func<Meter, string, string, string, object> CreateBridgedInstrume
899925 if ( methodInfo != null )
900926 {
901927 methodInfo = methodInfo . MakeGenericMethod ( genericType ) ;
902- // Wrap in a delegate that ignores the 4th argument (tags) for now (pass null)
903- return ( meter , name , unit , description ) => methodInfo . Invoke ( meter , new object [ ] { name , unit , description , null } ) ;
928+ // Use the 4-parameter overload and pass through the tags
929+ return ( meter , name , unit , description , tags ) => methodInfo . Invoke ( meter , new object [ ] { name , unit , description , tags } ) ;
904930 }
905931
906932 // Fallback: Try 3-parameter overload (name, unit, description)
@@ -918,7 +944,8 @@ private static Func<Meter, string, string, string, object> CreateBridgedInstrume
918944 }
919945
920946 methodInfo = methodInfo . MakeGenericMethod ( genericType ) ;
921- return ( meter , name , unit , description ) => methodInfo . Invoke ( meter , new object [ ] { name , unit , description } ) ;
947+ // Fallback to 3-parameter version, ignoring tags
948+ return ( meter , name , unit , description , tags ) => methodInfo . Invoke ( meter , new object [ ] { name , unit , description } ) ;
922949 }
923950 catch ( Exception ex )
924951 {
@@ -978,10 +1005,71 @@ private static Instrument CreateBridgedObservableUpDownCounter<T>(Meter meter, s
9781005
9791006 private static Meter CreateBridgedMeter ( object meter )
9801007 {
1008+ //https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.meter?view=net-10.0
9811009 dynamic dynamicMeter = meter ;
9821010
983- // TODO: Tags and Scope are not available on older meters, need a way to dynamically get them for newer versions
984- return new Meter ( dynamicMeter . Name , dynamicMeter . Version ) ;
1011+ var meterType = meter . GetType ( ) ;
1012+ var name = ( string ) dynamicMeter . Name ;
1013+ var version = ( string ) dynamicMeter . Version ;
1014+
1015+ // Try to get Tags property if it exists (available in DiagnosticSource 8.0+)
1016+ IEnumerable < KeyValuePair < string , object > > tags = null ;
1017+ var tagsProperty = meterType . GetProperty ( "Tags" ) ;
1018+ if ( tagsProperty != null )
1019+ {
1020+ try
1021+ {
1022+ var tagsValue = tagsProperty . GetValue ( meter ) ;
1023+ if ( tagsValue != null )
1024+ {
1025+ // Tags is IEnumerable<KeyValuePair<string, object?>>?, need to convert to non-nullable
1026+ var tagsList = new List < KeyValuePair < string , object > > ( ) ;
1027+ foreach ( var tag in ( dynamic ) tagsValue )
1028+ {
1029+ tagsList . Add ( new KeyValuePair < string , object > ( tag . Key , tag . Value ?? string . Empty ) ) ;
1030+ }
1031+ if ( tagsList . Count > 0 )
1032+ {
1033+ tags = tagsList ;
1034+ Log . Finest ( $ "Bridged { tagsList . Count } tags from meter '{ name } '") ;
1035+ }
1036+ }
1037+ }
1038+ catch ( Exception ex )
1039+ {
1040+ Log . Debug ( ex , $ "Failed to bridge Tags from meter '{ name } '. Continuing without tags.") ;
1041+ }
1042+ }
1043+
1044+ // Try to get Scope property if it exists (available in DiagnosticSource 8.0+)
1045+ object scope = null ;
1046+ var scopeProperty = meterType . GetProperty ( "Scope" ) ;
1047+ if ( scopeProperty != null )
1048+ {
1049+ try
1050+ {
1051+ scope = scopeProperty . GetValue ( meter ) ;
1052+ if ( scope != null )
1053+ {
1054+ Log . Finest ( $ "Bridged scope from meter '{ name } ': { scope } ") ;
1055+ }
1056+ }
1057+ catch ( Exception ex )
1058+ {
1059+ Log . Debug ( ex , $ "Failed to bridge Scope from meter '{ name } '. Continuing without scope.") ;
1060+ }
1061+ }
1062+
1063+ // Create bridged meter with available properties
1064+ // Use the 4-parameter constructor if tags or scope are available, otherwise fall back to 2-parameter constructor
1065+ if ( tags != null || scope != null )
1066+ {
1067+ return new Meter ( name , version , tags , scope ) ;
1068+ }
1069+ else
1070+ {
1071+ return new Meter ( name , version ) ;
1072+ }
9851073 }
9861074
9871075 private void OnMeasurementRecorded < T > ( object instrument , T measurement , ReadOnlySpan < KeyValuePair < string , object > > tags , object state )
0 commit comments