1+ using System ;
2+ using System . Diagnostics ;
3+ using System . Linq ;
4+ using System . Reflection ;
5+ using HarmonyLib ;
6+ using OpenTelemetry . AutoInstrumentation . Digma . Utils ;
7+
8+ namespace OpenTelemetry . AutoInstrumentation . Digma . Instrumentation ;
9+
10+ public class UserCodeInstrumentation
11+ {
12+ private static readonly ActivitySourceProvider ActivitySourceProvider = new ( ) ;
13+ private static readonly MethodInfo PrefixMethodInfo = typeof ( UserCodeInstrumentation ) . GetMethod ( nameof ( Prefix ) , BindingFlags . Static | BindingFlags . NonPublic ) ;
14+ private static readonly MethodInfo FinalizerMethodInfo = typeof ( UserCodeInstrumentation ) . GetMethod ( nameof ( Finalizer ) , BindingFlags . Static | BindingFlags . NonPublic ) ;
15+
16+ private readonly Harmony _harmony ;
17+ private readonly string [ ] _namespaces ;
18+ private readonly bool _includePrivateMethods ;
19+
20+ public UserCodeInstrumentation ( Harmony harmony )
21+ {
22+ _harmony = harmony ;
23+ var namespacesStr = Environment . GetEnvironmentVariable ( "OTEL_DOTNET_AUTO_NAMESPACES" ) ;
24+ _namespaces = namespacesStr ? . Split ( ',' )
25+ . Select ( x => x . Trim ( ) )
26+ . Where ( x => ! string . IsNullOrWhiteSpace ( x ) )
27+ . ToArray ( )
28+ ?? Array . Empty < string > ( ) ;
29+ _includePrivateMethods = Environment . GetEnvironmentVariable ( "OTEL_DOTNET_AUTO_INCLUDE_PRIVATE_METHODS" )
30+ ? . Equals ( "true" , StringComparison . InvariantCultureIgnoreCase ) == true ;
31+
32+ Logger . LogInfo ( $ "Requested to auto-instrument { _namespaces . Length } namespaces:\n "+
33+ string . Join ( "\n " , _namespaces ) ) ;
34+ }
35+
36+ public void Instrument ( Assembly assembly )
37+ {
38+ var name = assembly . GetName ( ) . Name ;
39+ if ( ShouldInstrumentAssembly ( name ) )
40+ {
41+ var relevantTypes = assembly . GetTypes ( ) . Where ( ShouldInstrumentType ) . ToArray ( ) ;
42+ var methods = relevantTypes
43+ . SelectMany ( t => t
44+ . GetMethods ( BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic )
45+ . Where ( m => ShouldInstrumentMethod ( t , m ) ) )
46+ . ToArray ( ) ;
47+ foreach ( var method in methods )
48+ {
49+ PatchMethod ( method ) ;
50+ }
51+ }
52+ }
53+
54+ private void PatchMethod ( MethodInfo originalMethodInfo )
55+ {
56+ var methodFullName = $ "{ originalMethodInfo . DeclaringType ? . FullName } .{ originalMethodInfo . Name } ";
57+ try
58+ {
59+ var prefix = DoesAlreadyStartActivity ( originalMethodInfo )
60+ ? null
61+ : new HarmonyMethod ( PrefixMethodInfo ) ;
62+
63+ var finalizer = new HarmonyMethod ( FinalizerMethodInfo ) ;
64+
65+ _harmony . Patch ( originalMethodInfo , prefix : prefix , finalizer : finalizer ) ;
66+ Logger . LogInfo ( $ "Patched method { methodFullName } "+ ( prefix == null ? " (partially)" : "" ) ) ;
67+ }
68+ catch ( Exception e )
69+ {
70+ Logger . LogError ( $ "Failed to patch { methodFullName } ", e ) ;
71+ }
72+
73+ }
74+
75+ private bool ShouldInstrumentAssembly ( string assemblyName )
76+ {
77+ return _namespaces . Any ( ns => ns . StartsWith ( assemblyName , StringComparison . OrdinalIgnoreCase ) ||
78+ assemblyName . StartsWith ( ns , StringComparison . OrdinalIgnoreCase ) ) ;
79+ }
80+
81+ private bool ShouldInstrumentType ( Type type )
82+ {
83+ return ! typeof ( Delegate ) . IsAssignableFrom ( type ) &&
84+ ! type . IsGenericType &&
85+ _namespaces . Any ( ns => type . FullName ? . StartsWith ( ns , StringComparison . OrdinalIgnoreCase ) == true ) ;
86+ }
87+
88+ private bool ShouldInstrumentMethod ( Type type , MethodInfo methodInfo )
89+ {
90+ return methodInfo . DeclaringType == type &&
91+ ! methodInfo . IsAbstract &&
92+ ! methodInfo . IsSpecialName && // property accessors and operator overloading methods
93+ ! methodInfo . IsGenericMethod &&
94+ methodInfo . Name != "GetHashCode" &&
95+ methodInfo . Name != "Equals" &&
96+ methodInfo . Name != "ToString" &&
97+ methodInfo . Name != "Deconstruct" &&
98+ methodInfo . Name != "<Clone>$" &&
99+ ( methodInfo . IsPublic || _includePrivateMethods ) ;
100+ }
101+
102+ private bool DoesAlreadyStartActivity ( MethodInfo methodInfo )
103+ {
104+ var instructions = PatchProcessor . GetOriginalInstructions ( methodInfo ) ;
105+ foreach ( var instruction in instructions )
106+ {
107+ if ( instruction . operand is MethodInfo call &&
108+ call . DeclaringType == typeof ( ActivitySource ) &&
109+ call . Name == nameof ( System . Diagnostics . ActivitySource . StartActivity ) )
110+ {
111+ return true ;
112+ }
113+ }
114+
115+ return false ;
116+ }
117+
118+ private static void Prefix ( MethodBase __originalMethod , out Activity __state )
119+ {
120+ var activitySource = ActivitySourceProvider . GetOrCreate ( __originalMethod . DeclaringType ! ) ;
121+ var activity = activitySource . StartActivity ( __originalMethod . Name ) ;
122+ Logger . LogDebug ( $ "Opened Activity: { activity ? . Source . Name } .{ activity ? . OperationName } ") ;
123+ activity ? . SetTag ( DigmaSemanticConventions . ExtendedObservabilityPackage , __originalMethod . DeclaringType ? . Assembly . GetName ( ) . Name ) ;
124+ activity ? . SetCodeTags ( __originalMethod ) ;
125+ __state = activity ;
126+ }
127+
128+ private static void Finalizer ( MethodBase __originalMethod , Activity __state , Exception __exception )
129+ {
130+ var activity = __state ;
131+ if ( activity == null )
132+ return ;
133+
134+ if ( __exception != null )
135+ {
136+ activity . RecordException ( __exception ) ;
137+ activity . SetStatus ( ActivityStatusCode . Error ) ;
138+ }
139+ activity . Dispose ( ) ;
140+ Logger . LogDebug ( $ "Closed Activity: { activity . Source . Name } .{ activity . OperationName } ") ;
141+ }
142+ }
0 commit comments