返回首页

简介
本文介绍的方式来设计一个记录器,做什么文章的描述,建议:可能什么也没有发生时尽可能快地工作。尽可能在必要时尽可能多的信息,登录。
它的设计,而不是执行的问题。我想明确指出这一点。的文章中提供的实现是非常简单的(尽管它包含了大量的C巫术),它的只有约200行代码。此外,不应该采取这种实现是严重的项目,应当制定出适应的需要。例如,您可能会增加缓冲,自动重定向到一个新文件文件变得太大时,自动删除旧文件,等等。
但此日志的设计在我看来是非常宝贵的的。我使用了一个类似的类型,在我所有的应用程序的日志记录,它已被证明是伟大的。记录
日志是一个令人惊讶的重要组成部分的方案。 ,程序变得更加复杂和大 - 更重要的是有适当的记录。在许多情况下(如调试和崩溃的调查),日志几乎所有的信息你只有件。这是很难想象没有记录运行24 / 7的服务器。
是许多不同的记录系统。有很简单的包装类写入到文件(没有任何问题,每个人都可以在几分钟内创建),也有正在与缓冲,格式化的选项更严重的伐木工人,过滤,输出选择,等还有名为quot库阿帕奇logquot;(又名log4cxx),这是一个超巨型夸夸其谈的日志库加载功能万吨。
无论选择何种库,记录有以下不可避免的缺点:性能命中
记录是沉重的。在一些关键的代码块(如内环内)登录时,记录的性能完全可以杀死。许多记录库缓存(之前,实际上将输出发送到文件/控制台/等),从而降低了负载显著。但是:他们仍然是沉重的。即使没有书面资料,输出字符串的格式是一个复杂的操作。另外,在一个多线程的环境中,追加的高速缓存的东西需要同步(即联锁操作),其中也有对性能产生重大影响。缓存可能会导致许多令人吃惊的坏消息。例如,当一个程序崩溃,所有的缓存信息丢失。不良信息/垃圾比例
通常情况下,这是难以预料的问题出在哪里出现,会有什么问题的调查有用。记录过多导致性能下降,再加上日志成为无用的废话吨加载。在另一边,禁用日志记录在一些地方是危险的 - 你可能会删除的东西,那将是非常有价值的,如果不测。
许多记录库支持过滤。也就是说,你可以把很多日志语句在代码;然而,他们可能没有真正在运行过程中记录。一些库需要重新编译这个工作,别人需要重新启动程序,有的甚至可以在运行时重新配置。在这样一种方式,您可能平时工作在非常低的日志水平(过滤掉几乎所有的东西),并在调试过程中,把记录的水平。有的图书馆甚至允许不同的库/类不同的过滤选项。
到目前为止过滤是好的。但仍然不够好。其实,这意味着,如果第一次(当日志级别低)发生崩溃 - 你几乎没有任何信息。然后你开始调试:提高日志级别,并尝试重现该问题。但有些问题很难重现,特别是那些涉及到时序,线程等也提高日志级别后,程序的行为有所不同 - 由于采伐性能的影响,时间可能会改变。其实,你可能无法重现的问题时,日志上。如何杀死两个单杆野兔
现在,让我们看看另一个日志设计。在大多数记录器,在运行过程中,你使用一种类型的功能:一些函数/宏/运营商,是应该做的日志。在我们的设计中,我们有三个操作:记录,把所谓的检查站,倾倒了所有的检查站。以下是这些操作的语法:

// Actual logging.

PUTLOG("This is logged immediately. SomeStr=%s, SomeNum=%d" 

       << szSomeStr << nSomeNum);



// Declaring a checkpoint

CHECKPOINT("This is a checkpoint. SomeNum=%d" << nSomeNum)



// This sends all the checkpoints into the log

Log::PrintCheckpoints();

