首页 > 其他分享 >第13章 诊断

第13章 诊断

时间:2025-01-01 18:08:11浏览次数:9  
标签:StackTrace 13 调用 Console 诊断 计数器 WriteLine 日志

第13章 诊断

13.1 条件编译

预编译的指令见 4.16 预处理指令,我们这里的条件编译用到的指令有:

  • #if​​、#else​​、#endif​​、#elif​​

    条件编译指令可以进行 && ​、 || ​、 ! 运算。

预定义指令可以通过三种方式定义:

  1. 在文件中通过 #define 定义 Symbol
  2. 编译时传递 /define 参数
csc Program.cs /define:TESTMODE,PLAYMODE
  1. Visual Studio 在项目属性中定义编译 Symbol

13.1.2 Conditional​ 特性

Conditional​ 特性用法如下:

[Conditional ("LOGGINGMODE")]
static void LogStatus (string msg)
{
    string logFilePath = ...
    System.IO.File.AppendAllText (logFilePath, msg + "\r\n");
}

Conditional​ 特性指示编译器在所有调用 LogStatus​ 的地方使用 #if LOGGINGMODE ​ 进行包装,如果 LOGGINGMODE​ 没有定义,则 LogStatus​ 的调用在编译时将完全忽略,包括参数表达式的计算。即使 LogStatus​ 和调用者 不在同一程序集 该机制仍然有效。

C7.0 核心技术指南 第7版.pdf - p578 - C7.0 核心技术指南 第 7 版-P578-20240214151621

#if​ 相较 Conditional​ 的不足:

C7.0 核心技术指南 第7版.pdf - p577 - C7.0 核心技术指南 第 7 版-P577-20240214151902

13.2 Debug​ 和 Trace​ 类

Debug​ 和 Trace​ 类的方法都被 Conditional​ 进行了标注,区别如下:

  • 所有 Debug​ ​类中的方法都标记为 [Conditional("DEBUG")] ​​。
  • 所有 Trace​ ​类中的方法都标记为 [Conditional("TRACE")] ​​。

默认情况下,VS 在项目的 debug 配置中定义了 ** DEBUG ** 和 ** TRACE ** 符号,在 release 中仅定义了 ** TRACE ** 符号。

13.2.1 Fail​ 和 Assert​ 方法

Debug​ 和 Trace​ 类型都提供了 Fail ​和 Assert ​方法,区别如下:

  • Fail

    调用时将弹出对话框,询问“忽略”、“终止”还是“重试”。

  • Assert

    Assert​ 中的表达式返回 false​(意味着程序缺陷),将会自动调用 Fail​ 方法。

从技术上说,根据条件抛出异常也是一种断言,区别如下:

  • 抛出异常:反映了 调用者代码 缺陷
  • Assert​:反映了 当前方法的 缺陷

13.2.2 TraceListener​ 类

Debug​ 和 Trace​ 类的 Listeners​ 属性是 TraceListener ​ 实例的静态集合。它们负责处理 Write​、Fail​ 和 Trace​ 方法触发的信息。

默认情况下,该集合包含一个单独的监听器( DefaultTraceListener ​)。可以手动添加自定义监听器、移除默认监听器,以改变监听行为。既可以从零开始编写 TraceListener​(从 TraceListener​ 继承),也可使用以下预定义的监听器类型:

  1. TextWriterTraceListener ​​:将消息写入 Stream​ ​或者 TextWriter​​,或将消息追加到文件中。

    TextWriterTraceListener​ 类还进一步的划分为了:

    1. ConsoleTraceListener​​、
    2. DelimitedListTraceListener​​、
    3. XmlWriterTraceListener​​
    4. EventSchemaTraceListener​​.
  2. EventLogTraceListener ​​:将事件日志写入到 Windows 事件日志中。

  3. EventProviderTraceListener ​​:将事件写入操作系统的 Windows 事件追踪(EventTracingforWindows,ETW)子系统中(支持 WindowsVista 及其之后的操作系统)。

  4. WebPageTraceListener​​:写入某个 ASP.NET 页面。

