Index()
20 | {
21 | using var client = _httpClientFactory.CreateClient();
22 |
23 | // ReSharper disable once ExplicitCallerInfoArgument
24 | using var activity = ActivitySource.StartActivity("DoingStuff", ActivityKind.Internal);
25 | activity?.SetTag("CustomTag", "TagValue");
26 |
27 | await Task.Delay(100);
28 | var response = await client.GetAsync("http://elastic.co");
29 | await Task.Delay(50);
30 |
31 | activity?.SetStatus(response.StatusCode == HttpStatusCode.OK ? ActivityStatusCode.Ok : ActivityStatusCode.Error);
32 |
33 | return View();
34 | }
35 |
36 | public IActionResult Privacy() => View();
37 |
38 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
39 | public IActionResult Error() => View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
40 | }
41 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
2 | USER $APP_UID
3 | WORKDIR /app
4 | EXPOSE 8080
5 | EXPOSE 8081
6 |
7 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
8 | ARG BUILD_CONFIGURATION=Release
9 | WORKDIR /src
10 | COPY ["examples/Example.AspNetCore.Mvc/Example.AspNetCore.Mvc.csproj", "examples/Example.AspNetCore.Mvc/"]
11 | COPY ["src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj", "src/Elastic.OpenTelemetry/"]
12 | COPY ["examples/ServiceDefaults/ServiceDefaults.csproj", "examples/ServiceDefaults/"]
13 | RUN dotnet restore "examples/Example.AspNetCore.Mvc/Example.AspNetCore.Mvc.csproj"
14 | COPY . .
15 | WORKDIR "/src/examples/Example.AspNetCore.Mvc"
16 | RUN dotnet build "Example.AspNetCore.Mvc.csproj" -c $BUILD_CONFIGURATION -o /app/build
17 |
18 | FROM build AS publish
19 | ARG BUILD_CONFIGURATION=Release
20 | RUN dotnet publish "Example.AspNetCore.Mvc.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
21 |
22 | FROM base AS final
23 | WORKDIR /app
24 | COPY --from=publish /app/publish .
25 | ENTRYPOINT ["dotnet", "Example.AspNetCore.Mvc.dll"]
26 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Example.AspNetCore.Mvc.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 | Linux
8 | 1efafe93-6112-431d-b30f-786205a20ebe
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | .dockerignore
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Models/ErrorViewModel.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace Example.AspNetCore.Mvc.Models
6 | {
7 | public class ErrorViewModel
8 | {
9 | public string? RequestId { get; set; }
10 |
11 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Program.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | var builder = WebApplication.CreateBuilder(args);
6 |
7 | builder.AddElasticOpenTelemetry();
8 |
9 | builder.Services
10 | .AddHttpClient();
11 |
12 | builder.Services
13 | .AddControllersWithViews();
14 |
15 | var app = builder.Build();
16 |
17 | app.Logger.LogInformation("Process Id {ProcessId}", Environment.ProcessId);
18 |
19 | if (!app.Environment.IsDevelopment())
20 | {
21 | app.UseExceptionHandler("/Home/Error");
22 | app.UseHsts();
23 | }
24 |
25 | app.UseHttpsRedirection();
26 | app.UseStaticFiles();
27 | app.UseRouting();
28 | app.UseAuthorization();
29 |
30 | app.MapControllerRoute(
31 | "default",
32 | "{controller=Home}/{action=Index}/{id?}");
33 |
34 | app.Run();
35 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "http": {
4 | "commandName": "Project",
5 | "launchBrowser": true,
6 | "dotnetRunMessages": true,
7 | "applicationUrl": "http://localhost:5247"
8 | },
9 | "https": {
10 | "commandName": "Project",
11 | "launchBrowser": true,
12 | "environmentVariables": {
13 | "ASPNETCORE_ENVIRONMENT": "Development"
14 | },
15 | "dotnetRunMessages": true,
16 | "applicationUrl": "https://localhost:7295;http://localhost:5247"
17 | },
18 | "IIS Express": {
19 | "commandName": "IISExpress",
20 | "launchBrowser": true,
21 | "environmentVariables": {
22 | "ASPNETCORE_ENVIRONMENT": "Development"
23 | }
24 | }
25 | },
26 | "$schema": "http://json.schemastore.org/launchsettings.json",
27 | "iisSettings": {
28 | "windowsAuthentication": false,
29 | "anonymousAuthentication": true,
30 | "iisExpress": {
31 | "applicationUrl": "http://localhost:45954",
32 | "sslPort": 44302
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Views/E2E/Index.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Home Page";
3 | }
4 |
5 |
9 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Views/Home/Index.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Home Page";
3 | }
4 |
5 |
9 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Views/Home/Privacy.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Privacy Policy";
3 | }
4 | @ViewData["Title"]
5 |
6 | Use this page to detail your site's privacy policy.
7 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @model ErrorViewModel
2 | @{
3 | ViewData["Title"] = "Error";
4 | }
5 |
6 | Error.
7 | An error occurred while processing your request.
8 |
9 | @if (Model.ShowRequestId)
10 | {
11 |
12 | Request ID: @Model.RequestId
13 |
14 | }
15 |
16 | Development Mode
17 |
18 | Swapping to Development environment will display more detailed information about the error that occurred.
19 |
20 |
21 | The Development environment shouldn't be enabled for deployed applications.
22 | It can result in displaying sensitive information from exceptions to end users.
23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
24 | and restarting the app.
25 |
26 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @ViewData["Title"] - Example.Elastic.OpenTelemetry.AspNetCore
7 |
8 |
9 | @* ReSharper disable once Html.PathError *@
10 |
11 |
12 |
13 |
34 |
35 |
36 | @RenderBody()
37 |
38 |
39 |
40 |
45 |
46 |
47 |
48 | @await RenderSectionAsync("Scripts", required: false)
49 |
50 |
51 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Views/Shared/_Layout.cshtml.css:
--------------------------------------------------------------------------------
1 | /* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | for details on configuring this project to bundle and minify static web assets. */
3 |
4 | a.navbar-brand {
5 | white-space: normal;
6 | text-align: center;
7 | word-break: break-all;
8 | }
9 |
10 | a {
11 | color: #0077cc;
12 | }
13 |
14 | .btn-primary {
15 | color: #fff;
16 | background-color: #1b6ec2;
17 | border-color: #1861ac;
18 | }
19 |
20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
21 | color: #fff;
22 | background-color: #1b6ec2;
23 | border-color: #1861ac;
24 | }
25 |
26 | .border-top {
27 | border-top: 1px solid #e5e5e5;
28 | }
29 | .border-bottom {
30 | border-bottom: 1px solid #e5e5e5;
31 | }
32 |
33 | .box-shadow {
34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
35 | }
36 |
37 | button.accept-policy {
38 | font-size: 1rem;
39 | line-height: inherit;
40 | }
41 |
42 | .footer {
43 | position: absolute;
44 | bottom: 0;
45 | width: 100%;
46 | white-space: nowrap;
47 | line-height: 60px;
48 | }
49 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using Example.AspNetCore.Mvc.Models
2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
3 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | },
7 | "OpenTelemetry": {
8 | "IncludeFormattedMessage": true,
9 | "IncludeScopes": true,
10 | "ParseStateValues": true
11 | }
12 | },
13 | "AllowedHosts": "*",
14 | //"Elastic": {
15 | // "OpenTelemetry": {
16 | // "LogLevel": "Trace",
17 | // "LogDirectory": "C:\\Logs\\BrandNewLogs"
18 | // }
19 | //}
20 | }
21 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 14px;
3 | }
4 |
5 | @media (min-width: 768px) {
6 | html {
7 | font-size: 16px;
8 | }
9 | }
10 |
11 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
12 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
13 | }
14 |
15 | html {
16 | position: relative;
17 | min-height: 100%;
18 | }
19 |
20 | body {
21 | margin-bottom: 60px;
22 | }
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elastic/elastic-otel-dotnet/c6fdff4de457184ef9ab6ca3b1d9a06040156e22/examples/Example.AspNetCore.Mvc/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | // for details on configuring this project to bundle and minify static web assets.
3 |
4 | // Write your JavaScript code.
5 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/wwwroot/lib/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2021 Twitter, Inc.
4 | Copyright (c) 2011-2021 The Bootstrap Authors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) .NET Foundation and Contributors
4 |
5 | All rights reserved.
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/wwwroot/lib/jquery-validation/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright Jörn Zaefferer
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.Mvc/wwwroot/lib/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.WebApiAot/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "dotnet-ef": {
6 | "version": "9.0.2",
7 | "commands": [
8 | "dotnet-ef"
9 | ],
10 | "rollForward": false
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 | true
8 | true
9 | 205c18a1-356e-4e59-b15f-2a297cb8d4b8
10 | true
11 | latest
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.WebApiAot/Example.AspNetCore.WebApiAot.http:
--------------------------------------------------------------------------------
1 | @Example.AspNetCore.WebApiAot_HostAddress = http://localhost:5283
2 |
3 | GET {{Example.AspNetCore.WebApiAot_HostAddress}}/todos/
4 | Accept: application/json
5 |
6 | ###
7 |
8 | GET {{Example.AspNetCore.WebApiAot_HostAddress}}/todos/1
9 | Accept: application/json
10 |
11 | ###
12 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.WebApiAot/Program.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Diagnostics.CodeAnalysis;
6 | using System.Text.Json.Serialization;
7 | using OpenTelemetry;
8 | using OpenTelemetry.Trace;
9 |
10 | var builder = WebApplication.CreateSlimBuilder(args);
11 |
12 | builder.AddElasticOpenTelemetry(b => b.WithTracing(t => t.AddAspNetCoreInstrumentation()));
13 |
14 | builder.Services.ConfigureHttpJsonOptions(options =>
15 | {
16 | options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
17 | });
18 |
19 | var app = builder.Build();
20 |
21 | var sampleTodos = new Todo[] {
22 | new(1, "Walk the dog"),
23 | new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)),
24 | new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))),
25 | new(4, "Clean the bathroom"),
26 | new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2)))
27 | };
28 |
29 | var todosApi = app.MapGroup("/todos");
30 | todosApi.MapGet("/", () => sampleTodos);
31 | todosApi.MapGet("/{id}", (int id) =>
32 | sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
33 | ? Results.Ok(todo)
34 | : Results.NotFound());
35 |
36 | app.Run();
37 |
38 | [SuppressMessage("Design", "CA1050:Declare types in namespaces")]
39 | public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false);
40 |
41 | [JsonSerializable(typeof(Todo[]))]
42 | internal partial class AppJsonSerializerContext : JsonSerializerContext;
43 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.WebApiAot/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "http": {
5 | "commandName": "Project",
6 | "dotnetRunMessages": true,
7 | "launchBrowser": true,
8 | "launchUrl": "todos",
9 | "applicationUrl": "http://localhost:5283",
10 | "environmentVariables": {
11 | "ASPNETCORE_ENVIRONMENT": "Development"
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.WebApiAot/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/Example.AspNetCore.WebApiAot/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "Elastic": {
10 | "OpenTelemetry": {
11 | "LogLevel": "Trace",
12 | "LogDirectory": "C:\\Logs\\AotLogs"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/Example.AutoInstrumentation/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG OTEL_VERSION=1.9.0
2 | FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
3 | ARG TARGETPLATFORM
4 | ARG TARGETARCH
5 | ARG TARGETVARIANT
6 | RUN apt-get update && apt-get install -y unzip curl strace
7 | # Would love to run as non root but TestContainers does not utilize buildkit like `docker build` does OOTB
8 | #USER $APP_UID
9 | WORKDIR /app
10 | RUN chown app:app .
11 |
12 | FROM base AS otel
13 | ARG OTEL_VERSION
14 | # install OpenTelemetry .NET Automatic Instrumentation
15 | # the following commented line does not work from TestContainers because it does not utilize buildkit which `docker build` does OOTB
16 | #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
17 | ADD https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/v${OTEL_VERSION}/otel-dotnet-auto-install.sh otel-dotnet-auto-install.sh
18 | RUN chmod +x otel-dotnet-auto-install.sh
19 | RUN OTEL_DOTNET_AUTO_HOME="/app/otel" sh otel-dotnet-auto-install.sh
20 |
21 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build_example
22 | ENV _PROJECT="Example.AutoInstrumentation"
23 | ENV _PROJECTPATH="${_PROJECT}/${_PROJECT}.csproj"
24 | WORKDIR /work
25 | COPY ["examples/${_PROJECTPATH}", "examples/${_PROJECT}/"]
26 | RUN dotnet restore "examples/${_PROJECT}"
27 | COPY .git .git
28 | COPY examples/${_PROJECT} examples/${_PROJECT}
29 | WORKDIR "/work/examples/${_PROJECT}"
30 | RUN dotnet build "${_PROJECT}.csproj" -c Release -o /app/build_example
31 |
32 | FROM build_example AS publish_example
33 | RUN dotnet publish "Example.AutoInstrumentation.csproj" -c Release -o /app/example /p:UseAppHost=false
34 |
35 | FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build_distro
36 | ENV _PROJECT="Elastic.OpenTelemetry"
37 | ENV _PROJECTPATH="${_PROJECT}/${_PROJECT}.csproj"
38 | WORKDIR /work
39 | COPY ["src/${_PROJECTPATH}", "src/${_PROJECT}/"]
40 | RUN dotnet restore "src/${_PROJECTPATH}"
41 | COPY . .
42 | WORKDIR "/work/src/${_PROJECT}"
43 | RUN dotnet build "${_PROJECT}.csproj" -c Release
44 |
45 | FROM otel AS final
46 | ARG TARGETPLATFORM
47 | ARG TARGETARCH
48 | ARG TARGETVARIANT
49 | WORKDIR /app
50 | COPY --from=publish_example /app/example /app/example
51 | COPY --from=build_distro /work/.artifacts/bin/Elastic.OpenTelemetry/release_netstandard2.1/Elastic.OpenTelemetry.dll /app/otel/net/
52 | COPY --from=build_distro /work/.artifacts/bin/Elastic.OpenTelemetry/release_netstandard2.1/Elastic.OpenTelemetry.pdb /app/otel/net/
53 |
54 | ENV CORECLR_ENABLE_PROFILING="1"
55 | ENV CORECLR_PROFILER="{918728DD-259F-4A6A-AC2B-B85E1B658318}"
56 | ENV CORECLR_PROFILER_PATH="/app/otel/linux-${TARGETARCH}/OpenTelemetry.AutoInstrumentation.Native.so"
57 | ENV OTEL_DOTNET_AUTO_PLUGINS="Elastic.OpenTelemetry.AutoInstrumentationPlugin, Elastic.OpenTelemetry.AutoInstrumentation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=069ca2728db333c1"
58 |
59 | ENV OTEL_TRACES_EXPORTER=none
60 | ENV OTEL_METRICS_EXPORTER=none
61 | ENV OTEL_LOGS_EXPORTER=none
62 | ENV OTEL_SERVICE_NAME=ExampleInstrumentation
63 |
64 | ENV OTEL_LOG_LEVEL=debug
65 | ENV OTEL_DOTNET_AUTO_LOG_DIRECTORY=/app/logs
66 | ENV ELASTIC_OTEL_LOG_TARGETS=stdout
67 |
68 | ENV OTEL_DOTNET_AUTO_HOME="/app/otel"
69 | ENV OTEL_DOTNET_AUTO_LOGGER="console"
70 | ENV DOTNET_ADDITIONAL_DEPS="/app/otel/AdditionalDeps"
71 | ENV DOTNET_SHARED_STORE="/app/otel/store"
72 | ENV DOTNET_STARTUP_HOOKS="/app/otel/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll"
73 |
74 | ENV OTEL_DOTNET_AUTO_FAIL_FAST_ENABLED=true
75 |
76 | ENTRYPOINT ["dotnet", "/app/example/Example.AutoInstrumentation.dll"]
77 |
--------------------------------------------------------------------------------
/examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 | enable
7 | enable
8 | Linux
9 |
10 |
11 |
12 |
13 | .dockerignore
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/Example.AutoInstrumentation/Program.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | Console.WriteLine("Hello, World!");
6 |
7 | var httpClient = new HttpClient();
8 | for (var i = 0; i < 10; i++)
9 | {
10 | var response = await httpClient.GetAsync(new Uri("https://google.com"));
11 | Console.Write($"\rSent {i + 1} requests, last response: {response.StatusCode}.");
12 | await Task.Delay(TimeSpan.FromMilliseconds(100));
13 | }
14 | Console.WriteLine();
15 |
--------------------------------------------------------------------------------
/examples/Example.AutoInstrumentation/README.md:
--------------------------------------------------------------------------------
1 | # Example auto instrumentation plugin for OpenTelemetry .NET
2 |
3 | This is a very minimal .NET application that we use to validate our OpenTelemetry plugin loads correctly
4 |
5 | This happens automatically through our testing setup:
6 |
7 | ```bash
8 | $ ./build.sh test --test-suite=integration
9 | ```
10 |
11 | Which ends up running the tests in `/tests/AutoInstrumentation.IntegrationTests`
12 |
13 | To quickly see the `DockerFile` in action run the following from the root of this repository.
14 |
15 | ```bash
16 | $ docker build -t example.autoinstrumentation:latest -f examples/Example.AutoInstrumentation/Dockerfile --no-cache . && \
17 | docker run -it --rm -p 5000:8080 --name autoin example.autoinstrumentation:latest
18 | ```
19 |
20 | ```bash
21 | docker build -t distribution.autoinstrumentation:latest -f examples/Example.AutoInstrumentation/distribution.Dockerfile --platform linux/arm64 --no-cache . && \
22 | docker run -it --rm -p 5000:8080 --name distri --platform linux/arm64 distribution.autoinstrumentation:latest
23 | ```
--------------------------------------------------------------------------------
/examples/Example.AutoInstrumentation/distribution.Dockerfile:
--------------------------------------------------------------------------------
1 | ARG OTEL_VERSION=1.9.0
2 | FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
3 | ARG TARGETPLATFORM
4 | ARG TARGETARCH
5 | ARG TARGETVARIANT
6 |
7 | ENV _PROJECT="Example.AutoInstrumentation"
8 | ENV _PROJECTPATH="${_PROJECT}/${_PROJECT}.csproj"
9 |
10 | RUN apt-get update && apt-get install -y unzip
11 |
12 | WORKDIR /work
13 |
14 | COPY ["examples/${_PROJECTPATH}", "examples/${_PROJECT}/"]
15 | RUN dotnet restore -a $TARGETARCH "examples/${_PROJECT}"
16 |
17 | COPY .git .git
18 | COPY examples/${_PROJECT} examples/${_PROJECT}
19 | WORKDIR "/work/examples/${_PROJECT}"
20 | RUN dotnet publish "${_PROJECT}.csproj" -c Release -a $TARGETARCH --no-restore -o /app/example
21 |
22 |
23 | FROM build AS final
24 |
25 | COPY ".artifacts/elastic-distribution" /distro/elastic
26 |
27 | COPY --from=build /app/example /app/example
28 |
29 | ENV OTEL_DOTNET_AUTO_HOME="/app/otel"
30 | # Use already downloaded release assets (call ./build.sh redistribute locally if you run this dockerfile manually)
31 | RUN DOWNLOAD_DIR="/distro/elastic" sh /distro/elastic/elastic-dotnet-auto-install.sh
32 |
33 | ENV OTEL_LOG_LEVEL=debug
34 | ENTRYPOINT ["sh", "/app/otel/instrument.sh", "dotnet", "/app/example/Example.AutoInstrumentation.dll"]
35 |
--------------------------------------------------------------------------------
/examples/Example.Console/Example.Console.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/Example.Console/Program.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Example.Console;
6 |
7 | Console.WriteLine("Starting sample application.");
8 |
9 | Usage.BasicBuilderUsage();
10 |
11 | //await Usage.ComplexUsageAsync();
12 |
13 | Console.WriteLine("DONE");
14 |
--------------------------------------------------------------------------------
/examples/Example.Console/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Example.Elastic.OpenTelemetry": {
4 | "commandName": "Project",
5 | "environmentVariables": {
6 | "OTEL_RESOURCE_ATTRIBUTES": "service.name=SampleConsoleApp,service.version=1.0.0,deployment.environment=development"
7 | }
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/examples/Example.Console/Usage.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry;
6 | using Microsoft.Extensions.Logging;
7 | using OpenTelemetry;
8 |
9 | namespace Example.Console;
10 |
11 | internal static class Usage
12 | {
13 | public static void BasicBuilderUsage()
14 | {
15 | // NOTE: This sample assumes ENV VARs have been set to configure the Endpoint and Authorization header.
16 |
17 | //// Build an instrumentation session by creating an ElasticOpenTelemetryBuilder.
18 | //// The application will be instrumented until the session is disposed.
19 | //await using var session = new ElasticOpenTelemetryBuilder()
20 | // .WithTracing(b => b.AddSource(ActivitySourceName))
21 | // .Build();
22 |
23 | //await using var session2 = new ElasticOpenTelemetryBuilder().Build();
24 |
25 | //// This example adds the application activity source and fully customises the resource
26 | //await using var session3 = new ElasticOpenTelemetryBuilder()
27 | // .WithTracing(b => b
28 | // .AddSource(ActivitySourceName)
29 | // .ConfigureResource(r => r.Clear().AddService("CustomServiceName", serviceVersion: "2.2.2")))
30 | // .Build();
31 |
32 | //await using var session4 = new ElasticOpenTelemetryBuilder()
33 | // .WithTracing(t => t
34 | // .ConfigureResource(rb => rb.AddService("TracerProviderBuilder", "3.3.3"))
35 | // .AddRedisInstrumentation() // This can currently only be achieved using this overload or adding Elastic processors to the TPB (as below)
36 | // .AddSource(ActivitySourceName)
37 | // .AddConsoleExporter()
38 | // )
39 | // .WithTracing(tpb => tpb
40 | // .ConfigureResource(rb => rb.AddService("TracerProviderBuilder", "3.3.3"))
41 | // .AddRedisInstrumentation() // This can currently only be achieved using this overload or adding Elastic processors to the TPB (as below)
42 | // .AddSource(ActivitySourceName)
43 | // .AddConsoleExporter())
44 | // .Build();
45 |
46 | using var loggerFactory = LoggerFactory.Create(static builder =>
47 | {
48 | builder
49 | .AddFilter("Elastic.OpenTelemetry", LogLevel.Debug)
50 | .AddConsole();
51 | });
52 |
53 | var options = new ElasticOpenTelemetryOptions
54 | {
55 | AdditionalLoggerFactory = loggerFactory
56 | };
57 |
58 | using var sdk = OpenTelemetrySdk.Create(builder => builder
59 | .WithElasticDefaults(options));
60 |
61 | //This is the most flexible approach for a consumer as they can include our processor(s)
62 | //using var tracerProvider = Sdk.CreateTracerProviderBuilder()
63 | // .AddSource(ActivitySourceName)
64 | // .ConfigureResource(resource =>
65 | // resource.AddService(
66 | // serviceName: "OtelSdkApp",
67 | // serviceVersion: "1.0.0"))
68 | // .AddConsoleExporter()
69 | // .AddElasticProcessors()
70 | //// .Build();
71 |
72 | //await DoStuffAsync();
73 |
74 | //static async Task DoStuffAsync()
75 | //{
76 | // using var activity = ActivitySource.StartActivity("DoingStuff", ActivityKind.Internal);
77 | // activity?.SetTag("CustomTag", "TagValue");
78 |
79 | // await Task.Delay(100);
80 | // var response = await HttpClient.GetAsync("http://elastic.co");
81 | // await Task.Delay(50);
82 |
83 | // if (response.StatusCode == System.Net.HttpStatusCode.OK)
84 | // activity?.SetStatus(ActivityStatusCode.Ok);
85 | // else
86 | // activity?.SetStatus(ActivityStatusCode.Error);
87 | //}
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/examples/Example.MinimalApi/Example.MinimalApi.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/Example.MinimalApi/Program.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Diagnostics;
6 | using Example.MinimalApi;
7 | using OpenTelemetry;
8 |
9 | var builder = WebApplication.CreateBuilder(args);
10 |
11 | //builder.AddElasticOpenTelemetry();
12 |
13 | // This will add the OpenTelemetry services using Elastic defaults
14 | builder.AddServiceDefaults();
15 |
16 | builder.Services
17 | .AddHttpClient() // Adds IHttpClientFactory
18 | .AddElasticOpenTelemetry() // Adds app specific tracing
19 | .WithTracing(t => t.AddSource(Api.ActivitySourceName));
20 |
21 | var app = builder.Build();
22 |
23 | app.UseHttpsRedirection();
24 |
25 | app.MapGet("/", (IHttpClientFactory httpClientFactory, ILoggerFactory loggerFactory) =>
26 | Api.HandleRoot(httpClientFactory, loggerFactory));
27 |
28 | app.Run();
29 |
30 | namespace Example.MinimalApi
31 | {
32 | internal static class Api
33 | {
34 | public static string ActivitySourceName = "CustomActivitySource";
35 | private static readonly ActivitySource ActivitySource = new(ActivitySourceName);
36 |
37 | public static async Task HandleRoot(IHttpClientFactory httpClientFactory, ILoggerFactory loggerFactory)
38 | {
39 | var logger = loggerFactory.CreateLogger("Example.Api");
40 |
41 | logger.LogInformation("Doing stuff");
42 |
43 | using var client = httpClientFactory.CreateClient();
44 |
45 | using var activity = ActivitySource.StartActivity("DoingStuff", ActivityKind.Internal);
46 | activity?.SetTag("custom-tag", "TagValue");
47 |
48 | await Task.Delay(100);
49 | var response = await client.GetAsync("http://elastic.co"); // using this URL will require 2 redirects
50 | await Task.Delay(50);
51 |
52 | if (response.StatusCode == System.Net.HttpStatusCode.OK)
53 | {
54 | activity?.SetStatus(ActivityStatusCode.Ok);
55 | return Results.Ok();
56 | }
57 |
58 | activity?.SetStatus(ActivityStatusCode.Error);
59 | return Results.StatusCode(500);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/examples/Example.MinimalApi/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:4214",
8 | "sslPort": 44348
9 | }
10 | },
11 | "profiles": {
12 | "http": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "launchUrl": "",
17 | "applicationUrl": "http://localhost:5146",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development",
20 | "OTEL_RESOURCE_ATTRIBUTES": "service.name=minimal-api-example"
21 | }
22 | },
23 | "https": {
24 | "commandName": "Project",
25 | "dotnetRunMessages": true,
26 | "launchBrowser": true,
27 | "launchUrl": "",
28 | "applicationUrl": "https://localhost:7140;http://localhost:5146",
29 | "environmentVariables": {
30 | "ASPNETCORE_ENVIRONMENT": "Development",
31 | "OTEL_RESOURCE_ATTRIBUTES": "service.name=minimal-api-example"
32 | }
33 | },
34 | "IIS Express": {
35 | "commandName": "IISExpress",
36 | "launchBrowser": true,
37 | "launchUrl": "",
38 | "environmentVariables": {
39 | "ASPNETCORE_ENVIRONMENT": "Development",
40 | "OTEL_RESOURCE_ATTRIBUTES": "service.name=minimal-api-example"
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/Example.MinimalApi/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning",
6 | "Elastic.OpenTelemetry": "Trace"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/Example.MinimalApi/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning",
6 | "Elastic.OpenTelemetry": "Warning"
7 | }
8 | },
9 | "AllowedHosts": "*",
10 | "AspNetCoreInstrumentation": {
11 | "RecordException": "true"
12 | },
13 | "Elastic": {
14 | "OpenTelemetry": {
15 | "LogDirectory": "C:\\Logs\\OtelDistro",
16 | "LogLevel": "Information"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/Example.WorkerService/Example.WorkerService.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 | dotnet-Example.Elastic.OpenTelemetry.Worker-3a9724de-5d6b-4e68-a21e-b90c655cc721
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/Example.WorkerService/Program.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Example.WorkerService;
6 | using OpenTelemetry;
7 |
8 | var builder = Host.CreateApplicationBuilder(args);
9 |
10 | builder.AddElasticOpenTelemetry(b => b
11 | .WithTracing(t => t.AddSource(Worker.DiagnosticName))
12 | .WithMetrics(m => m.AddMeter(Worker.DiagnosticName)));
13 |
14 | builder.Services.AddSingleton();
15 | builder.Services.AddHostedService();
16 |
17 | var host = builder.Build();
18 | host.Run();
19 |
--------------------------------------------------------------------------------
/examples/Example.WorkerService/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "Example.Elastic.OpenTelemetry.Worker": {
5 | "commandName": "Project",
6 | "dotnetRunMessages": true,
7 | "environmentVariables": {
8 | "DOTNET_ENVIRONMENT": "Development",
9 | "OTEL_RESOURCE_ATTRIBUTES": "service.name=WorkerService,service.version=1.0.0,deployment.environment=development",
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/Example.WorkerService/Worker.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 |
6 | using System.Diagnostics;
7 | using System.Diagnostics.Metrics;
8 | using System.Runtime.CompilerServices;
9 |
10 | namespace Example.WorkerService;
11 |
12 | public class QueueReader
13 | {
14 | public async IAsyncEnumerable GetMessages([EnumeratorCancellation] CancellationToken ctx = default)
15 | {
16 | while (!ctx.IsCancellationRequested)
17 | {
18 | // Get messages from queue/service bus
19 | await Task.Delay(TimeSpan.FromSeconds(5), ctx);
20 |
21 | yield return new Message(Guid.NewGuid().ToString());
22 | }
23 | }
24 | }
25 |
26 | public record class Message(string Id) { }
27 |
28 | public class Worker : BackgroundService
29 | {
30 | public const string DiagnosticName = "Elastic.Processor";
31 |
32 | private static readonly ActivitySource ActivitySource = new(DiagnosticName);
33 | private static readonly Meter Meter = new(DiagnosticName);
34 | private static readonly Counter MessagesReadCounter = Meter.CreateCounter("elastic.processor.messages_read");
35 |
36 | private readonly ILogger _logger;
37 | private readonly QueueReader _queueReader;
38 |
39 | private static readonly Random Random = new();
40 |
41 | public Worker(ILogger logger, QueueReader queueReader)
42 | {
43 | _logger = logger;
44 | _queueReader = queueReader;
45 | }
46 |
47 | protected override async Task ExecuteAsync(CancellationToken stoppingToken)
48 | {
49 | await foreach (var message in _queueReader.GetMessages().WithCancellation(stoppingToken))
50 | {
51 | using var activity = ActivitySource.StartActivity("Process message", ActivityKind.Internal);
52 |
53 | activity?.SetTag("elastic.message.id", message.Id);
54 |
55 | if (MessagesReadCounter.Enabled)
56 | MessagesReadCounter.Add(1);
57 |
58 | var success = await ProcessMessageAsync(message);
59 |
60 | if (!success)
61 | {
62 | _logger.LogError("Unable to process message {Id}", message.Id);
63 | activity?.SetStatus(ActivityStatusCode.Error);
64 | }
65 | }
66 | }
67 |
68 | private static async Task ProcessMessageAsync(Message message)
69 | {
70 | await Task.Delay(Random.Next(100, 300)); // simulate processing
71 | return Random.Next(10) < 8; // simulate 80% success
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/examples/Example.WorkerService/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.Hosting.Lifetime": "Information",
6 | "Elastic": "Trace"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/Example.WorkerService/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.Hosting.Lifetime": "Information"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/ServiceDefaults/ServiceDefaults.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/examples/k8s/elastic-otel-dotnet.yml:
--------------------------------------------------------------------------------
1 | apiVersion: opentelemetry.io/v1alpha1
2 | kind: Instrumentation
3 | metadata:
4 | name: elastic-otel-dotnet
5 | spec:
6 | env:
7 | - name: OTEL_EXPORTER_OTLP_ENDPOINT
8 | valueFrom:
9 | secretKeyRef:
10 | name: elastic-otel
11 | key: endpoint
12 | exporter:
13 | endpoint: $OTEL_EXPORTER_OTLP_ENDPOINT
14 | propagators:
15 | - tracecontext
16 | - baggage
17 | - b3
18 | sampler:
19 | type: parentbased_traceidratio
20 | argument: "1.0"
21 | dotnet:
22 | image: docker.elastic.co/observability/elastic-otel-dotnet:edge
23 | env:
24 | - name: OTEL_EXPORTER_OTLP_HEADERS
25 | valueFrom:
26 | secretKeyRef:
27 | name: elastic-otel
28 | key: apiKey
29 | - name: OTEL_LOG_LEVEL
30 | value: "info"
31 | - name: ELASTIC_OTEL_LOG_TARGETS
32 | value: "stdout"
33 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/k8s/my-dotnet-application.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: my-dotnet-application
5 | labels:
6 | app: my-dotnet-application
7 | spec:
8 | containers:
9 | - image: asp-net-example:latest
10 | imagePullPolicy: Never
11 | name: asp-net-example
12 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "9.0.100",
4 | "rollForward": "latestFeature",
5 | "allowPrerelease": false
6 | }
7 | }
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 | $(SolutionRoot)\build\keys\keypair.snk
7 |
8 | nuget-icon.png
9 | README.md
10 |
11 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
12 |
13 | true
14 | true
15 | true
16 | True
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | nuget-icon.png
26 | True
27 | nuget-icon.png
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.AutoInstrumentation/_instrument.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | setlocal ENABLEDELAYEDEXPANSION
3 |
4 | :: This script is expected to be used in a build that specified a RuntimeIdentifier (RID)
5 | set BASE_PATH=%~dp0
6 | set COR_PROFILER_PATH=%BASE_PATH%OpenTelemetry.AutoInstrumentation.Native.dll
7 |
8 | :: Validate
9 | IF EXIST %COR_PROFILER_PATH% (
10 | set CORECLR_PROFILER_PATH=!COR_PROFILER_PATH!
11 | ) ELSE (
12 | set "COR_PROFILER_PATH="
13 |
14 | echo Unable to locate the native profiler inside current directory, possibly due to runtime identifier not being specified when building/publishing. ^
15 | Attempting to use the native profiler from runtimes\win-x64\native and runtimes\win-x86\native subdirectories.
16 |
17 | set COR_PROFILER_PATH_64=%BASE_PATH%runtimes\win-x64\native\OpenTelemetry.AutoInstrumentation.Native.dll
18 | set COR_PROFILER_PATH_32=%BASE_PATH%runtimes\win-x86\native\OpenTelemetry.AutoInstrumentation.Native.dll
19 |
20 | IF EXIST !COR_PROFILER_PATH_64! (
21 | IF EXIST !COR_PROFILER_PATH_32! (
22 | set CORECLR_PROFILER_PATH_32=!COR_PROFILER_PATH_32!
23 | ) ELSE (
24 | set "COR_PROFILER_PATH_32="
25 | )
26 | set CORECLR_PROFILER_PATH_64=!COR_PROFILER_PATH_64!
27 | ) ELSE (
28 | set "COR_PROFILER_PATH_64="
29 | IF EXIST !COR_PROFILER_PATH_32! (
30 | set CORECLR_PROFILER_PATH_32=!COR_PROFILER_PATH_32!
31 | ) ELSE (
32 | set "COR_PROFILER_PATH_32="
33 | echo Unable to locate the native profiler. 1>&2
34 | exit /b 1
35 | )
36 | )
37 | )
38 |
39 | :: Settings for .NET Framework
40 | set COR_ENABLE_PROFILING=1
41 | set COR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658318}
42 |
43 | :: On .NET Framework automatic assembly redirection MUST be disabled. This setting
44 | :: is ignored on .NET. This is necessary because the NuGet package doesn't bring
45 | :: the pre-defined versions of the transitive dependencies used in the automatic
46 | :: redirection. Instead the transitive dependencies versions are determined by
47 | :: the NuGet version resolution algorithm when building the application.
48 | set OTEL_DOTNET_AUTO_NETFX_REDIRECT_ENABLED=false
49 |
50 | :: Settings for .NET
51 | set ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper
52 | set CORECLR_ENABLE_PROFILING=1
53 | set CORECLR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658318}
54 | set DOTNET_STARTUP_HOOKS=%BASE_PATH%OpenTelemetry.AutoInstrumentation.StartupHook.dll
55 |
56 | :: Settings for OpenTelemetry
57 | set OTEL_DOTNET_AUTO_HOME=%BASE_PATH%
58 | set OTEL_DOTNET_AUTO_RULE_ENGINE_ENABLED=false
59 |
60 | @echo on
61 | %*
62 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.AutoInstrumentation/_instrument.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | BASE_PATH="$(cd "$(dirname "$0")" && pwd)"
4 |
5 | CORECLR_PROFILER_PATH="$(ls ${BASE_PATH}/OpenTelemetry.AutoInstrumentation.Native.* 2>/dev/null)"
6 |
7 | status=$?
8 | if [ $status -ne 0 ]; then
9 | echo "Unable to locate the native profiler inside current directory, possibly due to runtime identifier not being specified when building/publishing.
10 | Attempting to detect the runtime and use the native profiler from corresponding subdirectory of runtimes directory."
11 |
12 | case "$(uname -s | tr '[:upper:]' '[:lower:]')" in
13 | linux*)
14 | if [ "$(ldd /bin/ls | grep -m1 'musl')" ]; then
15 | OS_TYPE="linux-musl"
16 | else
17 | OS_TYPE="linux"
18 | fi
19 | FILE_EXTENSION=".so"
20 | ;;
21 | darwin*)
22 | OS_TYPE="osx"
23 | FILE_EXTENSION=".dylib"
24 | ;;
25 | esac
26 |
27 | case "$OS_TYPE" in
28 | "linux"|"linux-musl"|"osx")
29 | ;;
30 | *)
31 | echo "Detected operating system type not supported." >&2
32 | exit 1
33 | ;;
34 | esac
35 |
36 | case $(uname -m) in
37 | x86_64) ARCHITECTURE="x64" ;;
38 | aarch64) ARCHITECTURE="arm64" ;;
39 | esac
40 |
41 | case "$ARCHITECTURE" in
42 | "x64"|"arm64")
43 | ;;
44 | *)
45 | echo "Detected architecture not supported." >&2
46 | exit 1
47 | ;;
48 | esac
49 | CORECLR_PROFILER_PATH="${BASE_PATH}/runtimes/${OS_TYPE}-${ARCHITECTURE}/native/OpenTelemetry.AutoInstrumentation.Native${FILE_EXTENSION}"
50 | if [ ! -f "${CORECLR_PROFILER_PATH}" ]; then
51 | echo "Unable to locate the native profiler." >&2
52 | exit 1
53 | fi
54 | fi
55 |
56 | export CORECLR_PROFILER_PATH
57 |
58 | # Settings for .NET
59 | export ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper
60 | export CORECLR_ENABLE_PROFILING=1
61 | export CORECLR_PROFILER="{918728DD-259F-4A6A-AC2B-B85E1B658318}"
62 |
63 | export DOTNET_STARTUP_HOOKS=${BASE_PATH}/OpenTelemetry.AutoInstrumentation.StartupHook.dll
64 |
65 | # Settings for OpenTelemetry
66 | export OTEL_DOTNET_AUTO_HOME=${BASE_PATH}
67 | export OTEL_DOTNET_AUTO_RULE_ENGINE_ENABLED=false
68 |
69 | exec "$@"
70 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.AutoInstrumentation/instrument.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | setlocal
3 |
4 | :: This script is expected to be used in a build that specified a RuntimeIdentifier (RID)
5 | set BASE_PATH=%~dp0
6 |
7 | set OTEL_DOTNET_AUTO_PLUGINS=Elastic.OpenTelemetry.AutoInstrumentationPlugin, Elastic.OpenTelemetry.AutoInstrumentation
8 |
9 | call %BASE_PATH%_instrument.cmd %*
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.AutoInstrumentation/instrument.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 | False
8 | Always
9 | Always
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.AutoInstrumentation/instrument.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # This script is expected to be used in a build that specified a RuntimeIdentifier (RID)
4 | BASE_PATH="$(cd "$(dirname "$0")" && pwd)"
5 |
6 | export OTEL_DOTNET_AUTO_PLUGINS="Elastic.OpenTelemetry.AutoInstrumentationPlugin, Elastic.OpenTelemetry.AutoInstrumentation"
7 |
8 | . $BASE_PATH/_instrument.sh
9 |
10 | exec "$@"
11 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/BuilderState.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace Elastic.OpenTelemetry.Core;
6 |
7 | ///
8 | /// Used to store bootstrap information and a single component instance that will later be
9 | /// tracked per builder (OpenTelemetryBuilder, TracerProviderBuilder, MeterProviderBuilder
10 | /// or LoggerProviderBuilder) instance.
11 | ///
12 | internal sealed class BuilderState(ElasticOpenTelemetryComponents components, string identifier)
13 | {
14 | private int _useElasticDefaultsCounter = 1;
15 |
16 | public ElasticOpenTelemetryComponents Components { get; } = components;
17 |
18 | public string InstanceIdentifier { get; } = identifier;
19 |
20 | public void IncrementWithElasticDefaults() =>
21 | Interlocked.Increment(ref _useElasticDefaultsCounter);
22 |
23 | public int WithElasticDefaultsCounter => _useElasticDefaultsCounter;
24 | }
25 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/ConfigCell.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace Elastic.OpenTelemetry.Configuration;
6 |
7 | internal class ConfigCell(string key, T value)
8 | {
9 | public string Key { get; } = key;
10 | public T? Value { get; private set; } = value;
11 | public ConfigSource Source { get; set; } = ConfigSource.Default;
12 |
13 | public void Assign(T value, ConfigSource source)
14 | {
15 | Value = value;
16 | Source = source;
17 | }
18 |
19 | public override string ToString() => $"{Key}: '{Value}' from [{Source}]";
20 | }
21 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/ConfigSource.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace Elastic.OpenTelemetry.Configuration;
6 |
7 | internal enum ConfigSource
8 | {
9 | Default, // Default value assigned within this class
10 | Environment, // Loaded from an environment variable
11 | // ReSharper disable once InconsistentNaming
12 | IConfiguration, // Bound from an IConfiguration instance
13 | Property, // Set via property initializer
14 | Options // Set via user provided ElasticOpenTelemetryOptions
15 | }
16 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/ElasticOpenTelemetryOptions.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Configuration;
6 | using Microsoft.Extensions.Logging;
7 |
8 | #pragma warning disable IDE0130 // Namespace does not match folder structure
9 | namespace Elastic.OpenTelemetry;
10 | #pragma warning restore IDE0130 // Namespace does not match folder structure
11 |
12 | ///
13 | /// Defines options which can be used to finely-tune the behaviour of the Elastic
14 | /// distribution of OpenTelemetry.
15 | ///
16 | public class ElasticOpenTelemetryOptions
17 | {
18 | ///
19 | /// The output directory where the Elastic Distribution of OpenTelemetry .NET will write log files.
20 | ///
21 | ///
22 | /// When configured, a file log will be created in this directory with the name
23 | /// {ProcessName}_{UtcUnixTimeMilliseconds}_{ProcessId}.instrumentation.log .
24 | /// This log file includes log messages from the OpenTelemetry SDK and the Elastic distribution.
25 | ///
26 | public string? LogDirectory { get; init; }
27 |
28 | ///
29 | /// The log level to use when writing log files.
30 | ///
31 | ///
32 | /// Valid values are:
33 | ///
34 | /// None Disables logging.
35 | /// Critical Failures that require immediate attention.
36 | /// Error Errors and exceptions that cannot be handled.
37 | /// Warning Abnormal or unexpected events.
38 | /// Information General information about the distribution and OpenTelemetry SDK.
39 | /// Debug Rich debugging and development.
40 | /// Trace Contain the most detailed messages.
41 | ///
42 | ///
43 | /// When unset, this defaults to Warning .
44 | ///
45 | ///
46 | public LogLevel? LogLevel { get; init; }
47 |
48 | ///
49 | /// Control the targets that the Elastic Distribution of OpenTelemetry .NET will log to.
50 | ///
51 | public LogTargets? LogTargets { get; init; }
52 |
53 | ///
54 | /// Skips registration of OLTP exporters by the Elastic Distribution of OpenTelemetry .NET.
55 | ///
56 | /// When unset, this defaults to false .
57 | public bool? SkipOtlpExporter { get; init; }
58 |
59 | ///
60 | /// An additional to which logs will be written.
61 | ///
62 | public ILogger? AdditionalLogger { get; init; }
63 |
64 | ///
65 | /// An that can be used to create an additional
66 | /// to which logs will be written.
67 | ///
68 | public ILoggerFactory? AdditionalLoggerFactory { get; init; }
69 |
70 | ///
71 | /// Skips automatic registration of instrumentation libraries via assembly scanning by the Elastic Distribution of OpenTelemetry .NET.
72 | ///
73 | /// When unset, this defaults to false .
74 | public bool? SkipInstrumentationAssemblyScanning { get; init; }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/EnvironmentVariables.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace Elastic.OpenTelemetry.Configuration;
6 |
7 | internal static class EnvironmentVariables
8 | {
9 | // ReSharper disable InconsistentNaming
10 | // ReSharper disable IdentifierTypo
11 | public const string ELASTIC_OTEL_SKIP_OTLP_EXPORTER = nameof(ELASTIC_OTEL_SKIP_OTLP_EXPORTER);
12 | public const string ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING = nameof(ELASTIC_OTEL_SKIP_ASSEMBLY_SCANNING);
13 | public const string ELASTIC_OTEL_LOG_TARGETS = nameof(ELASTIC_OTEL_LOG_TARGETS);
14 |
15 | public const string OTEL_DOTNET_AUTO_LOG_DIRECTORY = nameof(OTEL_DOTNET_AUTO_LOG_DIRECTORY);
16 | public const string OTEL_LOG_LEVEL = nameof(OTEL_LOG_LEVEL);
17 |
18 | public const string DOTNET_RUNNING_IN_CONTAINER = nameof(DOTNET_RUNNING_IN_CONTAINER);
19 | public const string OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED);
20 |
21 | public const string OTEL_DOTNET_AUTO_TRACES_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_TRACES_INSTRUMENTATION_ENABLED);
22 | public const string OTEL_DOTNET_AUTO_METRICS_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_METRICS_INSTRUMENTATION_ENABLED);
23 | public const string OTEL_DOTNET_AUTO_LOGS_INSTRUMENTATION_ENABLED = nameof(OTEL_DOTNET_AUTO_LOGS_INSTRUMENTATION_ENABLED);
24 |
25 | public const string OTEL_EXPORTER_OTLP_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_ENDPOINT);
26 | public const string OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT);
27 | public const string OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_METRICS_ENDPOINT);
28 | public const string OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = nameof(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT);
29 |
30 | public const string OTEL_EXPORTER_OTLP_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_PROTOCOL);
31 | public const string OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_TRACES_PROTOCOL);
32 | public const string OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_METRICS_PROTOCOL);
33 | public const string OTEL_EXPORTER_OTLP_LOGS_PROTOCOL = nameof(OTEL_EXPORTER_OTLP_LOGS_PROTOCOL);
34 |
35 | public const string OTEL_EXPORTER_OTLP_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_TIMEOUT);
36 | public const string OTEL_EXPORTER_OTLP_TRACES_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_TRACES_TIMEOUT);
37 | public const string OTEL_EXPORTER_OTLP_METRICS_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_METRICS_TIMEOUT);
38 | public const string OTEL_EXPORTER_OTLP_LOGS_TIMEOUT = nameof(OTEL_EXPORTER_OTLP_LOGS_TIMEOUT);
39 | public const string OTEL_EXPORTER_OTLP_HEADERS = nameof(OTEL_EXPORTER_OTLP_HEADERS);
40 | public const string OTEL_EXPORTER_OTLP_TRACES_HEADERS = nameof(OTEL_EXPORTER_OTLP_TRACES_HEADERS);
41 | public const string OTEL_EXPORTER_OTLP_METRICS_HEADERS = nameof(OTEL_EXPORTER_OTLP_METRICS_HEADERS);
42 | public const string OTEL_EXPORTER_OTLP_LOGS_HEADERS = nameof(OTEL_EXPORTER_OTLP_LOGS_HEADERS);
43 | // ReSharper enable IdentifierTypo
44 | // ReSharper enable InconsistentNaming
45 | }
46 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/Instrumentations/LogInstrumentation.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using NetEscapades.EnumGenerators;
6 |
7 | namespace Elastic.OpenTelemetry.Configuration.Instrumentations;
8 |
9 | ///
10 | /// A hash set to enable for auto-instrumentation.
11 | ///
12 | ///
13 | /// Explicitly enable specific libraries.
14 | ///
15 | internal class LogInstrumentations(IEnumerable instrumentations) : HashSet(instrumentations)
16 | {
17 | ///
18 | /// All available libraries.
19 | ///
20 | public static readonly LogInstrumentations All = new([.. LogInstrumentationExtensions.GetValues()]);
21 |
22 | ///
23 | public override string ToString()
24 | {
25 | if (Count == 0)
26 | return "None";
27 | if (Count == All.Count)
28 | return "All";
29 | if (All.Count - Count < All.Count)
30 | return $"All Except: {string.Join(", ", All.Except(this).Select(i => i.ToStringFast()))}";
31 |
32 | return string.Join(", ", this.Select(i => i.ToStringFast()));
33 | }
34 | }
35 |
36 | ///
37 | /// Available logging instrumentations.
38 | ///
39 | [EnumExtensions]
40 | internal enum LogInstrumentation
41 | {
42 | /// ILogger instrumentation.
43 | // ReSharper disable once InconsistentNaming
44 | ILogger
45 | }
46 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/Instrumentations/MetricInstrumentation.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using NetEscapades.EnumGenerators;
6 |
7 | namespace Elastic.OpenTelemetry.Configuration.Instrumentations;
8 |
9 | ///
10 | /// A hash set to enable for auto-instrumentation.
11 | ///
12 | ///
13 | /// Explicitly enable specific libraries.
14 | ///
15 | internal class MetricInstrumentations(IEnumerable instrumentations) : HashSet(instrumentations)
16 | {
17 | ///
18 | /// All available libraries.
19 | ///
20 | public static readonly MetricInstrumentations All = new([.. MetricInstrumentationExtensions.GetValues()]);
21 |
22 | ///
23 | public override string ToString()
24 | {
25 | if (Count == 0)
26 | return "None";
27 | if (Count == All.Count)
28 | return "All";
29 | if (All.Count - Count < All.Count)
30 | return $"All Except: {string.Join(", ", All.Except(this).Select(i => i.ToStringFast()))}";
31 |
32 | return string.Join(", ", this.Select(i => i.ToStringFast()));
33 | }
34 | }
35 |
36 | ///
37 | /// Available metric instrumentations.
38 | ///
39 | [EnumExtensions]
40 | internal enum MetricInstrumentation
41 | {
42 | /// ASP.NET Framework.
43 | AspNet,
44 | /// ASP.NET Core.
45 | AspNetCore,
46 | /// System.Net.Http.HttpClient and System.Net.HttpWebRequest metrics.
47 | HttpClient,
48 | /// OpenTelemetry.Instrumentation.Runtime metrics.
49 | NetRuntime,
50 | /// OpenTelemetry.Instrumentation.Process metrics.
51 | Process,
52 | /// NServiceBus metrics.
53 | NServiceBus
54 | }
55 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/Instrumentations/TraceInstrumentation.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using NetEscapades.EnumGenerators;
6 |
7 | namespace Elastic.OpenTelemetry.Configuration.Instrumentations;
8 |
9 | ///
10 | /// A hash set to enable for auto-instrumentation.
11 | ///
12 | ///
13 | /// Explicitly enable specific libraries.
14 | ///
15 | internal class TraceInstrumentations(IEnumerable instrumentations) : HashSet(instrumentations)
16 | {
17 | ///
18 | /// All available libraries.
19 | ///
20 | public static readonly TraceInstrumentations All = new([.. TraceInstrumentationExtensions.GetValues()]);
21 |
22 | ///
23 | public override string ToString()
24 | {
25 | if (Count == 0)
26 | return "None";
27 | if (Count == All.Count)
28 | return "All";
29 | if (All.Count - Count < All.Count)
30 | return $"All Except: {string.Join(", ", All.Except(this).Select(i => i.ToStringFast()))}";
31 |
32 | return string.Join(", ", this.Select(i => i.ToStringFast()));
33 | }
34 | }
35 |
36 | ///
37 | /// Available trace instrumentations.
38 | ///
39 | [EnumExtensions]
40 | internal enum TraceInstrumentation
41 | {
42 | /// ASP.NET (.NET Framework) MVC / WebApi.
43 | AspNet,
44 |
45 | /// ASP.NET Core.
46 | AspNetCore,
47 |
48 | /// Azure SDK.
49 | Azure,
50 |
51 | /// Elastic.Clients.Elasticsearch.
52 | Elasticsearch,
53 |
54 | /// Elastic.Transport.
55 | ElasticTransport,
56 |
57 | /// Microsoft.EntityFrameworkCore.
58 | EntityFrameworkCore,
59 |
60 | /// GraphQL.
61 | Graphql,
62 |
63 | /// Grpc.Net.Client.
64 | GrpcNetClient,
65 |
66 | /// System.Net.Http.HttpClient and System.Net.HttpWebRequest.
67 | HttpClient,
68 |
69 | /// Confluent.Kafka.
70 | Kafka,
71 |
72 | /// MassTransit.
73 | MassTransit,
74 |
75 | /// MongoDB.Driver.
76 | MongoDb,
77 |
78 | /// MySqlConnector.
79 | MysqlConnector,
80 |
81 | /// MySql.Data.
82 | MysqlData,
83 |
84 | /// Npgsql >=6.0.0.
85 | Npgsql,
86 |
87 | /// NServiceBus.
88 | NServiceBus,
89 |
90 | /// Oracle.ManagedDataAccess.Core and Oracle.ManagedDataAccess.
91 | OracleMda,
92 |
93 | /// Quartz.
94 | Quartz,
95 |
96 | /// Microsoft.Data.SqlClient, System.Data.SqlClient and System.Data (shipped with.NET Framework).
97 | SqlClient,
98 |
99 | /// StackExchange.Redis.
100 | StackExchangeRedis,
101 |
102 | /// WCF client.
103 | WcfClient,
104 |
105 | /// WCF server.
106 | WcfService
107 | }
108 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/LogTargets.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace Elastic.OpenTelemetry.Configuration;
6 |
7 | ///
8 | /// Control how the distribution should globally log.
9 | ///
10 | [Flags]
11 | public enum LogTargets
12 | {
13 | ///
14 | /// No global logging.
15 | ///
16 | None,
17 |
18 | ///
19 | /// Enable file logging. Use
20 | /// and to set any values other than the defaults.
21 | ///
22 | File = 1 << 0, //1
23 |
24 | ///
25 | /// Write to standard out, useful in scenarios where file logging might not be an option or harder to set up.
26 | /// e.g. containers, k8s, etc.
27 | ///
28 | StdOut = 1 << 1, //2
29 | }
30 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/Parsers/SharedParsers.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Diagnostics;
6 | using Microsoft.Extensions.Logging;
7 | using static System.StringComparison;
8 | using static System.StringSplitOptions;
9 |
10 | namespace Elastic.OpenTelemetry.Configuration.Parsers;
11 |
12 | internal static class SharedParsers
13 | {
14 | internal static LogLevel? LogLevelParser(string? s) =>
15 | !string.IsNullOrEmpty(s) ? LogLevelHelpers.ToLogLevel(s!) : null;
16 |
17 | internal static LogTargets? LogTargetsParser(string? s)
18 | {
19 | if (string.IsNullOrWhiteSpace(s))
20 | return null;
21 |
22 | var logTargets = LogTargets.None;
23 | var found = false;
24 |
25 | foreach (var target in s!.Split([';', ','], RemoveEmptyEntries))
26 | if (IsSet(target, "stdout"))
27 | logTargets |= LogTargets.StdOut;
28 | else if (IsSet(target, "file"))
29 | logTargets |= LogTargets.File;
30 | else if (IsSet(target, "none"))
31 | logTargets |= LogTargets.None;
32 | return !found ? null : logTargets;
33 |
34 | bool IsSet(string k, string v)
35 | {
36 | var b = k.Trim().Equals(v, InvariantCultureIgnoreCase);
37 | if (b)
38 | found = true;
39 | return b;
40 | }
41 | }
42 |
43 | internal static string? StringParser(string? s) => !string.IsNullOrEmpty(s) ? s : null;
44 |
45 | internal static bool? BoolParser(string? s) =>
46 | s switch
47 | {
48 | "1" => true,
49 | "0" => false,
50 | _ => bool.TryParse(s, out var boolValue) ? boolValue : null
51 | };
52 | }
53 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/SdkActivationMethod.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace Elastic.OpenTelemetry.Configuration;
6 |
7 | internal enum SdkActivationMethod
8 | {
9 | NuGet,
10 | AutoInstrumentation
11 | }
12 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Configuration/Signals.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using NetEscapades.EnumGenerators;
6 |
7 | namespace Elastic.OpenTelemetry.Configuration;
8 |
9 | //
10 | // Observability signals to enable, defaults to all.
11 | //
12 | [Flags]
13 | [EnumExtensions]
14 | internal enum Signals
15 | {
16 | ///
17 | /// No Elastic defaults will be included, acting effectively as a vanilla OpenTelemetry.
18 | ///
19 | None,
20 |
21 | ///
22 | /// Include Elastic Distribution of OpenTelemetry .NET tracing defaults.
23 | ///
24 | Traces = 1 << 0, //1
25 |
26 | ///
27 | /// Include Elastic Distribution of OpenTelemetry .NET metrics defaults.
28 | ///
29 | Metrics = 1 << 1, //2
30 |
31 | ///
32 | /// Include Elastic Distribution of OpenTelemetry .NET logging defaults.
33 | ///
34 | Logs = 1 << 2, //4
35 |
36 | ///
37 | /// (Default) Include all Elastic Distribution of OpenTelemetry .NET logging defaults.
38 | ///
39 | All = ~0
40 | }
41 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Diagnostics/AgentLoggingHelpers.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Diagnostics;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace Elastic.OpenTelemetry.Diagnostics;
9 |
10 | internal static class AgentLoggingHelpers
11 | {
12 | public static void WriteLogLine(
13 | this ILogger logger,
14 | Activity? activity,
15 | int managedThreadId,
16 | DateTime dateTime,
17 | LogLevel logLevel,
18 | string logLine,
19 | string? spanId)
20 | {
21 | var state = new LogState
22 | {
23 | Activity = activity,
24 | ManagedThreadId = managedThreadId,
25 | DateTime = dateTime,
26 | SpanId = spanId
27 | };
28 |
29 | logger.Log(logLevel, 0, state, null, (_, _) => logLine);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Diagnostics/CompositeLogger.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Configuration;
6 | using Elastic.OpenTelemetry.Core;
7 | using Elastic.OpenTelemetry.Diagnostics;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace Elastic.OpenTelemetry.Diagnostics;
11 |
12 | ///
13 | /// A composite logger for use inside the distribution which logs to the
14 | /// and optionally an additional .
15 | ///
16 | ///
17 | /// If disposed, triggers disposal of the .
18 | ///
19 | internal sealed class CompositeLogger(CompositeElasticOpenTelemetryOptions options) : IDisposable, IAsyncDisposable, ILogger
20 | {
21 | public const string LogCategory = "Elastic.OpenTelemetry";
22 |
23 | public FileLogger FileLogger { get; } = new(options);
24 | public StandardOutLogger ConsoleLogger { get; } = new(options);
25 |
26 | private ILogger? _additionalLogger = options.AdditionalLogger;
27 | private bool _isDisposed;
28 |
29 | public void Dispose()
30 | {
31 | _isDisposed = true;
32 | if (_additionalLogger is IDisposable ad)
33 | ad.Dispose();
34 | FileLogger.Dispose();
35 | }
36 |
37 | public async ValueTask DisposeAsync()
38 | {
39 | _isDisposed = true;
40 | if (_additionalLogger is IAsyncDisposable ad)
41 | await ad.DisposeAsync().ConfigureAwait(false);
42 | await FileLogger.DisposeAsync().ConfigureAwait(false);
43 | }
44 |
45 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
46 | {
47 | if (_isDisposed)
48 | return;
49 |
50 | if (FileLogger.IsEnabled(logLevel))
51 | FileLogger.Log(logLevel, eventId, state, exception, formatter);
52 |
53 | if (ConsoleLogger.IsEnabled(logLevel))
54 | ConsoleLogger.Log(logLevel, eventId, state, exception, formatter);
55 |
56 | if (_additionalLogger is not null && _additionalLogger.IsEnabled(logLevel))
57 | _additionalLogger.Log(logLevel, eventId, state, exception, formatter);
58 | }
59 |
60 | public bool LogFileEnabled => FileLogger.FileLoggingEnabled;
61 |
62 | public string LogFilePath => FileLogger.LogFilePath ?? string.Empty;
63 |
64 | public void SetAdditionalLogger(ILogger logger, SdkActivationMethod activationMethod, ElasticOpenTelemetryComponents components)
65 | {
66 | if (HasAdditionalLogger)
67 | return;
68 |
69 | components.Logger.LogInformation("Added additional ILogger to composite logger.");
70 |
71 | _additionalLogger = logger;
72 | _additionalLogger.LogDistroPreamble(activationMethod, components);
73 | }
74 |
75 | internal bool HasAdditionalLogger => _additionalLogger is not null;
76 |
77 | public bool IsEnabled(LogLevel logLevel) =>
78 | ConsoleLogger.IsEnabled(logLevel) || FileLogger.IsEnabled(logLevel) || (_additionalLogger?.IsEnabled(logLevel) ?? false);
79 |
80 | public IDisposable BeginScope(TState state) where TState : notnull =>
81 | new CompositeDisposable(FileLogger.BeginScope(state), _additionalLogger?.BeginScope(state));
82 |
83 | private class CompositeDisposable(params IDisposable?[] disposables) : IDisposable
84 | {
85 | public void Dispose()
86 | {
87 | foreach (var disposable in disposables)
88 | disposable?.Dispose();
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Diagnostics/LogLevelHelpers.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace Elastic.OpenTelemetry.Diagnostics;
8 |
9 | internal static class LogLevelHelpers
10 | {
11 | public const string Critical = "Critical";
12 | public const string Error = "Error";
13 | public const string Warning = "Warning";
14 | public const string Information = "Information";
15 | public const string Debug = "Debug";
16 | public const string Trace = "Trace";
17 | public const string None = "None";
18 |
19 | public static LogLevel? ToLogLevel(string logLevelString)
20 | {
21 | if (logLevelString.Equals(Trace, StringComparison.OrdinalIgnoreCase))
22 | return LogLevel.Trace;
23 | if (logLevelString.Equals(Debug, StringComparison.OrdinalIgnoreCase))
24 | return LogLevel.Debug;
25 | if (logLevelString.Equals("Info", StringComparison.OrdinalIgnoreCase))
26 | return LogLevel.Information;
27 | if (logLevelString.Equals(Information, StringComparison.OrdinalIgnoreCase))
28 | return LogLevel.Information;
29 | if (logLevelString.Equals("Warn", StringComparison.OrdinalIgnoreCase))
30 | return LogLevel.Warning;
31 | if (logLevelString.Equals(Warning, StringComparison.OrdinalIgnoreCase))
32 | return LogLevel.Warning;
33 | if (logLevelString.Equals(Error, StringComparison.OrdinalIgnoreCase))
34 | return LogLevel.Error;
35 | if (logLevelString.Equals(Critical, StringComparison.OrdinalIgnoreCase))
36 | return LogLevel.Critical;
37 | if (logLevelString.Equals(None, StringComparison.OrdinalIgnoreCase))
38 | return LogLevel.None;
39 | return null;
40 | }
41 |
42 | public static string AsString(this LogLevel logLevel) =>
43 | logLevel switch
44 | {
45 | LogLevel.Critical => Critical,
46 | LogLevel.Error => Error,
47 | LogLevel.Warning => Warning,
48 | LogLevel.Information => Information,
49 | LogLevel.Debug => Debug,
50 | LogLevel.Trace => Trace,
51 | LogLevel.None => None,
52 | _ => string.Empty
53 | };
54 | }
55 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Diagnostics/LogState.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Collections;
6 | using System.Diagnostics;
7 |
8 | namespace Elastic.OpenTelemetry.Diagnostics;
9 |
10 | internal class LogState : IReadOnlyList>
11 | {
12 | private readonly Activity? _activity;
13 |
14 | public Activity? Activity
15 | {
16 | get => _activity;
17 | init
18 | {
19 | _values.Add(new KeyValuePair(nameof(Activity), value));
20 | _activity = value;
21 | }
22 | }
23 |
24 | private readonly int _managedThreadId;
25 |
26 | public int ManagedThreadId
27 | {
28 | get => _managedThreadId;
29 | init => _managedThreadId = value;
30 | }
31 |
32 | private readonly DateTime _dateTime;
33 |
34 | public DateTime DateTime
35 | {
36 | get => _dateTime;
37 | init => _dateTime = value;
38 | }
39 |
40 | private readonly string? _spanId;
41 |
42 | public string? SpanId
43 | {
44 | get => _spanId;
45 | init => _spanId = value;
46 | }
47 |
48 | private readonly List> _values = [];
49 |
50 | public IEnumerator> GetEnumerator() => _values.GetEnumerator();
51 |
52 | IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator();
53 |
54 | public int Count => _values.Count;
55 |
56 | public KeyValuePair this[int index] => _values[index];
57 | }
58 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Diagnostics/StandardOutLogger.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Configuration;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace Elastic.OpenTelemetry.Diagnostics;
9 |
10 | internal sealed class StandardOutLogger(CompositeElasticOpenTelemetryOptions options) : ILogger
11 | {
12 | private readonly LogLevel _configuredLogLevel = options.LogLevel;
13 |
14 | private readonly LoggerExternalScopeProvider _scopeProvider = new();
15 |
16 | private bool StandardOutLoggingEnabled { get; } = options.GlobalLogEnabled && options.LogTargets.HasFlag(LogTargets.StdOut);
17 |
18 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
19 | {
20 | if (!IsEnabled(logLevel))
21 | return;
22 |
23 | var logLine = LogFormatter.Format(logLevel, eventId, state, exception, formatter);
24 |
25 | if (logLevel > LogLevel.Warning)
26 | Console.Error.WriteLine(logLine);
27 | else
28 | Console.Out.WriteLine(logLine);
29 | }
30 |
31 | // We skip logging for any log level higher (numerically) than the configured log level
32 | public bool IsEnabled(LogLevel logLevel) => StandardOutLoggingEnabled && _configuredLogLevel <= logLevel;
33 |
34 | public IDisposable BeginScope(TState state) where TState : notnull => _scopeProvider.Push(state);
35 | }
36 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Diagnostics/StringBuilderCache.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 | using System.Text;
5 |
6 | namespace Elastic.OpenTelemetry.Diagnostics;
7 |
8 | // SOURCE: https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/StringBuilderCache.cs
9 | // Licensed to the .NET Foundation under one or more agreements.
10 | // The .NET Foundation licenses this file to you under the MIT license.
11 | internal static class StringBuilderCache
12 | {
13 | // The value 360 was chosen in discussion with performance experts as a compromise between using
14 | // as little memory per thread as possible and still covering a large part of short-lived
15 | // StringBuilder creations on the startup path of VS designers.
16 | internal const int MaxBuilderSize = 360;
17 | private const int DefaultCapacity = 64; // At least as large as the prefix
18 |
19 | [ThreadStatic]
20 | private static StringBuilder? CachedInstance;
21 |
22 | /// Get a StringBuilder for the specified capacity.
23 | /// If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied.
24 | public static StringBuilder Acquire(int capacity = DefaultCapacity)
25 | {
26 | if (capacity <= MaxBuilderSize)
27 | {
28 | var sb = CachedInstance;
29 | if (sb != null)
30 | // Avoid StringBuilder block fragmentation by getting a new StringBuilder
31 | // when the requested size is larger than the current capacity
32 | if (capacity <= sb.Capacity)
33 | {
34 | CachedInstance = null;
35 | sb.Clear();
36 | return sb;
37 | }
38 | }
39 |
40 | return new StringBuilder(capacity);
41 | }
42 |
43 | /// Place the specified builder in the cache if it is not too big.
44 | public static void Release(StringBuilder sb)
45 | {
46 | if (sb.Capacity <= MaxBuilderSize)
47 | CachedInstance = sb;
48 | }
49 |
50 | /// ToString() the StringBuilder, Release it to the cache, and return the resulting string.
51 | public static string GetStringAndRelease(StringBuilder sb)
52 | {
53 | var result = sb.ToString();
54 | Release(sb);
55 | return result;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Elastic.OpenTelemetry.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Library
5 | netstandard2.0;netstandard2.1;net462;net8.0;net9.0
6 | enable
7 | enable
8 | false
9 | latest
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/ElasticOpenTelemetryComponents.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Configuration;
6 | using Elastic.OpenTelemetry.Diagnostics;
7 | using Microsoft.Extensions.Logging;
8 | using Microsoft.Extensions.Logging.Abstractions;
9 |
10 | namespace Elastic.OpenTelemetry.Core;
11 |
12 | internal sealed class ElasticOpenTelemetryComponents(
13 | CompositeLogger logger,
14 | LoggingEventListener loggingEventListener,
15 | CompositeElasticOpenTelemetryOptions options) : IDisposable, IAsyncDisposable
16 | {
17 | public CompositeLogger Logger { get; } = logger;
18 | public LoggingEventListener LoggingEventListener { get; } = loggingEventListener;
19 | public CompositeElasticOpenTelemetryOptions Options { get; } = options;
20 |
21 | internal void SetAdditionalLogger(ILogger logger, SdkActivationMethod activationMethod)
22 | {
23 | if (logger is not NullLogger)
24 | Logger.SetAdditionalLogger(logger, activationMethod, this);
25 | }
26 |
27 | public void Dispose()
28 | {
29 | Logger.Dispose();
30 | LoggingEventListener.Dispose();
31 | }
32 |
33 | public async ValueTask DisposeAsync()
34 | {
35 | await Logger.DisposeAsync().ConfigureAwait(false);
36 | await LoggingEventListener.DisposeAsync().ConfigureAwait(false);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Exporters/ElasticUserAgentHandler.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | #if NETFRAMEWORK
6 | using System.Net.Http;
7 | #endif
8 |
9 | namespace Elastic.OpenTelemetry.Exporters;
10 |
11 | internal class ElasticUserAgentHandler(string userAgent) : HttpClientHandler
12 | {
13 | private readonly string _userAgent = userAgent;
14 |
15 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
16 | {
17 | request.Headers.Remove("User-Agent");
18 | request.Headers.Add("User-Agent", _userAgent);
19 |
20 | return base.SendAsync(request, cancellationToken);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Exporters/OtlpExporterDefaults.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Core;
6 | using OpenTelemetry.Exporter;
7 |
8 | #if NETFRAMEWORK
9 | using System.Net.Http;
10 | #endif
11 |
12 | namespace Elastic.OpenTelemetry.Exporters;
13 |
14 | internal static class OtlpExporterDefaults
15 | {
16 | internal static readonly HttpMessageHandler Handler = new ElasticUserAgentHandler($"elastic-otlp-dotnet/{VersionHelper.InformationalVersion}");
17 |
18 | public static void OtlpExporterOptions(OtlpExporterOptions options) =>
19 | options.HttpClientFactory = () =>
20 | {
21 | var client = new HttpClient(Handler);
22 | return client;
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Extensions/ActivityExtensions.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Diagnostics;
6 | using Elastic.OpenTelemetry.Processors;
7 |
8 | #pragma warning disable IDE0130 // Namespace does not match folder structure
9 | namespace Elastic.OpenTelemetry.Core;
10 | #pragma warning restore IDE0130 // Namespace does not match folder structure
11 |
12 | internal static class ActivityExtensions
13 | {
14 | public static bool TryCompress(this Activity buffered, Activity sibling)
15 | {
16 | Composite? composite = null;
17 |
18 | var property = buffered.GetCustomProperty("Composite");
19 |
20 | if (property is Composite c)
21 | composite = c;
22 |
23 | var isAlreadyComposite = composite is not null;
24 |
25 | var canBeCompressed = isAlreadyComposite
26 | ? buffered.TryToCompressComposite(sibling, composite!)
27 | : buffered.TryToCompressRegular(sibling, ref composite);
28 |
29 | if (!canBeCompressed)
30 | return false;
31 |
32 | if (!isAlreadyComposite)
33 | {
34 | composite ??= new Composite();
35 | composite.Count = 1;
36 | composite.DurationSum = buffered.Duration.Milliseconds;
37 | }
38 |
39 | composite!.Count++;
40 | composite.DurationSum += sibling.Duration.Milliseconds;
41 |
42 | buffered.SetCustomProperty("Composite", composite);
43 |
44 | var endTime = sibling.StartTimeUtc.Add(sibling.Duration);
45 | buffered.SetEndTime(endTime);
46 |
47 | sibling.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
48 |
49 | return true;
50 | }
51 |
52 | private static bool TryToCompressRegular(this Activity buffered, Activity sibling, ref Composite? composite)
53 | {
54 | if (!buffered.IsSameKind(sibling))
55 | return false;
56 |
57 | if (buffered.OperationName == sibling.OperationName)
58 | {
59 | // TODO - Duration configuration check
60 |
61 | composite ??= new Composite();
62 | composite.CompressionStrategy = "exact_match";
63 | return true;
64 | }
65 |
66 | // TODO - Duration configuration check
67 | composite ??= new Composite();
68 | composite.CompressionStrategy = "same_kind";
69 | // TODO - Set name
70 | return true;
71 | }
72 |
73 | private static bool TryToCompressComposite(this Activity buffered, Activity sibling, Composite composite) =>
74 | composite.CompressionStrategy switch
75 | {
76 | "exact_match" => buffered.IsSameKind(sibling) && buffered.OperationName == sibling.OperationName,// && sibling.Duration <= Configuration.SpanCompressionExactMatchMaxDuration;
77 | "same_kind" => buffered.IsSameKind(sibling),// && sibling.Duration <= Configuration.SpanCompressionSameKindMaxDuration;
78 | _ => false,
79 | };
80 |
81 | // TODO - Further implementation if possible
82 | private static bool IsSameKind(this Activity current, Activity other) =>
83 | current.Kind == other.Kind;
84 | // We don't have a direct way to establish which attribute(s) to use to assess these
85 | //&& Subtype == other.Subtype
86 | //&& _context.IsValueCreated && other._context.IsValueCreated
87 | //&& Context?.Service?.Target == other.Context?.Service?.Target;
88 | }
89 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Extensions/LoggerFactoryExtensions.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Diagnostics;
6 | using Microsoft.Extensions.Logging;
7 |
8 | #pragma warning disable IDE0130 // Namespace does not match folder structure
9 | namespace Elastic.OpenTelemetry.Core;
10 | #pragma warning restore IDE0130 // Namespace does not match folder structure
11 |
12 | internal static class LoggerFactoryExtensions
13 | {
14 | public static ILogger CreateElasticLogger(this ILoggerFactory loggerFactory) =>
15 | loggerFactory.CreateLogger(CompositeLogger.LogCategory);
16 | }
17 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Extensions/OpenTelemetryLoggerOptionsExtensions.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Configuration;
6 | using Elastic.OpenTelemetry.Diagnostics;
7 | using Microsoft.Extensions.Logging;
8 | using OpenTelemetry.Logs;
9 |
10 | #pragma warning disable IDE0130 // Namespace does not match folder structure
11 | namespace OpenTelemetry;
12 | #pragma warning restore IDE0130 // Namespace does not match folder structure
13 |
14 | ///
15 | /// Extension methods for used to register
16 | /// the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
17 | ///
18 | internal static class OpenTelemetryLoggerOptionsExtensions
19 | {
20 | ///
21 | /// Ensures Elastic Distribution of OpenTelemetry (EDOT) .NET options are set for .
22 | ///
23 | /// The to configure.
24 | /// An to use for diagnostic logging.
25 | /// Thrown when the is null.
26 | /// Thrown when the is null.
27 | public static void WithElasticDefaults(this OpenTelemetryLoggerOptions options, ILogger logger)
28 | {
29 | #if NET
30 | ArgumentNullException.ThrowIfNull(options);
31 | ArgumentNullException.ThrowIfNull(logger);
32 | #else
33 | if (options is null)
34 | throw new ArgumentNullException(nameof(options));
35 |
36 | if (logger is null)
37 | throw new ArgumentNullException(nameof(logger));
38 | #endif
39 |
40 | options.IncludeFormattedMessage = true;
41 |
42 | // IncludeScopes is disabled until we have a resolution to duplicate attributes
43 | // See:
44 | // - https://github.com/open-telemetry/opentelemetry-dotnet/issues/4324
45 | // - https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/39304
46 | // options.IncludeScopes = true;
47 |
48 | logger.LogConfiguredSignalProvider(nameof(Signals.Logs), nameof(OpenTelemetryLoggerOptions), "");
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Extensions/ResourceBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Core;
6 | using Elastic.OpenTelemetry.Diagnostics;
7 | using Elastic.OpenTelemetry.SemanticConventions;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using OpenTelemetry.Resources;
10 |
11 | #pragma warning disable IDE0130 // Namespace does not match folder structure
12 | namespace Elastic.OpenTelemetry.Resources;
13 | #pragma warning restore IDE0130 // Namespace does not match folder structure
14 |
15 | internal static class ResourceBuilderExtensions
16 | {
17 | ///
18 | /// The unique ID for this instance of the application/service.
19 | ///
20 | private static readonly string ApplicationInstanceId = Guid.NewGuid().ToString();
21 |
22 | ///
23 | /// Used to track the number of times any variation of `WithElasticDefaults` is invoked by consuming
24 | /// code across all instances. This allows us to warn about potential
25 | /// misconfigurations.
26 | ///
27 | private static int WithElasticDefaultsCallCount;
28 |
29 | internal static ResourceBuilder WithElasticDefaultsCore(
30 | this ResourceBuilder builder,
31 | BuilderState builderState,
32 | IServiceCollection? services,
33 | Action? configure) =>
34 | WithElasticDefaultsCore(builder, builderState.Components, services, configure);
35 |
36 | internal static ResourceBuilder WithElasticDefaultsCore(
37 | this ResourceBuilder builder,
38 | ElasticOpenTelemetryComponents components,
39 | IServiceCollection? services,
40 | Action? configure)
41 | {
42 | var callCount = Interlocked.Increment(ref WithElasticDefaultsCallCount);
43 |
44 | components.Logger.LogWithElasticDefaultsCallCount(callCount, nameof(ResourceBuilder));
45 |
46 | return SignalBuilder.WithElasticDefaults(builder, components.Options, components, services,
47 | (ResourceBuilder builder, BuilderState builderState, IServiceCollection? services) =>
48 | {
49 | var attributes = new Dictionary
50 | {
51 | { ResourceSemanticConventions.AttributeServiceInstanceId, ApplicationInstanceId },
52 | { ResourceSemanticConventions.AttributeTelemetryDistroName, "elastic" },
53 | { ResourceSemanticConventions.AttributeTelemetryDistroVersion, VersionHelper.InformationalVersion }
54 | };
55 |
56 | builder.AddAttributes(attributes);
57 |
58 | foreach (var attribute in attributes)
59 | {
60 | if (attribute.Value is not null)
61 | {
62 | var value = attribute.Value.ToString() ?? "";
63 | builderState.Components.Logger.LogAddingResourceAttribute(attribute.Key, value, builderState.InstanceIdentifier);
64 | }
65 | }
66 |
67 | configure?.Invoke(builder, builderState);
68 | });
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Instrumentation/ContribResourceDetectors.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Core;
6 |
7 | namespace Elastic.OpenTelemetry.Instrumentation;
8 |
9 | internal static class ContribResourceDetectors
10 | {
11 | // Note: We do not currently attempt to automatically register detectors for cloud
12 | // environments such as AWS, Azure and GCP because each have several different
13 | // deployment scenarios with related extension methods. Therefore, it is best for the
14 | // consumer to register those explicitly.
15 |
16 | // Note: This is defined as a static method and allocates the array each time.
17 | // This is intentional, as we expect this to be invoked once (or worst case, few times).
18 | // After initialisation, the array is no longer required and can be reclaimed by the GC.
19 | // This is likley to be overall more efficient for the common scenario as we don't keep
20 | // an object alive for the lifetime of the application.
21 | public static InstrumentationAssemblyInfo[] GetContribResourceDetectors() =>
22 | [
23 | new()
24 | {
25 | Name = "Container",
26 | AssemblyName = "OpenTelemetry.Resources.Container",
27 | FullyQualifiedType = "OpenTelemetry.Resources.ContainerResourceBuilderExtensions",
28 | InstrumentationMethod = "AddContainerDetector"
29 | },
30 |
31 | new()
32 | {
33 | Name = "OperatingSystem",
34 | AssemblyName = "OpenTelemetry.Resources.OperatingSystem",
35 | FullyQualifiedType = "OpenTelemetry.Resources.OperatingSystemResourceBuilderExtensions",
36 | InstrumentationMethod = "AddOperatingSystemDetector"
37 | },
38 |
39 | new()
40 | {
41 | Name = "Process",
42 | AssemblyName = "OpenTelemetry.Resources.Process",
43 | FullyQualifiedType = "OpenTelemetry.Resources.ProcessResourceBuilderExtensions",
44 | InstrumentationMethod = "AddProcessDetector"
45 | }
46 | ];
47 | }
48 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Instrumentation/InstrumentationAssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace Elastic.OpenTelemetry.Core;
6 |
7 | internal readonly struct InstrumentationAssemblyInfo
8 | {
9 | public readonly required string Name { get; init; }
10 | public readonly required string AssemblyName { get; init; }
11 | public readonly required string FullyQualifiedType { get; init; }
12 | public readonly required string InstrumentationMethod { get; init; }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Processors/Composite.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace Elastic.OpenTelemetry.Processors;
6 |
7 | // TODO - Consider a struct, but consider if this would get copied too much
8 | internal class Composite
9 | {
10 | ///
11 | /// A string value indicating which compression strategy was used. The valid values are `exact_match` and `same_kind`
12 | ///
13 | public string CompressionStrategy { get; set; } = "exact_match";
14 |
15 | ///
16 | /// Count is the number of compressed spans the composite span represents. The minimum count is 2, as a composite span represents at least two spans.
17 | ///
18 | public int Count { get; set; }
19 |
20 | ///
21 | /// Sum of the durations of all compressed spans this composite span represents in milliseconds.
22 | ///
23 | public double DurationSum { get; set; }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Processors/SpanCompressionProcessor.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 | using System.Diagnostics;
5 | using System.Runtime.CompilerServices;
6 | using Elastic.OpenTelemetry.Core;
7 | using Elastic.OpenTelemetry.Processors;
8 | using OpenTelemetry;
9 |
10 | #pragma warning disable IDE0130 // Namespace does not match folder structure
11 | namespace Elastic.OpenTelemetry.Processors;
12 | #pragma warning restore IDE0130 // Namespace does not match folder structure
13 |
14 | /// A processor that can mark spans as compressed/composite
15 | internal sealed class SpanCompressionProcessor : BaseProcessor
16 | {
17 | private readonly ConditionalWeakTable _compressionBuffer = new();
18 |
19 | ///
20 | public override void OnStart(Activity data)
21 | {
22 | if (data.DisplayName == "ChildSpanCompression")
23 | data.SetCustomProperty("IsExitSpan", true); // Later, we'll have to infer this from the Activity Source and Name (if practical)
24 |
25 | base.OnStart(data);
26 | }
27 |
28 | ///
29 | public override void OnEnd(Activity data)
30 | {
31 | if (data.Parent is null)
32 | {
33 | base.OnEnd(data);
34 | return;
35 | }
36 |
37 | var property = data.GetCustomProperty("IsExitSpan");
38 |
39 | if (!IsCompressionEligible(data, property) || data.Parent!.IsStopped)
40 | {
41 | FlushBuffer(data.Parent!);
42 | base.OnEnd(data);
43 | return;
44 | }
45 |
46 | if (_compressionBuffer.TryGetValue(data.Parent!, out var compressionBuffer))
47 | if (!compressionBuffer.TryCompress(data))
48 | {
49 | FlushBuffer(data.Parent!);
50 | _compressionBuffer.Add(data.Parent!, data);
51 | }
52 | else
53 | {
54 | _compressionBuffer.Add(data.Parent!, data);
55 | data.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
56 | }
57 |
58 | base.OnEnd(data);
59 |
60 | static bool IsCompressionEligible(Activity data, object? property) =>
61 | property is true && data.Status is ActivityStatusCode.Ok or ActivityStatusCode.Unset;
62 | }
63 |
64 | private void FlushBuffer(Activity data)
65 | {
66 | if (!_compressionBuffer.TryGetValue(data, out var compressionBuffer))
67 | return;
68 |
69 | // This recreates the initial activity now we know it's final end time and can record it.
70 | using var activity = compressionBuffer.Source.StartActivity(compressionBuffer.DisplayName, compressionBuffer.Kind, compressionBuffer.Parent!.Context,
71 | compressionBuffer.TagObjects, compressionBuffer.Links, compressionBuffer.StartTimeUtc);
72 |
73 | var property = compressionBuffer.GetCustomProperty("Composite");
74 |
75 | if (property is Composite composite)
76 | {
77 | activity?.SetTag("span_compression.strategy", composite.CompressionStrategy);
78 | activity?.SetTag("span_compression.count", composite.Count);
79 | activity?.SetTag("span_compression.duration", composite.DurationSum);
80 | }
81 |
82 | var endTime = compressionBuffer.StartTimeUtc.Add(compressionBuffer.Duration);
83 | activity?.SetEndTime(endTime);
84 | activity?.Stop();
85 | _compressionBuffer.Remove(data);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Processors/SpanCounterProcessor.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 | using System.Diagnostics;
5 | using System.Diagnostics.Metrics;
6 | using OpenTelemetry;
7 |
8 | #pragma warning disable IDE0130 // Namespace does not match folder structure
9 | namespace Elastic.OpenTelemetry.Processors;
10 | #pragma warning restore IDE0130 // Namespace does not match folder structure
11 |
12 | /// An example processor that emits the number of spans as a metric
13 | internal sealed class SpanCounterProcessor : BaseProcessor
14 | {
15 | private static readonly Meter Meter = new("Elastic.OpenTelemetry", "1.0.0");
16 | private static readonly Counter Counter = Meter.CreateCounter("span-export-count");
17 |
18 | ///
19 | public override void OnEnd(Activity data)
20 | {
21 | Counter.Add(1);
22 | base.OnEnd(data);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/Processors/StackTraceProcessor.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 | using System.Diagnostics;
5 | using OpenTelemetry;
6 |
7 | #pragma warning disable IDE0130 // Namespace does not match folder structure
8 | namespace Elastic.OpenTelemetry.Processors;
9 | #pragma warning restore IDE0130 // Namespace does not match folder structure
10 |
11 | /// A processor that includes stack trace information of long running spans.
12 | internal sealed class StackTraceProcessor : BaseProcessor
13 | {
14 | ///
15 | public override void OnStart(Activity data)
16 | {
17 | //for now always capture stack trace on start
18 | var stackTrace = new StackTrace(true);
19 | data.SetCustomProperty("_stack_trace", stackTrace);
20 | base.OnStart(data);
21 | }
22 |
23 | ///
24 | public override void OnEnd(Activity data)
25 | {
26 | if (data.GetCustomProperty("_stack_trace") is not StackTrace stackTrace)
27 | return;
28 | if (data.Duration < TimeSpan.FromMilliseconds(2))
29 | return;
30 |
31 | data.SetTag("code.stacktrace", stackTrace);
32 | base.OnEnd(data);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/SemanticConventions/ResourceSemanticConventions.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 | namespace Elastic.OpenTelemetry.SemanticConventions;
5 |
6 | internal static class ResourceSemanticConventions
7 | {
8 | public const string AttributeTelemetryDistroName = "telemetry.distro.name";
9 | public const string AttributeTelemetryDistroVersion = "telemetry.distro.version";
10 |
11 | public const string AttributeServiceName = "service.name";
12 | public const string AttributeServiceInstanceId = "service.instance.id";
13 | }
14 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/SemanticConventions/TraceSemanticConventions.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace Elastic.OpenTelemetry.SemanticConventions;
6 |
7 | internal static class TraceSemanticConventions
8 | {
9 | // HTTP
10 | public const string HttpScheme = "http.scheme";
11 | public const string HttpTarget = "http.target";
12 |
13 | // NET
14 | public const string NetHostName = "net.host.name";
15 | public const string NetHostPort = "net.host.port";
16 |
17 | // SERVER
18 | public const string ServerAddress = "server.address";
19 | public const string ServerPort = "server.port";
20 |
21 | // URL
22 | public const string UrlPath = "url.path";
23 | public const string UrlQuery = "url.query";
24 | public const string UrlScheme = "url.scheme";
25 | }
26 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry.Core/VersionHelper.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 | using System.Reflection;
5 |
6 | namespace Elastic.OpenTelemetry.Core;
7 |
8 | internal static class VersionHelper
9 | {
10 | static VersionHelper()
11 | {
12 | var assemblyInformationalVersion = typeof(VersionHelper).Assembly.GetCustomAttribute()?.InformationalVersion;
13 | InformationalVersion = ParseAssemblyInformationalVersion(assemblyInformationalVersion);
14 | }
15 |
16 | internal static string InformationalVersion { get; }
17 |
18 | private static string ParseAssemblyInformationalVersion(string? informationalVersion)
19 | {
20 | if (string.IsNullOrWhiteSpace(informationalVersion))
21 | informationalVersion = "1.0.0";
22 |
23 | /*
24 | * InformationalVersion will be in the following format:
25 | * {majorVersion}.{minorVersion}.{patchVersion}.{pre-release label}.{pre-release version}.{gitHeight}+{Git SHA of current commit}
26 | * Ex: 1.5.0-alpha.1.40+807f703e1b4d9874a92bd86d9f2d4ebe5b5d52e4
27 | * The following parts are optional: pre-release label, pre-release version, git height, Git SHA of current commit
28 | */
29 |
30 | var indexOfPlusSign = informationalVersion!.IndexOf('+');
31 | return indexOfPlusSign > 0
32 | ? informationalVersion[..indexOfPlusSign]
33 | : informationalVersion;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Library
5 | netstandard2.0;netstandard2.1;net462;net8.0;net9.0
6 | Elastic Distribution of OpenTelemetry .NET
7 | OpenTelemetry extensions for Elastic Observability.
8 | Elastic;OpenTelemetry;OTel;Observability;APM;Monitoring;Logging;Metrics;Tracing;Telemetry
9 | enable
10 | enable
11 | true
12 | true
13 | $(NoWarn);NU5131
14 | latest
15 |
16 |
17 |
18 | true
19 | true
20 | false
21 | true
22 |
23 |
24 |
25 | $(NoWarn);nullable
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry/Extensions/ResourceBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Diagnostics.CodeAnalysis;
6 | using Elastic.OpenTelemetry.Core;
7 | using Elastic.OpenTelemetry.Diagnostics;
8 | using Elastic.OpenTelemetry.Instrumentation;
9 | using Elastic.OpenTelemetry.Resources;
10 | using Microsoft.Extensions.DependencyInjection;
11 |
12 | #if NET
13 | using System.Runtime.CompilerServices;
14 | #endif
15 |
16 | #pragma warning disable IDE0130 // Namespace does not match folder structure
17 | namespace OpenTelemetry.Resources;
18 | #pragma warning restore IDE0130 // Namespace does not match folder structure
19 |
20 | ///
21 | /// Provides extension methods on the used to register
22 | /// the Elastic Distribution of OpenTelemetry (EDOT) .NET defaults.
23 | ///
24 | internal static class ResourceBuilderExtensions
25 | {
26 | ///
27 | /// Used to track the number of times any variation of `WithElasticDefaults` is invoked by consuming
28 | /// code across all instances. This allows us to warn about potenital
29 | /// misconfigurations.
30 | ///
31 | private static int WithElasticDefaultsCallCount;
32 |
33 | internal static ResourceBuilder WithElasticDefaults(this ResourceBuilder builder, BuilderState builderState, IServiceCollection? services) =>
34 | WithElasticDefaults(builder, builderState.Components, services);
35 |
36 | internal static ResourceBuilder WithElasticDefaults(this ResourceBuilder builder, ElasticOpenTelemetryComponents components, IServiceCollection? services)
37 | {
38 | var callCount = Interlocked.Increment(ref WithElasticDefaultsCallCount);
39 |
40 | components.Logger.LogWithElasticDefaultsCallCount(callCount, nameof(ResourceBuilder));
41 |
42 | return builder.WithElasticDefaultsCore(components, services, ConfigureBuilder);
43 | }
44 |
45 | [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "The call to `AssemblyScanning.AddInstrumentationViaReflection` " +
46 | "is guarded by a RuntimeFeature.IsDynamicCodeSupported` check and therefore this method is safe to call in AoT scenarios.")]
47 | private static void ConfigureBuilder(ResourceBuilder builder, BuilderState builderState)
48 | {
49 | builder.AddHostDetector();
50 | builderState.Components.Logger.LogResourceDetectorAdded("HostDetector", builderState.InstanceIdentifier);
51 |
52 | builder.AddProcessRuntimeDetector();
53 | builderState.Components.Logger.LogResourceDetectorAdded("ProcessRuntimeDetector", builderState.InstanceIdentifier);
54 |
55 | #if NET
56 | if (RuntimeFeature.IsDynamicCodeSupported)
57 | #endif
58 | {
59 | SignalBuilder.AddInstrumentationViaReflection(builder, builderState.Components,
60 | ContribResourceDetectors.GetContribResourceDetectors(), builderState.InstanceIdentifier);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry/Hosting/ElasticOpenTelemetryService.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Core;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.Hosting;
8 | using Microsoft.Extensions.Logging;
9 | using Microsoft.Extensions.Logging.Abstractions;
10 |
11 | namespace Elastic.OpenTelemetry.Hosting;
12 |
13 | ///
14 | /// Used to attempt to attach an additional logger, typically in ASP.NET Core scenarios, so that logs
15 | /// are written to any configured destinations.
16 | ///
17 | internal sealed class ElasticOpenTelemetryService(IServiceProvider serviceProvider) : IHostedLifecycleService
18 | {
19 | private ElasticOpenTelemetryComponents? _components;
20 |
21 | public Task StartingAsync(CancellationToken cancellationToken)
22 | {
23 | var loggerFactory = serviceProvider.GetService();
24 | var logger = loggerFactory?.CreateElasticLogger() ?? NullLogger.Instance;
25 |
26 | _components = serviceProvider.GetService();
27 | _components?.SetAdditionalLogger(logger, ElasticOpenTelemetry.ActivationMethod);
28 |
29 | return Task.CompletedTask;
30 | }
31 |
32 | public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
33 |
34 | public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
35 |
36 | public Task StoppingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
37 |
38 | public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
39 |
40 | public async Task StoppedAsync(CancellationToken cancellationToken)
41 | {
42 | if (_components?.Logger is not null)
43 | await _components.Logger.DisposeAsync().ConfigureAwait(false);
44 |
45 | if (_components?.LoggingEventListener is not null)
46 | await _components.LoggingEventListener.DisposeAsync().ConfigureAwait(false);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Elastic.OpenTelemetry/Instrumentation/ContribMetricsInstrumentation.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Core;
6 |
7 | namespace Elastic.OpenTelemetry.Instrumentation;
8 |
9 | internal static class ContribMetricsInstrumentation
10 | {
11 | // Note: This is defined as a static method and allocates the array each time.
12 | // This is intentional, as we expect this to be invoked once (or worst case, few times).
13 | // After initialisation, the array is no longer required and can be reclaimed by the GC.
14 | // This is likley to be overall more efficient for the common scenario as we don't keep
15 | // an object alive for the lifetime of the application.
16 | public static InstrumentationAssemblyInfo[] GetMetricsInstrumentationAssembliesInfo() =>
17 | [
18 | new()
19 | {
20 | Name = "AspNet",
21 | AssemblyName = "OpenTelemetry.Instrumentation.AspNet",
22 | FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions",
23 | InstrumentationMethod = "AddAspNetInstrumentation"
24 | },
25 |
26 | new()
27 | {
28 | Name = "AspNetCore",
29 | AssemblyName = "OpenTelemetry.Instrumentation.AspNetCore",
30 | FullyQualifiedType = "OpenTelemetry.Metrics.AspNetCoreInstrumentationMeterProviderBuilderExtensions",
31 | InstrumentationMethod = "AddAspNetCoreInstrumentation"
32 | },
33 |
34 | new()
35 | {
36 | Name = "AWS",
37 | AssemblyName = "OpenTelemetry.Instrumentation.AWS",
38 | FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions",
39 | InstrumentationMethod = "AddAWSInstrumentation"
40 | },
41 |
42 | new()
43 | {
44 | Name = "Cassandra",
45 | AssemblyName = "OpenTelemetry.Instrumentation.Cassandra",
46 | FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions",
47 | InstrumentationMethod = "AddCassandraInstrumentation"
48 | },
49 |
50 | new()
51 | {
52 | Name = "Kafka (Producer)",
53 | AssemblyName = "OpenTelemetry.Instrumentation.ConfluentKafka",
54 | FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions",
55 | InstrumentationMethod = "AddKafkaProducerInstrumentation"
56 | },
57 |
58 | new()
59 | {
60 | Name = "Kafka (Consumer)",
61 | AssemblyName = "OpenTelemetry.Instrumentation.ConfluentKafka",
62 | FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions",
63 | InstrumentationMethod = "AddKafkaConsumerInstrumentation"
64 | },
65 |
66 | new()
67 | {
68 | Name = "EventCounters",
69 | AssemblyName = "OpenTelemetry.Instrumentation.EventCounters",
70 | FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions",
71 | InstrumentationMethod = "AddEventCountersInstrumentation"
72 | },
73 |
74 | new()
75 | {
76 | Name = "HTTP",
77 | AssemblyName = "OpenTelemetry.Instrumentation.Http",
78 | FullyQualifiedType = "OpenTelemetry.Metrics.HttpClientInstrumentationMeterProviderBuilderExtensions",
79 | InstrumentationMethod = "AddHttpClientInstrumentation"
80 | },
81 |
82 | new()
83 | {
84 | Name = "Runtime",
85 | AssemblyName = "OpenTelemetry.Instrumentation.Runtime",
86 | FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions",
87 | InstrumentationMethod = "AddRuntimeInstrumentation"
88 | },
89 |
90 | new()
91 | {
92 | Name = "Process",
93 | AssemblyName = "OpenTelemetry.Instrumentation.Process",
94 | FullyQualifiedType = "OpenTelemetry.Metrics.MeterProviderBuilderExtensions",
95 | InstrumentationMethod = "AddProcessInstrumentation"
96 | }
97 | ];
98 | }
99 |
--------------------------------------------------------------------------------
/test-applications/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 | $(SolutionRoot)\build\keys\keypair.snk
7 |
8 |
--------------------------------------------------------------------------------
/test-applications/WebApi/Program.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | var builder = WebApplication.CreateBuilder(args);
6 |
7 | var app = builder.Build();
8 |
9 | app.MapGet("/", () => { });
10 |
11 | app.MapGet("/http", static async ctx =>
12 | {
13 | var client = new HttpClient();
14 | using var response = await client.GetAsync("https://example.com");
15 | ctx.Response.StatusCode = 200;
16 | });
17 |
18 | app.Run();
19 |
20 | public partial class Program { }
21 |
--------------------------------------------------------------------------------
/test-applications/WebApi/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "http": {
5 | "commandName": "Project",
6 | "dotnetRunMessages": true,
7 | "launchBrowser": false,
8 | "applicationUrl": "http://localhost:5134",
9 | "environmentVariables": {
10 | "ASPNETCORE_ENVIRONMENT": "Development"
11 | }
12 | },
13 | "https": {
14 | "commandName": "Project",
15 | "dotnetRunMessages": true,
16 | "launchBrowser": false,
17 | "applicationUrl": "https://localhost:7208;http://localhost:5134",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test-applications/WebApi/README.md:
--------------------------------------------------------------------------------
1 | # WebApi Test Application
2 |
3 | This is a .NET 9, minimal API project, used for in-memory integration testing via
4 | the `WebApplicationBuilder` from `Microsoft.AspNetCore.Mvc.Testing`.
--------------------------------------------------------------------------------
/test-applications/WebApi/WebApi.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test-applications/WebApi/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/test-applications/WebApiDotNet8/WebApiDotNet8/Program.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | namespace WebApiDotNet8;
6 |
7 | public class ProgramV8
8 | {
9 | public static void Main(string[] args)
10 | {
11 | var builder = WebApplication.CreateBuilder(args);
12 |
13 | var app = builder.Build();
14 |
15 | app.MapGet("/", () => { });
16 |
17 | app.MapGet("/http", static async ctx =>
18 | {
19 | var client = new HttpClient();
20 | using var response = await client.GetAsync("https://example.com");
21 | ctx.Response.StatusCode = 200;
22 | });
23 |
24 | app.Run();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test-applications/WebApiDotNet8/WebApiDotNet8/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:18563",
8 | "sslPort": 44316
9 | }
10 | },
11 | "profiles": {
12 | "http": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "launchUrl": "weatherforecast",
17 | "applicationUrl": "http://localhost:5226",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | },
22 | "https": {
23 | "commandName": "Project",
24 | "dotnetRunMessages": true,
25 | "launchBrowser": true,
26 | "launchUrl": "weatherforecast",
27 | "applicationUrl": "https://localhost:7156;http://localhost:5226",
28 | "environmentVariables": {
29 | "ASPNETCORE_ENVIRONMENT": "Development"
30 | }
31 | },
32 | "IIS Express": {
33 | "commandName": "IISExpress",
34 | "launchBrowser": true,
35 | "launchUrl": "weatherforecast",
36 | "environmentVariables": {
37 | "ASPNETCORE_ENVIRONMENT": "Development"
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test-applications/WebApiDotNet8/WebApiDotNet8/WebApiDotNet8.http:
--------------------------------------------------------------------------------
1 | @WebApiDotNet8_HostAddress = http://localhost:5226
2 |
3 | GET {{WebApiDotNet8_HostAddress}}/weatherforecast/
4 | Accept: application/json
5 |
6 | ###
7 |
--------------------------------------------------------------------------------
/test-applications/WebApiDotNet8/WebApiDotNet8/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/tests/.runsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/AutoInstrumentation.IntegrationTests/AutoInstrumentation.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | Elastic.OpenTelemetry.AutoInstrumentation.IntegrationTests
8 | xUnit1041
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/tests/AutoInstrumentation.IntegrationTests/ExampleApplicationContainer.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Runtime.InteropServices;
6 | using DotNet.Testcontainers;
7 | using DotNet.Testcontainers.Builders;
8 | using DotNet.Testcontainers.Configurations;
9 | using DotNet.Testcontainers.Containers;
10 | using DotNet.Testcontainers.Images;
11 | using Nullean.Xunit.Partitions;
12 | using Nullean.Xunit.Partitions.Sdk;
13 | using Xunit;
14 |
15 | [assembly: TestFramework(Partition.TestFramework, Partition.Assembly)]
16 |
17 | namespace Elastic.OpenTelemetry.AutoInstrumentation.IntegrationTests;
18 |
19 | // ReSharper disable once ClassNeverInstantiated.Global
20 | public class ExampleApplicationContainer : IPartitionLifetime
21 | {
22 | private readonly IContainer _container;
23 | private readonly IOutputConsumer _output;
24 | private readonly IFutureDockerImage _image;
25 |
26 | // docker build -t example.autoinstrumentation:latest -f examples/Example.AutoInstrumentation/Dockerfile . \
27 | // && docker run -it --rm -p 5000:8080 --name autoin example.autoinstrumentation:latest
28 |
29 | public ExampleApplicationContainer()
30 | {
31 | ConsoleLogger.Instance.DebugLogLevelEnabled = true;
32 | var directory = CommonDirectoryPath.GetSolutionDirectory();
33 | _image = new ImageFromDockerfileBuilder()
34 | .WithDockerfileDirectory(directory, string.Empty)
35 | .WithDockerfile("examples/Example.AutoInstrumentation/Dockerfile")
36 | .WithLogger(ConsoleLogger.Instance)
37 | .WithBuildArgument("TARGETARCH", RuntimeInformation.ProcessArchitecture switch
38 | {
39 | Architecture.Arm64 => "arm64",
40 | Architecture.X64 => "x64",
41 | Architecture.X86 => "x86",
42 | _ => "unsupported"
43 | })
44 | .Build();
45 |
46 | _output = Consume.RedirectStdoutAndStderrToStream(new MemoryStream(), new MemoryStream());
47 | _container = new ContainerBuilder()
48 | .WithImage(_image)
49 | .WithPortBinding(5000, 8080)
50 | .WithLogger(ConsoleLogger.Instance)
51 | .WithOutputConsumer(_output)
52 | .Build();
53 | }
54 |
55 | public async Task InitializeAsync()
56 | {
57 | await _image.CreateAsync().ConfigureAwait(false);
58 |
59 | await _container.StartAsync().ConfigureAwait(false);
60 | }
61 |
62 | public async Task DisposeAsync() => await _container.StopAsync().ConfigureAwait(false);
63 |
64 | public string FailureTestOutput()
65 | {
66 | _output.Stdout.Seek(0, SeekOrigin.Begin);
67 | using var streamReader = new StreamReader(_output.Stdout, leaveOpen: true);
68 | return streamReader.ReadToEnd();
69 | }
70 |
71 | public int? MaxConcurrency => null;
72 | }
73 |
--------------------------------------------------------------------------------
/tests/AutoInstrumentation.IntegrationTests/PluginLoaderTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Runtime.InteropServices;
6 | using Nullean.Xunit.Partitions.Sdk;
7 | using Xunit;
8 |
9 | namespace Elastic.OpenTelemetry.AutoInstrumentation.IntegrationTests;
10 |
11 | public class PluginLoaderTests(ExampleApplicationContainer exampleApplicationContainer) : IPartitionFixture
12 | {
13 | [NotWindowsCiFact]
14 | public async Task ObserveDistributionPluginLoad()
15 | {
16 | await Task.Delay(TimeSpan.FromSeconds(3));
17 |
18 | var output = exampleApplicationContainer.FailureTestOutput();
19 |
20 | Assert.False(string.IsNullOrWhiteSpace(output));
21 | Assert.Contains("Elastic Distribution of OpenTelemetry (EDOT) .NET:", output);
22 | Assert.Contains("ElasticOpenTelemetryBuilder initialized", output);
23 | Assert.Contains("Added 'Elastic.OpenTelemetry.Processors.ElasticCompatibilityProcessor'", output);
24 | }
25 | }
26 |
27 | public class NotWindowsCiFact : FactAttribute
28 | {
29 | public NotWindowsCiFact()
30 | {
31 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
32 | Skip = "We can not run this test in a virtualized windows environment";
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 | $(SolutionRoot)\build\keys\keypair.snk
7 |
8 |
9 | True
10 |
11 |
12 | true
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | true
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | runtime; build; native; contentfiles; analyzers; buildtransitive
32 | all
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.EndToEndTests/DistributedFixture/ITrafficSimulator.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Net;
6 | using Xunit;
7 |
8 | namespace Elastic.OpenTelemetry.EndToEndTests.DistributedFixture;
9 |
10 | public interface ITrafficSimulator
11 | {
12 | Task Start(DistributedApplicationFixture distributedInfra);
13 | }
14 |
15 | public class DefaultTrafficSimulator : ITrafficSimulator
16 | {
17 | public async Task Start(DistributedApplicationFixture distributedInfra)
18 | {
19 | for (var i = 0; i < 10; i++)
20 | {
21 | var get = await distributedInfra.AspNetApplication.HttpClient.GetAsync("e2e");
22 | Assert.Equal(HttpStatusCode.OK, get.StatusCode);
23 | _ = await get.Content.ReadAsStringAsync();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.EndToEndTests/Elastic.OpenTelemetry.EndToEndTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 | false
8 | true
9 | xUnit1041
10 | 588f828e-db42-4b45-9783-023f03243753
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.EndToEndTests/EndToEndOptions.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.EndToEndTests;
6 | using Elastic.OpenTelemetry.EndToEndTests.DistributedFixture;
7 | using Microsoft.Extensions.Configuration;
8 | using Nullean.Xunit.Partitions;
9 | using Xunit;
10 |
11 | [assembly: TestFramework(Partition.TestFramework, Partition.Assembly)]
12 | [assembly: PartitionOptions(typeof(EndToEndOptions))]
13 |
14 | namespace Elastic.OpenTelemetry.EndToEndTests;
15 |
16 | public class EndToEndOptions : PartitionOptions
17 | {
18 | public override void OnBeforeTestsRun()
19 | {
20 | var configuration = new ConfigurationBuilder()
21 | .AddEnvironmentVariables()
22 | .AddUserSecrets()
23 | .Build();
24 |
25 | var testSuite = Environment.GetEnvironmentVariable("TEST_SUITE");
26 |
27 | //only validate credentials if we are actually running the e2e suite
28 | if (testSuite == null || (
29 | !testSuite.Equals("e2e", StringComparison.InvariantCultureIgnoreCase)
30 | && !testSuite.Equals("all", StringComparison.InvariantCultureIgnoreCase))
31 | )
32 | return;
33 |
34 | try
35 | {
36 | Assert.False(string.IsNullOrWhiteSpace(configuration["E2E:Endpoint"]), userMessage: "Missing E2E:Endpoint configuration");
37 | Assert.False(string.IsNullOrWhiteSpace(configuration["E2E:Authorization"]), userMessage: "Missing E2E:Authorization configuration");
38 | Assert.False(string.IsNullOrWhiteSpace(configuration["E2E:BrowserEmail"]), userMessage: "Missing E2E:BrowserEmail configuration");
39 | Assert.False(string.IsNullOrWhiteSpace(configuration["E2E:BrowserPassword"]), userMessage: "Missing E2E:BrowserPassword configuration");
40 | }
41 | catch (Exception e)
42 | {
43 | Console.WriteLine();
44 | Console.WriteLine(e.Message);
45 | Console.WriteLine();
46 | throw;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.EndToEndTests/README.md:
--------------------------------------------------------------------------------
1 | # E2E Tests: Elastic's .NET OpenTelemetry Distribution
2 |
3 |
4 | ## Target Environment
5 |
6 | Requires an already running serverless observability project on cloud.
7 |
8 | The configuration can be provided either as asp.net secrets or environment variables.
9 |
10 | ```bash
11 | dotnet user-secrets set "E2E:Endpoint" "" --project tests/Elastic.OpenTelemetry.EndToEndTests
12 | dotnet user-secrets set "E2E:Authorization" "" --project tests/Elastic.OpenTelemetry.EndToEndTests
13 | ```
14 |
15 | The equivalent environment variables are `E2E__ENDPOINT` and `E2E__AUTHORIZATION`. For local development setting
16 | secrets is preferred.
17 |
18 | This ensures the instrumented applications will send OTLP data.
19 |
20 | ## Browser authentication
21 |
22 | The tests require a headless browser to login. This requires a non OAuth login to be setup on your serverless
23 | observability project.
24 |
25 | To do this is to invite an email address you own to your organization:
26 |
27 | https://cloud.elastic.co/account/members
28 |
29 | This user only needs instance access to the `Target Environment`.
30 |
31 | **NOTE:** since you can only be part of a single organization on cloud be sure that the organization you are part of is
32 | not used for any production usecases and you have clearance from the organization owner.
33 |
34 | By default accounts on cloud are part of their own personal organization.
35 |
36 | Once invited and accepted the invited email can be used to login during the automated tests.
37 |
38 | These can be provided again as user secrets:
39 |
40 | ```bash
41 | dotnet user-secrets set "E2E:BrowserEmail" "" --project tests/Elastic.OpenTelemetry.EndToEndTests
42 | dotnet user-secrets set "E2E:BrowserPassword" "" --project tests/Elastic.OpenTelemetry.EndToEndTests
43 | ```
44 |
45 | or environment variables (`E2E__BROWSEREMAIL` and `E2E__BROWSERPASSWORD`).
46 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.EndToEndTests/ServiceTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.EndToEndTests.DistributedFixture;
6 | using Microsoft.Playwright;
7 | using Nullean.Xunit.Partitions.Sdk;
8 | using Xunit;
9 | using Xunit.Abstractions;
10 | using static Microsoft.Playwright.Assertions;
11 |
12 | namespace Elastic.OpenTelemetry.EndToEndTests;
13 |
14 | public class EndToEndTests(ITestOutputHelper output, DistributedApplicationFixture fixture)
15 | : IPartitionFixture, IAsyncLifetime
16 | {
17 | public ITestOutputHelper Output { get; } = output;
18 | private readonly string _testName = string.Empty;
19 | private IPage _page = null!;
20 |
21 | [Fact]
22 | public void EnsureApplicationWasStarted() => Assert.True(fixture.Started);
23 |
24 | [Fact]
25 | public async Task LatencyShowsAGraph()
26 | {
27 | var timeout = (float)TimeSpan.FromSeconds(30).TotalMilliseconds;
28 |
29 | // click on service in service overview page.
30 | var uri = new Uri(fixture.ApmUI.KibanaAppUri, $"/app/apm/services/{fixture.ServiceName}/overview").ToString();
31 | await _page.GotoAsync(uri, new() { Timeout = timeout });
32 | await Expect(_page.GetByRole(AriaRole.Heading, new() { Name = "Latency", Exact = true }))
33 | .ToBeVisibleAsync(new() { Timeout = timeout });
34 | }
35 |
36 | public async Task InitializeAsync() => _page = await fixture.ApmUI.NewProfiledPage(_testName);
37 |
38 | public async Task DisposeAsync()
39 | {
40 | var success = PartitionContext.TestException == null;
41 | await fixture.ApmUI.StopTrace(_page, success, _testName);
42 |
43 | if (success)
44 | return;
45 |
46 | fixture.WriteFailureTestOutput(Output);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.EndToEndTests/test.runsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/Aot/NativeAotCompatibilityTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | // Just testing on one platform for now to speed up tests
6 | #if NET9_0
7 | using System.Runtime.InteropServices;
8 | using Xunit.Abstractions;
9 |
10 | namespace Elastic.OpenTelemetry.Tests.Aot
11 | {
12 | public class NativeAotCompatibilityTests(ITestOutputHelper output)
13 | {
14 | private readonly ITestOutputHelper _output = output;
15 |
16 | [Fact]
17 | public async Task CanPublishAotApp()
18 | {
19 | var workingDir = Environment.CurrentDirectory;
20 | var indexOfSolutionFolder = workingDir.AsSpan().LastIndexOf("elastic-otel-dotnet");
21 | workingDir = workingDir.AsSpan()[..(indexOfSolutionFolder + "elastic-otel-dotnet".Length)].ToString();
22 | workingDir = Path.Combine(workingDir, "examples", "Example.AspNetCore.WebApiAot");
23 |
24 | var rid = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win-x64" : "linux-x64";
25 |
26 | var dotnetPublishProcess = new Process
27 | {
28 | StartInfo =
29 | {
30 | FileName = "dotnet",
31 | Arguments = $"publish -c Release -r {rid}",
32 | WorkingDirectory = workingDir,
33 | RedirectStandardError = true,
34 | RedirectStandardOutput = true,
35 | UseShellExecute = false,
36 | CreateNoWindow = true
37 | }
38 | };
39 |
40 | dotnetPublishProcess.ErrorDataReceived += (sender, args) =>
41 | {
42 | if (!string.IsNullOrEmpty(args.Data))
43 | {
44 | _output.WriteLine("[ERROR] " + args.Data);
45 | }
46 | };
47 |
48 | dotnetPublishProcess.OutputDataReceived += (sender, args) =>
49 | {
50 | if (!string.IsNullOrEmpty(args.Data))
51 | {
52 | _output.WriteLine(args.Data);
53 | }
54 | };
55 |
56 | dotnetPublishProcess.Start();
57 | dotnetPublishProcess.BeginOutputReadLine();
58 | dotnetPublishProcess.BeginErrorReadLine();
59 |
60 | await dotnetPublishProcess.WaitForExitAsync();
61 |
62 | Assert.Equal(0, dotnetPublishProcess.ExitCode);
63 | }
64 | }
65 | }
66 | #endif
67 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/Configuration/Instrumentations/LogInstrumentationsTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Configuration.Instrumentations;
6 |
7 | namespace Elastic.OpenTelemetry.Tests.Configuration.Instrumentations;
8 |
9 | public class LogInstrumentationsTests
10 | {
11 | [Fact]
12 | public void AllTest()
13 | {
14 | var instrumentations = new LogInstrumentations([LogInstrumentation.ILogger]);
15 |
16 | Assert.Equal("All", instrumentations.ToString());
17 | }
18 |
19 | [Fact]
20 | public void NoneTest()
21 | {
22 | var instrumentations = new LogInstrumentations([]);
23 |
24 | Assert.Equal("None", instrumentations.ToString());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/Configuration/Instrumentations/MetricInstrumentationsTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Configuration.Instrumentations;
6 |
7 | namespace Elastic.OpenTelemetry.Tests.Configuration.Instrumentations;
8 |
9 | public class MetricInstrumentationsTests
10 | {
11 | [Fact]
12 | public void AllTest()
13 | {
14 | var instrumentations = new MetricInstrumentations(
15 | [
16 | MetricInstrumentation.AspNet,
17 | MetricInstrumentation.AspNetCore,
18 | MetricInstrumentation.HttpClient,
19 | MetricInstrumentation.NetRuntime,
20 | MetricInstrumentation.NServiceBus,
21 | MetricInstrumentation.Process
22 | ]);
23 |
24 | Assert.Equal("All", instrumentations.ToString());
25 | }
26 |
27 | [Fact]
28 | public void SomeTest()
29 | {
30 | var instrumentations = new MetricInstrumentations(
31 | [
32 | MetricInstrumentation.HttpClient,
33 | MetricInstrumentation.NetRuntime,
34 | MetricInstrumentation.NServiceBus,
35 | MetricInstrumentation.Process
36 | ]);
37 |
38 | Assert.StartsWith("All Except: AspNet, AspNetCore", instrumentations.ToString());
39 | }
40 |
41 | [Fact]
42 | public void NoneTest()
43 | {
44 | var instrumentations = new MetricInstrumentations([]);
45 |
46 | Assert.Equal("None", instrumentations.ToString());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/Configuration/Instrumentations/TraceInstrumentationsTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Configuration.Instrumentations;
6 |
7 | namespace Elastic.OpenTelemetry.Tests.Configuration.Instrumentations;
8 |
9 | public class TraceInstrumentationsTests
10 | {
11 | [Fact]
12 | public void AllTest()
13 | {
14 | var instrumentations = new TraceInstrumentations(
15 | [
16 | TraceInstrumentation.AspNet,
17 | TraceInstrumentation.AspNetCore,
18 | TraceInstrumentation.Azure,
19 | TraceInstrumentation.Elasticsearch,
20 | TraceInstrumentation.ElasticTransport,
21 | TraceInstrumentation.EntityFrameworkCore,
22 | TraceInstrumentation.Graphql,
23 | TraceInstrumentation.GrpcNetClient,
24 | TraceInstrumentation.HttpClient,
25 | TraceInstrumentation.Kafka,
26 | TraceInstrumentation.MassTransit,
27 | TraceInstrumentation.MongoDb,
28 | TraceInstrumentation.MysqlConnector,
29 | TraceInstrumentation.MysqlData,
30 | TraceInstrumentation.Npgsql,
31 | TraceInstrumentation.NServiceBus,
32 | TraceInstrumentation.OracleMda,
33 | TraceInstrumentation.Quartz,
34 | TraceInstrumentation.SqlClient,
35 | TraceInstrumentation.StackExchangeRedis,
36 | TraceInstrumentation.WcfClient,
37 | TraceInstrumentation.WcfService
38 | ]);
39 |
40 | Assert.Equal("All", instrumentations.ToString());
41 | }
42 |
43 | [Fact]
44 | public void SomeTest()
45 | {
46 | var instrumentations = new TraceInstrumentations(
47 | [
48 | TraceInstrumentation.Azure,
49 | TraceInstrumentation.Elasticsearch,
50 | TraceInstrumentation.ElasticTransport,
51 | TraceInstrumentation.EntityFrameworkCore,
52 | TraceInstrumentation.Graphql,
53 | TraceInstrumentation.GrpcNetClient,
54 | TraceInstrumentation.HttpClient,
55 | TraceInstrumentation.Kafka,
56 | TraceInstrumentation.MassTransit,
57 | TraceInstrumentation.MongoDb,
58 | TraceInstrumentation.MysqlConnector,
59 | TraceInstrumentation.MysqlData,
60 | TraceInstrumentation.Npgsql,
61 | TraceInstrumentation.NServiceBus,
62 | TraceInstrumentation.OracleMda,
63 | TraceInstrumentation.Quartz,
64 | TraceInstrumentation.SqlClient,
65 | TraceInstrumentation.StackExchangeRedis,
66 | TraceInstrumentation.WcfClient,
67 | TraceInstrumentation.WcfService
68 | ]);
69 |
70 | Assert.StartsWith("All Except: AspNet, AspNetCore", instrumentations.ToString());
71 | }
72 |
73 | [Fact]
74 | public void NoneTest()
75 | {
76 | var instrumentations = new TraceInstrumentations([]);
77 |
78 | Assert.Equal("None", instrumentations.ToString());
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/Diagnostics/LoggingTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Text.RegularExpressions;
6 | using OpenTelemetry;
7 | using OpenTelemetry.Metrics;
8 | using Xunit.Abstractions;
9 |
10 | namespace Elastic.OpenTelemetry.Tests;
11 |
12 | public partial class LoggingTests(ITestOutputHelper output)
13 | {
14 | private readonly ITestOutputHelper _output = output;
15 |
16 | [GeneratedRegex(@"\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{6}\]\[-*\]\[Information\]\s+Elastic Distribution of OpenTelemetry \(EDOT\) \.NET:.*")]
17 | private static partial Regex EdotPreamble();
18 |
19 | [GeneratedRegex(@"\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]\[\d{6}\]\[-*\]\[Debug\]\s+Reusing existing shared components\.\s+")]
20 | private static partial Regex UsingSharedComponents();
21 |
22 | [Fact]
23 | public void LoggingIsEnabled_WhenConfiguredViaTracerProviderBuilder()
24 | {
25 | var logger = new TestLogger(_output);
26 | var options = new ElasticOpenTelemetryOptions { AdditionalLogger = logger, SkipOtlpExporter = true };
27 |
28 | using var tracerProvider = Sdk.CreateTracerProviderBuilder()
29 | .WithElasticDefaults(options)
30 | .Build();
31 |
32 | Assert.Single(logger.Messages, m => EdotPreamble().IsMatch(m));
33 | }
34 |
35 | [Fact]
36 | public void LoggingIsEnabled_WhenConfiguredViaMeterProviderBuilder()
37 | {
38 | var logger = new TestLogger(_output);
39 | var options = new ElasticOpenTelemetryOptions { AdditionalLogger = logger, SkipOtlpExporter = true };
40 |
41 | using var tracerProvider = Sdk.CreateMeterProviderBuilder()
42 | .WithElasticDefaults(options)
43 | .Build();
44 |
45 | Assert.Single(logger.Messages, m => EdotPreamble().IsMatch(m));
46 | }
47 |
48 | [Fact]
49 | public void LoggingPreamble_IsSkipped_WhenReusingSharedComponents()
50 | {
51 | var logger = new TestLogger(_output);
52 | var options = new ElasticOpenTelemetryOptions { AdditionalLogger = logger, SkipOtlpExporter = true };
53 |
54 | using var tracerProvider = Sdk.CreateTracerProviderBuilder()
55 | .WithElasticDefaults(options)
56 | .Build();
57 |
58 | Assert.Single(logger.Messages, m => EdotPreamble().IsMatch(m));
59 |
60 | using var meterProvider = Sdk.CreateMeterProviderBuilder()
61 | .WithElasticDefaults(options)
62 | .ConfigureResource(rb => rb.AddService("Test", "1.0.0"))
63 | .AddInMemoryExporter(new List())
64 | .Build();
65 |
66 | // On this builder, because we are reusing the same ElasticOpenTelemetryOptions, shared components will be available,
67 | // and as such, the pre-amble should not be output a second time.
68 | Assert.Single(logger.Messages, m => EdotPreamble().IsMatch(m));
69 | Assert.Contains(logger.Messages, m => UsingSharedComponents().IsMatch(m));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0;net9.0
5 | enable
6 | enable
7 | false
8 | true
9 | $(MSBuildProjectDirectory)\test.runsettings
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/Extensions/HostApplicationBuilderTests.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using System.Text;
6 | using Elastic.OpenTelemetry.Core;
7 |
8 | namespace Elastic.OpenTelemetry.Tests.Extensions;
9 |
10 | public class HostApplicationBuilderTests
11 | {
12 | [Fact]
13 | public void AddElasticOpenTelemetry_AppliesConfigAndOptions_InExpectedOrder()
14 | {
15 | const string fileLogDirectory = "C:\\Temp";
16 |
17 | var options = new ElasticOpenTelemetryOptions
18 | {
19 | LogDirectory = fileLogDirectory,
20 | LogLevel = LogLevel.Critical
21 | };
22 |
23 | var json = $$"""
24 | {
25 | "Elastic": {
26 | "OpenTelemetry": {
27 | "LogDirectory": "C:\\Json",
28 | "LogLevel": "Trace",
29 | "ElasticDefaults": "All",
30 | "SkipOtlpExporter": true,
31 | "SkipInstrumentationAssemblyScanning": true
32 | }
33 | }
34 | }
35 | """;
36 |
37 | var builder = Host.CreateApplicationBuilder();
38 |
39 | builder.Configuration.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json)));
40 | builder.AddElasticOpenTelemetry(options);
41 |
42 | var app = builder.Build();
43 |
44 | var components = app.Services.GetRequiredService();
45 |
46 | Assert.Equal(fileLogDirectory, components.Options.LogDirectory);
47 | Assert.Equal(LogLevel.Critical, components.Options.LogLevel);
48 | Assert.True(components.Options.SkipOtlpExporter);
49 | Assert.True(components.Options.SkipInstrumentationAssemblyScanning);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | global using System.Diagnostics;
6 | global using Elastic.OpenTelemetry.Processors;
7 | global using OpenTelemetry.Resources;
8 | global using OpenTelemetry.Trace;
9 | global using Xunit;
10 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/Logging/ElasticLoggingDefaults.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using OpenTelemetry;
6 | using OpenTelemetry.Logs;
7 | using Xunit.Abstractions;
8 |
9 | namespace Elastic.OpenTelemetry.Tests.Logging;
10 |
11 | public class ElasticLoggingDefaults(ITestOutputHelper output)
12 | {
13 | private readonly ITestOutputHelper _output = output;
14 |
15 | [Fact]
16 | public async Task FormattedMessageAndScopesOptions_AreEnabled()
17 | {
18 | var exportedItems = new List();
19 |
20 | var host = Host.CreateDefaultBuilder();
21 | host.ConfigureServices(s =>
22 | {
23 | var options = new ElasticOpenTelemetryOptions()
24 | {
25 | SkipOtlpExporter = true,
26 | AdditionalLogger = new TestLogger(_output)
27 | };
28 |
29 | s.AddElasticOpenTelemetry(options)
30 | .WithLogging(lpb => lpb.AddInMemoryExporter(exportedItems));
31 | });
32 |
33 | var ctx = new CancellationTokenRegistration();
34 |
35 | using (var app = host.Build())
36 | {
37 | _ = app.RunAsync(ctx.Token);
38 |
39 | var factory = app.Services.GetRequiredService();
40 | var logger = factory.CreateLogger("Test");
41 |
42 | using (logger.BeginScope(new List>
43 | {
44 | new("customData", "aCustomValue"),
45 | }))
46 | {
47 | logger.LogWarning("This is a {WhatAmI}", "warning");
48 | }
49 |
50 | await ctx.DisposeAsync();
51 | }
52 |
53 | var logRecord = exportedItems.Last();
54 |
55 | Assert.Equal("This is a warning", logRecord.FormattedMessage);
56 |
57 | logRecord.ForEachScope((scope, _) =>
58 | {
59 | var values = scope.Scope as IEnumerable>;
60 |
61 | Assert.NotNull(values);
62 | var entry = Assert.Single(values);
63 |
64 | Assert.Equal("customData", entry.Key);
65 | Assert.Equal("aCustomValue", entry.Value);
66 | }, null);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Elastic.OpenTelemetry.Tests": {
4 | "commandName": "Project",
5 | "launchBrowser": true,
6 | "environmentVariables": {
7 | "ASPNETCORE_ENVIRONMENT": "Development"
8 | },
9 | "applicationUrl": "https://localhost:54104;http://localhost:54105"
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/TestLogger.cs:
--------------------------------------------------------------------------------
1 | // Licensed to Elasticsearch B.V under one or more agreements.
2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3 | // See the LICENSE file in the project root for more information
4 |
5 | using Elastic.OpenTelemetry.Diagnostics;
6 | using Xunit.Abstractions;
7 |
8 | namespace Elastic.OpenTelemetry.Tests;
9 |
10 | public class TestLogger(ITestOutputHelper testOutputHelper) : ILogger
11 | {
12 | private readonly List _messages = [];
13 | private readonly ITestOutputHelper _testOutputHelper = testOutputHelper;
14 |
15 | public IReadOnlyCollection Messages => _messages.AsReadOnly();
16 |
17 | public IDisposable BeginScope(TState state) where TState : notnull => NoopDisposable.Instance;
18 |
19 | public bool IsEnabled(LogLevel logLevel) => true;
20 |
21 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
22 | {
23 | var message = LogFormatter.Format(logLevel, eventId, state, exception, formatter);
24 | _messages.Add(message);
25 | _testOutputHelper.WriteLine(message);
26 | if (exception != null)
27 | _testOutputHelper.WriteLine(exception.ToString());
28 | }
29 |
30 | private class NoopDisposable : IDisposable
31 | {
32 | public static readonly NoopDisposable Instance = new();
33 |
34 | public void Dispose() { }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Elastic.OpenTelemetry.Tests/test.runsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://localhost:4318
5 | http/protobuf
6 | 10
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/xunit.runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3 | "diagnosticMessages": true,
4 | "internalDiagnosticMessages": true,
5 | "longRunningTestSeconds": 2
6 | }
--------------------------------------------------------------------------------