From d5b8ec6a03275335a647b302d6ee2eac4bbfc83b Mon Sep 17 00:00:00 2001 From: Mohamed Asaker Date: Fri, 21 Jun 2024 11:52:48 -0700 Subject: [PATCH] Added DotNet sample app to test X-Ray Sampler --- .../sample-apps/dotnet/.gitignore | 38 ++++++ .../dotnet/Controllers/AppController.cs | 122 ++++++++++++++++++ .../sample-apps/dotnet/GlobalSuppressions.cs | 21 +++ .../sample-apps/dotnet/Program.cs | 22 ++++ .../dotnet/Properties/launchSettings.json | 30 +++++ .../sample-apps/dotnet/README.md | 12 ++ .../sample-apps/dotnet/Startup.cs | 69 ++++++++++ .../dotnet/appsettings.Development.json | 9 ++ .../sample-apps/dotnet/appsettings.json | 10 ++ .../dotnet/dotnet-sample-app.csproj | 23 ++++ .../sample-apps/dotnet/stylecop.json | 16 +++ 11 files changed, 372 insertions(+) create mode 100644 centralized-sampling-tests/sample-apps/dotnet/.gitignore create mode 100644 centralized-sampling-tests/sample-apps/dotnet/Controllers/AppController.cs create mode 100644 centralized-sampling-tests/sample-apps/dotnet/GlobalSuppressions.cs create mode 100644 centralized-sampling-tests/sample-apps/dotnet/Program.cs create mode 100644 centralized-sampling-tests/sample-apps/dotnet/Properties/launchSettings.json create mode 100644 centralized-sampling-tests/sample-apps/dotnet/README.md create mode 100644 centralized-sampling-tests/sample-apps/dotnet/Startup.cs create mode 100644 centralized-sampling-tests/sample-apps/dotnet/appsettings.Development.json create mode 100644 centralized-sampling-tests/sample-apps/dotnet/appsettings.json create mode 100644 centralized-sampling-tests/sample-apps/dotnet/dotnet-sample-app.csproj create mode 100644 centralized-sampling-tests/sample-apps/dotnet/stylecop.json diff --git a/centralized-sampling-tests/sample-apps/dotnet/.gitignore b/centralized-sampling-tests/sample-apps/dotnet/.gitignore new file mode 100644 index 00000000..afdf3de2 --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/.gitignore @@ -0,0 +1,38 @@ + +*.swp +*.*~ +project.lock.json +.DS_Store +*.pyc +nupkg/ + +# Visual Studio Code +.vscode + +# Rider +.idea + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +msbuild.log +msbuild.err +msbuild.wrn + +# Visual Studio 2015 +.vs/ diff --git a/centralized-sampling-tests/sample-apps/dotnet/Controllers/AppController.cs b/centralized-sampling-tests/sample-apps/dotnet/Controllers/AppController.cs new file mode 100644 index 00000000..7cebdcfe --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/Controllers/AppController.cs @@ -0,0 +1,122 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; + +namespace dotnet_sample_app.Controllers; + +[ApiController] +[Route("[controller]")] +public class AppController : ControllerBase +{ + private static readonly ActivitySource Tracer = new ("centralized-sampling-tests"); + + [HttpGet] + [Route("/")] + public string Default() + { + return "Application started!"; + } + + // Get endpoint for /getSampled that requires three header values for user, service_name, and + // required Returns the number of times a span was sampled out of the creation of 1000 spans + [HttpGet] + [Route("/getSampled")] + public int GetSampled() + { + this.Request.Headers.TryGetValue("user", out var userAttribute); + this.Request.Headers.TryGetValue("service_name", out var name); + this.Request.Headers.TryGetValue("required", out var required); + this.Request.Headers.TryGetValue("totalSpans", out var totalSpans); + + ActivityTagsCollection attributes = new ActivityTagsCollection + { + { "http.request.method", "GET" }, + { "url.full", "http://localhost:8080/getSampled" }, + { "user", userAttribute }, + { "http.route", "/getSampled" }, + { "required", required }, + { "url.path", "/getSampled" }, + }; + + return this.GetSampledSpanCount(name, totalSpans, attributes); + } + + // Post endpoint for /getSampled that requires three header values for user, service_name, and + // required Returns the number of times a span was sampled out of the creation of 1000 spans + [HttpPost] + [Route("/getSampled")] + public int PostSampled() + { + this.Request.Headers.TryGetValue("user", out var userAttribute); + this.Request.Headers.TryGetValue("service_name", out var name); + this.Request.Headers.TryGetValue("required", out var required); + this.Request.Headers.TryGetValue("totalSpans", out var totalSpans); + + ActivityTagsCollection attributes = new ActivityTagsCollection + { + { "http.request.method", "POST" }, + { "url.full", "http://localhost:8080/getSampled" }, + { "user", userAttribute }, + { "http.route", "/getSampled" }, + { "required", required }, + { "url.path", "/getSampled" }, + }; + + return this.GetSampledSpanCount(name, totalSpans, attributes); + } + + // Post endpoint for /getSampled that requires three header values for user, service_name, and + // required Returns the number of times a span was sampled out of the creation of 1000 spans + [HttpGet] + [Route("/importantEndpoint")] + public int ImportantEndpoint() + { + this.Request.Headers.TryGetValue("user", out var userAttribute); + this.Request.Headers.TryGetValue("service_name", out var name); + this.Request.Headers.TryGetValue("required", out var required); + this.Request.Headers.TryGetValue("totalSpans", out var totalSpans); + + ActivityTagsCollection attributes = new ActivityTagsCollection + { + { "http.request.method", "GET" }, + { "url.full", "http://localhost:8080/importantEndpoint" }, + { "user", userAttribute }, + { "http.route", "/importantEndpoint" }, + { "required", required }, + { "url.path", "/importantEndpoint" }, + }; + + return this.GetSampledSpanCount(name, totalSpans, attributes); + } + + /** + * Creates x amount of spans with x being supplied by totalSpans and returns how many of those + * spans were sampled + * + * @param name name of the span that will end up being the service-name + * @param totalSpans number of spans to make + * @param attributes attributes to set for the span + * @return the number of times a span was sampled + */ + private int GetSampledSpanCount(string name, string totalSpans, ActivityTagsCollection attributes) + { + int numSampled = 0; + int spans = int.Parse(totalSpans); + + for (int i = 0; i < spans; i++) + { + Activity.Current = null; + using (Activity activity = Tracer.StartActivity(ActivityKind.Server, tags: attributes, name: name)) + { + if (activity?.Recorded == true && activity?.IsAllDataRequested == true) + { + numSampled++; + } + } + } + + return numSampled; + } +} diff --git a/centralized-sampling-tests/sample-apps/dotnet/GlobalSuppressions.cs b/centralized-sampling-tests/sample-apps/dotnet/GlobalSuppressions.cs new file mode 100644 index 00000000..82393708 --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/GlobalSuppressions.cs @@ -0,0 +1,21 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "member", Target = "~M:dotnet_sample_app.Controllers.AppController.AWSSDKCall~System.String")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "member", Target = "~M:dotnet_sample_app.Controllers.AppController.Default~System.String")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "member", Target = "~M:dotnet_sample_app.Controllers.AppController.OutgoingHttp~System.String")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "member", Target = "~M:dotnet_sample_app.Program.CreateHostBuilder(System.String[])~Microsoft.Extensions.Hosting.IHostBuilder")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "member", Target = "~M:dotnet_sample_app.Program.Main(System.String[])")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "member", Target = "~M:dotnet_sample_app.Startup.#ctor(Microsoft.Extensions.Configuration.IConfiguration)")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "member", Target = "~M:dotnet_sample_app.Startup.Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder,Microsoft.AspNetCore.Hosting.IWebHostEnvironment)")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "member", Target = "~M:dotnet_sample_app.Startup.ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "member", Target = "~P:dotnet_sample_app.Startup.Configuration")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "type", Target = "~T:dotnet_sample_app.Controllers.AppController")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "type", Target = "~T:dotnet_sample_app.Program")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Reviewed", Scope = "type", Target = "~T:dotnet_sample_app.Startup")] diff --git a/centralized-sampling-tests/sample-apps/dotnet/Program.cs b/centralized-sampling-tests/sample-apps/dotnet/Program.cs new file mode 100644 index 00000000..91ae0b92 --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/Program.cs @@ -0,0 +1,22 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace dotnet_sample_app; + +public class Program +{ + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); +} diff --git a/centralized-sampling-tests/sample-apps/dotnet/Properties/launchSettings.json b/centralized-sampling-tests/sample-apps/dotnet/Properties/launchSettings.json new file mode 100644 index 00000000..d12010db --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:8080" + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "dotnet_sample_app": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:8080", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "LISTEN_ADDRESS": "http://localhost:8080", + "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317", + "OTEL_EXPORTER_OTLP_INSECURE": "True" + } + } + } +} diff --git a/centralized-sampling-tests/sample-apps/dotnet/README.md b/centralized-sampling-tests/sample-apps/dotnet/README.md new file mode 100644 index 00000000..3ac5833d --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/README.md @@ -0,0 +1,12 @@ +# DotNet integration tests sample-app + +## Run + +### Run as a command + +This dotnet sample app uses .Net 8 so make sure that's it's available on your machine. +Run this command in the directory `dotnet` to run the sample-app + +```shell +dotnet run +``` diff --git a/centralized-sampling-tests/sample-apps/dotnet/Startup.cs b/centralized-sampling-tests/sample-apps/dotnet/Startup.cs new file mode 100644 index 00000000..710d29be --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/Startup.cs @@ -0,0 +1,69 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Sampler.AWS; +using OpenTelemetry.Trace; + +namespace dotnet_sample_app; + +public class Startup +{ + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + + var serviceName = "adot-integ-test"; + + var resourceBuilder = ResourceBuilder + .CreateDefault() + .AddService(serviceName: serviceName) + .AddTelemetrySdk(); + + Sdk.CreateTracerProviderBuilder() + .AddSource("centralized-sampling-tests") + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .SetResourceBuilder(resourceBuilder) + .SetSampler(AWSXRayRemoteSampler.Builder(resourceBuilder.Build()) // you must provide a resource + .SetPollingInterval(TimeSpan.FromSeconds(1)) + .SetEndpoint("http://localhost:2000") + .Build()) + .Build(); + + AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } +} diff --git a/centralized-sampling-tests/sample-apps/dotnet/appsettings.Development.json b/centralized-sampling-tests/sample-apps/dotnet/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/centralized-sampling-tests/sample-apps/dotnet/appsettings.json b/centralized-sampling-tests/sample-apps/dotnet/appsettings.json new file mode 100644 index 00000000..d9d9a9bf --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/centralized-sampling-tests/sample-apps/dotnet/dotnet-sample-app.csproj b/centralized-sampling-tests/sample-apps/dotnet/dotnet-sample-app.csproj new file mode 100644 index 00000000..9e39c0b2 --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/dotnet-sample-app.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp8.0 + dotnet_sample_app + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/centralized-sampling-tests/sample-apps/dotnet/stylecop.json b/centralized-sampling-tests/sample-apps/dotnet/stylecop.json new file mode 100644 index 00000000..f694a545 --- /dev/null +++ b/centralized-sampling-tests/sample-apps/dotnet/stylecop.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace" + }, + "documentationRules": { + "copyrightText": "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: Apache-2.0", + "xmlHeader": false, + "documentExposedElements": true, + "documentInternalElements": false, + "documentInterfaces": false, + "excludeFromPunctuationCheck": [ "seealso", "param", "returns", "summary", "typeparam", "exception", "remarks" ] + } + } +}