【APM】NET Traces, Metrics and Logs to OLTP
系列文章目录
【APM】Observability Solution
【APM】Build an environment for Traces, Metrics and Logs of App by OpenTelemetry
【APM】NET Traces, Metrics and Logs to OLTP
【APM】How to enable Trace to Logs on Grafana?
前言
前一篇分享了Observability(可观测性)环境的安装,本篇继续分享其应用。
一、Create a new project by NET
ASP.Net Core WebApi
二、PackageReference installation
- Npgsql
- Npgsql.EntityFrameworkCore.PostgreSQL
- Npgsql.OpenTelemetry
- OpenTelemetry.Exporter.Console
- OpenTelemetry.Exporter.OpenTelemetryProtocol
- OpenTelemetry.Extensions.Hosting
- OpenTelemetry.Instrumentation.AspNetCore
- OpenTelemetry.Instrumentation.Http
- OpenTelemetry.Instrumentation.Runtime
三、Configure OpenTelemetry logging, metrics, & tracing
1.Program.cs
代码如下(示例):
private static readonly string ServiceName = typeof(Program).Namespace ?? "NetLog2Otlp";private static readonly string ServiceVersion = typeof(Program).Assembly.GetName().Version?.ToString() ?? "unknown";public static void Main(string[] args){var builder = WebApplication.CreateBuilder(args);#region Configure OpenTelemetry logging, metrics, & tracingstring otlpEndpoint = builder.Configuration.GetValue("Otlp:Endpoint", defaultValue: "http://localhost:4317");// Configure OpenTelemetry logging, metrics, & tracing with auto-start using the// AddOpenTelemetry extension from OpenTelemetry.Extensions.Hosting.builder.Services.AddOpenTelemetry().ConfigureResource(r => r.AddService(serviceName: ServiceName,serviceVersion: ServiceVersion,serviceInstanceId: Environment.MachineName)).WithTracing(_builder =>{// Tracing// Ensure the TracerProvider subscribes to any custom ActivitySources._builder.AddSource(InstrumentationSource.ActivitySourceName).SetSampler(new AlwaysOnSampler()).AddHttpClientInstrumentation() // 监控 HttpClient 请求.AddAspNetCoreInstrumentation() // 监控 ASP.NET Core 请求.AddNpgsql(); // 监控 SQL 查询// Use IConfiguration binding for AspNetCore instrumentation options.builder.Services.Configure<AspNetCoreTraceInstrumentationOptions>(builder.Configuration.GetSection("AspNetCoreInstrumentation"));_builder.AddOtlpExporter(otlpOptions =>{// Use IConfiguration directly for Otlp exporter endpoint option.otlpOptions.Endpoint = new Uri(otlpEndpoint);});}).WithMetrics(_builder =>{// Metrics// Ensure the MeterProvider subscribes to any custom Meters._builder.AddMeter(InstrumentationSource.MeterName).SetExemplarFilter(ExemplarFilterType.TraceBased).AddRuntimeInstrumentation() // 监控 .NET 运行时指标.AddHttpClientInstrumentation() // 监控 HttpClient 请求.AddAspNetCoreInstrumentation(); // 监控 ASP.NET Core 请求_builder.AddOtlpExporter(otlpOptions =>{// Use IConfiguration directly for Otlp exporter endpoint option.otlpOptions.Endpoint = new Uri(otlpEndpoint);});}).WithLogging(_builder =>{// Note: See appsettings.json Logging:OpenTelemetry section for configuration._builder.AddOtlpExporter(otlpOptions =>{// Use IConfiguration directly for Otlp exporter endpoint option.otlpOptions.Endpoint = new Uri(otlpEndpoint);}).AddConsoleExporter();});#endregion// Add services to the container....
2.appsettings.json
代码如下(示例):
{"Logging": {"LogLevel": {"Default": "Warning","Microsoft": "Warning","Microsoft.AspNetCore": "Warning","Microsoft.Hosting.Lifetime": "Warning","System": "Warning","NetLog2Otlp": "Information"},"OpenTelemetry": {"IncludeFormattedMessage": true,"IncludeScopes": true,"ParseStateValues": true}},"Otlp": {"Endpoint": "http://localhost:4317"},"AspNetCoreInstrumentation": {"RecordException": "true"},"AllowedHosts": "*"
}
3.InstrumentationSource.cs
namespace NetLog2Otlp.AspNetCore;using System.Diagnostics;
using System.Diagnostics.Metrics;/// <summary>
/// It is recommended to use a custom type to hold references for
/// ActivitySource and Instruments. This avoids possible type collisions
/// with other components in the DI container.
/// </summary>
public sealed class InstrumentationSource : IDisposable
{internal const string ActivitySourceName = "NetLog2Otlp.AspNetCore";internal const string MeterName = "NetLog2Otlp.AspNetCore";private readonly Meter meter;public InstrumentationSource(){string? version = typeof(InstrumentationSource).Assembly.GetName().Version?.ToString();this.ActivitySource = new ActivitySource(ActivitySourceName, version);this.meter = new Meter(MeterName, version);this.FreezingDaysCounter = this.meter.CreateCounter<long>("weather.days.freezing", description: "The number of days where the temperature is below freezing");}public ActivitySource ActivitySource { get; }public Counter<long> FreezingDaysCounter { get; }public void Dispose(){this.ActivitySource.Dispose();this.meter.Dispose();}
}
4.OtlpController.cs
using Microsoft.AspNetCore.Mvc;
using Npgsql;
using System.Collections.Generic;
using System.Reflection.Emit;
using Microsoft.EntityFrameworkCore;// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860namespace NetLog2Otlp.Controllers
{[Route("api/[controller]")][ApiController]public class OtlpController : ControllerBase{private readonly ILogger<OtlpController> _logger;// 使用依赖注入获取 Loggerpublic OtlpController(ILogger<OtlpController> logger){_logger = logger;}// GET: api/<OtlpController>[HttpGet]public IEnumerable<string> Get(){_logger.LogInformation("Start logging at {time}", DateTime.Now);try{ExecuteSql();_logger.LogInformation(" ExecuteSql finished at {time}", DateTime.Now);ExecuteEF();_logger.LogInformation(" ExecuteEF finished at {time}", DateTime.Now);HttpRequest();_logger.LogInformation(" Test API called at {time}", DateTime.Now);int i = 0;var j = i / 0; // This will throw a divide by zero exception}catch (Exception ex){_logger.LogError(ex, " An error occurred during the logging process.");}finally{_logger.LogInformation("End logging at {time}", DateTime.Now);}return new string[] { "value1", "value2", DateTime.Now.ToString() };}// GET api/<OtlpController>/5[HttpGet("{id}")]public string Get(int id = 0){_logger.LogDebug("LogDebug");_logger.LogInformation("LogInformation");_logger.LogWarning("LogWarning");_logger.LogError("LogError");_logger.LogInformation("Get by id API called at {time}, id: {id}", DateTime.Now, id.ToString());return string.Format("value: {0}", id.ToString());}void ExecuteSql(){// PostgreSQL 连接字符串var connectionString = "Host=192.168.1.1;Port=5432;Username=user;Password=user;Database=TestDB";// 使用 Npgsql 连接到 PostgreSQL 数据库using (var connection = new NpgsqlConnection(connectionString)){connection.Open();// 创建查询命令using (var command = new NpgsqlCommand("SELECT * FROM test1", connection)){// 执行查询并读取结果using (var reader = command.ExecuteReader()){while (reader.Read()){// 假设你的表中有列 "id" 和 "name"string id = reader.GetString(0); // 获取第一列的值string name = reader.GetString(1); // 获取第二列的值Console.WriteLine($"ID: {id}, Name: {name}");}}}}}void ExecuteEF(){using (var context = new MyDbContext()){// Read all entriesvar entities = context.MyEntities.ToList();// Create a new entryvar newEntity = new MyEntity { apid = "ExecuteEF", name = "New Entity", site = "*", url = "*" };context.MyEntities.Add(newEntity);context.SaveChanges();// Update an entryvar entity = context.MyEntities.Where(o => o.apid == "ExecuteEF").FirstOrDefault();if (entity.apid != null){entity.name = "Updated Name";context.SaveChanges();}// Delete an entrycontext.MyEntities.Remove(entity);context.SaveChanges();}}async void HttpRequest(){try{// 這裡替換成你要請求的 URLstring url = "https://www.baidu.com";using (var client = new HttpClient()){HttpResponseMessage response = await client.GetAsync(url);response.EnsureSuccessStatusCode(); // 會拋出異常如果 HTTP 請求不成功string responseBody = await response.Content.ReadAsStringAsync();Console.WriteLine(responseBody);}}catch (HttpRequestException e){Console.WriteLine("Request error: {0}", e.Message);}}}public class MyDbContext : DbContext{public MyDbContext(){}public MyDbContext(DbContextOptions<MyDbContext> options): base(options){}// Define DbSet for each entity in your database schemapublic DbSet<MyEntity> MyEntities { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){// PostgreSQL 连接字符串var connectionString = "Host=192.168.1.1;Port=5432;Username=user;Password=user;Database=TestDB";// Configure PostgreSQL as the database provideroptionsBuilder.UseNpgsql(connectionString);}protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<MyEntity>().HasKey(e => e.apid); // 指定主鍵是 apid 屬性modelBuilder.Entity<MyEntity>().ToTable("test1"); // 將 MyEntity 對應到名為 "test1" 的資料庫表}}public class MyEntity{public string apid { get; set; }public string name { get; set; }public string url { get; set; }public string site { get; set; }}}
四、项目文件结构及运行结果
1.项目文件结构
2.运行结果
Traces
Trace to Logs
总结
本文介绍了 NET Traces, Metrics and Logs to OLTP,以及如何在Grafana呈现 Traces 和 Trace to Logs。下篇文章将介绍 Trace to Logs 是如何实现?