【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, & tracing

            string 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=397860

namespace NetLog2Otlp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class OtlpController : ControllerBase
    {
        private readonly ILogger<OtlpController> _logger;

        // 使用依赖注入获取 Logger
        public 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 entries
                var entities = context.MyEntities.ToList();

                // Create a new entry
                var newEntity = new MyEntity { apid = "ExecuteEF", name = "New Entity", site = "*", url = "*" };
                context.MyEntities.Add(newEntity);
                context.SaveChanges();

                // Update an entry
                var entity = context.MyEntities.Where(o => o.apid == "ExecuteEF").FirstOrDefault();
                if (entity.apid != null)
                {
                    entity.name = "Updated Name";
                    context.SaveChanges();
                }

                // Delete an entry
                context.MyEntities.Remove(entity);
                context.SaveChanges();
            }
        }

        async void HttpRequest()
        {
            try
            {
                // 這裡替換成你要請求的 URL
                string 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 schema
        public 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 provider
            optionsBuilder.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 是如何实现?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值