C7.0 核心技术指南 第7版.pdf - p581 - C7.0 核心技术指南 第 7 版-P581-20240215094613

提纲
mindmap TraceListener 预定义 DefaultTraceListener TextWriterTraceListener EventLogTraceListener EventProviderTraceListener WebPageTraceListener 常用成员 Filter IndentLevel IndentSize TraceOutputOptions AutoFlush ["Flush()"] ["Close()"]
DefaultTraceListener

这个默认的监听器有两个关键特性:

  1. 当连接到调试器时(例如 Visual Studio 的调试器),将消息输出到 调试输出窗口 ;否则忽略消息内容。
  2. 当调用 Fail​ 方法时(或 Assert​ 失败时),不论是否附加了调试器,都 弹出对话框询问用户是继续、忽略还是重试(附加/调试)
EventLogTraceListener

该监听器将信息输出至 Windows 事件日志 ,不同的方法将输出不同类型的日志:

  1. 信息:

    Write​、Fail​、Assert

  2. 警告:

    TraceWarning

  3. 错误:

    TraceError

使用方式
Trace.Listeners.Clear();
Trace.Listeners.Add(new TextWriterTraceListener("D:\\trace.txt"));
TextWriter tw = Console.Out;
Trace.Listeners.Add(new TextWriterTraceListener(tw));
// 如下代码需用管理员权限运行
if (!EventLog.SourceExists("WinFormsApp1"))
    EventLog.CreateEventSource("WinFormsApp1", "Application");
Trace.Listeners.Add(new EventLogTraceListener("WinFormsApp1"));
TraceListener​ 的属性

TraceListener ​有如下属性进行自定义行为:

  1. Filter​​ ​属性

    TraceFilter​​ 类型,消息过滤器。可以使用其预定义子类(例如 EventTypeFilter​​、SourceFilter​​),也可以派生 TraceFilter​​ 并重写 ShouldTrace​​ 方法。

  2. IndentLevel​​、IndentSize​ ​属性

    用于 控制缩进

  3. TraceOutputOptions​ ​属性

    可以用来 写入额外的数据 ,调用 Trace ​​​ 方法时会应用 TraceOutputOptions​​​ 中的配置:

    TextWriterTraceListener tl = new TextWriterTraceListener (Console.Out);
    tl.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.Callstack;
    ...
    Trace.TraceWarning("Orange alert");
    // 输出:
    // DiagTest.vshost.exe Warning: O : Orange alert
    //        DateTime=2007-03-08T05:57:13.6250000Z
    //        Callstack=    at System.Environment.GetStackTrace(Exception e,BooleanneedFileInfo)
    //        at System.Environment.get_StackTrace()    at..
    

13.2.3 刷新并关闭监听器

TextWriterTraceListener​ 会先缓存再写入,需要适时关闭或刷新监听器。方法有三:

  1. 使用 Debug(Trace).Flush ​ ​方法刷新监听器

  2. 关闭程序时调用 Debug(Trace).Close ​ ​方法

    该方法会隐式调用 Flush ​方法,并关闭文件句柄。

  3. 设置 Debut(Trace).AutoFlush ​ ​属性为 true

    每条消息后强制执行 Flush ​方法。

C7.0 核心技术指南 第7版.pdf - p582 - C7.0 核心技术指南 第 7 版-P582-20240215171006

13.3 调试器的集成

13.3.1 附加和断点

System.Diagnostics ​命名空间中的静态 Debugger ​类提供了与调试器交互的基本函数:

  1. Break

    断点。启动调试器并将进程附加在调试器上,并在当前执行点挂起。

  2. Launch

    除挂起外,和 Break ​无区别。

  3. Log

    向调试器窗口输出信息。

  4. IsAttached

    是否已有调试器附加到了当前应用程序上。

13.3.2 Debugger​ 特性

