Using NLog with F# Interactive in Visual Studio - Need documentation

前端 未结 1 817
太阳男子
太阳男子 2020-12-17 05:27

I have a need to capture the input and output of F# functions when using F# Interactive. I am able to get NLog to work just fine when the program is run under Visual Studio

相关标签:
1条回答
  • 2020-12-17 05:47

    The Problem

    The problem with using NLog from F# Interactive is that NLog thinks that the Temp directory is where to find NLog.config and never succeeds. The way around this is to programmatically locate NLog.config for NLog.

    Things to know to solve this problem:

    1. When you run F# Interactive from within Visual Studio, it sets the current working directory to a temp file.

      > System.Environment.CurrentDirectory;; 
      val it : string = "C:\Users\Eric\AppData\Local\Temp"
      
    2. NLog logging requires three components:
      a. reference to NLog.dll.
      b. configuration file.
      c. calls to a logger method from code.

    3. NLog can be configured in many ways, both programmatically and using config files.
    4. AppData is a hidden folder. Guess what that means when using Windows Explorer.
    5. To get the location of the application with F# Interactive within Visual Studio you need __SOURCE_DIRECTORY__. See F# Spec 3.11 Identifier Replacements
    6. NLog.conf can use a full file path. Obvious but necessary.
    7. NLog file targets have an autoFlush option.
    8. NLog can be installed into a Visual Studio project using NuGet.
    9. Most of the info here comes from NLog Wiki.

    Instead of jumping right into the F# Interactive solution, the following progression will be used because a DLL will need to be created to setup and hold the functions for use with NLog from F# Interactive.

    1. Create a solution with three projects and install NLog.
      Solution Name: NLogExample
      Project 1 - Library, Name: Log - holds extension functions that call NLog
      Project 2 - Library, Name: MyLibrary - used to generate a demo DLL that uses Log functions.
      Project 3 - Console Application, Name: Main - used to generate a demo EXE that uses Log functions.

    2. a. Manually create NLog.config
      b. Access NLog.config from as a running project
      c. Log a message to the file

    3. a. Programmatically create a configuration
      b. Create a configuration for a running project and log a message to the file

    4. Create a configuration and log a message to the file using F# Interactive

    1. Create a solution with three projects and install NLog

    Using Visual Studio create the three projects.

    Install NLog for all three projects.

    2.a. Manually create NLog.config

    Note: For these examples to work when __SOURCE_DIRECTORY__;; is run from F# Interactive it should report a directory that is part of the project and NOT the Temp directory.

    Note: All the paths in this answer are relative to the solution directory.
    When you see <Solution directory> substitute in your actual solution directory.

    Path: <Solution director>\NLog.config

    <?xml version="1.0" encoding="utf-8" ?>
    <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          throwExceptions="true">
    
      <targets>
        <target xsi:type="File"
                name="file"
                fileName="<Solution directory>\log.txt"
                autoFlush="true"
          />
        </targets>
    
      <rules>
        <logger name="*"
                minlevel="Trace"
                writeTo="file"
        />
      </rules>
    </nlog>
    

    Note: Remember to change <Solution directory> to an actual path and set autoFlush="true"

    Note: Adding NLog.config to the solution makes it easier to view/modify the file.

    2.b. Access NLog.config from as a running project

    In Log.Library1.fs

    namespace Log
    
    module MyLog =
    
        let configureNLog () =
          let projectPath = __SOURCE_DIRECTORY__
          let soulutionPath = projectPath + "\.."
          let configPath = soulutionPath + @"\NLog.config"
          let xmlConfig = new NLog.Config.XmlLoggingConfiguration(configPath)
          NLog.LogManager.Configuration <- xmlConfig
    
        let NLogConfigToString () = 
          let targets = NLog.LogManager.Configuration.AllTargets
          let out = ""
          let out = Seq.fold (fun out target -> out + (sprintf "%A\n" target)) out targets
          let rules = NLog.LogManager.Configuration.LoggingRules
          let out = Seq.fold (fun out rule -> out + (sprintf "%A\n" rule)) out rules
          out
    
        let printNLogConfig () = 
           Printf.printfn "%s" (NLogConfigToString ())
    

    and for the Log project add a reference to System.XML

    In Main.Program.fs

    open Log
    
    [<EntryPoint>]
    let main argv = 
    
        MyLog.configureNLog ()
        MyLog.printNLogConfig ()
    
        0 // return an integer exit code
    

    and for the Main project add a reference to the Log project and set the Main project as the startup project.

    When run this should output to the console:

    File Target[file]
    logNamePattern: (:All) levels: [ Trace Debug Info Warn Error Fatal ] appendTo: [ file ]
    

    2.c. Log a message to the file

    In Log.Library1.fs

    namespace Log
    
    open NLog
    
    module MyLog =
    
        let configureNLog () =
          let projectPath = __SOURCE_DIRECTORY__
          let soulutionPath = projectPath + "\.."
          let configPath = soulutionPath + @"\NLog.config"
          let xmlConfig = new NLog.Config.XmlLoggingConfiguration(configPath)
          NLog.LogManager.Configuration <- xmlConfig
    
        let NLogConfigToString () = 
          let targets = NLog.LogManager.Configuration.AllTargets
          let out = ""
          let out = Seq.fold (fun out target -> out + (sprintf "%A\n" target)) out targets
          let rules = NLog.LogManager.Configuration.LoggingRules
          let out = Seq.fold (fun out rule -> out + (sprintf "%A\n" rule)) out rules
          out
    
        let printNLogConfig () = 
           Printf.printfn "%s" (NLogConfigToString ())
    
        let evalTracer = LogManager.GetLogger("file")
    

    In Main.Program.fs

    open Log
    open Library1
    
    [<EntryPoint>]
    
        let main argv = 
    
            MyLog.configureNLog ()
            MyLog.printNLogConfig ()
    
            // Add as many of these as needed
            MyLog.evalTracer.Trace("In Main @1.")
    
            MyFunctions.test001 ()
    
            0 // return an integer exit code
    

    and for the Main project add a reference to the MyLibrary project.

    In MyLibrary.Library1.fs

    namespace Library1
    
    open Log
    
    module MyFunctions =
    
        let test001 () =
            MyLog.evalTracer.Trace("In Library @1.")
    

    and for the MyLibrary project add a reference to the Log project.

    When run the log file log.txt should contain something similar to:

    2016-03-28 11:03:52.4963|TRACE|file|In Main @1.
    2016-03-28 11:03:52.5263|TRACE|file|In Library @1
    

    3.a. Programmatically create a configuration

    If a NLog.config file exist delete it to verify that the code created a new configuration but did not create a file.

    To set the configuration programmatically using F# you need to know:

    1. This FileName string is a layout which may include instances of layout renderers. This lets you use a single target to write to multiple files.
    2. SimpleLayout - Represents a string with embedded placeholders that can render contextual information.

    To Log.Library1.fs add

    let configureNLogPrgramatically () =
      let config = new NLog.Config.LoggingConfiguration()
      let fileTarget = new NLog.Targets.FileTarget()
      let projectPath = __SOURCE_DIRECTORY__
      let soulutionPath = projectPath + "\.."
      let filePath = soulutionPath + @"\log.txt"
      let layout = new NLog.Layouts.SimpleLayout(filePath)
      fileTarget.Name <- "file"
      fileTarget.FileName <- layout
      fileTarget.AutoFlush <- true
      config.AddTarget("file", fileTarget)
      let rule1 = new NLog.Config.LoggingRule("*",NLog.LogLevel.Trace,fileTarget)
      config.LoggingRules.Add(rule1)
      NLog.LogManager.Configuration <- config
    

    3.b. Create a configuration for a running project and log a message to the file

    In Main.Program.fs

    open Log
    open Library1
    
    [<EntryPoint>]
    
        let main argv = 
    
            MyLog.configureNLogPrgramatically ()
            MyLog.printNLogConfig ()
    
            // Add as many of these as needed
            MyLog.evalTracer.Trace("In Main @1.")
    
            MyFunctions.test001 ()
    
            0 // return an integer exit code
    

    When run the log file log.txt should contain something similar to:

    2016-03-28 11:16:07.2901|TRACE|file|In Main @1.
    2016-03-28 11:16:07.3181|TRACE|file|In Library @1.
    

    and note that a NLog.config file was NOT created.

    4. Create a configuration and log a message to the file using F# Interactive

    In MyLibrary.Script.fsx

    // print out __SOURCE_DIRECTORY__ to make sure we are not using the Temp directory
    printfn __SOURCE_DIRECTORY__
    
    #I __SOURCE_DIRECTORY__
    
    // Inform F# Interactive where to find functions in Log module
    #I "../Log/bin/Debug/"
    #r "Log.dll"
    
    open Log
    
    // Functions in Log module can now be run.
    MyLog.configureNLogPrgramatically ()
    MyLog.printNLogConfig ()
    
    // Inform F# Interactive where to find functions in MyLibrary module
    #I "../MyLibrary/bin/Debug/"
    #r "MyLibrary.dll"
    
    open Library1
    
    // Functions in MyLibrary module can now be run.
    MyFunctions.test001 ()
    

    When the script is executed with F# Interactive

    Microsoft (R) F# Interactive version 14.0.23413.0
    Copyright (c) Microsoft Corporation. All Rights Reserved.
    
    For help type #help;;
    
    > 
    <Solution directory>\MyLibrary
    val it : unit = ()
    
    --> Added <Solution directory>\MyLibrary' to library include path
    
    
    --> Added <Solution directory>\MyLibrary\../Log/bin/Debug/' to library include path
    
    
    --> Referenced <Solution directory>\MyLibrary\../Log/bin/Debug/Log.dll'
    
    File Target[file]
    logNamePattern: (:All) levels: [ Trace Debug Info Warn Error Fatal ] appendTo: [ file ]
    
    
    --> Added <Solution directory>\MyLibrary\../MyLibrary/bin/Debug/' to library include path
    
    
    --> Referenced <Solution directory>\MyLibrary\../MyLibrary/bin/Debug/MyLibrary.dll'
    
    
    val it : unit = ()
    
    > 
    

    The log file log.txt should contain something similar to:

    2016-03-28 11:42:41.5417|TRACE|file|In Library @1.
    

    Also, this will log while you still have an active F# Interactive session, so you can peek at the log between executing commands.

    0 讨论(0)
提交回复
热议问题