Skip to content

Commit

Permalink
Rudimentary auto instrumentation integration tests (#120)
Browse files Browse the repository at this point in the history
* Add example instrumentation project and dockerfile

* Very rudimentary plugin tests

* rename to .IntegrationTests

* dotnet format

* add integrate target

* fix build usage missing pattern match

* fix license header

* move to TARGETARCH

* put debug print of archs at the top

* remove copy of netfx dll

* Release => release

* hack to enforce copy

* more debugging why we can copy files from build_plugin on GithubActions

* quote files

* attempt #5 to get a copy from build_plugin to work on Github Action

* attempt #6 to get a copy from build_plugin to work on Github Action

* attempt #7 to get a copy from build_plugin to work on Github Action

* attempt 8 to get a copy from build_plugin to work on Github Action

* attempt 9 to get a copy from build_plugin to work on Github Action

* attempt 10 to get a copy from build_plugin to work on Github Action

* manually add TARGETARCH to TestContainer build

* ensure arg is available in last step in build

* debug message

* remove debug message

* remove debug message

* skip on windows and dockerfile cleanup

* share OTEL_VERSION properly over stages
  • Loading branch information
Mpdreamz committed Jul 10, 2024
1 parent 45fd319 commit ae1c927
Show file tree
Hide file tree
Showing 16 changed files with 337 additions and 15 deletions.
22 changes: 22 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
**/.dockerignore
**/.env
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
14 changes: 14 additions & 0 deletions Elastic.OpenTelemetry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "examples
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.OpenTelemetry.AutoInstrumentationPlugin", "src\Elastic.OpenTelemetry.AutoInstrumentationPlugin\Elastic.OpenTelemetry.AutoInstrumentationPlugin.csproj", "{B61D749B-21E5-430D-B50D-CA02EBAA7F2F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.AutoInstrumentation", "examples\Example.AutoInstrumentation\Example.AutoInstrumentation.csproj", "{F3AA76EC-C7D8-42DA-947D-4376B6562772}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoInstrumentation.IntegrationTests", "tests\AutoInstrumentation.IntegrationTests\AutoInstrumentation.IntegrationTests.csproj", "{782E4DC1-8186-4BAC-B2F4-89E6DF22A4DD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -101,6 +105,14 @@ Global
{B61D749B-21E5-430D-B50D-CA02EBAA7F2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B61D749B-21E5-430D-B50D-CA02EBAA7F2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B61D749B-21E5-430D-B50D-CA02EBAA7F2F}.Release|Any CPU.Build.0 = Release|Any CPU
{F3AA76EC-C7D8-42DA-947D-4376B6562772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3AA76EC-C7D8-42DA-947D-4376B6562772}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3AA76EC-C7D8-42DA-947D-4376B6562772}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3AA76EC-C7D8-42DA-947D-4376B6562772}.Release|Any CPU.Build.0 = Release|Any CPU
{782E4DC1-8186-4BAC-B2F4-89E6DF22A4DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{782E4DC1-8186-4BAC-B2F4-89E6DF22A4DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{782E4DC1-8186-4BAC-B2F4-89E6DF22A4DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{782E4DC1-8186-4BAC-B2F4-89E6DF22A4DD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -116,6 +128,8 @@ Global
{206203BD-3EBA-4E9A-8881-1189D95AB037} = {4E95C87B-655B-4BC3-8F2A-DF06B7AAB7E9}
{A3D1ED4D-863B-45D7-9829-305DD33B4CE5} = {4E95C87B-655B-4BC3-8F2A-DF06B7AAB7E9}
{B61D749B-21E5-430D-B50D-CA02EBAA7F2F} = {E622CFF2-C6C4-40FB-BE42-7C4F2B38B75A}
{F3AA76EC-C7D8-42DA-947D-4376B6562772} = {4E95C87B-655B-4BC3-8F2A-DF06B7AAB7E9}
{782E4DC1-8186-4BAC-B2F4-89E6DF22A4DD} = {AAD39891-0B70-47FA-A212-43E1AAE5DF56}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {573B2B5F-8CBB-4D52-A55A-4E65E282AAFB}
Expand Down
2 changes: 2 additions & 0 deletions build/scripts/CommandLine.fs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Build =
| [<CliPrefix(CliPrefix.None);SubCommand>] Test

| [<CliPrefix(CliPrefix.None);SubCommand>] Unit_Test
| [<CliPrefix(CliPrefix.None);SubCommand>] Integrate
| [<CliPrefix(CliPrefix.None);SubCommand>] End_To_End

| [<CliPrefix(CliPrefix.None);SubCommand>] Format
Expand Down Expand Up @@ -49,6 +50,7 @@ with
| Build -> "Run build"

| Unit_Test -> "alias to providing: test --test-suite=unit"
| Integrate -> "alias to providing: test --test-suite=integration"
| End_To_End -> "alias to providing: test --test-suite=e2e"
| Test -> "runs a clean build and then runs all the tests unless --test-suite is provided"
| Release -> "runs build, tests, and create and validates the packages shy of publishing them"
Expand Down
1 change: 1 addition & 0 deletions build/scripts/Targets.fs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ let Setup (parsed:ParseResults<Build>) =
| Build -> Build.Cmd [Clean; CheckFormat] [] build

| End_To_End -> Build.Cmd [] [Build] <| runTests E2E
| Integrate -> Build.Cmd [] [Build] <| runTests Integration
| Unit_Test -> Build.Cmd [] [Build] <| runTests Unit
| Test -> Build.Cmd [] [Build] test

Expand Down
86 changes: 86 additions & 0 deletions examples/Example.AutoInstrumentation/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
ARG OTEL_VERSION=1.7.0
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
ARG TARGETPLATFORM
ARG TARGETARCH
ARG TARGETVARIANT
RUN apt-get update && apt-get install -y unzip curl strace
# Would love to run as non root but TestContainers does not utilize buildkit like `docker build` does OOTB
#USER $APP_UID
WORKDIR /app
RUN chown app:app .

FROM base AS otel
ARG OTEL_VERSION
# install OpenTelemetry .NET Automatic Instrumentation
# the following commented line does not work from TestContainers because it does not utilize buildkit which `docker build` does OOTB
#ADD --chown=$APP_UID --chmod=777 https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/v${OTEL_VERSION}/otel-dotnet-auto-install.sh otel-dotnet-auto-install.sh
ADD https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/v${OTEL_VERSION}/otel-dotnet-auto-install.sh otel-dotnet-auto-install.sh
RUN chmod +x otel-dotnet-auto-install.sh
RUN OTEL_DOTNET_AUTO_HOME="/app/otel" sh otel-dotnet-auto-install.sh


FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build_example
WORKDIR /src
COPY ["examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj", "examples/Example.AutoInstrumentation/"]
RUN dotnet restore "examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj"
COPY . .
WORKDIR "/src/examples/Example.AutoInstrumentation"
RUN dotnet build "Example.AutoInstrumentation.csproj" -c Release -o /app/build_example

FROM build_example AS publish_example
RUN dotnet publish "Example.AutoInstrumentation.csproj" -c Release -o /app/example /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build_plugin
WORKDIR /src
COPY ["README.md", "."]
COPY ["LICENSE.txt", "."]
COPY ["NOTICE.txt", "."]
COPY ["src/Elastic.OpenTelemetry.AutoInstrumentationPlugin/Elastic.OpenTelemetry.AutoInstrumentationPlugin.csproj", "src/Elastic.OpenTelemetry.AutoInstrumentationPlugin/"]
RUN dotnet restore "src/Elastic.OpenTelemetry.AutoInstrumentationPlugin/Elastic.OpenTelemetry.AutoInstrumentationPlugin.csproj"
COPY . .
WORKDIR "/src/src/Elastic.OpenTelemetry.AutoInstrumentationPlugin"
RUN dotnet build "Elastic.OpenTelemetry.AutoInstrumentationPlugin.csproj" -c release
RUN mkdir -p /app/temp
RUN cp -r /src/.artifacts/bin /app/temp

FROM otel AS final
ARG TARGETPLATFORM
ARG TARGETARCH
ARG TARGETVARIANT
WORKDIR /app
COPY --from=publish_example /app/example /app/example
COPY --from=otel /app/otel /app/otel

# This `RUN true` is a bit of magic that I don't care to understand
# https://github.com/moby/moby/issues/37965
# Seems to relate to the fs driver on GitHub Actions failing to copy files from the `build_plugin` layer.
# Hence we copy the whole .artifacts/bin folder manually and copy the files to /app/otel/net
RUN true
COPY --from=build_plugin /app/temp /app/temp
RUN true
RUN ls -al /app/temp/bin
RUN mkdir -p /app/otel/net
RUN cp "/app/temp/bin/Elastic.OpenTelemetry.AutoInstrumentationPlugin/release_net8.0/Elastic.OpenTelemetry.AutoInstrumentationPlugin.dll" /app/otel/net/
RUN cp "/app/temp/bin/Elastic.OpenTelemetry/release_net8.0/Elastic.OpenTelemetry.dll" /app/otel/net/

ENV CORECLR_ENABLE_PROFILING="1"
ENV CORECLR_PROFILER="{918728DD-259F-4A6A-AC2B-B85E1B658318}"
ENV CORECLR_PROFILER_PATH="/app/otel/linux-${TARGETARCH}/OpenTelemetry.AutoInstrumentation.Native.so"
ENV OTEL_DOTNET_AUTO_PLUGINS="Elastic.OpenTelemetry.AutoInstrumentationPlugin.ElasticAutoInstrumentationPlugin, Elastic.OpenTelemetry.AutoInstrumentationPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=069ca2728db333c1"

ENV OTEL_TRACES_EXPORTER=none
ENV OTEL_METRICS_EXPORTER=none
ENV OTEL_LOGS_EXPORTER=none
ENV OTEL_SERVICE_NAME=ExampleInstrumentation

ENV OTEL_LOG_LEVEL=info
ENV ELASTIC_OTEL_LOG_LEVEL=trace
ENV ELASTIC_OTEL_LOG_TARGETS=stdout
ENV OTEL_DOTNET_AUTO_LOG_DIRECTORY=/app/logs

ENV OTEL_DOTNET_AUTO_HOME="/app/otel"
ENV DOTNET_ADDITIONAL_DEPS="/app/otel/AdditionalDeps"
ENV DOTNET_SHARED_STORE="/app/otel/store"
ENV DOTNET_STARTUP_HOOKS="/app/otel/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll"

ENTRYPOINT ["dotnet", "/app/example/Example.AutoInstrumentation.dll"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>

<ItemGroup>
<Content Include="..\..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>

</Project>
14 changes: 14 additions & 0 deletions examples/Example.AutoInstrumentation/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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

Console.WriteLine("Hello, World!");

var httpClient = new HttpClient();
for (var i = 0; i < 10; i++)
{
var response = await httpClient.GetAsync(new Uri("https://google.com"));
Console.Write($"\rSent {i + 1} requests, last response: {response.StatusCode}.");
await Task.Delay(TimeSpan.FromMilliseconds(100));
}
Console.WriteLine();
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net6.0;net462</TargetFrameworks>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net462</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>False</IsPackable>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ public record ElasticOpenTelemetryBuilderOptions
/// </summary>
internal IServiceCollection? Services { get; init; }

private static readonly ElasticOpenTelemetryOptions DefaultDistroOptions = new();
/// <summary>
/// Advanced options which can be used to finely-tune the behaviour of the Elastic
/// distribution of OpenTelemetry.
/// </summary>
public ElasticOpenTelemetryOptions DistroOptions
{
get => _elasticOpenTelemetryOptions ?? new();
get => _elasticOpenTelemetryOptions ?? DefaultDistroOptions;
init => _elasticOpenTelemetryOptions = value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ public bool GlobalLogEnabled
{
get
{
var isActive = (_logLevel.HasValue || !string.IsNullOrWhiteSpace(_logDirectory) || _logTargets.HasValue);
if (isActive)
{
if (_logLevel is LogLevel.None)
isActive = false;
else if (_logTargets is LogTargets.None)
isActive = false;
}
var isActive = _logLevel.HasValue || !string.IsNullOrWhiteSpace(_logDirectory) || _logTargets.HasValue;
if (!isActive)
return isActive;

if (_logLevel is LogLevel.None)
isActive = false;
else if (_logTargets is LogTargets.None)
isActive = false;
return isActive;
}
}
Expand Down
14 changes: 11 additions & 3 deletions src/Elastic.OpenTelemetry/Diagnostics/Logging/CompositeLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,25 @@ internal sealed class CompositeLogger(ElasticOpenTelemetryBuilderOptions options
public const string LogCategory = "Elastic.OpenTelemetry";

public FileLogger FileLogger { get; } = new(options.DistroOptions);
public StandardOutLogger ConsoleLogger { get; } = new(options.DistroOptions);

private ILogger? _additionalLogger = options.Logger;
private bool _isDisposed;

public void Dispose()
{
_isDisposed = true;
if (_additionalLogger is IDisposable ad)
ad.Dispose();
FileLogger.Dispose();
}

public ValueTask DisposeAsync()
public async ValueTask DisposeAsync()
{
_isDisposed = true;
return FileLogger.DisposeAsync();
if (_additionalLogger is IAsyncDisposable ad)
await ad.DisposeAsync().ConfigureAwait(false);
await FileLogger.DisposeAsync().ConfigureAwait(false);
}

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
Expand All @@ -43,6 +48,9 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
if (FileLogger.IsEnabled(logLevel))
FileLogger.Log(logLevel, eventId, state, exception, formatter);

if (ConsoleLogger.IsEnabled(logLevel))
ConsoleLogger.Log(logLevel, eventId, state, exception, formatter);

if (_additionalLogger == null)
return;

Expand All @@ -55,7 +63,7 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except

public void SetAdditionalLogger(ILogger? logger) => _additionalLogger ??= logger;

public bool IsEnabled(LogLevel logLevel) => FileLogger.IsEnabled(logLevel) || (_additionalLogger?.IsEnabled(logLevel) ?? false);
public bool IsEnabled(LogLevel logLevel) => ConsoleLogger.IsEnabled(logLevel) || FileLogger.IsEnabled(logLevel) || (_additionalLogger?.IsEnabled(logLevel) ?? false);

public IDisposable BeginScope<TState>(TState state) where TState : notnull =>
new CompositeDisposable(FileLogger.BeginScope(state), _additionalLogger?.BeginScope(state));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public FileLogger(ElasticOpenTelemetryOptions options)
{
_scopeProvider = new LoggerExternalScopeProvider();
_configuredLogLevel = options.LogLevel;
FileLoggingEnabled = options.GlobalLogEnabled;
FileLoggingEnabled = options.GlobalLogEnabled && options.LogTargets.HasFlag(LogTargets.File);

if (!FileLoggingEnabled)
return;
Expand Down
32 changes: 32 additions & 0 deletions src/Elastic.OpenTelemetry/Diagnostics/Logging/StandardOutLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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 Elastic.OpenTelemetry.Configuration;
using Microsoft.Extensions.Logging;

namespace Elastic.OpenTelemetry.Diagnostics.Logging;

internal sealed class StandardOutLogger(ElasticOpenTelemetryOptions options) : ILogger
{
private readonly LogLevel _configuredLogLevel = options.LogLevel;

private readonly LoggerExternalScopeProvider _scopeProvider = new();

private bool StandardOutLoggingEnabled { get; } = options.GlobalLogEnabled && options.LogTargets.HasFlag(LogTargets.StdOut);

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
// We skip logging for any log level higher (numerically) than the configured log level
if (!IsEnabled(logLevel))
return;

var logLine = LogFormatter.Format(logLevel, eventId, state, exception, formatter);
Console.WriteLine(logLine);

}

public bool IsEnabled(LogLevel logLevel) => StandardOutLoggingEnabled && _configuredLogLevel <= logLevel;

public IDisposable BeginScope<TState>(TState state) where TState : notnull => _scopeProvider.Push(state);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Elastic.OpenTelemetry.AutoInstrumentation.IntegrationTests</RootNamespace>
<NoWarn>xUnit1041</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Nullean.Xunit.Partitions" Version="0.5.0" />
<PackageReference Include="Testcontainers" Version="3.9.0"/>
</ItemGroup>

</Project>
Loading

0 comments on commit ae1c927

Please sign in to comment.