DebuggerStepThrough ​和 DebuggerHidden ​特性可以应用于方法构造器、和

  • DebuggerStepThrough

    允许调试器在 单步执行 时跳过某些代码,但如果在该代码中发生异常,调试器 仍会停留

  • DebuggerHidden

    调试器完全忽略这段代码,即使发生异常 也不停留

13.4 进程与线程处理

13.4.1 检查运行中的进程

Process.GetProcessXXX ​ ​方法可以获得相应进程(包含托管、非托管进程)。每一个 Process​ ​实例都拥有诸多属性:名称、ID、优先级、内存、处理器利用率、窗口句柄等。

Process.GetCurrentProcess ​ ​方法返回当前的进程。如果创建了额外的应用程序域,它们将共享同一个进程。

如果需要终止一个进程,可以调用 Kill ​方法。

13.4.2 在进程中检查线程

Process.Threads ​ ​属性可用于枚举其他进程内的所有线程,为 ProcessThread​ ​类型。该对象用于管理而非执行同步任务。ProcessThread​ ​对象提供了相应线程的诊断信息,并允许对某些属性进行控制,例如优先级和处理器亲和性。

13.5 StackTrace​ 和 StackFrame​ 类

StackTrace​ ​和 StackFrame​ ​类提供了执行调用栈的只读视图,主要用于诊断目的。StackTrace​ ​代表了 一个完整的调用栈 ,而 StackFrame​ ​代表了 调用栈中的一个单独的方法调用

获取当前线程调用栈(StackTrace​)的快照,方式有二:

  1. 使用 StackTrace 的无参构造器。

  2. 使用 StackTrace bool 参数构造器。

    若参数传入 true​,且存在 pdb 文件,StackTrace​ 将读取文件名、行号等数据。

获取调用帧(StackFrame​),方法有二:

  • StackTrace.GetFrame ​​

    获取特定调用帧

  • StackTrace.GetFrames ​​

    获取所有调用帧

获取整个 StackTrace ​基本信息的最简单的方法是调用 ToString ​方法。当然也可以手动操作,方式如下:

static void Main() { A(); }
static void A() { B(); }
static void B() { C(); }
static void C()
{
    StackTrace s = new StackTrace(true);
    Console.WriteLine("Total frames:   " + s.FrameCount);
    Console.WriteLine("Current method: " + s.GetFrame(0).GetMethod().Name);
    Console.WriteLine("Calling method: " + s.GetFrame(1).GetMethod().Name);
    Console.WriteLine("Entry method:   " + s.GetFrame(s.FrameCount - 1).GetMethod().Name);

    Console.WriteLine("Call Stack:");
    foreach (var f in s.GetFrames())
    {
        Console.WriteLine(
        "  File:   " + f.GetFileName() +
        "  Line:   " + f.GetFileLineNumber() +
        "  Col:    " + f.GetFileColumnNumber() +
        "  Offset: " + f.GetILOffset() +
        "  Method: " + f.GetMethod().Name);
    }
}

其输出如下:

C7.0 核心技术指南 第7版.pdf - p585 - C7.0 核心技术指南 第 7 版-P585-20240215181746

C7.0 核心技术指南 第7版.pdf - p586 - C7.0 核心技术指南 第 7 版-P586-20240215224138

另见22.10 Suspend 和 Resume 方法,获取线程的调用栈信息

StackTrace stackTrace = null;
targetThread.Suspend();
try { stackTrace = new StackTrace (targetThread, true); }
finally { targetThread.Resume(); }

13.6 Windows 事件日志

标准 Windows 事件日志有三种,按名称分为:

  1. 应用程序
  2. 系统
  3. 安全

应用程序日志是大多数应用程序通常写入日志的地方。

13.6.1 写入事件日志

写入前需创建相应的日志源,创建后,调用 EventLog.WriteEntry​ 写入时需提供日志名称、源名称和消息数据

image

使用方式如下:

const string SourceName = "WinFormsApp1";
if (!EventLog.SourceExists(SourceName))
    EventLog.CreateEventSource(SourceName, "Application");
EventLog.WriteEntry(SourceName, "测试Windows日志", EventLogEntryType.Information);

