Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for Elastic.Apm.Agent instantiation #63

Merged
merged 7 commits into from
Jan 14, 2019
7 changes: 3 additions & 4 deletions sample/SampleAspNetCoreApp/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Elastic.Apm.AspNetCore;
using Elastic.Apm;
using Elastic.Apm.AspNetCore;
using Elastic.Apm.DiagnosticSource;
using Elastic.Apm.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
Expand Down Expand Up @@ -38,9 +39,7 @@ public void ConfigureServices(IServiceCollection services)
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseElasticApm(Configuration);
new ElasticCoreListeners().Start();
new ElasticEntityFrameworkCoreListener().Start();
app.UseElasticApm(Configuration, new HttpDiagnosticsSubscriber(), new EfCoreDiagnosticsSubscriber());

if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
Expand Down
23 changes: 7 additions & 16 deletions src/Elastic.Apm.AspNetCore/ApmMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Elastic.Apm.Api;
using Elastic.Apm.AspNetCore.Config;
using Elastic.Apm.Config;
using Elastic.Apm.Helpers;
using Elastic.Apm.Model.Payload;
using Microsoft.AspNetCore.Http;
Expand All @@ -19,29 +22,17 @@ namespace Elastic.Apm.AspNetCore
public class ApmMiddleware
{
private readonly RequestDelegate _next;
private readonly ITracer _tracer;

public ApmMiddleware(RequestDelegate next)
public ApmMiddleware(RequestDelegate next, ITracer tracer)
{
_next = next;

var service = new Service
{
Agent = new Service.AgentC
{
Name = Consts.AgentName,
Version = Consts.AgentVersion
},
Name = Assembly.GetEntryAssembly()?.GetName().Name,
Framework = new Framework { Name = "ASP.NET Core", Version = "2.1" }, //TODO: Get version
Language = new Language { Name = "C#" } //TODO
};

Agent.Tracer.Service = service;
_tracer = tracer;
}

public async Task InvokeAsync(HttpContext context)
{
var transaction = Agent.Tracer.StartTransaction($"{context.Request.Method} {context.Request.Path}",
var transaction = _tracer.StartTransaction($"{context.Request.Method} {context.Request.Path}",
Transaction.TypeRequest);

transaction.Context = new Context
Expand Down
44 changes: 34 additions & 10 deletions src/Elastic.Apm.AspNetCore/ApmMiddlewareExtension.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Reflection;
using Elastic.Apm.AspNetCore.Config;
using Elastic.Apm.AspNetCore.DiagnosticListener;
using Elastic.Apm.DiagnosticSource;
using Elastic.Apm.Report;
using Elastic.Apm.Model.Payload;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;

Expand All @@ -20,18 +22,40 @@ public static class ApmMiddlewareExtension
/// doing this the agent will read agent related configurations through this IConfiguration instance.
/// </param>
/// <param name="payloadSender">Payload sender.</param>
/// <param name="subscribers">Specify which diagnostic source subscribers you want to connect</param>
public static IApplicationBuilder UseElasticApm(
this IApplicationBuilder builder, IConfiguration configuration = null, IPayloadSender payloadSender = null
this IApplicationBuilder builder,
IConfiguration configuration = null,
params IDiagnosticsSubscriber[] subscribers
gregkalapos marked this conversation as resolved.
Show resolved Hide resolved
)
{
if (configuration != null) Agent.Config = new MicrosoftExtensionsConfig(configuration);

if (payloadSender != null) Agent.PayloadSender = payloadSender;

System.Diagnostics.DiagnosticListener.AllListeners
.Subscribe(new DiagnosticInitializer(new List<IDiagnosticListener> { new AspNetCoreDiagnosticListener() }));
var service = new Service
{
Agent = new Service.AgentC
{
Name = Consts.AgentName,
Version = Consts.AgentVersion
},
Name = Assembly.GetEntryAssembly()?.GetName().Name,
Framework = new Framework { Name = "ASP.NET Core", Version = "2.1" }, //TODO: Get version
Language = new Language { Name = "C#" } //TODO
};
var configReader = configuration != null ? new MicrosoftExtensionsConfig(configuration) : null;
var config = new AgentComponents(configurationReader: configReader, service: service);
Agent.Setup(config);
return UseElasticApm(builder, Agent.Instance, subscribers);
}

return builder.UseMiddleware<ApmMiddleware>();
internal static IApplicationBuilder UseElasticApm(
this IApplicationBuilder builder,
ApmAgent agent,
params IDiagnosticsSubscriber[] subscribers
)
{
var subs = new List<IDiagnosticsSubscriber>(subscribers ?? Array.Empty<IDiagnosticsSubscriber>());
subs.Add(new AspNetCoreDiagnosticsSubscriber());
agent.Subscribe(subs.ToArray());
return builder.UseMiddleware<ApmMiddleware>(agent.Tracer);
}
}
}
71 changes: 41 additions & 30 deletions src/Elastic.Apm.AspNetCore/Config/MicrosoftExtensionsConfig.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
using Elastic.Apm.Config;
using System;
using System.Collections.Generic;
using Elastic.Apm.Config;
using Elastic.Apm.Logging;
using Elastic.Apm.Model.Payload;
using Elastic.Apm.Report;
using Microsoft.Extensions.Configuration;

namespace Elastic.Apm.AspNetCore.Config
Expand All @@ -7,54 +12,60 @@ namespace Elastic.Apm.AspNetCore.Config
/// An agent-config provider based on Microsoft.Extensions.Configuration.IConfiguration.
/// It uses environment variables as fallback
/// </summary>
public class MicrosoftExtensionsConfig : AbstractAgentConfig
public class MicrosoftExtensionsConfig : AbstractConfigurationReader, IConfigurationReader
{
private readonly IConfiguration _configuration;

public MicrosoftExtensionsConfig(IConfiguration configuration)
internal const string Origin = "Configuration Provider";

public static (string LevelSubKey, string Level, string Urls) Keys = (
LevelSubKey: "LogLevel",
Level: $"ElasticApm:LogLevel",
Urls: "ElasticApm:ServerUrls"
);

public MicrosoftExtensionsConfig(IConfiguration configuration, AbstractLogger logger = null) : base(logger)
{
_configuration = configuration;
_configuration.GetSection("ElasticApm")
?
_configuration.GetSection("ElasticApm")?
.GetReloadToken()
.RegisterChangeCallback(ChangeCallback, configuration.GetSection("ElasticApm"));
}


protected override (string value, string configType, string configKey) ReadServerUrls()
private LogLevel? _logLevel;
public LogLevel LogLevel
{
var configValue = _configuration[MicrosoftExtensionConfigConsts.ServerUrls];
return string.IsNullOrEmpty(configValue)
? (_configuration[EnvVarConsts.ServerUrls], "environment variable", EnvVarConsts.ServerUrls)
: (configValue, "IConfiguration", MicrosoftExtensionConfigConsts.ServerUrls);
get
{
if (_logLevel.HasValue) return _logLevel.Value;

var l = ParseLogLevel(ReadFallBack(Keys.Level, EnvironmentConfigurationReader.Keys.Level));
_logLevel = l;
return l;
}
}

protected override (string value, string configType, string configKey) ReadLogLevel()
public IReadOnlyList<Uri> ServerUrls => ParseServerUrls(ReadFallBack(Keys.Urls, EnvironmentConfigurationReader.Keys.Urls));

private ConfigurationKeyValue Read(string key) => Kv(key, _configuration[key], Origin);

private ConfigurationKeyValue ReadFallBack(string key, string fallBack)
{
var configValue = _configuration[MicrosoftExtensionConfigConsts.LogLevel];
return string.IsNullOrEmpty(configValue)
? (_configuration[EnvVarConsts.LogLevel], "environment variable", EnvVarConsts.LogLevel)
: (configValue, "IConfiguration", MicrosoftExtensionConfigConsts.LogLevel);
var primary = Read(key);
if (!string.IsNullOrWhiteSpace(primary.Value)) return primary;
var secondary = Kv(key, _configuration[fallBack], EnvironmentConfigurationReader.Origin);
return secondary;
}

private void ChangeCallback(object obj)
{
var (newLogLevel, isError)
= ParseLogLevel((obj as IConfigurationSection)?[MicrosoftExtensionConfigConsts.LogLevel.Split(':')[1]]);
if (!(obj is IConfigurationSection section)) return;

if (!isError && newLogLevel.HasValue && newLogLevel.Value != LogLevelBackingField)
{
LogLevelBackingField = newLogLevel;
Logger?.LogInfo($"Updated log level to {LogLevelBackingField.ToString()}");
}
var newLogLevel = ParseLogLevel(Kv(Keys.LevelSubKey, section[Keys.LevelSubKey], Origin));
if (_logLevel.HasValue && newLogLevel == _logLevel.Value) return;

if (isError) Logger?.LogInfo($"Updating log level failed, current log level: {LogLevelBackingField.ToString()}");
_logLevel = newLogLevel;
Logger?.LogInfo($"Updated log level to {newLogLevel}");
}
}

internal static class MicrosoftExtensionConfigConsts
{
public static string LogLevel => "ElasticApm:LogLevel";
public static string ServerUrls => "ElasticApm:ServerUrls";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ public class AspNetCoreDiagnosticListener : IDiagnosticListener
{
private readonly AbstractLogger _logger;

public AspNetCoreDiagnosticListener()
=> _logger = Agent.CreateLogger(Name);
public AspNetCoreDiagnosticListener(IApmAgent agent) => _logger = agent.Logger;

public string Name => "Microsoft.AspNetCore";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Elastic.Apm.Config;
using Elastic.Apm.DiagnosticListeners;
using Elastic.Apm.DiagnosticSource;
using Elastic.Apm.Logging;

namespace Elastic.Apm.AspNetCore.DiagnosticListener
{
/// <summary>
/// Manages all listeners that are generated by Types which are part of .netstandard 2.0
/// </summary>
public class AspNetCoreDiagnosticsSubscriber : IDiagnosticsSubscriber
{
/// <summary>
/// Start listening for diagnosticsource events. Only listens for sources that are part of the Agent.Core package.
/// </summary>
public IDisposable Subscribe(IApmAgent agent) => System.Diagnostics.DiagnosticListener
.AllListeners
.Subscribe(new DiagnosticInitializer(new [] { new AspNetCoreDiagnosticListener(agent), }));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Data;
using Elastic.Apm.Api;
using Elastic.Apm.DiagnosticSource;
using Elastic.Apm.Logging;
using Elastic.Apm.Model.Payload;
using Microsoft.EntityFrameworkCore.Diagnostics;

Expand All @@ -12,6 +13,11 @@ namespace Elastic.Apm.EntityFrameworkCore
internal class EfCoreDiagnosticListener : IDiagnosticListener
{
private readonly ConcurrentDictionary<Guid, ISpan> _spans = new ConcurrentDictionary<Guid, ISpan>();

public EfCoreDiagnosticListener(IApmAgent agent) => Logger = agent.Logger;

private AbstractLogger Logger { get; }

public string Name => "Microsoft.EntityFrameworkCore";

public void OnCompleted() { }
Expand Down
22 changes: 22 additions & 0 deletions src/Elastic.Apm.EntityFrameworkCore/EfCoreDiagnosticsSubscriber.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Elastic.Apm.Config;
using Elastic.Apm.DiagnosticSource;
using Elastic.Apm.Logging;

namespace Elastic.Apm.EntityFrameworkCore
{
/// <summary>
/// Manages the Entity Framework Core listener, which listenes to EF Core events
/// </summary>
public class EfCoreDiagnosticsSubscriber : IDiagnosticsSubscriber
{
/// <summary>
/// Start listening for EF Core diagnosticsource events
/// </summary>
public IDisposable Subscribe(IApmAgent agentComponents) => DiagnosticListener
.AllListeners
.Subscribe(new DiagnosticInitializer(new [] { new EfCoreDiagnosticListener(agentComponents) }));
}
}

This file was deleted.

Loading