Skip to content

Instantly share code, notes, and snippets.

@mathis-m
Last active August 2, 2020 17:12
Show Gist options
  • Save mathis-m/35983c4f617729f8e6ef78f6601cc1a1 to your computer and use it in GitHub Desktop.
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.
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);
}
}
}
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