Skip to content

Commit

Permalink
Add diagnostics, file logging and ILogger integration (#34)
Browse files Browse the repository at this point in the history
* Add diagnostics, file logging and ILogger integration

* Honour configured logging level

* More initial logging

* Cleanup whitespace

* Fix encoding

* Refine DI integration and log messages
  • Loading branch information
stevejgordon authored Feb 13, 2024
1 parent 86cf85e commit 592036b
Show file tree
Hide file tree
Showing 24 changed files with 1,223 additions and 151 deletions.
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<UseArtifactsOutput>true</UseArtifactsOutput>
<SolutionRoot>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), build.bat))</SolutionRoot>
<ArtifactsPath>$(MSBuildThisFileDirectory).artifacts</ArtifactsPath>
<ExposedPublicKey>002400000480000094000000060200000024000052534131000400000100010015b0fa59d868c7f3ea2ae67567b19e102465745f01b430a38a42b92fd41a0f5869bec1f2b33b589d78662af432fe6b789ef72d4738f7b1a86264d7aeb5185ed8995b2bb104e7c5c58845f1a618be829e410fa34a6bd7d714ece191ed68a66333a83ae7456ee32e9aeb54bc1d7410ae8c344367257e9001abb5e96ce1f1d97696</ExposedPublicKey>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MinVer" Version="4.3.0" PrivateAssets="all" />
Expand Down
5 changes: 3 additions & 2 deletions examples/Example.Elastic.OpenTelemetry.Worker/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
using Example.Elastic.OpenTelemetry.Worker;

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

builder.Services.AddElasticOpenTelemetry("CustomActivitySource");
builder.EnableElasticOpenTelemetry("CustomActivitySource");

builder.Services.AddHostedService<Worker>();

var host = builder.Build();
host.Run();
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
"Microsoft.Hosting.Lifetime": "Information",
"Elastic": "Trace"
}
}
}
218 changes: 133 additions & 85 deletions src/Elastic.OpenTelemetry/Agent.cs
Original file line number Diff line number Diff line change
@@ -1,97 +1,145 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information
using OpenTelemetry;
using System.Reflection;
using Microsoft.Extensions.Logging;

using static Elastic.OpenTelemetry.Diagnostics.ElasticOpenTelemetryDiagnosticSource;

namespace Elastic.OpenTelemetry;