13.6.2 读取事件日志

步骤如下:

  1. 传入日志名称(三种日志名称之一),实例化 EventLog​ 对象
  2. 指定日志所在计算机的名称(可选)
  3. 通过 Entries ​集合读取日志

使用方式如下:

EventLog log = new EventLog("Application");

Console.WriteLine(log.Entries.Count);

EventLogEntry last = log.Entries[log.Entries.Count - 1];
Console.WriteLine("Index:   " + last.Index);
Console.WriteLine("Source:  " + last.Source);
Console.WriteLine("Type:    " + last.EntryType);
Console.WriteLine("Time:    " + last.TimeWritten);
Console.WriteLine("Message: " + last.Message);

可以使用静态方法 EventLog.GetEventLogs​​(需要管理员权限)来枚举当前(或者其他)计算机的所有日志名称,以下代码通常情况下至少会打印“应用程序”、“安全”以及“系统”:

foreach (EventLog log in EventLog.GetEventLogs()) {
    Console.WriteLine(log.LogDisplayName);
}

13.6.3 监视事件日志

EntryWritten ​事件相当于一个钩子,可以在 Windows 事件日志被写入时获得通知。使用步骤如下:

  1. 实例化 EventLog ​并将它的 EnableRaisingEvents ​属性设置为 true
  2. 处理 EntryWritten ​事件。

代码如下:

static void Main()
{
    using (var log = new EventLog("Application"))
    {
        log.EnableRaisingEvents = true;
        log.EntryWritten += DisplayEntry;
        Console.ReadLine();
    }
}

static void DisplayEntry(object sender, EntryWrittenEventArgs e)
{
    EventLogEntry entry = e.Entry;
    Console.WriteLine(entry.Message);
}

Info

仅 .NET Framework 原生支持。

13.7 性能计数器

Windows 日志用于获取信息进行事后分析,性能计数器则提供运行时监控状态的能力。用本节提到的 API,我们可用在软件上显示 CPU 利用率、内存使用情况等。

image

13.7.1 遍历可用的计数器

以下示例将遍历计算机上的所有可用性能计数器。对于那些支持实例的计数器,则遍历每一个实例的计数器:

var cats = PerformanceCounterCategory.GetCategories();
foreach (var cat in cats) {
    Console.WriteLine("Category: " + cat.CategoryName);

    var instances = cat.GetInstanceNames();
    // 不支持实例的计数器,如CPU核心
    if(instances.Length == 0) {
        foreach(var ctr in cat.GetCounters())
            Console.WriteLine("  Counter: " + ctr.CounterName);
    }
    else {
    // 支持实例的计数器,如应用程序进程
        foreach (var instance in instances) {
            Console.WriteLine("  Instance: " + instance);
            if (cat.InstanceExists(instance))
                foreach (var ctr in cat.GetCounters(instance))
                    Console.WriteLine("    Counter: " + ctr.CounterName);
        }
    }
}

性能计数器类别(Performance Counter Categories)

性能计数器类别是一组逻辑相关的性能计数器的集合。例如,“Processor​”是一个性能计数器类别,它包含了与 CPU 性能相关的计数器,如 CPU 的利用率。

实例(Instances)

某些性能计数器类别下的计数器可以有多个实例。实例通常对应于系统中的资源或对象,例如,在 “Processor​” 类别下,每个 CPU 核心可能是一个实例;在 “Process​” 类别下,每个运行中的进程都是一个实例。

性能计数器(Counters)

在给定的类别下,性能计数器是实际测量的指标。例如,在 “Processor​” 类别下,可能有 “% Processor Time”(处理器时间百分比)这样的计数器。

关于每个进程的性能计数器

不是每个进程都包含自己的性能计数器类别,而是某些性能计数器类别(如 “Process​”)包含了多个实例,每个实例对应于一个进程。这些实例下的计数器反映了该进程的性能指标,如 CPU 使用率、内存使用量等。因此,通过这种方式,你可以监控每个进程的性能。

13.7.2 检索(查看)性能计数器

