从设计的角度来看,记录的最佳实践是什么?[关闭]


11

我想将日志记录添加到当前正在处理的应用程序中。我之前添加了日志记录,这不是这里的问题。

但是从面向对象语言的设计角度来看,遵循OOP和模式的最佳记录实践是什么?

注意:我目前正在C#中执行此操作,因此显然欢迎使用C#中的示例。我也想看看Java和Ruby中的示例。


编辑:我正在使用log4net。我只是不知道插入它的最佳方法是什么。

Answers:


6

我建议的最佳实践是使用log4j而不是自己动手。(从Java移植到了C#和Ruby,因此它适用于您感兴趣的所有3种语言。)

如果通读该手册页,您会发现其他几种最佳实践。例如轻量级,可在应用程序外部进行配置,能够独立打开和关闭应用程序不同部分的日志记录等等。


5

在我工作的地方,我们编写了许多.NET桌面应用程序。我们通常在组件中实现2个事件,一个事件用于记录信息,另一个事件用于记录异常(尽管我们经常让异常冒出来,而不是引发单独的事件。这取决于情况)。使用这种体系结构,我们的任何图书馆都无需知道如何实现日志记录或如何使用,存储或处理信息。然后,我们让应用程序以适合该应用程序的方式来处理日志事件。几年前,这种体系结构使我们从使用MS Enterprise Library日志记录切换到BitFactory的日志记录组件的过程非常简单。


+1以使用事件/观察者模式:更改观察者,您已更改日志记录
Matthieu M.


2

就个人而言,我选择了日志记录框架(在我的情况下为Entlib,因为我使用.NET),并编写了AOP方面进行日志记录。

然后,您可以为任何方法/属性/类/命名空间赋予属性,并向其中添加日志记录,而不会弄乱源代码。


这听起来很有趣,但是对于您将能够记录的内容以及日志的信息性(例如,不仅仅是“方法”的工具化),我持保留态度。希望看到这种方法的一个可行示例,以了解可以做什么和不能做什么。尤其是当我刚开始使用新应用程序时,我想看看我可以携带这款产品到哪里/走多远。
Marjan Venema

@marjan Venema:犀利的后期文档中有一个示例记录了输入/退出方法的方面。doc.sharpcrafters.com/postsharp/2.0/##PostSharp.chm/html/…在Post Sharp的情况下,它将在构建时将代码从属性编织到源代码中,因此不会像其他代码一样影响性能。
史蒂文·埃弗斯

1

我当前正在使用的系统使用事件驱动的体系结构和消息传递,因此我们系统中的大多数操作都是命令的结果,并且它们会导致事件(作为分派的DTO类,而不是标准的委托事件)。我们附加事件处理程序,其唯一目的是处理日志记录。这种设计可以帮助我们避免重复,也不必修改现有代码来添加/更改功能。

这是一个此类日志记录类的示例,该类处理从应用程序的狭窄部分记录的所有事件(那些事件与我们从中导入的特定内容源有关)。

我不一定会说这是最佳实践,因为我似乎改变了主意,想经常记录什么和如何记录日志,而且每次我需要使用日志来诊断问题时,我不可避免地会找到改进记录的方法。我记录的信息。

不过,我要说的是,记录相关信息(尤其是以Ctrl-F /查找可搜索的方式)是最重要的部分。

第二个最重要的部分是从你的主逻辑获取日志代码了-它可以使一个方法丑陋,长而曲折的非常快。

public class MctLogger :
    IEventHandler<StoryImported>,
    IEventHandler<StoryScanned>,
    IEventHandler<SourceDirectoryMissing>,
    IEventHandler<SourceDirectoryAccessError>,
    IEventHandler<CannotCreateScannedStoryDirectory>,
    IEventHandler<CannotReadStoryDocument>,
    IEventHandler<StorySkippedPastCutoff>,
    IEventHandler<StorySkippedDuplicateUniqueId>,
    IEventHandler<StorySkippedByFilter>
{

    public void Observe(StoryImported e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StoryImported");
        log.Info("Story Unique ID: {Story.UniqueId}, Content ID: {ContentId}, Title: {Story.Headline}".SmartFormat(e));
    }

    public void Observe(StoryScanned e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StoryScanned");
        log.Info("Story Unique ID: {Story.UniqueId}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
    }

    public void Observe(SourceDirectoryMissing e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.SourceDirectoryMissing");
        log.Error("Directory: " + e.Directory);
    }

    public void Observe(SourceDirectoryAccessError e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.SourceDirectoryAccessError");
        log.Error(e.Exception, "Exception: " + e.Exception.Message);
    }

    public void Observe(CannotCreateScannedStoryDirectory e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.CannotCreateScannedStoryDirectory");
        log.Error(e.Exception, "Directory: {Directory}, Exception: {Exception.Message}".SmartFormat(e));
    }

    public void Observe(CannotReadStoryDocument e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.CannotReadStoryDocument");
        if (e.Exception == null) {
            log.Warn("File: {FilePath}".SmartFormat(e));
        }
        else {
            log.Warn(e.Exception, "File: {FilePath}, Exception: {Exception.Message}".SmartFormat(e));
        }
    }

    public void Observe(StorySkippedPastCutoff e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StorySkippedPastCutoff");
        log.Warn("Story Unique ID: {Story.UniqueId}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
    }

    public void Observe(StorySkippedDuplicateUniqueId e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StorySkippedDuplicateUniqueId");
        log.Warn("Story Unique ID: {Story.UniqueId}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
    }

    public void Observe(StorySkippedByFilter e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StorySkippedByFilter");
        log.Warn("Story Unique ID: {Story.UniqueId}, Reason: {Reason}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
    }
}

1

正如其他人所说,使用log4jlog4net其他一些精心构建的日志框架。

我倾向于不喜欢记录代码妨碍业务逻辑。这就是为什么我使用Log4PostSharp。这意味着我可以使用面向方面的编程来注释这样的方法:

[Log(LogLevel.Info, "Counting characters.")]
int CountCharacters(string arg) 
{
    return arg.Length;
}

或这样的程序集中的每个方法:

[assembly: Log(AttributeTargetTypes = "*", 
 EntryLevel = LogLevel.Debug, ExitLevel = LogLevel.Debug, 
 ExceptionLevel = LogLevel.Error)]

0

我不确定是否有任何框架可以做到这一点,但是从设计的角度来看,我会将需要登录的信息建模为以下三类:

  1. 方法级别跟踪
  2. 异常记录
  3. 额外的运行时信息开发人员认为,这对于调查运行时故障(或与仅运行时情况相关的任何行为)的情况至关重要。

对于前两类,我理想的日志记录框架应将它们作为后期构建过程来处理,并且对开发人员是透明的。声明性地向程序集添加日志记录,如下所示:

Trace YourNamespace.* [public methods|constructors]
{  # options
   ignore trivial methods,
   format: "{time stamp}: {method name}({parameter list})",
   condition: "{Context.Instance.UserID in (12432,23432)}",
}

Exception YourNamespace.Program.Main [unhandled exceptions]
{
  format: "{time stamp}: {Context.Instance.UserId} {exception}",
  action: die,  # options are throw, swallow,
}

对于第三类,程序员可以只创建一个或多个专用的“记录”方法,并利用对第一类的跟踪。日志记录方法只不过是为可以应用跟踪规则的存根提供服务。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.