diff --git a/.appveyor.yml b/.appveyor.yml index 1a5fbd5..7dabee8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -16,7 +16,8 @@ build_script: - ps: .\dotnet-install.ps1 -Runtime dotnet -Version 2.0.7 # Required for building the netcoreapp2.1 FrontendWeb (Razor throws an error otherwise) - ps: .\dotnet-install.ps1 -Runtime dotnet -Version 2.1.23 - ps: .\dotnet-install.ps1 -Runtime dotnet -Version 3.1.10 - - ps: .\dotnet-install.ps1 -Version 5.0.101 + - ps: .\dotnet-install.ps1 -Runtime dotnet -Version 5.0.12 + - ps: .\dotnet-install.ps1 -Version 6.0.100 - ps: .\build.ps1 test: off diff --git a/.travis.yml b/.travis.yml index 96b71c6..5f09080 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ matrix: dist: xenial sudo: required mono: none - dotnet: 5.0.101 + dotnet: 6.0.100 addons: apt: sources: @@ -17,6 +17,7 @@ matrix: - dotnet-hosting-2.0.7 - aspnetcore-runtime-2.1 - aspnetcore-runtime-3.1 + - aspnetcore-runtime-5.0 env: global: diff --git a/OpenTracing.Contrib.sln b/OpenTracing.Contrib.sln index 8ea8435..c0a655e 100644 --- a/OpenTracing.Contrib.sln +++ b/OpenTracing.Contrib.sln @@ -70,6 +70,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrdersApi", "samples\net5.0 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrafficGenerator", "samples\net5.0\TrafficGenerator\TrafficGenerator.csproj", "{3369C8DF-DCC2-4934-BBA0-222BE6E5D649}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net6.0", "net6.0", "{03935477-E35E-4EEE-9944-8742E0CB5CFD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomersApi", "samples\net6.0\CustomersApi\CustomersApi.csproj", "{872A3B2E-1A28-4B3C-A83D-7C99513B36FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontendWeb", "samples\net6.0\FrontendWeb\FrontendWeb.csproj", "{4756D99B-BAA2-4B9E-AAE3-F521C38134DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrdersApi", "samples\net6.0\OrdersApi\OrdersApi.csproj", "{73108F76-DAF0-48A1-9FE7-F17395672316}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "samples\net6.0\Shared\Shared.csproj", "{85F652F4-BF5E-4CBF-BD1F-5BE305DC4589}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrafficGenerator", "samples\net6.0\TrafficGenerator\TrafficGenerator.csproj", "{AE063835-3A23-4037-900D-9FFCC3234C8F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -148,6 +160,26 @@ Global {3369C8DF-DCC2-4934-BBA0-222BE6E5D649}.Debug|Any CPU.Build.0 = Debug|Any CPU {3369C8DF-DCC2-4934-BBA0-222BE6E5D649}.Release|Any CPU.ActiveCfg = Release|Any CPU {3369C8DF-DCC2-4934-BBA0-222BE6E5D649}.Release|Any CPU.Build.0 = Release|Any CPU + {872A3B2E-1A28-4B3C-A83D-7C99513B36FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {872A3B2E-1A28-4B3C-A83D-7C99513B36FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {872A3B2E-1A28-4B3C-A83D-7C99513B36FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {872A3B2E-1A28-4B3C-A83D-7C99513B36FB}.Release|Any CPU.Build.0 = Release|Any CPU + {4756D99B-BAA2-4B9E-AAE3-F521C38134DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4756D99B-BAA2-4B9E-AAE3-F521C38134DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4756D99B-BAA2-4B9E-AAE3-F521C38134DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4756D99B-BAA2-4B9E-AAE3-F521C38134DD}.Release|Any CPU.Build.0 = Release|Any CPU + {73108F76-DAF0-48A1-9FE7-F17395672316}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73108F76-DAF0-48A1-9FE7-F17395672316}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73108F76-DAF0-48A1-9FE7-F17395672316}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73108F76-DAF0-48A1-9FE7-F17395672316}.Release|Any CPU.Build.0 = Release|Any CPU + {85F652F4-BF5E-4CBF-BD1F-5BE305DC4589}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85F652F4-BF5E-4CBF-BD1F-5BE305DC4589}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85F652F4-BF5E-4CBF-BD1F-5BE305DC4589}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85F652F4-BF5E-4CBF-BD1F-5BE305DC4589}.Release|Any CPU.Build.0 = Release|Any CPU + {AE063835-3A23-4037-900D-9FFCC3234C8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE063835-3A23-4037-900D-9FFCC3234C8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE063835-3A23-4037-900D-9FFCC3234C8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE063835-3A23-4037-900D-9FFCC3234C8F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -174,6 +206,12 @@ Global {E68B95C3-E0B4-489A-9FE9-3A46183D7AE9} = {064684F8-44D7-4151-81E0-79FCEF3EB744} {9185D267-C28C-471E-80DF-9621F03FDF6B} = {064684F8-44D7-4151-81E0-79FCEF3EB744} {3369C8DF-DCC2-4934-BBA0-222BE6E5D649} = {064684F8-44D7-4151-81E0-79FCEF3EB744} + {03935477-E35E-4EEE-9944-8742E0CB5CFD} = {36333C22-54F7-403C-ABC4-BECE4EE3F52D} + {872A3B2E-1A28-4B3C-A83D-7C99513B36FB} = {03935477-E35E-4EEE-9944-8742E0CB5CFD} + {4756D99B-BAA2-4B9E-AAE3-F521C38134DD} = {03935477-E35E-4EEE-9944-8742E0CB5CFD} + {73108F76-DAF0-48A1-9FE7-F17395672316} = {03935477-E35E-4EEE-9944-8742E0CB5CFD} + {85F652F4-BF5E-4CBF-BD1F-5BE305DC4589} = {03935477-E35E-4EEE-9944-8742E0CB5CFD} + {AE063835-3A23-4037-900D-9FFCC3234C8F} = {03935477-E35E-4EEE-9944-8742E0CB5CFD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {832F86C2-4B74-4259-8073-04EE0C37FBCD} diff --git a/README.md b/README.md index a7e9f09..253d549 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ _**IMPORTANT:** OpenTracing and OpenCensus have merget to form **[OpenTelemetry] ## Supported .NET versions -This project currently only supports apps targeting `netcoreapp2.1` (.NET Core 2.1), `netcoreapp3.1` (.NET Core 3.1) or net5.0 (.NET 5.0)! +This project currently only supports apps targeting `netcoreapp2.1` (.NET Core 2.1), `netcoreapp3.1` (.NET Core 3.1), net5.0 (.NET 5.0), or .NET 6.0! This project DOES NOT support the full .NET framework as that uses different instrumentation code. diff --git a/global.json b/global.json index f2363cb..46047c7 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "5.0.101" + "version": "6.0.100", + "rollForward": "feature" } } diff --git a/launch-sample.ps1 b/launch-sample.ps1 index 5bb2324..1a503e0 100644 --- a/launch-sample.ps1 +++ b/launch-sample.ps1 @@ -1,7 +1,7 @@ [CmdletBinding(PositionalBinding = $false)] param( - [ValidateSet("net5.0", "netcoreapp3.1", "netcoreapp2.1")] - [string] $Framework = "net5.0" + [ValidateSet("net6.0", "net5.0", "netcoreapp3.1", "netcoreapp2.1")] + [string] $Framework = "net6.0" ) dotnet build diff --git a/samples/net6.0/CustomersApi/Controllers/CustomersController.cs b/samples/net6.0/CustomersApi/Controllers/CustomersController.cs new file mode 100644 index 0000000..d96efe9 --- /dev/null +++ b/samples/net6.0/CustomersApi/Controllers/CustomersController.cs @@ -0,0 +1,40 @@ +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Samples.CustomersApi.DataStore; + +namespace Samples.CustomersApi.Controllers +{ + [Route("customers")] + public class CustomersController : Controller + { + private readonly CustomerDbContext _dbContext; + private readonly ILogger _logger; + + public CustomersController(CustomerDbContext dbContext, ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + [HttpGet] + public IActionResult Index() + { + return Json(_dbContext.Customers.ToList()); + } + + [HttpGet("{id:int}")] + public IActionResult Index(int id) + { + var customer = _dbContext.Customers.FirstOrDefault(x => x.CustomerId == id); + + if (customer == null) + return NotFound(); + + // ILogger events are sent to OpenTracing as well! + _logger.LogInformation("Returning data for customer {CustomerId}", id); + + return Json(customer); + } + } +} diff --git a/samples/net6.0/CustomersApi/CustomersApi.csproj b/samples/net6.0/CustomersApi/CustomersApi.csproj new file mode 100644 index 0000000..a5cb192 --- /dev/null +++ b/samples/net6.0/CustomersApi/CustomersApi.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + + + + + + + + + + + + + diff --git a/samples/net6.0/CustomersApi/DataStore/CustomerDbContext.cs b/samples/net6.0/CustomersApi/DataStore/CustomerDbContext.cs new file mode 100644 index 0000000..f1f433b --- /dev/null +++ b/samples/net6.0/CustomersApi/DataStore/CustomerDbContext.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using Shared; + +namespace Samples.CustomersApi.DataStore +{ + public class CustomerDbContext : DbContext + { + public CustomerDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Customers { get; 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/net6.0/CustomersApi/Program.cs b/samples/net6.0/CustomersApi/Program.cs new file mode 100644 index 0000000..a08e1b1 --- /dev/null +++ b/samples/net6.0/CustomersApi/Program.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Shared; + +namespace Samples.CustomersApi +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder + .UseStartup() + .UseUrls(Constants.CustomersUrl); + }) + .ConfigureServices(services => + { + // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) + services.AddJaeger(); + + // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core + 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"); + }); + + builder.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")); + }); + }); + + }); + } + } +} diff --git a/samples/net6.0/CustomersApi/Properties/launchSettings.json b/samples/net6.0/CustomersApi/Properties/launchSettings.json new file mode 100644 index 0000000..e40eb1b --- /dev/null +++ b/samples/net6.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/net6.0/CustomersApi/Startup.cs b/samples/net6.0/CustomersApi/Startup.cs new file mode 100644 index 0000000..94fe751 --- /dev/null +++ b/samples/net6.0/CustomersApi/Startup.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Samples.CustomersApi.DataStore; + +namespace Samples.CustomersApi +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + // Adds a Sqlite DB to show EFCore traces. + services + .AddDbContext(options => + { + options.UseSqlite("Data Source=DataStore/customers.db"); + }); + + services.AddMvc(); + + services.AddHealthChecks() + .AddDbContextCheck(); + } + + public void Configure(IApplicationBuilder app) + { + // Load some dummy data into the db. + BootstrapDataStore(app.ApplicationServices); + + app.UseDeveloperExceptionPage(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + endpoints.MapHealthChecks("/health"); + }); + } + + private void BootstrapDataStore(IServiceProvider serviceProvider) + { + using (var scope = serviceProvider.CreateScope()) + { + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Seed(); + } + } + } +} diff --git a/samples/net6.0/CustomersApi/appsettings.json b/samples/net6.0/CustomersApi/appsettings.json new file mode 100644 index 0000000..723c096 --- /dev/null +++ b/samples/net6.0/CustomersApi/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/net6.0/FrontendWeb/Controllers/HomeController.cs b/samples/net6.0/FrontendWeb/Controllers/HomeController.cs new file mode 100644 index 0000000..fee4005 --- /dev/null +++ b/samples/net6.0/FrontendWeb/Controllers/HomeController.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Shared; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Newtonsoft.Json; + +namespace Samples.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/net6.0/FrontendWeb/FrontendWeb.csproj b/samples/net6.0/FrontendWeb/FrontendWeb.csproj new file mode 100644 index 0000000..bf8dc4d --- /dev/null +++ b/samples/net6.0/FrontendWeb/FrontendWeb.csproj @@ -0,0 +1,12 @@ + + + + net6.0 + + + + + + + + diff --git a/samples/net6.0/FrontendWeb/Program.cs b/samples/net6.0/FrontendWeb/Program.cs new file mode 100644 index 0000000..5949d12 --- /dev/null +++ b/samples/net6.0/FrontendWeb/Program.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Shared; + +namespace Samples.FrontendWeb +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder + .UseStartup() + .UseUrls(Constants.FrontendUrl); + }) + .ConfigureServices(services => + { + // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) + services.AddJaeger(); + + // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core + services.AddOpenTracing(); + }); + } + } +} diff --git a/samples/net6.0/FrontendWeb/Properties/launchSettings.json b/samples/net6.0/FrontendWeb/Properties/launchSettings.json new file mode 100644 index 0000000..8870b99 --- /dev/null +++ b/samples/net6.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/net6.0/FrontendWeb/Startup.cs b/samples/net6.0/FrontendWeb/Startup.cs new file mode 100644 index 0000000..7cd933c --- /dev/null +++ b/samples/net6.0/FrontendWeb/Startup.cs @@ -0,0 +1,31 @@ +using System.Net.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Samples.FrontendWeb +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute(); + }); + } + } +} diff --git a/samples/net6.0/FrontendWeb/Views/Home/Index.cshtml b/samples/net6.0/FrontendWeb/Views/Home/Index.cshtml new file mode 100644 index 0000000..8948537 --- /dev/null +++ b/samples/net6.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/net6.0/FrontendWeb/Views/Home/PlaceOrder.cshtml b/samples/net6.0/FrontendWeb/Views/Home/PlaceOrder.cshtml new file mode 100644 index 0000000..70a6ebc --- /dev/null +++ b/samples/net6.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/net6.0/FrontendWeb/Views/_ViewImports.cshtml b/samples/net6.0/FrontendWeb/Views/_ViewImports.cshtml new file mode 100644 index 0000000..f8cbd18 --- /dev/null +++ b/samples/net6.0/FrontendWeb/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using Samples.FrontendWeb +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/samples/net6.0/FrontendWeb/appsettings.json b/samples/net6.0/FrontendWeb/appsettings.json new file mode 100644 index 0000000..36ea555 --- /dev/null +++ b/samples/net6.0/FrontendWeb/appsettings.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information", + "OpenTracing": "Debug" + } + } +} diff --git a/samples/net6.0/OrdersApi/Controllers/OrdersController.cs b/samples/net6.0/OrdersApi/Controllers/OrdersController.cs new file mode 100644 index 0000000..a2f286a --- /dev/null +++ b/samples/net6.0/OrdersApi/Controllers/OrdersController.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using OpenTracing; +using OrdersApi.DataStore; +using Shared; + +namespace Samples.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.Value); + + var order = new Order + { + CustomerId = cmd.CustomerId.Value, + 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/net6.0/OrdersApi/DataStore/Order.cs b/samples/net6.0/OrdersApi/DataStore/Order.cs new file mode 100644 index 0000000..09bca0c --- /dev/null +++ b/samples/net6.0/OrdersApi/DataStore/Order.cs @@ -0,0 +1,19 @@ +using System; +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; } + + [Required, Range(1, 100)] + public int Quantity { get; set; } + } +} diff --git a/samples/net6.0/OrdersApi/DataStore/OrdersDbContext.cs b/samples/net6.0/OrdersApi/DataStore/OrdersDbContext.cs new file mode 100644 index 0000000..67a1ae4 --- /dev/null +++ b/samples/net6.0/OrdersApi/DataStore/OrdersDbContext.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; + +namespace OrdersApi.DataStore +{ + public class OrdersDbContext : DbContext + { + public OrdersDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Orders { get; set; } + + public void Seed() + { + if (Database.EnsureCreated()) + { + Database.Migrate(); + + SaveChanges(); + } + } + } +} diff --git a/samples/net6.0/OrdersApi/OrdersApi.csproj b/samples/net6.0/OrdersApi/OrdersApi.csproj new file mode 100644 index 0000000..f7b0734 --- /dev/null +++ b/samples/net6.0/OrdersApi/OrdersApi.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + + + + + + + + + + + + + diff --git a/samples/net6.0/OrdersApi/Program.cs b/samples/net6.0/OrdersApi/Program.cs new file mode 100644 index 0000000..57f53a6 --- /dev/null +++ b/samples/net6.0/OrdersApi/Program.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Shared; + +namespace Samples.OrdersApi +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder + .UseStartup() + .UseUrls(Constants.OrdersUrl); + }) + .ConfigureServices(services => + { + // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) + services.AddJaeger(); + + // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core + 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"); + }); + }); + }); + } + } +} diff --git a/samples/net6.0/OrdersApi/Properties/launchSettings.json b/samples/net6.0/OrdersApi/Properties/launchSettings.json new file mode 100644 index 0000000..6b818d0 --- /dev/null +++ b/samples/net6.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/net6.0/OrdersApi/Startup.cs b/samples/net6.0/OrdersApi/Startup.cs new file mode 100644 index 0000000..4385702 --- /dev/null +++ b/samples/net6.0/OrdersApi/Startup.cs @@ -0,0 +1,57 @@ +using System; +using System.Net.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using OrdersApi.DataStore; + +namespace Samples.OrdersApi +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + // Adds a SqlServer DB to show EFCore traces. + services + .AddDbContext(options => + { + options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Orders-net5;Trusted_Connection=True;MultipleActiveResultSets=true"); + }); + + services.AddSingleton(); + + services.AddMvc(); + + services.AddHealthChecks() + .AddDbContextCheck(); + } + + public void Configure(IApplicationBuilder app) + { + // Load some dummy data into the db. + BootstrapDataStore(app.ApplicationServices); + + app.UseDeveloperExceptionPage(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute(); + endpoints.MapHealthChecks("/health"); + }); + } + + private void BootstrapDataStore(IServiceProvider serviceProvider) + { + using (var scope = serviceProvider.CreateScope()) + { + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Seed(); + } + } + } +} diff --git a/samples/net6.0/OrdersApi/appsettings.json b/samples/net6.0/OrdersApi/appsettings.json new file mode 100644 index 0000000..723c096 --- /dev/null +++ b/samples/net6.0/OrdersApi/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/net6.0/Shared/Constants.cs b/samples/net6.0/Shared/Constants.cs new file mode 100644 index 0000000..8c1dabf --- /dev/null +++ b/samples/net6.0/Shared/Constants.cs @@ -0,0 +1,11 @@ +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/net6.0/Shared/Customer.cs b/samples/net6.0/Shared/Customer.cs new file mode 100644 index 0000000..b819e2c --- /dev/null +++ b/samples/net6.0/Shared/Customer.cs @@ -0,0 +1,18 @@ +namespace Shared +{ + public class Customer + { + public int CustomerId { get; set; } + public string Name { get; set; } + + public Customer() + { + } + + public Customer(int customerId, string name) + { + CustomerId = customerId; + Name = name; + } + } +} diff --git a/samples/net6.0/Shared/JaegerServiceCollectionExtensions.cs b/samples/net6.0/Shared/JaegerServiceCollectionExtensions.cs new file mode 100644 index 0000000..2e8cb57 --- /dev/null +++ b/samples/net6.0/Shared/JaegerServiceCollectionExtensions.cs @@ -0,0 +1,55 @@ +using System; +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; + + 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 => _jaegerUri.IsBaseOf(request.RequestUri)); + }); + + return services; + } + } +} diff --git a/samples/net6.0/Shared/PlaceOrderCommand.cs b/samples/net6.0/Shared/PlaceOrderCommand.cs new file mode 100644 index 0000000..31a8f00 --- /dev/null +++ b/samples/net6.0/Shared/PlaceOrderCommand.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Shared +{ + public class PlaceOrderCommand + { + [Required] + public int? CustomerId { get; set; } + + [Required, StringLength(10)] + public string ItemNumber { get; set; } + + [Required, Range(1, 100)] + public int Quantity { get; set; } + } +} diff --git a/samples/net6.0/Shared/Shared.csproj b/samples/net6.0/Shared/Shared.csproj new file mode 100644 index 0000000..f267cda --- /dev/null +++ b/samples/net6.0/Shared/Shared.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + + + + + + + + + + + + + + + diff --git a/samples/net6.0/TrafficGenerator/Program.cs b/samples/net6.0/TrafficGenerator/Program.cs new file mode 100644 index 0000000..f02674d --- /dev/null +++ b/samples/net6.0/TrafficGenerator/Program.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace TrafficGenerator +{ + class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) + services.AddJaeger(); + + services.AddOpenTracing(); + + services.AddHostedService(); + }); + } +} diff --git a/samples/net6.0/TrafficGenerator/TrafficGenerator.csproj b/samples/net6.0/TrafficGenerator/TrafficGenerator.csproj new file mode 100644 index 0000000..59ad19b --- /dev/null +++ b/samples/net6.0/TrafficGenerator/TrafficGenerator.csproj @@ -0,0 +1,12 @@ + + + + net6.0 + + + + + + + + diff --git a/samples/net6.0/TrafficGenerator/Worker.cs b/samples/net6.0/TrafficGenerator/Worker.cs new file mode 100644 index 0000000..b6d7e98 --- /dev/null +++ b/samples/net6.0/TrafficGenerator/Worker.cs @@ -0,0 +1,58 @@ +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +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/net6.0/TrafficGenerator/appsettings.json b/samples/net6.0/TrafficGenerator/appsettings.json new file mode 100644 index 0000000..789de74 --- /dev/null +++ b/samples/net6.0/TrafficGenerator/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Warning", + "Microsoft.AspNetCore.Hosting": "Information" + } + } +} diff --git a/src/OpenTracing.Contrib.NetCore/OpenTracing.Contrib.NetCore.csproj b/src/OpenTracing.Contrib.NetCore/OpenTracing.Contrib.NetCore.csproj index 7762753..d753162 100644 --- a/src/OpenTracing.Contrib.NetCore/OpenTracing.Contrib.NetCore.csproj +++ b/src/OpenTracing.Contrib.NetCore/OpenTracing.Contrib.NetCore.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1;netcoreapp3.1;net5.0 + netcoreapp2.1;netcoreapp3.1;net5.0;net6.0 Adds OpenTracing instrumentation for .NET Core apps that use the `Microsoft.Extensions.*` stack. Instrumented components: HttpClient calls, ASP.NET Core, Entity Framework Core and any other library that uses DiagnosticSource events. opentracing;distributed-tracing;tracing;netcore @@ -60,7 +60,25 @@ Instrumented components: HttpClient calls, ASP.NET Core, Entity Framework Core a - + + + + + + + + + + + + + + + + + + +