In the previous step, the tracing applied to every build until you deleted the fabric. Sometimes you want to keep the fabric in the source code, for instance to investigate the same problem again later, but you do not want the tracing in the builds that you release.
In this step, the fabric registers the interceptors only when the TraceCalls MSBuild property is True. In any
other build, the fabric does nothing, and the calls to InventoryClient are not rewritten.
Enabling the tracing
To build and run the program with the tracing, set the property on the command line:
dotnet run --property:TraceCalls=True
The program then writes the same trace as in the previous step:
[trace] OrderService.CanOrder -> InventoryClient.GetStock(sku: BOOK) returned 10 in 1.371 ms.
[trace] OrderService.PlaceOrder -> InventoryClient.Reserve(sku: BOOK, quantity: 3) succeeded in 0.242 ms.
[trace] OrderService.PlaceOrder -> InventoryClient.Reserve(sku: PEN, quantity: 5) threw InvalidOperationException in 0.600 ms: Only 2 items of 'PEN' are in stock.
When you run dotnet run without the property, the program writes nothing.
Implementation
The fabric reads the MSBuild property with the TryGetProperty method of amender.Project, and returns before it
registers the interceptors when the property is missing or is not True:
1using Metalama.Extensions.Interceptors;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using Metalama.Framework.Fabrics;
5using Metalama.Samples.CallTracing.Inventory;
6using System.Diagnostics;
7
8namespace Metalama.Samples.CallTracing2;
9
10// Traces the calls to InventoryClient when the TraceCalls MSBuild property is True.
11internal class TracingFabric : ProjectFabric
12{
13 public override void AmendProject( IProjectAmender amender )
14 {
15 // Register the interceptors only when the build sets the TraceCalls property.
16 if ( !amender.Project.TryGetProperty( "TraceCalls", out var traceCalls )
17 || !bool.TryParse( traceCalls, out var isEnabled ) || !isEnabled )
18 {
19 return;
20 }
21
22 amender.InterceptMethods(
23 calls => calls
24 .Type( typeof(InventoryClient) )
25 .AllMethods()
26 .RedirectToSynthesizedMethod( nameof(this.Trace) )
27 .WithGranularity( InterceptorGranularity.Member ) );
28 }
29
30 [Template]
31 public dynamic? Trace()
32 {
33 var interceptedMethod = meta.Interception.Method.InterceptedMethod;
34
35 // Build the description of the call: the caller, the called method, and the arguments.
36 var arguments = new List<string>();
37
38 foreach ( var parameter in meta.Interception.Method.ArgumentParameters )
39 {
40 arguments.Add( $"{parameter.Name}: {parameter.Value}" );
41 }
42
43 var call = $"{meta.Interception.CallingType!.Name}.{meta.Interception.CallingMember!.Name} -> "
44 + $"{interceptedMethod.DeclaringType.Name}.{interceptedMethod.Name}"
45 + $"({string.Join( ", ", arguments )})";
46
47 var stopwatch = Stopwatch.StartNew();
48
49 try
50 {
51 var result = meta.Proceed();
52
53 if ( interceptedMethod.ReturnType.Equals( SpecialType.Void ) )
54 {
55 Console.WriteLine( $"[trace] {call} succeeded in {stopwatch.Elapsed.TotalMilliseconds:F3} ms." );
56 }
57 else
58 {
59 Console.WriteLine(
60 $"[trace] {call} returned {result} in {stopwatch.Elapsed.TotalMilliseconds:F3} ms." );
61 }
62
63 return result;
64 }
65 catch ( Exception e )
66 {
67 Console.WriteLine(
68 $"[trace] {call} threw {e.GetType().Name} in {stopwatch.Elapsed.TotalMilliseconds:F3} ms: {e.Message}" );
69
70 throw;
71 }
72 }
73}
74
A fabric can read only the MSBuild properties that the project declares as CompilerVisibleProperty items. The
project file therefore declares the TraceCalls property:
<ItemGroup>
<CompilerVisibleProperty Include="TraceCalls"/>
</ItemGroup>
The template is the same as in the previous step.
Instead of an MSBuild property, the fabric could test a preprocessor symbol with amender.Project.PreprocessorSymbols,
or the build configuration with amender.Project.Configuration.