如何使用 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
注意doSomething
的env
参数类型会自动推断为#ILoggerProvider
(即继承ILoggerProvider
)。
想象稍后在相同的业务逻辑中我们需要访问配置(以及日志记录)。在这种情况下,我们不需要新参数,而是以类似的方式从相同的env
参数访问配置:let configuration = getConfiguration env
,前提是还创建了用于配置的通用模块:
[<Interface>]
type IConfigurationProvider =
abstract Configuration: IConfiguration
let getConfiguration (env: #IConfigurationProvider) = env.Configuration
在这种情况下,doSomething
的 env
参数类型会自动重新推断为
'a (requires 'a :> ILoggerProvider and 'a :> IConfigurationProvider)
这意味着它应该同时继承ILoggerProvider
和IConfigurationProvider
。
在应用程序级别,您需要创建环境实例并将其传递给业务逻辑,以便提供对日志记录和配置(以及可能的其他服务)的访问。您可以通过以下方式进行:
[<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 的高级特性
如何将 NLog 目标与 Microsoft ILogger 过滤器结合使用?