





























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. 打开浏览器


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

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/
可在以下菜单查看
数据流-追踪
追踪
日志

end,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。