我一直在研究日志记录选项并选择 NLog 并记录到数据库
我对函数式编程相当陌生,并且有 C# OOP 背景。
如何在 F# 中以函数式方式实现日志记录?
我是吗
我想避免仅仅因为我的项目很小而使用商业日志记录选项。
谢谢你的时间。
通过静态方法访问记录器
这种方法对于实验应用程序来说很好,但一般来说它是服务定位器的实例,通常被认为是反模式。
更可靠的替代方案是依赖注入(DI)。由于 F# 是混合语言,因此您可以使用 OOP 概念并以 OOP 的常见方式应用 DI 模式。但是,如果您想使用函数式风格,F# 中的 DI 没有单一的通用函数式模式。
在顶层创建记录器并将其传递给每个函数
这只是在函数代码中使用 DI 最直接的方法。这种方法可能适用于小型应用程序,但是如果除了日志记录之外还有其他横切问题(例如配置),则最终可能会出现参数爆炸,从而使代码变得混乱。有不同的方法来解决这个问题。我已经解决了这里描述的方法。
您可以通过以下方式应用此方法。
您的业务逻辑函数可能如下所示:
let doSomething env =
let logger = getLogger env
logger.Debug("Do something")
// Do something
Run Code Online (Sandbox Code Playgroud)
这意味着logger引用是通过env参数提供并通过函数访问的getLogger。
此代码应引用通用日志记录模块,该模块可能如下所示:
[<Interface>]
type ILoggerProvider =
abstract Logger: ILogger
let getLogger (env: #ILoggerProvider) = env.Logger
Run Code Online (Sandbox Code Playgroud)
env请注意, 的参数类型doSomething会自动推断为#ILoggerProvider(即继承ILoggerProvider)。
想象一下,稍后在相同的业务逻辑中,我们需要访问配置(以及日志记录)。在这种情况下,我们不需要新参数,而是env以类似的方式从同一参数访问配置:let configuration = getConfiguration env,前提是还创建了用于配置的公共模块:
[<Interface>]
type IConfigurationProvider =
abstract Configuration: IConfiguration
let getConfiguration (env: #IConfigurationProvider) = env.Configuration
Run Code Online (Sandbox Code Playgroud)
env在这种情况下,的参数类型doSomething会自动重新推断,
'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
Run Code Online (Sandbox Code Playgroud)
请注意,很容易使用新的横切关注点来扩展此代码,因为每个业务逻辑功能仅了解所使用的服务(例如日志记录、配置),并且对IEnvironment接口及其所有内容一无所知。
您可以在这里找到我的示例的完整版本。
您可以在此处找到有关 F# 中 DI 方法的更全面概述。