查看性能计数器的方式如下:

  1. 实例化 PerformanceCounter ​对象

  2. 调用 NextValue ​或者 NextSample ​方法。

    • NextValue ​返回简单的 float ​值;
    • NextSample ​返回 CounterSample ​对象,该对象包含高级属性,例如 CounterFrequency​、TimeStamp​、BaseValue ​以及 RawValue​。

下面列举一些简单的用法:

获取 CPU 整体使用率
using (PerformanceCounter pc = new PerformanceCounter("Processor", "% Processor Time", "_Total"))
    Console.WriteLine(pc.NextValue());
获取当前进程内存消耗
var procName = Process.GetCurrentProcess().ProcessName;
using (PerformanceCounter pc = new PerformanceCounter("Process", "Private Bytes", procName))
    Console.WriteLine(pc.NextValue());

PerformanceCounter ​并没有公开 ValueChanged ​事件,因此如果需要监视各种变化则必须使用轮询的方法。

13.7.3 创建计数器并写入性能数据

自定义计数器常见场景有:

  1. 应用性能监控

    开发者可以为自己的应用程序创建自定义的性能计数器来监控关键操作的性能,例如,数据库查询的平均响应时间、每秒处理的事务数、队列长度等。这些数据可以帮助开发者了解应用程序在实际运行中的性能状况,并及时发现性能瓶颈。

  2. 系统健康检查

  3. 动态性能调优

  4. 软件测试和质量保证

  5. 安全监控

如下实例代码演示了如何创建分组该分组下的所有计数器

var category = "Nutshell Monitoring";

var eatenPerMin = "Macadamias eaten so far";
var tooHard = "Macadamias deemed too hard";
if (!PerformanceCounterCategory.Exists(category))
{
    var cd = new CounterCreationDataCollection();

    cd.Add(new CounterCreationData(eatenPerMin,
        "Number of macadamias consumed, including shelling time",
        PerformanceCounterType.NumberOfItems32));

    cd.Add(new CounterCreationData(tooHard,
        "Number of macadamias that will not crack, despite much effort",
        PerformanceCounterType.NumberOfItems32));

    PerformanceCounterCategory.Create(category, "Test Category", PerformanceCounterCategoryType.SingleInstance, cd);
}

image

如果之后希望在该分组下添加更多的性能计数器,必须先调用 PerformanceCounterCategory.Delete​ 方法删除旧的分组。

C7.0 核心技术指南 第7版.pdf - p593 - C7.0 核心技术指南 第 7 版-P593-20240216163440

一旦性能计数器创建完成,就可以实例化 PerformanceCounter​,将 ReadOnly ​属性设置为 false​,并对 RawValue ​属性赋值来更新计数器的值。也可以使用 Increament ​和 IncreamentBy ​方法来更新现有的值:

var category = "Nutshell Monitoring";
var eatenPerMin = "Macadamias eaten so far";

using(PerformanceCounter pc = new PerformanceCounter(category, eatenPerMin, ""))
{
    pc.ReadOnly = false;
    pc.RawValue = 1000;
    pc.Increment();
    pc.IncrementBy(10);
    Console.WriteLine(pc.NextValue());
}

image

13.8 Stopwatch​ 类

此处简单介绍几个Stopwatch​属性:

  • ElapsedTicks

    long​ 类型,返回计数值。

  • Stopwatch.Frequency

    long 类型,表示计数频率。ElapsedTicks​ 除以该值可用得到对应的秒数。

可以直接通过 Stopwatch.ElapsedMilliseconds​ 属性获得用时,更为简便。

标签:StackTrace,13,调用,Console,诊断,计数器,WriteLine,日志
From: https://www.cnblogs.com/hihaojie/p/18646141/chapter-13-diagnosis-ysy8c