日志和检查点声明支持可变数目的参数与printf类似的格式字符串。唯一的区别是参数LT,LT;,而不是逗号分隔。
检查站是一个临时的对象,其中包含一个表达式,它是随时可以登录,但还没有登录。它的生命宣言(大括号)的范围内。如果没有什么不好的事情发生 - 它被悄悄地拆除的范围是退出时,并没有记录。然而,如果东西不好的事情发生 - 你通话记录:PrintCheckpoints和所有检查站被追加到日志一次。
把事情说清楚,让我们来看看下面的例子:{C}
当日志:PrintCheckpoints是第一次(在最内层的范围内)的称为 - 它会看到所有三个检查站。当它被称为未来的时间 - 最内层的检查点,将已被删除,它会只看到quot; Onequot;和"Twoquot;而且,当日志:PrintCheckpoints是所谓的第三次 - 只有第一个检查站将被打印。
在上面的例子中,所有的检查站位于一个功能,但是这不是强制性的。关卡可能会在不同的功能宣称,他们仍然都是可见的。此外,如果一个函数的递归调用 - 其检查站将被视为递归以及。换句话说,检查站是可见的,直到他们的生存期到期。
让我们来看看另一个例子,这是我们的记录更多或不太现实的示范:
void SomeWorkerFunc()

{

    CHECKPOINT(_T("WorkerThread=%d") << GetCurrentThreadId())



    // ...

    // eventually process some I/O for client

    pClient->ProcessIo();

    // ...

}



void Client::ProcessIo()

{

    CHECKPOINT(_T("Request from client ID=%u") << m_ID)

    

    // ...

    // eventually decide to send something to the client

    CHECKPOINT(_T("Sending file path=%s") << szSomeFilePath)



    HANDLE hFile = CreateFile(szSomeFilePath, /* ... */);

    ReadFile(/* ... */);

    // ...

}



void ReadFile(/* ... */)



