惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
LangChain Blog
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
A
About on SuperTechFans
J
Java Code Geeks
量子位
博客园 - 三生石上(FineUI控件)
博客园 - Franky
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
IT之家
IT之家
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
雷峰网
雷峰网
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
Martin Fowler
Martin Fowler
有赞技术团队
有赞技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

博客园 - 无心々菜

InfluxDB2 在 docker 环境下部署,以及备份还原 高并发 - 配置2 高并发 - 配置 地图 - QGIS加载geojson,并导入Postgis数据库 VUE2 - Geoserver打印矢量PDF地图 - 在VUE2中的应用 地图 - Geoserver打印矢量PDF地图 Docker - 离线+无root环境安装 docker-rootless 运维 - 哪吒监控 Postgres 数据库在docker环境下分布式部署(arm64框架) Postgres 数据库在docker环境下分布式部署 Https - 配置方案 VUE3 - Docker部署 + traefik反代 VUE2 - Docker部署 + traefik反代 高斯DB 数据库的安装 NETCORE - 使用 EFCORE 调用 高斯DB数据库 JMeter - 压力测试 NETCORE - IdentityServer4 多节点部署 NETCORE - IdentityServer4 相关文档 .NET框架 - NETCORE + API + EF(DBFirst) + PostgresSql Docker 部署 APISIX + etcd Docker部署martin 矢量切片服务器 Postgres 数据库的安装 NETCORE - 调用 RUSTFS Docker - 部署 RustFS 对像存储 Docker - 使用 Docker Secrets 敏感数据传输 Docker - 部署 Consul KV 统一管理所有项目的配置 Docker - 部署 HashiCorp Vault 开源密钥管理工具 kong 网关下集成 Consul服务注册与发现 Docker 部署 kong 网关 Docker - 部署Consul 新
OpenObserver - 系统接口监控
无心々菜 · 2026-04-17 · via 博客园 - 无心々菜

 OpenObserver - 系统接口监控

1. 拉取镜像

docker pull public.ecr.aws/zinclabs/openobserve:latest

2. docker-compose

  openobserve:
    image: public.ecr.aws/zinclabs/openobserve:latest
    container_name: openobserve
    restart: unless-stopped
    ports:
      - "5080:5080"
    volumes:
      - ./openobserve-data:/data
    environment:
      ZO_ROOT_USER_EMAIL: "root@example.com"
      ZO_ROOT_USER_PASSWORD: "Pass*No01"
      

3. 启动

docker-compose up  -d openobserve

4. 打开浏览器

http://localhost:5080/

image

image

5.   .net8 项目中安装 nuget 包

dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http

image

6. 项目中 Program.cs


#region OpenTelemetry
builder.Services.AddMyOpenTelemetry(configuration);
#endregion

7. 增加文件 OpenTelemetryServiceExtensions.cs

查看代码
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Instrumentation.AspNetCore;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;

namespace Rail.Medium.Middleware
{
    public static class OpenTelemetryServiceExtensions
    {
        public static IServiceCollection AddMyOpenTelemetry(
            this IServiceCollection services,
            IConfiguration configuration)
        {
            var otelConfig = configuration.GetSection("OpenTelemetry");

            if (otelConfig == null)
            {
                Console.WriteLine("[警告] OpenTelemetry 配置不存在");
                return services;
            }

            var endpoint = otelConfig["Endpoint"];
            var username = otelConfig["Credentials:Username"];
            var password = otelConfig["Credentials:Password"];
            var serviceName = otelConfig["ServiceName"];

            if (string.IsNullOrWhiteSpace(endpoint) ||
                string.IsNullOrWhiteSpace(username) ||
                string.IsNullOrWhiteSpace(password) ||
                string.IsNullOrWhiteSpace(serviceName))
            {
                Console.WriteLine("[警告] OpenTelemetry 配置不完整");
                return services;
            }

            var credentials = Convert.ToBase64String(
                Encoding.UTF8.GetBytes($"{username}:{password}"));

            var resourceBuilder = ResourceBuilder.CreateDefault()
                .AddService(serviceName)
                .AddAttributes(new Dictionary<string, object>
                {
                    ["environment"] = otelConfig["Environment"] ?? "unknown",
                    ["service.version"] = otelConfig["ServiceVersion"] ?? "unknown"
                });

            services.AddOpenTelemetry()
                .WithTracing(tracing => tracing
                    .SetResourceBuilder(resourceBuilder)
                    .AddAspNetCoreInstrumentation(options => ConfigureAspNetCore(options))
                    .AddHttpClientInstrumentation()
                    .AddOtlpExporter(opt =>
                    {
                        opt.Endpoint = new Uri(endpoint);
                        opt.Headers = $"Authorization=Basic {credentials}";
                        opt.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf;
                    }));

            return services;
        }

        private static void ConfigureAspNetCore(AspNetCoreTraceInstrumentationOptions options)
        {
            options.EnrichWithHttpRequest = (activity, request) =>
             {
                 SetClientIp(request, activity);
                 SetUserAgent(request, activity);
                 SetServerInfo(activity);
                 SetLoginRequestInfo(request, activity);
                 TryReadUserFromJwt(request, activity);
             };

            options.EnrichWithHttpResponse = (activity, response) =>
            {
                SetLoginResult(response, activity);
            };


        }