相关文章

  • Spring Data REST 远程代码执行漏洞(CVE-2017-8046)分析与复现13
    前言2009年9月Spring3.0RC1发布后,Spring就引入了SpEL(SpringExpressionLanguage)。对于开发者而言,引入新的工具显然是令人兴奋的,但是对于运维人员,也许是噩耗的开始。类比Struts2框架,会发现绝大部分的安全漏洞都和ognl脱不了干系。尤其是远程命令执行漏洞,占据了多少甲方乙方......
  • 第13章 多情刺客无情剑
    上次说到豫让第一次行刺,赵无恤未被刺中,但他没要豫让的命,而是把豫让放走了。为什么赵无恤会放走豫让呢?因为赵无恤最看重的是臣子对上级的忠贞,所以要对他网开一面。想当初晋阳城被围得水泄不通的时候,赵家危在旦夕,赵无恤手下很多人都丧失了信心,都想投敌,见了赵无恤,连礼数上都变得怠......
  • 11.13
    微软官方博客公布了MSEdge浏览器过去一年的一系列统计数字:用户完成了逾百亿次AI聊天;自动翻译了38万亿个字;休眠标签节省了7万亿MB内存;在MSN上每天浏览了逾8亿篇文章;使用Drop跨设备共享了4600万条消息和文件;Bing日活用户逾1.4亿;移动应用屏蔽了逾1.8万亿个跟......
  • hot100-一刷-13堆(共3道题)
    215.数组中的第K个最大元素题目链接题目描述代码实现分析:后面可以看下官方题解的,手动写排序或者大顶堆。代码:classSolution{publicintfindKthLargest(int[]nums,intk){PriorityQueue<Integer>pq=newPriorityQueue<>((n1,n2)->n1-n2);......
  • [rustGUI][iced]基于rust的GUI库iced(0.13)的部件学习(00):iced简单窗口的实现以及在窗口显
    前言本文是关于iced库的部件介绍,iced库是基于rust的GUI库,作者自述是受Elm启发。iced目前的版本是0.13.1,相较于此前的0.12版本,有较大改动。本合集是基于新版本的关于分部件(widget)的使用介绍,包括源代码介绍、实例使用等。环境配置系统:window10平台:visualstudiocode语言:rust......
  • 一种旋转机械故障诊断方法(MATLAB R2018A)
    本实例通过从旋转机械振动信号中提取相关特征,然后使用 KNN 进行故障聚类故障包括轴承故障(bearing)、齿轮啮合故障(gearmesh)、不平衡故障(imbalance)、不对中故障(misalignment)、复合故障(multifault)、谐振故障(resonance)首先,观察一下各故障的时域波形及对应的频谱图,至于各谱线的物......
  • 1367. 二叉树中的链表
    给你一棵以 root 为根的二叉树和一个 head 为第一个节点的链表。如果在二叉树中,存在一条一直向下的路径,且每个点的数值恰好一一对应以 head 为首的链表中每个节点的值,那么请你返回 True ,否则返回 False 。一直向下的路径的意思是:从树中某个节点开始,一直连续向......
  • BHQ-3 acid|cas:1338332-66-3|BHQ-3酸
    BHQ-3acid,也被称为BHQ-3羧酸或黑洞猝灭剂3羧酸,是一种具有特别暗猝灭特性的多功能有机化合物。以下是对BHQ-3acid的详细介绍:一、基本特性英文名称:BHQ-3acidCAS号:1338332-66-3分子式:C33H35N6O2+分子量:547.68外观:深紫色粉末结构式:二、化学结构BHQ-3acid的分子中含有一个......
  • 《计算机组成及汇编语言原理》阅读笔记:p133-p159
    《计算机组成及汇编语言原理》学习第11天,p133-p159总结,总计27页。一、技术总结1.segment(1)定义Broadlyspeaking,acontiguoussectionofmemory.Morespecifically,asectionofmemoryreferencedbyoneofthesegmentregistersofthe80x86family.Theme......
  • leetcode137. 只出现一次的数字 II
    题目:        给你一个整数数组 nums,除某个元素仅出现一次外,其余每个元素都恰出现三次。请你找出并返回那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。示例1:输入:nums=[2,2,3,2]输出:3示例2:输入:nums=[......