diff --git a/OpenTracing.Contrib.sln b/OpenTracing.Contrib.sln index 1e4b893..c83c7e6 100644 --- a/OpenTracing.Contrib.sln +++ b/OpenTracing.Contrib.sln @@ -67,6 +67,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "samples\net7.0\Sh EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrafficGenerator", "samples\net7.0\TrafficGenerator\TrafficGenerator.csproj", "{69E6E77E-646D-475A-9B6B-C5511C21B11C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net8.0", "net8.0", "{71609BAA-55BF-4A0A-AE05-83D87644E44C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomersApi", "samples\net8.0\CustomersApi\CustomersApi.csproj", "{0DF66128-3C35-4301-98E2-8822FB56B146}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrontendWeb", "samples\net8.0\FrontendWeb\FrontendWeb.csproj", "{744E71C6-79A5-4566-BCBA-221AF084A7EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrdersApi", "samples\net8.0\OrdersApi\OrdersApi.csproj", "{63E80769-4B3E-4897-ABD1-237226ED0629}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "samples\net8.0\Shared\Shared.csproj", "{05A112D5-65C4-4F7C-A974-94E5EFBE32DC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrafficGenerator", "samples\net8.0\TrafficGenerator\TrafficGenerator.csproj", "{67A203A9-9EAC-4375-9A32-E42E91C8AA22}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -145,6 +157,26 @@ Global {69E6E77E-646D-475A-9B6B-C5511C21B11C}.Debug|Any CPU.Build.0 = Debug|Any CPU {69E6E77E-646D-475A-9B6B-C5511C21B11C}.Release|Any CPU.ActiveCfg = Release|Any CPU {69E6E77E-646D-475A-9B6B-C5511C21B11C}.Release|Any CPU.Build.0 = Release|Any CPU + {0DF66128-3C35-4301-98E2-8822FB56B146}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DF66128-3C35-4301-98E2-8822FB56B146}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DF66128-3C35-4301-98E2-8822FB56B146}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DF66128-3C35-4301-98E2-8822FB56B146}.Release|Any CPU.Build.0 = Release|Any CPU + {744E71C6-79A5-4566-BCBA-221AF084A7EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {744E71C6-79A5-4566-BCBA-221AF084A7EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {744E71C6-79A5-4566-BCBA-221AF084A7EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {744E71C6-79A5-4566-BCBA-221AF084A7EF}.Release|Any CPU.Build.0 = Release|Any CPU + {63E80769-4B3E-4897-ABD1-237226ED0629}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63E80769-4B3E-4897-ABD1-237226ED0629}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63E80769-4B3E-4897-ABD1-237226ED0629}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63E80769-4B3E-4897-ABD1-237226ED0629}.Release|Any CPU.Build.0 = Release|Any CPU + {05A112D5-65C4-4F7C-A974-94E5EFBE32DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05A112D5-65C4-4F7C-A974-94E5EFBE32DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05A112D5-65C4-4F7C-A974-94E5EFBE32DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05A112D5-65C4-4F7C-A974-94E5EFBE32DC}.Release|Any CPU.Build.0 = Release|Any CPU + {67A203A9-9EAC-4375-9A32-E42E91C8AA22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67A203A9-9EAC-4375-9A32-E42E91C8AA22}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67A203A9-9EAC-4375-9A32-E42E91C8AA22}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67A203A9-9EAC-4375-9A32-E42E91C8AA22}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -171,6 +203,12 @@ Global {48CD3687-6EB7-47CD-A611-8D579FBCBB3B} = {E46F4333-859A-4CC5-BD2A-7FE8892861BE} {B4A8B209-AA79-4C34-9A12-F03E3423B330} = {E46F4333-859A-4CC5-BD2A-7FE8892861BE} {69E6E77E-646D-475A-9B6B-C5511C21B11C} = {E46F4333-859A-4CC5-BD2A-7FE8892861BE} + {71609BAA-55BF-4A0A-AE05-83D87644E44C} = {36333C22-54F7-403C-ABC4-BECE4EE3F52D} + {0DF66128-3C35-4301-98E2-8822FB56B146} = {71609BAA-55BF-4A0A-AE05-83D87644E44C} + {744E71C6-79A5-4566-BCBA-221AF084A7EF} = {71609BAA-55BF-4A0A-AE05-83D87644E44C} + {63E80769-4B3E-4897-ABD1-237226ED0629} = {71609BAA-55BF-4A0A-AE05-83D87644E44C} + {05A112D5-65C4-4F7C-A974-94E5EFBE32DC} = {71609BAA-55BF-4A0A-AE05-83D87644E44C} + {67A203A9-9EAC-4375-9A32-E42E91C8AA22} = {71609BAA-55BF-4A0A-AE05-83D87644E44C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {832F86C2-4B74-4259-8073-04EE0C37FBCD} diff --git a/samples/net8.0/CustomersApi/CustomersApi.csproj b/samples/net8.0/CustomersApi/CustomersApi.csproj new file mode 100644 index 0000000..99ecb9b --- /dev/null +++ b/samples/net8.0/CustomersApi/CustomersApi.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/samples/net8.0/CustomersApi/DataStore/CustomerDbContext.cs b/samples/net8.0/CustomersApi/DataStore/CustomerDbContext.cs new file mode 100644 index 0000000..55c61d5 --- /dev/null +++ b/samples/net8.0/CustomersApi/DataStore/CustomerDbContext.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; +using Shared; + +namespace CustomersApi.DataStore; + +public class CustomerDbContext : DbContext +{ + public CustomerDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Customers => Set(); + + public void Seed() + { + if (Database.EnsureCreated()) + { + Database.Migrate(); + + Customers.Add(new Customer(1, "Marcel Belding")); + Customers.Add(new Customer(2, "Phyllis Schriver")); + Customers.Add(new Customer(3, "Estefana Balderrama")); + Customers.Add(new Customer(4, "Kenyetta Lone")); + Customers.Add(new Customer(5, "Vernita Fernald")); + Customers.Add(new Customer(6, "Tessie Storrs")); + + SaveChanges(); + } + } +} diff --git a/samples/net8.0/CustomersApi/Program.cs b/samples/net8.0/CustomersApi/Program.cs new file mode 100644 index 0000000..57048ec --- /dev/null +++ b/samples/net8.0/CustomersApi/Program.cs @@ -0,0 +1,74 @@ +using CustomersApi.DataStore; +using Microsoft.EntityFrameworkCore; +using Shared; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.WebHost.UseUrls(Constants.CustomersUrl); + +// Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) +builder.Services.AddJaeger(); + +// Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core +builder.Services.AddOpenTracing(ot => +{ + ot.ConfigureAspNetCore(options => + { + // We don't need any tracing data for our health endpoint. + options.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path == "/health"); + }); + + ot.ConfigureEntityFrameworkCore(options => + { + // This is an example for how certain EF Core commands can be ignored. + // As en example, we're ignoring the "PRAGMA foreign_keys=ON;" commands that are executed by Sqlite. + // Remove this code to see those statements. + options.IgnorePatterns.Add(cmd => cmd.Command.CommandText.StartsWith("PRAGMA")); + }); +}); + +// Adds a Sqlite DB to show EFCore traces. +builder.Services.AddDbContext(options => +{ + options.UseSqlite("Data Source=DataStore/customers.db"); +}); + +builder.Services.AddHealthChecks() + .AddDbContextCheck(); + + +var app = builder.Build(); + + +// Load some dummy data into the db. +using (var scope = app.Services.CreateScope()) +{ + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Seed(); +} + + +// Configure the HTTP request pipeline. + +app.MapGet("/", () => "Customers API"); + +app.MapHealthChecks("/health"); + +app.MapGet("/customers", async (CustomerDbContext dbContext) => await dbContext.Customers.ToListAsync()); + +app.MapGet("/customers/{id}", async (int id, CustomerDbContext dbContext, ILogger logger) => +{ + var customer = await dbContext.Customers.FirstOrDefaultAsync(x => x.CustomerId == id); + + if (customer == null) + return Results.NotFound(); + + // ILogger events are sent to OpenTracing as well! + logger.LogInformation("Returning data for customer {CustomerId}", id); + + return Results.Ok(customer); +}); + +app.Run(); diff --git a/samples/net8.0/CustomersApi/Properties/launchSettings.json b/samples/net8.0/CustomersApi/Properties/launchSettings.json new file mode 100644 index 0000000..e40eb1b --- /dev/null +++ b/samples/net8.0/CustomersApi/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "CustomersApi": { + "commandName": "Project", + "launchBrowser": false, + "launchUrl": "http://localhost:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/net8.0/CustomersApi/appsettings.json b/samples/net8.0/CustomersApi/appsettings.json new file mode 100644 index 0000000..723c096 --- /dev/null +++ b/samples/net8.0/CustomersApi/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/net8.0/FrontendWeb/Controllers/HomeController.cs b/samples/net8.0/FrontendWeb/Controllers/HomeController.cs new file mode 100644 index 0000000..17ad7ae --- /dev/null +++ b/samples/net8.0/FrontendWeb/Controllers/HomeController.cs @@ -0,0 +1,71 @@ +using System.Text; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Newtonsoft.Json; +using Shared; + +namespace FrontendWeb.Controllers; + +public class HomeController : Controller +{ + private readonly HttpClient _httpClient; + + public HomeController(HttpClient httpClient) + { + _httpClient = httpClient; + } + + [HttpGet] + public IActionResult Index() + { + return View(); + } + + [HttpGet] + public async Task PlaceOrder() + { + ViewBag.Customers = await GetCustomers(); + return View(new PlaceOrderCommand { ItemNumber = "ABC11", Quantity = 1 }); + } + + [HttpPost, ValidateAntiForgeryToken] + public async Task PlaceOrder(PlaceOrderCommand cmd) + { + if (!ModelState.IsValid) + { + ViewBag.Customers = await GetCustomers(); + return View(cmd); + } + + string body = JsonConvert.SerializeObject(cmd); + + var request = new HttpRequestMessage + { + Method = HttpMethod.Post, + RequestUri = new Uri(Constants.OrdersUrl + "orders"), + Content = new StringContent(body, Encoding.UTF8, "application/json") + }; + + await _httpClient.SendAsync(request); + + return RedirectToAction("Index"); + } + + private async Task> GetCustomers() + { + var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(Constants.CustomersUrl + "customers") + }; + + var response = await _httpClient.SendAsync(request); + + response.EnsureSuccessStatusCode(); + + var body = await response.Content.ReadAsStringAsync(); + + return JsonConvert.DeserializeObject>(body) + .Select(x => new SelectListItem { Value = x.CustomerId.ToString(), Text = x.Name }); + } +} diff --git a/samples/net8.0/FrontendWeb/FrontendWeb.csproj b/samples/net8.0/FrontendWeb/FrontendWeb.csproj new file mode 100644 index 0000000..002f5c1 --- /dev/null +++ b/samples/net8.0/FrontendWeb/FrontendWeb.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/samples/net8.0/FrontendWeb/Program.cs b/samples/net8.0/FrontendWeb/Program.cs new file mode 100644 index 0000000..f32cb02 --- /dev/null +++ b/samples/net8.0/FrontendWeb/Program.cs @@ -0,0 +1,28 @@ +using Shared; + + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.WebHost.UseUrls(Constants.FrontendUrl); + +// Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) +builder.Services.AddJaeger(); + +// Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core +builder.Services.AddOpenTracing(); + +builder.Services.AddSingleton(); + +builder.Services.AddMvc(); + + +var app = builder.Build(); + + +// Configure the HTTP request pipeline. + +app.MapDefaultControllerRoute(); + +app.Run(); diff --git a/samples/net8.0/FrontendWeb/Properties/launchSettings.json b/samples/net8.0/FrontendWeb/Properties/launchSettings.json new file mode 100644 index 0000000..8870b99 --- /dev/null +++ b/samples/net8.0/FrontendWeb/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "FrontendWeb": { + "commandName": "Project", + "launchBrowser": false, + "launchUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/net8.0/FrontendWeb/Views/Home/Index.cshtml b/samples/net8.0/FrontendWeb/Views/Home/Index.cshtml new file mode 100644 index 0000000..8948537 --- /dev/null +++ b/samples/net8.0/FrontendWeb/Views/Home/Index.cshtml @@ -0,0 +1,8 @@ + + +FrontendWeb +

FrontendWeb

+

This is the beautiful web frontend.

+

Refresh this page a few times and check the Jaeger UI - you should already see traces!

+ +

Place an order

diff --git a/samples/net8.0/FrontendWeb/Views/Home/PlaceOrder.cshtml b/samples/net8.0/FrontendWeb/Views/Home/PlaceOrder.cshtml new file mode 100644 index 0000000..70a6ebc --- /dev/null +++ b/samples/net8.0/FrontendWeb/Views/Home/PlaceOrder.cshtml @@ -0,0 +1,13 @@ +@model Shared.PlaceOrderCommand + + +FrontendWeb +

FrontendWeb

+

Place an order

+ +
+
Customer: @Html.DropDownListFor(x => x.CustomerId, (IEnumerable)ViewBag.Customers)
+
ItemNumber: @Html.TextBoxFor(x => x.ItemNumber)
+
Quantity: @Html.TextBoxFor(x => x.Quantity)
+ +
diff --git a/samples/net8.0/FrontendWeb/Views/_ViewImports.cshtml b/samples/net8.0/FrontendWeb/Views/_ViewImports.cshtml new file mode 100644 index 0000000..1113d34 --- /dev/null +++ b/samples/net8.0/FrontendWeb/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using FrontendWeb +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/samples/net8.0/FrontendWeb/appsettings.json b/samples/net8.0/FrontendWeb/appsettings.json new file mode 100644 index 0000000..36ea555 --- /dev/null +++ b/samples/net8.0/FrontendWeb/appsettings.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information", + "OpenTracing": "Debug" + } + } +} diff --git a/samples/net8.0/OrdersApi/Controllers/OrdersController.cs b/samples/net8.0/OrdersApi/Controllers/OrdersController.cs new file mode 100644 index 0000000..aa0095c --- /dev/null +++ b/samples/net8.0/OrdersApi/Controllers/OrdersController.cs @@ -0,0 +1,76 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using OpenTracing; +using OrdersApi.DataStore; +using Shared; + +namespace OrdersApi.Controllers; + +[Route("orders")] +public class OrdersController : Controller +{ + private readonly OrdersDbContext _dbContext; + private readonly HttpClient _httpClient; + private readonly ITracer _tracer; + + public OrdersController(OrdersDbContext dbContext, HttpClient httpClient, ITracer tracer) + { + _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); + } + + [HttpGet] + public async Task Index() + { + var orders = await _dbContext.Orders.ToListAsync(); + + return Ok(orders.Select(x => new { x.OrderId }).ToList()); + } + + [HttpPost] + public async Task Index([FromBody] PlaceOrderCommand cmd) + { + var customer = await GetCustomer(cmd.CustomerId); + + var order = new Order + { + CustomerId = cmd.CustomerId, + ItemNumber = cmd.ItemNumber, + Quantity = cmd.Quantity + }; + + _dbContext.Orders.Add(order); + + await _dbContext.SaveChangesAsync(); + + _tracer.ActiveSpan?.Log(new Dictionary { + { "event", "OrderPlaced" }, + { "orderId", order.OrderId }, + { "customer", order.CustomerId }, + { "customer_name", customer.Name }, + { "item_number", order.ItemNumber }, + { "quantity", order.Quantity } + }); + + return Ok(); + } + + private async Task GetCustomer(int customerId) + { + var request = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(Constants.CustomersUrl + "customers/" + customerId) + }; + + var response = await _httpClient.SendAsync(request); + + response.EnsureSuccessStatusCode(); + + var body = await response.Content.ReadAsStringAsync(); + + return JsonConvert.DeserializeObject(body); + } +} diff --git a/samples/net8.0/OrdersApi/DataStore/Order.cs b/samples/net8.0/OrdersApi/DataStore/Order.cs new file mode 100644 index 0000000..b2572fd --- /dev/null +++ b/samples/net8.0/OrdersApi/DataStore/Order.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace OrdersApi.DataStore; + +public class Order +{ + [Key] + public int OrderId { get; set; } + + public int CustomerId { get; set; } + + [Required, StringLength(10)] + public string ItemNumber { get; set; } = string.Empty; + + [Required, Range(1, 100)] + public int Quantity { get; set; } +} diff --git a/samples/net8.0/OrdersApi/DataStore/OrdersDbContext.cs b/samples/net8.0/OrdersApi/DataStore/OrdersDbContext.cs new file mode 100644 index 0000000..730384a --- /dev/null +++ b/samples/net8.0/OrdersApi/DataStore/OrdersDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; + +namespace OrdersApi.DataStore; + +public class OrdersDbContext : DbContext +{ + public OrdersDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Orders => Set(); + + public void Seed() + { + if (Database.EnsureCreated()) + { + Database.Migrate(); + + SaveChanges(); + } + } +} diff --git a/samples/net8.0/OrdersApi/OrdersApi.csproj b/samples/net8.0/OrdersApi/OrdersApi.csproj new file mode 100644 index 0000000..965d17c --- /dev/null +++ b/samples/net8.0/OrdersApi/OrdersApi.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/samples/net8.0/OrdersApi/Program.cs b/samples/net8.0/OrdersApi/Program.cs new file mode 100644 index 0000000..01e56d1 --- /dev/null +++ b/samples/net8.0/OrdersApi/Program.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore; +using OrdersApi.DataStore; +using Shared; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.WebHost.UseUrls(Constants.OrdersUrl); + +// Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) +builder.Services.AddJaeger(); + +// Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core +builder.Services.AddOpenTracing(builder => +{ + builder.ConfigureAspNetCore(options => + { + // We don't need any tracing data for our health endpoint. + options.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path == "/health"); + }); +}); + +// Adds a SqlServer DB to show EFCore traces. +builder.Services.AddDbContext(options => +{ + options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Orders-net5;Trusted_Connection=True;MultipleActiveResultSets=true"); +}); + +builder.Services.AddSingleton(); + +builder.Services.AddMvc(); + +builder.Services.AddHealthChecks() + .AddDbContextCheck(); + + +var app = builder.Build(); + + +// Load some dummy data into the db. +using (var scope = app.Services.CreateScope()) +{ + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Seed(); +} + + +// Configure the HTTP request pipeline. + +app.MapGet("/", () => "Orders API"); + +app.MapHealthChecks("/health"); + +app.MapDefaultControllerRoute(); + +app.Run(); diff --git a/samples/net8.0/OrdersApi/Properties/launchSettings.json b/samples/net8.0/OrdersApi/Properties/launchSettings.json new file mode 100644 index 0000000..6b818d0 --- /dev/null +++ b/samples/net8.0/OrdersApi/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "OrdersApi": { + "commandName": "Project", + "launchBrowser": false, + "launchUrl": "http://localhost:5002", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/net8.0/OrdersApi/appsettings.json b/samples/net8.0/OrdersApi/appsettings.json new file mode 100644 index 0000000..723c096 --- /dev/null +++ b/samples/net8.0/OrdersApi/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/net8.0/Shared/Constants.cs b/samples/net8.0/Shared/Constants.cs new file mode 100644 index 0000000..9979429 --- /dev/null +++ b/samples/net8.0/Shared/Constants.cs @@ -0,0 +1,10 @@ +namespace Shared; + +public class Constants +{ + public const string FrontendUrl = "http://localhost:5000/"; + + public const string CustomersUrl = "http://localhost:5001/"; + + public const string OrdersUrl = "http://localhost:5002/"; +} diff --git a/samples/net8.0/Shared/Customer.cs b/samples/net8.0/Shared/Customer.cs new file mode 100644 index 0000000..48248f5 --- /dev/null +++ b/samples/net8.0/Shared/Customer.cs @@ -0,0 +1,19 @@ +namespace Shared; + +public class Customer +{ + public int CustomerId { get; set; } + public string Name { get; set; } + + public Customer() + { + CustomerId = 0; + Name = string.Empty; + } + + public Customer(int customerId, string name) + { + CustomerId = customerId; + Name = name; + } +} diff --git a/samples/net8.0/Shared/JaegerServiceCollectionExtensions.cs b/samples/net8.0/Shared/JaegerServiceCollectionExtensions.cs new file mode 100644 index 0000000..b2f8f91 --- /dev/null +++ b/samples/net8.0/Shared/JaegerServiceCollectionExtensions.cs @@ -0,0 +1,53 @@ +using System.Reflection; +using Jaeger; +using Jaeger.Reporters; +using Jaeger.Samplers; +using Jaeger.Senders.Thrift; +using Microsoft.Extensions.Logging; +using OpenTracing; +using OpenTracing.Contrib.NetCore.Configuration; +using OpenTracing.Util; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class JaegerServiceCollectionExtensions +{ + private static readonly Uri _jaegerUri = new Uri("http://localhost:14268/api/traces"); + + public static IServiceCollection AddJaeger(this IServiceCollection services) + { + if (services == null) + throw new ArgumentNullException(nameof(services)); + + services.AddSingleton(serviceProvider => + { + string serviceName = Assembly.GetEntryAssembly()?.GetName().Name ?? "unknown-service"; + + ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); + + ISampler sampler = new ConstSampler(sample: true); + + IReporter reporter = new RemoteReporter.Builder() + .WithSender(new HttpSender.Builder(_jaegerUri.ToString()).Build()) + .Build(); + + ITracer tracer = new Tracer.Builder(serviceName) + .WithLoggerFactory(loggerFactory) + .WithSampler(sampler) + .WithReporter(reporter) + .Build(); + + GlobalTracer.Register(tracer); + + return tracer; + }); + + // Prevent endless loops when OpenTracing is tracking HTTP requests to Jaeger. + services.Configure(options => + { + options.IgnorePatterns.Add(request => request.RequestUri != null && _jaegerUri.IsBaseOf(request.RequestUri)); + }); + + return services; + } +} diff --git a/samples/net8.0/Shared/PlaceOrderCommand.cs b/samples/net8.0/Shared/PlaceOrderCommand.cs new file mode 100644 index 0000000..a538e2c --- /dev/null +++ b/samples/net8.0/Shared/PlaceOrderCommand.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Shared; + +public class PlaceOrderCommand +{ + [Required, Range(1, int.MaxValue)] + public int CustomerId { get; set; } + + [Required, StringLength(10)] + public string ItemNumber { get; set; } = string.Empty; + + [Required, Range(1, 100)] + public int Quantity { get; set; } +} diff --git a/samples/net8.0/Shared/Shared.csproj b/samples/net8.0/Shared/Shared.csproj new file mode 100644 index 0000000..ac57a2c --- /dev/null +++ b/samples/net8.0/Shared/Shared.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/samples/net8.0/TrafficGenerator/Program.cs b/samples/net8.0/TrafficGenerator/Program.cs new file mode 100644 index 0000000..23f4934 --- /dev/null +++ b/samples/net8.0/TrafficGenerator/Program.cs @@ -0,0 +1,15 @@ +using TrafficGenerator; + +using var host = Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) + services.AddJaeger(); + + services.AddOpenTracing(); + + services.AddHostedService(); + }) + .Build(); + +await host.RunAsync(); diff --git a/samples/net8.0/TrafficGenerator/TrafficGenerator.csproj b/samples/net8.0/TrafficGenerator/TrafficGenerator.csproj new file mode 100644 index 0000000..78c7ee3 --- /dev/null +++ b/samples/net8.0/TrafficGenerator/TrafficGenerator.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/samples/net8.0/TrafficGenerator/Worker.cs b/samples/net8.0/TrafficGenerator/Worker.cs new file mode 100644 index 0000000..0717557 --- /dev/null +++ b/samples/net8.0/TrafficGenerator/Worker.cs @@ -0,0 +1,51 @@ +using Shared; + +namespace TrafficGenerator; + +public class Worker : BackgroundService +{ + private readonly ILogger _logger; + + public Worker(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + HttpClient customersHttpClient = new HttpClient(); + customersHttpClient.BaseAddress = new Uri(Constants.CustomersUrl); + + HttpClient ordersHttpClient = new HttpClient(); + ordersHttpClient.BaseAddress = new Uri(Constants.OrdersUrl); + + + while (!stoppingToken.IsCancellationRequested) + { + HttpResponseMessage ordersHealthResponse = await ordersHttpClient.GetAsync("health"); + _logger.LogInformation($"Health of 'orders'-endpoint: '{ordersHealthResponse.StatusCode}'"); + + HttpResponseMessage customersHealthResponse = await customersHttpClient.GetAsync("health"); + _logger.LogInformation($"Health of 'customers'-endpoint: '{customersHealthResponse.StatusCode}'"); + + _logger.LogInformation("Requesting customers"); + + HttpResponseMessage response = await customersHttpClient.GetAsync("customers"); + + _logger.LogInformation($"Response was '{response.StatusCode}'"); + + await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); + } + } + catch (TaskCanceledException) + { + /* Application should be stopped -> no-op */ + } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled exception"); + } + } +} diff --git a/samples/net8.0/TrafficGenerator/appsettings.json b/samples/net8.0/TrafficGenerator/appsettings.json new file mode 100644 index 0000000..789de74 --- /dev/null +++ b/samples/net8.0/TrafficGenerator/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Warning", + "Microsoft.AspNetCore.Hosting": "Information" + } + } +}