        //=========================================================
        // 客户端 IP
        //=========================================================
        private static void SetClientIp(HttpRequest request, Activity activity)
        {
            string ip = null;

            var xff = request.Headers["X-Forwarded-For"].FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(xff))
            {
                ip = xff.Split(',')[0].Trim();
            }

            if (string.IsNullOrWhiteSpace(ip))
            {
                ip = request.Headers["X-Real-IP"].FirstOrDefault();
            }

            if (string.IsNullOrWhiteSpace(ip))
            {
                ip = request.HttpContext.Connection.RemoteIpAddress?.ToString();
            }

            if (!string.IsNullOrWhiteSpace(ip))
            {
                activity?.SetTag("client.ip", ip);
            }
        }

        //=========================================================
        // 浏览器信息
        //=========================================================
        private static void SetUserAgent(HttpRequest request, Activity activity)
        {
            var ua = request.Headers["User-Agent"].FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(ua))
            {
                activity?.SetTag("user.agent", ua);
            }
        }

        //=========================================================
        // 当前机器 / Docker
        //=========================================================
        private static void SetServerInfo(Activity activity)
        {
            activity?.SetTag("server.name", Environment.MachineName);
            activity?.SetTag("container.name", Environment.GetEnvironmentVariable("HOSTNAME"));
        }

        //=========================================================
        // 登录请求识别
        //=========================================================
        private static void SetLoginRequestInfo(HttpRequest request, Activity activity)
        {
            var path = request.Path.Value?.ToLower();

            if (path != "/connect/token")
                return;

            activity?.SetTag("auth.endpoint", "login");

            if (!request.HasFormContentType)
                return;

            var grantType = request.Form["grant_type"].FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(grantType))
            {
                activity?.SetTag("grant.type", grantType);
            }

            var clientId = request.Form["client_id"].FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(clientId))
            {
                activity?.SetTag("client.id", clientId);
            }

            var username = request.Form["username"].FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(username))
            {
                activity?.SetTag("login.username", username);
            }
        }

        //=========================================================
        // 登录结果
        //=========================================================
        private static void SetLoginResult(HttpResponse response, Activity activity)
        {
            var path = response.HttpContext.Request.Path.Value?.ToLower();

            if (path != "/connect/token")
                return;

            if (response.StatusCode == 200)
            {
                activity?.SetTag("login.result", "success");
            }
            else
            {
                activity?.SetTag("login.result", "fail");
            }
        }

        //=========================================================
        // 解析 JWT Token
        //=========================================================
        private static void TryReadUserFromJwt(HttpRequest request, Activity activity)
        {
            var auth = request.Headers["Authorization"].FirstOrDefault();

            if (string.IsNullOrWhiteSpace(auth))
                return;

            if (!auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            var token = auth.Substring(7).Trim();

            if (string.IsNullOrWhiteSpace(token))
                return;

            try
            {
                var handler = new JwtSecurityTokenHandler();

                if (!handler.CanReadToken(token))
                    return;

                var jwt = handler.ReadJwtToken(token);

                // 用户ID
                var userId = GetClaim(jwt, "sub", ClaimTypes.NameIdentifier);
                if (!string.IsNullOrWhiteSpace(userId))
                {
                    activity?.SetTag("user.id", userId);
                }

                // 用户名
                var userName = GetClaim(jwt, "name", ClaimTypes.Name, "preferred_username");
                if (!string.IsNullOrWhiteSpace(userName))
                {
                    activity?.SetTag("user.name", userName);
                }

                // client_id
                var clientId = GetClaim(jwt, "client_id");
                if (!string.IsNullOrWhiteSpace(clientId))
                {
                    activity?.SetTag("client.id", clientId);
                }

                // 角色
                var roles = jwt.Claims
                    .Where(x => x.Type == "role" || x.Type == ClaimTypes.Role)
                    .Select(x => x.Value)
                    .Distinct()
                    .ToArray();

                if (roles.Length > 0)
                {
                    activity?.SetTag("user.roles", string.Join(",", roles));
                }

                // scope
                var scope = GetClaim(jwt, "scope");
                if (!string.IsNullOrWhiteSpace(scope))
                {
                    activity?.SetTag("scope", scope);
                }

                // tenant
                var tenant = GetClaim(jwt, "tenant_id", "tenant");
                if (!string.IsNullOrWhiteSpace(tenant))
                {
                    activity?.SetTag("tenant.id", tenant);
                }
            }
            catch
            {
                // token 异常忽略
            }
        }

        //=========================================================
        // Claim读取助手
        //=========================================================
        private static string GetClaim(JwtSecurityToken jwt, params string[] names)
        {
            foreach (var name in names)
            {
                var value = jwt.Claims.FirstOrDefault(x => x.Type == name)?.Value;
                if (!string.IsNullOrWhiteSpace(value))
                {
                    return value;
                }
            }

            return null;
        }
    }
}

8. 测试 

调用接口后,查看 http://localhost:5080/web/

可在以下菜单查看

  数据流-追踪

  追踪

  日志

image

end,