初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace PCL.Core.Utils.Diagnostics;
|
||||
|
||||
public static class StackHelper
|
||||
{
|
||||
private const string Unknown = "<unknown>";
|
||||
|
||||
// 返回“直接调用者”的可读名称(例如 Namespace.Type.Method(paramTypes))
|
||||
// includeNamespace: 是否包含命名空间
|
||||
// includeParameters: 是否包含参数类型列表
|
||||
// skipAppFrames: 额外跳过的应用层帧数量(例如你自己的日志包装器)
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static string GetDirectCallerName(
|
||||
bool includeNamespace = true,
|
||||
bool includeParameters = false,
|
||||
int skipAppFrames = 0)
|
||||
{
|
||||
var st = new StackTrace(skipFrames: 1, fNeedFileInfo: false);
|
||||
var frame = st.GetFrame(skipAppFrames);
|
||||
var method = frame?.GetMethod();
|
||||
if (method is null) return Unknown;
|
||||
|
||||
method = _TryMapAsyncOrIterator(method);
|
||||
return _FormatMethod(method, includeNamespace, includeParameters);
|
||||
}
|
||||
|
||||
// 获取前 maxFrames 层调用栈,返回格式化后的每一帧字符串
|
||||
// needFileInfo=true 时会解析 PDB 以拿到文件名与行号(昂贵)
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static IReadOnlyList<string> GetStack(
|
||||
int maxFrames = 10,
|
||||
bool includeNamespace = true,
|
||||
bool includeParameters = false,
|
||||
bool needFileInfo = false)
|
||||
{
|
||||
if (maxFrames <= 0) return [];
|
||||
|
||||
var st = new StackTrace(skipFrames: 1, fNeedFileInfo: needFileInfo);
|
||||
var list = new List<string>(capacity: Math.Min(maxFrames, st.FrameCount));
|
||||
|
||||
for (int i = 0, added = 0; i < st.FrameCount && added < maxFrames; i++)
|
||||
{
|
||||
var method = st.GetFrame(i)?.GetMethod();
|
||||
if (method is null) continue;
|
||||
|
||||
method = _TryMapAsyncOrIterator(method);
|
||||
var sig = _FormatMethod(method, includeNamespace, includeParameters);
|
||||
|
||||
if (needFileInfo)
|
||||
{
|
||||
var f = st.GetFrame(i);
|
||||
var file = f?.GetFileName();
|
||||
var line = f?.GetFileLineNumber() ?? 0;
|
||||
if (!string.IsNullOrEmpty(file) && line > 0)
|
||||
{
|
||||
sig = $"{sig} ({System.IO.Path.GetFileName(file)}:{line})";
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(sig);
|
||||
added++;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// 将 async/iterator 的 MoveNext 映射回原始方法名(尽力而为的启发式)
|
||||
private static MethodBase _TryMapAsyncOrIterator(MethodBase method)
|
||||
{
|
||||
if (method.Name != "MoveNext") return method;
|
||||
|
||||
var dt = method.DeclaringType;
|
||||
if (dt is null) return method;
|
||||
|
||||
// 典型生成类型名:<MethodName>d__12 或 <MethodName>g__Local|12_0
|
||||
var name = dt.Name;
|
||||
var lt = name.IndexOf('<');
|
||||
var gt = name.IndexOf('>');
|
||||
if (lt >= 0 && gt > lt + 1)
|
||||
{
|
||||
var originalName = name.Substring(lt + 1, gt - lt - 1);
|
||||
// 在声明类型的外层类型里查找同名方法(可能存在重载,选第一个匹配)
|
||||
var parent = dt.DeclaringType ?? dt; // 迭代器常为嵌套到原类型里
|
||||
foreach (var m in parent.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
if (string.Equals(m.Name, originalName, StringComparison.Ordinal))
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
private static string _FormatMethod(MethodBase method, bool includeNamespace, bool includeParameters, bool includeParameterNamespace = false)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var type = method.DeclaringType;
|
||||
if (type is not null)
|
||||
{
|
||||
sb.Append(_FormatTypeName(type, includeNamespace));
|
||||
sb.Append('.');
|
||||
}
|
||||
|
||||
sb.Append(method.Name);
|
||||
|
||||
if (method is MethodInfo { IsGenericMethod: true } mi)
|
||||
{
|
||||
sb.Append('[');
|
||||
var args = mi.GetGenericArguments();
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (i > 0) sb.Append(", ");
|
||||
sb.Append(_FormatTypeName(args[i], false));
|
||||
}
|
||||
sb.Append(']');
|
||||
}
|
||||
|
||||
if (includeParameters)
|
||||
{
|
||||
var ps = method.GetParameters();
|
||||
sb.Append('(');
|
||||
for (var i = 0; i < ps.Length; i++)
|
||||
{
|
||||
if (i > 0) sb.Append(", ");
|
||||
sb.Append(_FormatTypeName(ps[i].ParameterType, includeParameterNamespace));
|
||||
if (!string.IsNullOrEmpty(ps[i].Name))
|
||||
{
|
||||
sb.Append(' ').Append(ps[i].Name);
|
||||
}
|
||||
}
|
||||
sb.Append(')');
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string _FormatTypeName(Type t, bool includeNamespace)
|
||||
{
|
||||
if (t.IsGenericType)
|
||||
{
|
||||
var def = t.GetGenericTypeDefinition();
|
||||
var name = def.Name;
|
||||
var tick = name.IndexOf('`');
|
||||
if (tick >= 0) name = name[..tick];
|
||||
|
||||
var ns = includeNamespace ? (def.Namespace is null ? "" : def.Namespace + ".") : "";
|
||||
var sb = new StringBuilder(ns).Append(name).Append('[');
|
||||
var args = t.GetGenericArguments();
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (i > 0) sb.Append(", ");
|
||||
sb.Append(_FormatTypeName(args[i], false));
|
||||
}
|
||||
sb.Append(']');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
return includeNamespace && t.Namespace is not null
|
||||
? t.Namespace + "." + t.Name
|
||||
: t.Name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user