{

    CHECKPOINT(_T("Attempt to read %u bytes", dwBytes)

    TestSysCall(ReadFile(hFile, pBuf, dwBytes, /* ... */));

}



void TestSysCall(BOOL bVal)

{

    if (!bVal)

    {

        PUTLOG(_T("Error=%u") << GetLastError());

        Log::PrintCheckpoints();

        

        throw SomeException;

    }

}

在这个例子中,如果没有什么不好的事情发生,什么都不会被记录。然而,如果ReadFile的失败,以下将被排放到日志:
Error=187

Attempt to read 2048 bytes

Sending file path path=C:\Data\SomeFile.bin

Request from client ID=22487

WorkerThread=3047

注意:这里报告错误TestSysCall的唯一功能。它没有任何有关它发生的背景下的信息,它不知道什么起源的错误。它只有(并记录)错误代码。
是有可能实现这样一个传统的记录(检查站)的行为?理论上 - 是的。但是,这一要求的日志代码的数量巨大,看起来别扭,使程序完全不可读。代码看起来像这样:
也就是说,在这种情况下,你必须通过所有的上下文信息的所有功能和打印所有的日志语句。您将无法使用统一(上下文)功能,如I / O错误检查等常见的操作
事实上,没有人确实此。相反,人们要么不登录上下文信息,或记录它们之前有一个真正需要(我们已经讨论了这一缺点)。
事情的变化,当您使用检查点。现在,你应该毫不犹豫地提供尽可能多的信息,只在需要时将实际登录。实施细节PUTLOG
让我们开始与PUTLOG宏。它作为参数表达式,并立即记录本表达。表达式中的第一件事必须是一个格式化字符串。根据不同的multi-byte/Unicode编译器选项,字符串应该是多字节或Unicode。使用_T()宏自动使用正确的字符串,它的价值。因之参数必须由指定的格式化字符串的内容。如果没有参数需要 - 任何人都不应被指定。LT,LT;运营商(如你可能已经注意到)的参​​数必须分开。表达式求值一次。这是很重要的,如果表达的评价有副作用(例如:它可能包含有一些副作用的函数调用)。
正如我已经在开始时说,实施的日志是非常原始,是不应该使用。它不支持过滤。也就是说,表达的是始终评估和记录。还有没有可能选择哪个日志写入(的情况下,你可能想有几个不同的事情的日志输出)。您可能会注意到,到目前为止,我们没有看到任何记录初始化方法,也没有地方,我们可以指定哪些文件记录到。这是因为这种实现不写任何文件!它只是发送到调试输出格式化的字符串。
你应该治疗,只为骨架实施。如果你喜欢这个主意 - 提前去,把它和它适应您的需求。举例来说,如果要添加多个Logger对象的支持,你或许应该重新PUTLOG宏接受一个参数 - Logger对象。此外,您可能会支持过滤:要么运行时的过滤器选项(存储在指定的Logger对象)或编译时的日志记录的宏的定义。然而,你必须小心:不记录时,你有一个选项,评估或不给定的表达式。当然,更好地没有对其进行评估(性能提升),但小心副作用。CHECKPOINT
现在来的主要课程:检查站宏。有关如何设计和实施检查站的决定时,有某种程度的自由。我的实现的主要目标是:必须轻,并尽可能快。使用方便。
也就是说,如果它不会很方便,你会不会想用它。而且,如果这将是沉重和缓慢 - 你可能想,但不应该使用它。
为了了解究竟是什么检查点,我们首先需要认识到,在传统采伐执行哪些步骤。比方说,我们下面的日志:
PUTLOG(_T("My name is %s, Elapsed time=%d") 



          << GetMyName() << TimeNow() - nTimeStart);

在这个例子中,下面的步骤进行:记录表达式求值。被称为GetName和TimeNow。时间减去nTimeStart的值是计算出来的。日志记录的字符串格式化。这意味着一些缓冲区分配,呼吁建立最终的字符串和一个printf之类的函数的一个变种。或者,一些装饰品可能会做最后的字符串(开始时的时间戳,截至年底行)。该字符串被发送到输出(文件,控制台等)
CHECKPOINT宏作为一个参数的记录表达。这个表达式是立即进行评估,但最终的日志记录字符串没有被格式化,并没有发出。这意味着,只有上述链的第一步。
在这个例子中,GetName和TimeNow马上打电话,和时间减去nTimeStart价值计算。这是所有。所有剩下的步骤被推迟,直到检查点,将实际登录。这有以下影响:出色的性能。如果日志表达式不包含重的东西(如函数调用) - ,表达的评价是非常快的。由于实际的日志字符串的格式是推迟 - 表达式中的所有参数必须保持有效。
最后一句是什么意思?简单地说 - GetMyName和TimeNow返回值必须保持有效,直到检查点的生存期到期。例如,GetMyName可能会返回一个指针到一些字符串,这个字符串是最终删除/修改,而​​检查点还活着。这是必须避免的。
另一种变种:GetMyName而不是原始的字符串的指针返回值C字符串对象(无论是CString的,性病::字符串,或其他生物)。返回的对象是暂时的,它的寿命是有限的声明。 PUTLOG以来记录的字符串格式化立即使用这种事情有没有问题,但检查点使用,这将产生灾难性的后果。
是它可以实现检查点没有这些弊端?是的,但你必须为此付出。变种之一是立即格式的日志串。另一种方法 - 定义检查点,因此,它会立即使所有指定的字符串的副本。
付出的代价是否值得?绝对不是,太昂贵了。目前的实施过程中使用初始化短短几年的CPU周期,几个周期的清理,这是可以忽略不计在99%的情况下,我可以想像,即使是在最关键的代码块。而且,另一方面,它涉及到堆操作,重复的字符串,将数百到数千周期。这将使使用检查点不值钱。毕竟,在大多数情况下,这种考虑是无关紧要的。大多是用来检查站(至少我)没有在所有的参数,或者只是一些原始的。而且,如果它发生,你真的想用一些字符串可能过期 - 一种重复他们明确。在深入的技术细节
日志表达式解析(PUTLOG和检查点),并转换成一个记录结构,这是由以下定义:
template <size_t nSize>

struct Record {

    PCTSTR m_szFmt;

    int m_pParams[nSize / sizeof(int)];

};

m_szFmt是格式字符串,m_pParams - 所需要的所有参数格式最后的日志串,装在printf类似的功能确认的方式。
注意:记录是一个模板结构,它的模板参数是用于格式化所需的包装参数的大小。在运行时,实际的格式化参数都放在那里,但大小须持有他们是在编译时决定。这是如何做到的呢?那么,这里涉及激烈的彗星模板巫术。我让你,亲爱的读者,拼图这一点从代码。如果我现在告诉你如何编译和工程 - 我可能会破坏的文章,你可以得到的最显着的喜悦。
PUTLOG宏建立这样的记录,并立即记录。而且,检查点宏执行以下操作:生成的记录。声明一个检查点的类,它封装上面记录的临时对象。这个对象quot; registersquot;本身在c'tor(构造)和quot; unregistersquot;本身在德TOR(析构函数)。
是什么"; registerquot;和quot; unregisterquot;意思呢?让我们来看看代码。检查点的定义是这样:
struct Checkpoint {



    __declspec(thread) static Checkpoint* s_pTop;

    Checkpoint* m_pPrev;

    // ...



    template <size_t>

    Checkpoint(/* ... */); // register

     Checkpoint(); // unregister

};

s_pTop点最近quot; registeredquot;检查点,并m_pPrev成员之一,它取代的每一个检查点点。以这样的方式,检查站,形成一个堆栈的排序。
注意:s_pTop变量声明__declspec(线程)的语义。这意味着它是通过TLS访问。 (谁不熟悉的:TLS - quot;线程本地storagequot;这就像一个全局变量,但是,它对于每个线程是唯一的。)访问通过TLS的变量是非常快的。从性能的角度来看,这是访问一个普通变量相媲美。
__declspec(线程)不能使用,如果你的代码的一部分驻留在一个DLL。如果是这样的话 - 你必须明确访问的TLS(TlsGetValue / TlsSetValue)。您还必须确保您使用相同的TLS索引的EXE和所有的DLL,否则,您会收到几个检查站链,而只有一个,将在每个模块中可见。异常处理和检查站
在上面的例子,有一个明确的呼叫日志:PrintCheckpoints当程序检测到一个问题。特别是,其中一宗个案中,我们把它称为正是在抛出异常之前。但是,我们不能保证这个函数将被调用,在每一个被抛出的异常。有时候,我们将不得不调用一些第三方代码(如STL,MFC提高等),这是不知道我们的测井系统,它只是抛出一个异常。此外,异常可能是含蓄地提出,OS /硬件(如访问冲突)。顺便说一下,在这种情况下,它的最重要的转储的所有信息,但不幸的是,我们没有机会转储检查站。
后异常被抛出和捕获 - 倾销检查站是不可能的。因为他们不存在了。回想一下,检查站的存在,直到他们的生存期到期,异常被捕获后,堆栈已经平仓和所有自动(栈)变量被销毁。
幸运的是,有一个解决方案。有一个可能性就抛出异常后,做的东西,但之前的捕获和栈展开的发生。使用SEH(结构化异常处理),这是可能的的。那些不熟悉与SEH - 有大量的在线文档对此,我建议阅读。我也建议你阅读{A}。
这样的伎俩,我们应该可以更换或包裹传统的try / catch块与__try / __except的。这是你应该做的:
/////////////////////////////////

// traditional exc handling block

try {

    DoSomething();

catch (/* ... */) {

    Log::PrintCheckpoints(); // Oops! There's nothing there

}



/////////////////////////////////

// Now putting the __try/__except in the middle:

try {

    DoSomethingGuarded();

catch (/* ... */) {

    // Checkpoints already dumped

    // Now - handle the exception

}



void DoSomethingGuarded()

{

    __try {

        DoSomething();

    } __except (Log::PrintCheckpoints(), EXCEPTION_CONTINUE_SEARCH) {

        // never get there

    }

}



/////////////////////////////////

// Use only __try/__except, omit the traditional try/catch



__try {

    DoSomething();

} __except (Log::PrintCheckpoints(), EXCEPTION_EXECUTE_HANDLER) {

    // Checkpoints are already dumped.

    // Now - handle the exception

}

对于那些不熟悉与SEH,可能看起来复杂。但事实上,这是并不复杂。传统的C异常处理机制的实施(至少由MSVC)通过SEH的。然而,与C异常处理机制的SEH,是隐蔽。在以"revealquot,必须使用纯SEH的。结论
我一直使用现在这种类型很长一段时间的记录(多年),它已被证明是伟大的。我恨膨胀缓慢而沉重的东西的节目,我非常的最低限度。尤其是当涉及到​​高性能的方案,必须从硬件的最大挤压。而且,我个人觉得,这个日志记录击中的汗水点。
其实,我quot; inventedquot;这样一个记录,当我得知有关SEH和开始使​​用它。这显然​​优于C异常处理,再加上发现,C异常SEH的实施,使SEH实际上涵盖了所有的异常和崩溃处理。 SEH的最大的优势之一是异常后,你会得到一个机会做栈展开之前发生的事情。这是非常宝贵的调试的,因为整个协议栈,包括提高代码的异常,在这一点上仍然是有效的,它包含了非常有价值的信息。
在某些时候,我开始把一些quot; decorationsquot;在栈上,因为原料栈是没有那么多可读。这些装饰品包括一个单一字符串。最后,我想这些字符串添加到一些运行时参数,但字符串格式化是沉重的。此外,格式化字符串的长度是不知道在编译的时候,因此,在运行时,它应该是分配堆(否则可能会被截断,日志字符串),这是非常杀性能。这样,我开始使用quot; deferredquot;格式。有一天,我意识到检查站不仅是好的事故调查,他们实际上可以完全取代传统的日志记录。只要转换到检查站的所有日志记录语句。这将大部分的垃圾扔掉,因为没有必要通过所有的上下文信息无处不在。而且,日志的检查站,只在必要时:时例外(在他们认为异常的情况下),并在几个地方按需。
顺便说一下,最近,我有一些经验,这是一个巨大的和非常受欢迎的记录着巨大的选项和功能库与Apache日志(又名log4cxx)。我很惊讶:它出来,在传统的采伐外,他们也有类似的东西我没有什么!这就是所谓的NDC - ";嵌套诊断contextsquot;,这是每一个线程的后进先出队列(栈)的字符串,你可以压入/弹出,它们会自动添加到每个日志消息。
然而,他们的工作,这使得他们的国家数据中心的使用非常有限的,在我看来,也只有一个部分。这是非常重要的,这一机制以快速,便捷,log4cxx的NDC失去了这两个条件。从性能的角度来看,它的可怕 - 它采用了大量堆操作,字符串重复,等,从使用的方便,它的难言之隐 - 它只是需要字符串。此外,它允许不可控的推/弹出字符串:特别,你可以把一个字符串的地方而不是范围退出后回弹出。这显然​​会导致一个烂摊子。为了防止这种情况,必须使用RAII包装。否则,你的quot;嵌套诊断contextquot;可能包含的信息,没有更多的实际。此外,所有的NDC字符串自动添加到每个日志消息,这是不是你想要的的。
我跟一些人真的钦佩log4cxx。我问的NDC的主要用途应该是什么。他们告诉我关于线程的东西。也就是说,在多线程应用程序中,不同的线程登录到相同的文件,以及一切混合。 NDC是很好的区分来源于哪里。
但是,这是荒谬的。人根本没有意识到这种思想的巨大潜力。我做什么,更多或更少,应NDC的最明显的用途。这只是NDC的一定要快和轻量级,它不是。
正如我已经说 - 提供的实施是一个骨架。它没有做实际的日志记录。您可以使用一些其他的日志系统,支持过滤,缓冲等的包装,如果这是适用于您的情况。
我会明白的意见,正反两方面的。欢迎新的想法和批评。

回答

评论会员:midiway 时间:2012/01/25
我觉得缺乏一个适当的例子,主要是有关使用__try异常模型,所以我可以在调试器中的步骤,并更好地喘气
评论会员:zexspectrum 时间:2012/01/25
大,我会使用这种设计,在我的项目
评论会员:阿洛伊斯克劳斯 时间:2012/01/25
您的想法推迟,直到需要它的字符串格式化是非常好的。至于我可以看到你的每一个线程的基础上分配的链表。这是否意味着,当我调用一个方法PrintCheckpoints时,我会只看到我的当前线程的关卡?据我所知,设备驱动程序可确定,但因为线程推相当严重的今天,我宁愿一个全球性的线程安全的环形缓冲区,我可以转储需求。还是我错过在资料库中的东西呢?它使用相当长的一段C和宏技巧,以达到所期望的行为。非常酷刷新旧的C回忆。现在,我在黑暗中的C#端PERF是并不重要了{S0}

你,
阿洛伊斯克劳斯
评论会员:valdok 时间:2012/01/25
阿洛伊斯,

实施好,这有没有不同的线程之间的连接。当您转储检查站 - 你只能看到调用线程的检查站。但是,这实际上是我的意图。

检查站的目标是包含最相关的上下文信息。也就是说,当你发现一种在运行时的一个问题 - 你通常会看到刚刚过去的事情,你试图这样做,这是"冰山的顶端"。然后,你想知道:为什么及如何发生的?然后你去之前你做了什么坏事发生的历史。而这有望使画面清晰。

现在,其他线程在同一时刻可能会做别的事情。但是,这不一定是你的线程。恕我直言 - 倾倒所有的上下文信息,为所有的线程可以在事故调查有用。此外,你可能想整个进程的内存转储。然后,你可能希望重建的全貌。

对于"正常"程序错误,似乎当前线程的上下文是不够的。举例来说,我在文章如何记录的错误信息可能看起来提供的例子:

Error=187

	Attempt to read 2048 bytes

	Sending file path path=C:\Data\SomeFile.bin

	Request from client ID=22487

	WorkerThread=3047


恕我直言 - 它足够的短暂,有没有其他线程需要混合
。 还创建线程之间的互连将达到性能难免。另外,因为我们谈递延字符串格式化 - 实际的字符串格式还必须确保线程安全一些格式参数

我必须说,我还是光明的一面:,我是一个C / C + +程序员,我是一个非常无华。然而,大家都知道,历史永远是由获奖者的书面。由于时下的光明和黑暗的双方是故意交换
我现在的坏家伙,野蛮的,我不敢使用的"原始指针",因此,我负责内存损坏,不稳定,安全漏洞等
你说你是黑暗的一面,但是这是你сonscience的交谈。因为深深的心脏,你知道我的权利。

评论会员:wtwhite 时间:2012/01/25
非常实用和有用的方法,虽然这是一个耻辱,这是不可移植的SEH的手段。正如我在标题中说,我发现我惊讶的是,不使用的MinGW的G的SEH例外,所以这几乎限制了你的MSVC。 (虽然在Windows MSVC的可能CP上的大多数人使用
评论会员:valdok 时间:2012/01/25
wtwhite,
阅读文章的感谢。
既然你已经读过我在CI中的异常处理的文章可以直接去点。
与SEH这里有两种不同的问题。

SEH是Windows的特定机制AFAIK。例如,它不支持在Linux。MSVC编译器只适用于对SEH的通用支持。
所以,我看到不同的问题及其可能的解决方案。

在非Windows操作系统应该有一些方法来挂钩异常抛出机制。
(例如,它可能是可能取代MSVC __CxxThrowException功能的非模拟)。
我不知道如何MinGW的Windows上实现异常处理。如果它使用了一些专有的方式 - 这可能是做过类似的非Windows平台(挂钩投掷机制)
。然而,因为MinGW是为Windows的编译器 - 它可以实现MSVC一样,通过SEH的异常处理。

如果是这样的话 - 唯一缺少的一部分,是一个__try / __except和__try / __finally语法的通用支持。这实际上可以实现在汇编级别
评论会员:。TommyTooth 时间:2012/01/25
我的5票

除为SEH的一部分(这本身就需要一个教程),一切都很好地解释。
我给我的5
评论会员:。valdok 时间:2012/01/25
注意。很高兴你喜欢它。

关于SEH的:仅本章值得一个单独的文章(甚至数的)。在网络上还有大量的文档
评论会员:。PVL的 时间:2012/01/25
高性能应用程序的工作,我一直在执行相同的,主要用于崩溃转储"穷人吃"栈装饰分析的目的。 (从小事:LPCTSTR方法= _T("富:酒吧");和以轻型结构,保持一些值结束)。但我从来没有想过它作为递延的格式化(!)跟踪联系链。你绝对没有一个伟大的工作。我5
评论会员:!valdok 时间:2012/01/25
。非常感谢您的关注
如果你想使用它,有一些问题/建议 - 不要犹豫与我联系
评论会员:supercat9 时间:2012/01/25
作为一个程序员,我经常发现记录是一个痛苦。能够登录,以前做过的东西失败的事情,只有当它失败了,似乎是一个非常方便的的方法。
如果使用"使用"块,应该能够很好地获得托管语言类似的行为。
认识到你的文章的重点是与异常日志上的设计,而不是日志记录的详细信息,我不希望得到后者太多,但图一对夫妇点可能是值得一提的:

-1 - 这是非常方便的日志缓冲区将保持去年的'N'的东西,抛弃旧的轮流。实现的细节,但也许值得一提。

-2 - 登录时,我通常喜欢渲染字符串或执行了深刻的克隆操作,因为我很早就已经发现自己的方式太多次吠叫错了的树,因为引用类型已经改变日志记录执行的语句后的价值,但竟是产生之前的日志。你提到这个问题,也许是咬你比我少经常,因为您的喜好似乎矿井对面

无论如何,感谢您的文章。我没想到,包裹在使用块一个logger,如果一切顺利的话,有一个"丢弃日志"的语句块内的,但似乎是一个很好的方法
评论会员:。valdok 时间:2012/01/25
感谢很多阅读的文章。

是的,它似乎用块,它可以在托管代码中使用。虽然我不是一个在C#专家

现在,关于您的笔记:
这是非常方便的将保持在一个旋转的基础上的最后"N"的东西,抛弃旧的日志缓冲区。
这是良好的,当你保持在内存中的所有日志记录。但是,如果你转储文件 - 有没有必要放弃任何东西。在这种情况下,通常你只需要创建新的文件(当旧变大),和曾经在一段时间删除旧文件。通过这样,你总是有几个文件,最近的记录。

我一般喜欢渲染字符串或进行了深刻的克隆操作很早就...

那么,这是你的意见,我尊重这一点。你可以使用你所说的"深度克隆"或我称之为"递延格式",取决于你的想法会咬你:到死的对象/字符串,或性能下降
参考
评论会员:游客 时间:2012/01/25
。supercat9风险:|但是,如果你转储文件-没有必要放弃任何。我还以为你的日志记录的想法是,你只写日志文件,如果出现错误-检查站,否则将保留在内存中的东西。如果检查站的数量小,可能被罚款,但如果你有一个循环,这将运行100万次,每次执行一些检查站,它可以得到昂贵。保持在这种情况下的最后几个关卡,可能会使事情变得更加实用。风险到死的对象/字符串的引用,或性能退化。在我看来,至少在托管代码中,风险是不是一般人会访问死对象或字符串,而是最终将写入日志条目看起来完全合理的,但不匹配的值当它被排队。我想在某些情况下的尝试,以转换或克隆一切与否,将获得使用性能的处罚可能会呈现这样的日志不切实际,但有一些相当大的价值,以了解一个人的日志实际上是正确的
。valdok |好吧,现在我理解你所说的"始乱终弃"的意思
。在我的设计关卡是永远不会被丢弃。他们也没有要求一些人为的缓冲区。

这是你声明一个检查站时会发生什么:
一个新的检查站结构是在堆栈上分配。这种新的检查站,节省了以前顶一个指针。现在全球的每个线程的指针顶端的检查点这个新的一个点。
这是一个检查点到期时会发生什么:
全球每线程指针顶端的检查点恢复到以前顶的检查点的值(它被保存在c'tor)检查点结构分配堆栈内存被释放。
这是您打印所有检查点时会发生什么:
声明一个指针检查站。全球每个线程的指针顶端的检查站对其进行初始化。虽然这个指针非NULL日志转储到这个检查站的内容。指针重定向到先前的检查点(它的存储在这个检查站的成员) 正如您所看到的,有没有人为的缓冲区来存储检查点。所有你需要的是一个每线程变量,指向顶端的检查点。

另一点:当你有一个循环,执行万次 - 其实,你不创造一百万关卡:


for (int i = 0; i < 1000000; i++)

{

    CHECKPOINT(_T("Executing loop. Iteration=%d") << i)

    // do something here

}


在这个例子中,你永远有一百万关卡。这只是一个检查站,是创建和销毁万次。