如何使用 NLog 以功能方式登录 F#

Posted

技术标签:

【中文标题】如何使用 NLog 以功能方式登录 F#【英文标题】:How to Log in F# in a functional way using NLog 【发布时间】:2021-12-30 13:26:14 【问题描述】:

我一直在研究日志记录选项并选择了 NLog 并记录到数据库

我是函数式编程的新手,并且具有 C# OOP 的背景。

如何在 F# 中以功能方式实现日志记录? 我是吗

在顶层创建记录器,然后将其传递给每个函数 根据需要通过静态方法访问记录器(显然每次实例化记录器都会产生一些开销 - 但也许这没什么大不了的) 还有别的吗?

我想避免仅仅因为我的项目很小就使用商业日志记录选项。

感谢您的宝贵时间。

【问题讨论】:

【参考方案1】:

通过静态方法访问记录器

这种方法适合实验性应用,但一般来说它是Service Locator 的实例,通常被认为是反模式。

更可靠的替代方法是依赖注入 (DI)。由于 F# 是混合语言,因此您可以使用 OOP 概念并将 DI 模式以通用方式应用于 OOP。但是,如果你想使用函数式风格,F# 中没有单一的通用函数式 DI 模式。

在顶层创建记录器并将其传递给每个函数

这只是在函数式代码中使用 DI 最直接的方式。这种方法可能适用于小型应用程序,但是如果除了日志记录之外,您还有其他横切关注点(例如配置),则最终可能会导致参数爆炸,这会使代码变得混乱。 有不同的方法来解决这个问题。我已经解决了here 描述的方法。

您可以通过以下方式应用此方法。

您的业务逻辑函数可能如下所示:

let doSomething env = 
    let logger = getLogger env
    logger.Debug("Do something")
    // Do something

表示logger引用通过env参数提供,并通过函数getLogger访问。

此代码应引用通用日志记录模块,可能如下所示:

[<Interface>]
type ILoggerProvider =
    abstract Logger: ILogger

let getLogger (env: #ILoggerProvider) = env.Logger

注意doSomethingenv 参数类型会自动推断为#ILoggerProvider(即继承ILoggerProvider)。

想象稍后在相同的业务逻辑中我们需要访问配置(以及日志记录)。在这种情况下,我们不需要新参数,而是以类似的方式从相同的env 参数访问配置:let configuration = getConfiguration env,前提是还创建了用于配置的通用模块:

[<Interface>]
type IConfigurationProvider =
    abstract Configuration: IConfiguration

let getConfiguration (env: #IConfigurationProvider) = env.Configuration

在这种情况下,doSomethingenv 参数类型会自动重新推断为 'a (requires 'a :&gt; ILoggerProvider and 'a :&gt; IConfigurationProvider) 这意味着它应该同时继承ILoggerProviderIConfigurationProvider

在应用程序级别,您需要创建环境实例并将其传递给业务逻辑,以便提供对日志记录和配置(以及可能的其他服务)的访问。您可以通过以下方式进行:

[<Interface>] 
type IEnvironment = 
    inherit ILoggerProvider 
    inherit IConfigurationProvider
    
let createEnvironment (logger, configuration) = 
     new IEnvironment with
        member self.Logger = logger 
        member self.Configuration = configuration 

let createLogger () = // create logger...
    
let createConfiguration () = // create configuration...

let logger = createLogger ()
let configuration = createConfiguration ()
let env = createEnvironment (logger, configuration)

// Call business logic
doSomething env

请注意,使用新的横切关注点很容易扩展此代码,因为每个业务逻辑功能只知道使用的服务(例如日志记录、配置),而对IEnvironment 接口及其所有内容一无所知内容。

你可以找到我的例子的完整版here。

您可以找到here 对 F# 中 DI 方法的更全面概述。

【讨论】:

【参考方案2】:

由于日志记录本质上是不纯的,因此我知道没有一种特别干净的方法来进行日志记录。您基本上已经确定了问题中的两种解决方案。使用哪一个取决于日志的用途。

对于登录到外部服务,我会考虑创建一个 AppContext 类型,它是应用程序和用户设置的主页,并提供登录到例如的功能或方法。一个数据库。这种类型应该在你的函数中添加一个额外的参数,或者在你的类型中添加一个额外的字段,这取决于什么是最有意义的。

对于您的最低级别函数,而不是全部更改它们以接受附加参数,您应该考虑更改返回类型以包含您想要记录的信息,并将记录行为留给程序的更高级别部分。

对于控制台、滚动缓冲区或其他临时位置的日志记录,我认为创建一个 module 就可以了,它相当于一个 C# 静态类,并且只提供全局可访问的日志记录函数。

【讨论】:

以上是关于如何使用 NLog 以功能方式登录 F#的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ASP.NET Core 中使用 NLog 的高级特性

如何以编程方式登录到使用 Java 询问验证码的网站?

如何将 NLog 目标与 Microsoft ILogger 过滤器结合使用?

如何使用 Firebase 实现忘记密码功能?

以编程方式重新配置 NLog LoggingConfiguration 过滤器

如何使用 Spring-security 以编程方式登录用户?