Last active
August 2, 2020 17:12
-
-
Save mathis-m/35983c4f617729f8e6ef78f6601cc1a1 to your computer and use it in GitHub Desktop.
Integrate Elastic APMAgent into .Net Core Console Application using the convinient Microsoft.Extensions.DependencyInjection package.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Linq; | |
using System.Reflection; | |
using System.Threading.Tasks; | |
using System.Threading.Tasks.Dataflow; | |
using Elastic.Apm.Api; | |
using Elastic.Apm.Config; | |
using Elastic.Apm.DiagnosticSource; | |
using Elastic.Apm.Logging; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Logging; | |
namespace Elastic.Apm.Extensions.DependencyInjection | |
{ | |
public static class ElasticApmDependencyInjectionExtension | |
{ | |
// Integration into IServiceCollection. Entrypoint to integrate APM Agent Dependencies into Microsoft.Extensions.DependencyInjection infrastracture | |
public static IServiceCollection AddElasticApm(this IServiceCollection services, IConfiguration configuration, string env, params IDiagnosticsSubscriber[] subscribers) | |
{ | |
services.AddSingleton<IApmLogger>(sp => sp.GetApmLogger()); | |
services.AddSingleton<IConfigurationReader>(sp => | |
CreateInstanceOfInternalClass("Elastic.Apm.Extensions.Hosting", "Elastic.Apm.Extensions.Hosting.Config", "MicrosoftExtensionsConfig", new object[] | |
{ | |
configuration, | |
sp.GetService<IApmLogger>(), | |
env | |
}) as IConfigurationReader); | |
services.AddSingleton<AgentComponents>(sp => | |
{ | |
var components = new AgentComponents(sp.GetService<IApmLogger>(), sp.GetService<IConfigurationReader>()); | |
components.Service.Framework = new Framework { Name = ".NET Core", Version = System.Environment.Version.ToString(3) }; | |
components.Service.Language = new Language { Name = "C#" }; | |
return components; | |
}); | |
services.AddSingleton<IApmAgent>(sp => | |
{ | |
var agentConfig = sp.GetService<AgentComponents>(); | |
var agent = (IApmAgent)CreateInstanceOfInternalClass("Elastic.Apm", "Elastic.Apm", "ApmAgent", new object[] { agentConfig }); | |
agent.Subscribe(subscribers.ToArray()); | |
return agent; | |
}); | |
services.AddSingleton<ITracer>(sp => sp.GetRequiredService<IApmAgent>().Tracer); | |
return services; | |
} | |
public static async Task DoFinalWorkLoopIteration(this IApmAgent agent) | |
{ | |
var payloadSenderV2Type = GetInternalType("Elastic.Apm", "Elastic.Apm.Report", "PayloadSenderV2"); | |
if (agent.PayloadSender.GetType() == payloadSenderV2Type) | |
{ | |
BatchBlock<object> _eventQueue = payloadSenderV2Type | |
.GetField("_eventQueue", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(agent.PayloadSender) as BatchBlock<object>; | |
Task processQueueItems = payloadSenderV2Type | |
.GetMethod("ProcessQueueItems", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance).Invoke(agent.PayloadSender, new object[] { ReceiveAll(agent) }) as Task; | |
await processQueueItems; | |
} | |
} | |
private static object[] ReceiveAll(IApmAgent agent) | |
{ | |
var payloadSenderV2Type = GetInternalType("Elastic.Apm", "Elastic.Apm.Report", "PayloadSenderV2"); | |
if (agent.PayloadSender.GetType() == payloadSenderV2Type) | |
{ | |
BatchBlock<object> _eventQueue = payloadSenderV2Type | |
.GetField("_eventQueue", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(agent.PayloadSender) as BatchBlock<object>; | |
_eventQueue.TryReceiveAll(out var eventBatchToSend); | |
return eventBatchToSend.SelectMany(batch => batch).ToArray(); | |
} | |
return new object[] { }; | |
} | |
public static IApmLogger GetApmLogger(this IServiceProvider serviceProvider) | |
{ | |
return serviceProvider.GetService(typeof(ILoggerFactory)) is ILoggerFactory loggerFactory | |
? (IApmLogger)CreateInstanceOfInternalClass("Elastic.Apm.Extensions.Hosting", "Elastic.Apm.Extensions.Hosting", "NetCoreLogger", new object[] { loggerFactory }) | |
: (IApmLogger)GetStaticInternalProperty("Elastic.Apm", "Elastic.Apm.Logging", "ConsoleLogger", "Instance"); | |
} | |
private static Type GetInternalType(string assemblyName, string classNamespace, string className) | |
{ | |
Assembly assembly = Assembly.Load(assemblyName); | |
var type = assembly.GetType($"{classNamespace}.{className}"); | |
return type; | |
} | |
private static object GetStaticInternalProperty(string assemblyName, string classNamespace, string className, string propertyName) | |
{ | |
return GetInternalType(assemblyName, classNamespace, className) | |
.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Static) | |
.GetValue(null, null); | |
} | |
private static object CreateInstanceOfInternalClass(string assemblyName, string classNamespace, string className, object[] ctorArgs) | |
{ | |
Assembly assembly = Assembly.Load(assemblyName); | |
return assembly.CreateInstance($"{classNamespace}.{className}", false, | |
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, | |
null, ctorArgs, null, null); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
// Local or Deployed | |
#if DEBUG | |
var env = "Development"; | |
#else | |
var env = "Production"; | |
#endif | |
// Build IConfiguration | |
var config = new ConfigurationBuilder() | |
.SetBasePath(Directory.GetCurrentDirectory()) | |
.AddJsonFile("appsettings.json", true, true) | |
.AddJsonFile($"appsettings.{env}.json", optional: true) | |
.Build(); | |
// Setup DI | |
var serviceProvider = new ServiceCollection() | |
.AddSingleton<IConfiguration>(config) | |
.AddSingleton<ExampleService>() | |
.AddElasticApm(config, env, new HttpDiagnosticsSubscriber(), new EfCoreDiagnosticsSubscriber()) | |
.BuildServiceProvider(); | |
var logger = serviceProvider | |
.GetService<ILoggerFactory>() | |
.CreateLogger<Program>(); | |
var tracer = serviceProvider.GetService<ITracer>(); | |
// Main transaction | |
tracer.CaptureTransaction("ConsoleApplication run", "background", /*not async else transaction may not be queued befor WaitForFlush is called.*/ transaction => | |
{ | |
var service = serviceProvider.GetService<ExampleService>(); | |
try | |
{ | |
// Do some work | |
service.DoStuffAsync().Wait(); | |
} | |
catch (Exception e) | |
{ | |
logger.LogCritical("Unhandled Exception!", e); | |
transaction.CaptureException(e); | |
throw e; | |
} | |
}).Wait(); | |
// Wait for apm agent to flush event queue. | |
var agent = serviceProvider.GetService<IApmAgent>(); | |
agent | |
.DoFinalWorkLoopIteration() | |
.Wait(); | |
logger.LogDebug("Finished execution.!"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment