Prerequisites : Having Jaeger / Zipkin for visualization . The easy way for installing Jaeger is to have Jaeger all in one ( either via Docker, either run on PC)
Step 1: Modify the csproj to add this
<ItemGroup>
<PackageReference Include="OpenTelemetry" Version="1.1.0-beta1" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc3" />
<PackageReference Include="OpenTelemetry.Exporter.Jaeger" Version="1.1.0-beta1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc3" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc3" />
<PackageReference Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.0.0-rc3" />
<AdditionalFiles Include="../AutoMethod.txt" />
<PackageReference Include="AOPMethodsCommon" Version="2021.5.17.2230" />
<PackageReference Include="AOPMethodsGenerator" Version="2021.5.17.2230" />
</ItemGroup>
Optional : To see the RSCG files, add also
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
Step 2:
2.1 If ASP.NET Core app, add the following to Startup.cs
services.AddOpenTelemetryTracing(b =>
{
string nameAssemblyEntry = Assembly.GetEntryAssembly().GetName().Name;
var rb = ResourceBuilder.CreateDefault();
rb.AddService(nameAssemblyEntry);
var data = new Dictionary<string, object>() {
{ "PC", Environment.MachineName } };
data.Add("Exe", nameAssemblyEntry);
rb.AddAttributes(data);
_ = b
.AddAspNetCoreInstrumentation(opt=>
{
opt.RecordException = true;
})
.AddHttpClientInstrumentation(opt=>
{
opt.SetHttpFlavor = true;
})
.AddSqlClientInstrumentation(opt=>
{
opt.SetDbStatementForStoredProcedure = true;
opt.SetDbStatementForText = true;
opt.RecordException = true;
opt.EnableConnectionLevelAttributes = true;
})
.AddSource("MySource")
.SetResourceBuilder(rb)
.AddJaegerExporter(c =>
{
//var s = Configuration.GetSection("Jaeger");
//s.Bind(c);
c.AgentHost = "localhost";//TODO: put the name of the PC with Jaeger
});
})
;
2.2 If .NET Core executable , add the following code to main.cs
string nameAssemblyEntry = Assembly.GetEntryAssembly().GetName().Name;
var rb = ResourceBuilder.CreateDefault();
rb.AddService(nameAssemblyEntry);
var data = new Dictionary<string, object>() {
{ "PC", Environment.MachineName } };
data.Add("Exe", nameAssemblyEntry);
rb.AddAttributes(data);
openTelemetry = Sdk.CreateTracerProviderBuilder()
.AddSource("MySource")
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.SetResourceBuilder(rb)
.AddJaegerExporter(o =>
{
//var s = Configuration.GetSection("Jaeger");
//s.Bind(c);
c.AgentHost = "localhost";//TODO: put the name of the PC with Jaeger
})
.Build();
Step 3: Create near the .sln the file AutoMetods.ps1 with the following contents
function ReplaceWithAuto([string]$m )
{
$m=$m.Trim();
$firstpart= $method
$secondPart =""
#Write-Host $method
# do it fast now. Find first ( , name method is before that
#( could be done better: find from the last : add 1when found ( , add -1 when found ), stop at 0 )
# find first space before
$nr = ([regex]::Matches($m, [regex]::Escape(")") )).count
if($nr -gt 1){
$characters = [char[]]$method;
$nrOpen=0;
$iterator= $characters.Length;
do{
$iterator--;
if($characters[$iterator] -eq '('){
$nrOpen++;
}
if($characters[$iterator] -eq ')'){
$nrOpen--;
}
}while(($iterator -gt 0) -and ($nrOpen -ne 0))
$firstpart = $m.Substring(0,$iterator).Trim()
#return $firstpart
$spaceArr= $firstpart.Split(' ');
$firstpart =$spaceArr[$spaceArr.Length-1]
#return $firstpart
}
$arr = $firstpart.Split("(");
#Write-Host $arr[0]
$splitSpace = $arr[0].Split(" ")
$nameMethod = $splitSpace[$splitSpace.Length-1]
#Write-Host $nameMethod
[regex]$pattern = $nameMethod
#$method= ($method -replace [regex]::Escape($nameMethod) ,("auto"+$nameMethod))
$method= $pattern.Replace($method,("auto"+$nameMethod),1)
#Write-Host $method
return $method
}
function VerifyFolder()
{
Write-Host "starting"
$fileName = Get-ChildItem "*.cs" -Recurse
#$varItem = [regex]::Escape( "AOPMarkerMethod")
$varItem = "AOPMarkerMethod"
$filename | %{
$fileContent = gc $_
$x= ($fileContent -imatch "AOPMarkerMethod" )
#Write-Host $x.Length
if($x.Length -gt 0){
Write-Host $_.fullname
$LineNumbers =@( Select-String $_ -Pattern $varItem| Select-Object -ExpandProperty 'LineNumber')
Write-Host "found " $LineNumbers
Foreach ($LineNumber in $LineNumbers){
$method = $fileContent[$LineNumber]
Write-Host "Line where string is found: "$LineNumber
Write-Host $method
#Write-Host ReplaceWithAuto $method
$fileContent[$LineNumber] = ReplaceWithAuto($method)
Write-Host $fileContent[$LineNumber]
}
$fileContent |Set-Content $_.fullname
}
#(gc $_) -replace $varItem,"auto" |Set-Content $_.fullname
}
}
VerifyFolder
Step 4 : Create near the .sln Automethod.txt with the following content:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.CodeDom.Compiler;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Linq;
using OpenTelemetry.Trace;
namespace {{NamespaceName}} {
[GeneratedCode("AOPMethods", "{{version}}")]
[CompilerGenerated]
public partial class {{ClassName}}{
public static readonly ActivitySource MyActivitySource = new ActivitySource("MySource");
{{~ for mi in Methods ~}}
//{{mi.Name}}
{{
strAwait = ""
strAsync =""
if mi.CouldUseAsync == true
strAwait = " await "
strAsync = " async "
end
separator = ""
if(mi.NrParameters > 0)
separator = ","
end
returnString = ""
if mi.CouldReturnVoidFromAsync == false
returnString = " return "
end
}}
public {{strAsync}} {{mi.ReturnType}} {{mi.NewName}} ({{mi.parametersDefinitionCSharp }}
{{separator}}
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0){
var sw=Stopwatch.StartNew();
using var span = MyActivitySource.StartActivity("{{mi.NewName}}", ActivityKind.Producer);
span.Start();
//parameters
{{~ for miArg in mi.Parameters ~}}
var val{{ miArg.Key }} = System.Text.Json.JsonSerializer.Serialize({{ miArg.Key }});
span.SetTag("Argument_{{ miArg.Key }}",val{{ miArg.Key }});
{{ end}}
span.SetTag("method","{{mi.NewName}}");
span.SetTag("called from ",memberName);
span.SetTag("called from file",sourceFilePath);
span.SetTag("called from line",sourceLineNumber);
var utcTime =System.DateTime.UtcNow;
span.SetTag("UTCDate",utcTime.ToString("yyyyMMddHHmmss"));
var userEmail = ((System.Security.Claims.ClaimsPrincipal)System.Threading.Thread.CurrentPrincipal)?.Claims?.Where(c => c.Type == "email")?.SingleOrDefault()?.Value;
userEmail ??= "NoUserConfigured";
span.SetTag("user",userEmail);
span.SetStatus(Status.Ok);
try{
Console.WriteLine("--{{mi.Name}} start ");
{{returnString}} {{ strAwait }} {{mi.Name}}({{ mi.parametersCallCSharp }});
}
catch(Exception ex){
Console.WriteLine("--{{mi.Name}} exception ");
span?.RecordException(ex);
span.SetStatus(Status.Error);
throw;
}
finally{
Console.WriteLine("--{{mi.Name}} finally ");
span.SetTag("ElapsedMilliseconds",sw.Elapsed.TotalMilliseconds);
span.Stop();
//span.Duration= sw.Elapsed.TotalMilliseconds;
}
}//end {{mi.NewName}}
{{ end}}
}
}
Step 5: Decorate your method with [AOPMarkerMethod]Â ( namespace AOPMethodsCommon )
Something like this
[AOPMarkerMethod]
private NewCountry[] ArrangeCountriesSearch(NewCountry[] data)
And in the class definition
[AutoMethods(template = TemplateMethod.CustomTemplateFile,CustomTemplateFileName ="Automethod.txt", MethodSuffix = "bup")]
Step 6: Modify your CI to run
pwsh AutoMetods.ps1
or
powershell AutoMetods.ps1
Enjoy, that will be all !