/// <summary>
/// Supports building and accessing an <see cref="IAgent"/> which collects and ships observability signals.
/// </summary>
public static class Agent
public static partial class Agent
{
private static readonly object Lock = new();
private static IAgent? _current;

static Agent()
{
var assemblyInformationalVersion = typeof(Agent).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
InformationalVersion = ParseAssemblyInformationalVersion(assemblyInformationalVersion);
}

/// <summary>
/// Returns the singleton <see cref="IAgent"/> instance.
/// </summary>
/// <remarks>
/// If an instance is not already initialized, this will create and return a
/// default <see cref="IAgent"/> configured with recommended Elastic defaults.
/// </remarks>
public static IAgent Current
{
get
{
if (_current is not null) return _current;

lock (Lock)
{
// disable to satisfy double check lock pattern analyzer
// ReSharper disable once InvertIf
if (_current is null)
{
var agent = new AgentBuilder().Build();
_current = agent;
}
return _current;
}
}
}

internal static string InformationalVersion { get; }

/// <summary>
/// Builds an <see cref="IAgent"/>.
/// </summary>
/// <returns>An <see cref="IAgent"/> instance.</returns>
/// <exception cref="Exception">
/// An exception will be thrown if <see cref="Build"/>
/// is called more than once during the lifetime of an application.
/// </exception>
public static IAgent Build(Action<AgentBuilder>? configuration = null)
{
if (_current != null)
throw new Exception($"{nameof(Agent)}.{nameof(Build)} called twice or after {nameof(Agent)}.{nameof(Current)} was accessed.");

lock (Lock)
{
if (_current != null)
throw new Exception($"{nameof(Agent)}.{nameof(Build)} called twice or after {nameof(Agent)}.{nameof(Current)} was accessed.");

var agentBuilder = new AgentBuilder();
configuration?.Invoke(agentBuilder);
var agent = agentBuilder.Build();
_current = agent;
return _current;
}
}

internal static string ParseAssemblyInformationalVersion(string? informationalVersion)
{
if (string.IsNullOrWhiteSpace(informationalVersion))
{
informationalVersion = "1.0.0";
}

/*
* InformationalVersion will be in the following format:
* {majorVersion}.{minorVersion}.{patchVersion}.{pre-release label}.{pre-release version}.{gitHeight}+{Git SHA of current commit}
* Ex: 1.5.0-alpha.1.40+807f703e1b4d9874a92bd86d9f2d4ebe5b5d52e4
* The following parts are optional: pre-release label, pre-release version, git height, Git SHA of current commit
*/

var indexOfPlusSign = informationalVersion!.IndexOf('+');
return indexOfPlusSign > 0
? informationalVersion[..indexOfPlusSign]
: informationalVersion;
}
private static readonly object Lock = new();
private static IAgent? CurrentAgent;

static Agent()
{
var assemblyInformationalVersion = typeof(Agent).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
InformationalVersion = ParseAssemblyInformationalVersion(assemblyInformationalVersion);
}

/// <summary>
/// Returns the singleton <see cref="IAgent"/> instance.
/// </summary>
/// <remarks>
/// If an instance is not already initialized, this will create and return a
/// default <see cref="IAgent"/> configured with recommended Elastic defaults.
/// </remarks>
public static IAgent Current
{
get
{
if (CurrentAgent is not null)
return CurrentAgent;

lock (Lock)
{
// disable to satisfy double check lock pattern analyzer
// ReSharper disable once InvertIf
if (CurrentAgent is null)
{
var agent = new AgentBuilder().Build();
CurrentAgent = agent;
}
return CurrentAgent;
}
}
}

internal static string InformationalVersion { get; }

/// <summary>
/// Builds an <see cref="IAgent"/>.
/// </summary>
/// <returns>An <see cref="IAgent"/> instance.</returns>
/// <exception cref="Exception">
/// An exception will be thrown if <see cref="Build"/>
/// is called more than once during the lifetime of an application.
/// </exception>
public static IAgent Build(Action<AgentBuilder>? configuration = null)
{
CheckCurrent();

lock (Lock)
{
CheckCurrent();
var agentBuilder = new AgentBuilder();
configuration?.Invoke(agentBuilder);
var agent = agentBuilder.Build();
CurrentAgent = agent;
return CurrentAgent;
}

static void CheckCurrent()
{
if (CurrentAgent is not null)
{
Log(AgentBuildCalledMultipleTimesEvent);
throw new Exception();
}
}
}

internal const string BuildErrorMessage = $"{nameof(Agent)}.{nameof(Build)} called twice or after " +
$"{nameof(Agent)}.{nameof(Current)} was accessed.";

internal const string SetAgentErrorMessage = $"{nameof(Agent)}.{nameof(SetAgent)} called twice" +
$"or after {nameof(Agent)}.{nameof(Build)} or after {nameof(Agent)}.{nameof(Current)} was accessed.";

[LoggerMessage(EventId = 1, Level = LogLevel.Error, Message = SetAgentErrorMessage)]
internal static partial void SetAgentError(this ILogger logger);

/// <summary>
/// TODO
/// </summary>
/// <param name="agent"></param>
/// <param name="logger"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
internal static IAgent SetAgent(IAgent agent, ILogger logger)
{
CheckCurrent(logger);

lock (Lock)
{
CheckCurrent(logger);
logger.LogInformation($"Setting {nameof(CurrentAgent)}.");
CurrentAgent = agent;
return CurrentAgent;
}

static void CheckCurrent(ILogger logger)
{
if (CurrentAgent is not null)
{
Log(AgentSetAgentCalledMultipleTimesEvent);
logger.SetAgentError();
throw new Exception(SetAgentErrorMessage);
}
}
}

internal static string ParseAssemblyInformationalVersion(string? informationalVersion)
{
if (string.IsNullOrWhiteSpace(informationalVersion))
{
informationalVersion = "1.0.0";
}

/*
* InformationalVersion will be in the following format:
* {majorVersion}.{minorVersion}.{patchVersion}.{pre-release label}.{pre-release version}.{gitHeight}+{Git SHA of current commit}
* Ex: 1.5.0-alpha.1.40+807f703e1b4d9874a92bd86d9f2d4ebe5b5d52e4
* The following parts are optional: pre-release label, pre-release version, git height, Git SHA of current commit
*/

var indexOfPlusSign = informationalVersion!.IndexOf('+');
return indexOfPlusSign > 0
? informationalVersion[..indexOfPlusSign]
: informationalVersion;
}
}
Loading

0 comments on commit 592036b

Please sign in to comment.