Using NLog with F # Interactive in Visual Studio - Documentation Required
I need to capture the input and output of F # functions while using F # Interactive. I can get NLog to work fine when the program is launched in Visual Studio using F5 or Ctrl-F5. Also, the same methods that contain instructions for logging are simple and are called when called through F # Interactive; just nothing in the log file.
I also tried the following: F # Interactive for setting NLog links and still nothing in the log when launched from F # Interactive.
#I @"..\packages\NLog.2.0.0.2000\lib\net40"
#r @"NLog.dll"
And I even found this one that made me try each of these
NLog.Config.SimpleConfigurator.ConfigureForConsoleLogging()
NLog.Config.SimpleConfigurator.ConfigureForFileLogging(<full file name>)
and still nothing in the log file.
Does anyone know if Nlog can be used with F # Interactive?
If so, how is it done?
EDIT
I was able to get NLog to work with fsi.exe when it runs as a standalone. So now the problem is that NLog is finding the config file as NLog cannot find the config file starting from the fsi.exe location for Visual Studio. Looking at the use of NLog.dll.nlog in the NLog.dll directory.
source to share
Problem
The problem with using NLog from F # Interactive is that NLog thinks the directory Temp
is where it can be found NLog.config
and will never succeed. To do this, you need to programmatically find NLog.config
for NLog.
What you need to know to solve this problem:
-
When you start F # Interactive from Visual Studio, it sets the current working directory to a temp file.
> System.Environment.CurrentDirectory;; val it : string = "C:\Users\Eric\AppData\Local\Temp"
-
NLog requires three components:
a. reference to NLog.dll.
b. The configuration file.
from. calls the logger method from the code. - NLog can be configured in many ways, both programmatically and using configuration files.
- AppData is a hidden folder. Guess what that means when using Windows Explorer.
- To get the location of an application with F # Interactive in Visual Studio, you need
__SOURCE_DIRECTORY__
. See F # Spec 3.11 Identifier Substitutions - NLog.conf can use the full path to the file. Obvious but necessary.
- NLog file targets have an autoFlush option.
- NLog can be installed into a Visual Studio project using NuGet .
- Most of the information here comes from the NLog Wiki .
Instead of jumping straight into the F # interactive solution, the following progression will be used, as a DLL will need to be created to set up and store functions for use with NLog from F # Interactive.
-
Build a solution with three projects and install NLog.
Solution Name: NLogExample
Project 1 - Library, Name: Log Expiration Functions that Call NLog
Project 2 - Library, Name: MyLibrary - Used to create a demo DLL that uses the Log function.
Project 3 - Console Application, Name: Main - used to create a demo EXE using the Log functions. -
and. Manually create NLog.config
b. Accessing NLog.config from a running project
with. Write message to file -
and. Create a configuration programmatically
b. Create config for running project and register message in file -
Create config and register message to file using F # Interactive
1. Build a solution with three projects and install NLog
Using Visual Studio creates three projects.
Install NLog for all three projects.
2.a. Manually create NLog.config
Note. For these examples to work, when __SOURCE_DIRECTORY__;;
launched from F # Interactive, it must report a directory that is part of the project and NOT a directory Temp
.
Note. All paths in this answer refer to the solution directory.
When you see a <Solution directory>
replacement in your actual solution directory.
Way: <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 the actual path and setautoFlush="true"
Note. Adding NLog.config
to the solution makes it easier to view / modify the file.
2b. Accessing NLog.config from 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 link 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 project reference Log
and set the main project as a startup project.
This should output to the console on startup:
File Target[file] logNamePattern: (:All) levels: [ Trace Debug Info Warn Error Fatal ] appendTo: [ file ]
2.c. Write message to 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 project reference MyLibrary
.
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 project reference Log
.
When running, 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. Create a config programmatically
If the file exists NLog.config
, delete it to ensure that the code created a new configuration but did not create the file.
To set up a configuration programmatically using F #, you need to know:
- This FileName string represents a layout, which can include layout render instances. This allows one target to be used to write to multiple files.
- SimpleLayout - Represents a string with inline placeholders that can display contextual information.
In 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 config for running project and write message to 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 running, 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 the file NLog.config
was NOT .
4. Create config and register message to 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 a 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 look into the log between command executions.
source to share