├── Log4VFP.dll ├── log4net.dll ├── Log4VFPSource ├── Log4VFP │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Log4VFP.csproj │ └── Log4VFP.cs └── Log4VFP.sln ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── CONTRIBUTING.md ├── .gitattributes ├── verbose.config ├── compact.config ├── sample.prg ├── .gitignore ├── database.config ├── README.md └── log4vfp.prg /Log4VFP.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/Log4VFP/HEAD/Log4VFP.dll -------------------------------------------------------------------------------- /log4net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/Log4VFP/HEAD/log4net.dll -------------------------------------------------------------------------------- /Log4VFPSource/Log4VFP/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 📝 Provide a description of the new feature 11 | 12 | _What is the expected behavior of the proposed feature? What is the scenario this would be used?_ 13 | 14 | --- 15 | 16 | If you'd like to see this feature implemented, add a 👍 reaction to this post. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 📝 Provide detailed reproduction steps (if any) 11 | 12 | 1. … 13 | 2. … 14 | 3. … 15 | 16 | ### ✔️ Expected result 17 | 18 | _What is the expected result of the above steps?_ 19 | 20 | ### ❌ Actual result 21 | 22 | _What is the actual result of the above steps?_ 23 | 24 | ## 📷 Screenshots 25 | 26 | _Are there any useful screenshots? WinKey+Shift+S and then just paste them directly into the form_ 27 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # disable newline conversion on checkout with no conversion on check-in for all files 2 | * -text 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | *.prg merge=union 12 | 13 | # Standard to msysgit 14 | *.doc diff=astextplain 15 | *.DOC diff=astextplain 16 | *.docx diff=astextplain 17 | *.DOCX diff=astextplain 18 | *.dot diff=astextplain 19 | *.DOT diff=astextplain 20 | *.pdf diff=astextplain 21 | *.PDF diff=astextplain 22 | *.rtf diff=astextplain 23 | *.RTF diff=astextplain 24 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to Log4VFP 2 | 3 | ## Report a bug 4 | - Please check [issues](https://github.com/VFPX/Log4VFP/issues) if the bug has already been reported. 5 | - If you're unable to find an open issue addressing the problem, open a new one. Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring. 6 | 7 | ## Fix a bug or add an enhancement 8 | - Fork the project: see this [guide](https://www.dataschool.io/how-to-contribute-on-github/) for setting up and using a fork. 9 | - Make whatever changes are necessary. 10 | - Update the Releases section of README.md and describe the changes. Also, make any necessary changes to the documentation on that page. 11 | - Commit the changes. 12 | - Push to your fork. 13 | - Create a pull request; ensure the description clearly describes the problem and solution or the enhancement. 14 | 15 | ---- 16 | Last changed: 2022-04-09 -------------------------------------------------------------------------------- /verbose.config: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /compact.config: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Log4VFPSource/Log4VFP.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Log4VFP", "Log4VFP\Log4VFP.csproj", "{DD39F799-4C43-4935-9D8C-1DF1CFE2A536}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {DD39F799-4C43-4935-9D8C-1DF1CFE2A536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {DD39F799-4C43-4935-9D8C-1DF1CFE2A536}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {DD39F799-4C43-4935-9D8C-1DF1CFE2A536}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {DD39F799-4C43-4935-9D8C-1DF1CFE2A536}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Log4VFPSource/Log4VFP/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Log4VFP")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Log4VFP")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("dd39f799-4c43-4935-9d8c-1df1cfe2a536")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /sample.prg: -------------------------------------------------------------------------------- 1 | local lcConfigFile, ; 2 | lcLogFile, ; 3 | lcUser, ; 4 | loLogger, ; 5 | loException as Exception 6 | 7 | * Define some things. 8 | 9 | lcConfigFile = fullpath('compact.config') 10 | && the name of the configuration file to use; use this for a compact log 11 | *lcConfigFile = fullpath('verbose.config') 12 | && the name of the configuration file to use; use this for a verbose log 13 | *lcConfigFile = fullpath('database.config') 14 | && the name of the configuration file to use; use this to log to a SQL 15 | && Server database 16 | lcLogFile = lower(fullpath('applog.txt')) 17 | && the name of the log file to write to 18 | lcUser = 'DHENNIG' 19 | && the name of the current user 20 | 21 | * Initialize the logger. 22 | 23 | loLogger = newobject('Log4VFP', 'Log4VFP.prg') 24 | loLogger.cConfigurationFile = lcConfigFile 25 | && optional: uses a basic log4vfp.config (created if necessary) if not 26 | && specified 27 | loLogger.cUser = lcUser 28 | && optional: uses Windows user name if not specified 29 | loLogger.Open(lcLogFile) 30 | 31 | * Log the application start. 32 | 33 | loLogger.LogInfo('=================> App started at {0}', datetime()) 34 | loLogger.LogInfo('Application object created: version {0}', '1.0.1234') 35 | loLogger.LogInfo('Using {0} build {1} {2}', os(1), os(5), os(7)) 36 | 37 | * Log that an error occurred. 38 | 39 | try 40 | x = y 41 | catch to loException 42 | loLogger.LogError('Error {0} occurred: {1}', loException.ErrorNo, ; 43 | loException.Message) 44 | endtry 45 | 46 | * Log a process. 47 | 48 | wait window timeout 2 'Inserting a 2 second delay between logs (1)...' 49 | loLogger.StartMilestone('=================> Started process') 50 | wait window timeout 2 'Inserting a 2 second delay between logs (2)...' 51 | loLogger.LogInfo('Process done') 52 | 53 | * Shut down the logger and display the log. 54 | 55 | release loLogger 56 | modify file (lcLogFile) nowait 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | [Dd]ebug/ 3 | [Rr]elease/ 4 | x64/ 5 | *_i.c 6 | *_p.c 7 | *.ilk 8 | *.meta 9 | *.mht 10 | *.obj 11 | *.pch 12 | *.pdb 13 | *.pgc 14 | *.pgd 15 | *.rsp 16 | *.sbr 17 | *.tlb 18 | *.tli 19 | *.tlh 20 | *.tmp 21 | *.log 22 | *.user 23 | *.suo 24 | *.vspscc 25 | *.vssscc 26 | .builds 27 | 28 | # Tool Folders 29 | .vs 30 | .vscode 31 | 32 | #VFP and Web Connection 33 | *.fxp 34 | *.bak 35 | *.tmp 36 | *.tbk 37 | 38 | # Application 39 | log4net.config 40 | applog.txt 41 | 42 | # Visual C++ cache files 43 | ipch/ 44 | *.aps 45 | *.ncb 46 | *.opensdf 47 | *.sdf 48 | 49 | # Visual Studio profiler 50 | *.psess 51 | *.vsp 52 | *.vspx 53 | 54 | # Guidance Automation Toolkit 55 | *.gpState 56 | 57 | # ReSharper is a .NET coding add-in 58 | _ReSharper* 59 | 60 | # NCrunch 61 | *.ncrunch* 62 | .*crunch*.local.xml 63 | 64 | # Installshield output folder 65 | [Ee]xpress 66 | 67 | # DocProject is a documentation generator add-in 68 | DocProject/buildhelp/ 69 | DocProject/Help/*.HxT 70 | DocProject/Help/*.HxC 71 | DocProject/Help/*.hhc 72 | DocProject/Help/*.hhk 73 | DocProject/Help/*.hhp 74 | DocProject/Help/Html2 75 | DocProject/Help/html 76 | 77 | # Click-Once directory 78 | publish 79 | 80 | # Publish Web Output 81 | *.Publish.xml 82 | 83 | # NuGet Packages Directory 84 | packages 85 | 86 | # Windows Azure Build Output 87 | csx 88 | *.build.csdef 89 | 90 | # Windows Store app package directory 91 | AppPackages/ 92 | 93 | thumbs.db 94 | 95 | # Others 96 | [Bb]in 97 | [Oo]bj 98 | sql 99 | TestResults 100 | [Tt]est[Rr]esult* 101 | *.Cache 102 | ClientBin 103 | [Ss]tyle[Cc]op.* 104 | ~$* 105 | *.dbmdl 106 | Generated_Code #added for RIA/Silverlight projects 107 | 108 | #Html Help Builder 109 | HelpFile/_*.htm 110 | HelpFile/index*.* 111 | HelpFile/keywords.htm 112 | HelpFile/*.chm 113 | HelpFile/*.hh* 114 | HelpFile/*.zip 115 | HelpFile/*_recover.* 116 | 117 | -------------------------------------------------------------------------------- /database.config: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Log4VFPSource/Log4VFP/Log4VFP.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {DD39F799-4C43-4935-9D8C-1DF1CFE2A536} 8 | Library 9 | Properties 10 | Log4VFP 11 | Log4VFP 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\log4net.2.0.12\lib\net45\log4net.dll 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | xcopy /y "$(TargetPath)" "$(SolutionDir)..\" 57 | xcopy /y "$(TargetDir)log4net.dll" "$(SolutionDir)..\" 58 | 59 | 66 | -------------------------------------------------------------------------------- /Log4VFPSource/Log4VFP/Log4VFP.cs: -------------------------------------------------------------------------------- 1 | using log4net; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Log4VFP 10 | { 11 | /// 12 | /// This class creates and manages log4net loggers. 13 | /// 14 | public class LogManager 15 | { 16 | /// 17 | /// Constructor. 18 | /// 19 | /// 20 | /// The path for the logger settings file. 21 | /// 22 | /// 23 | /// The path for the log file. 24 | /// 25 | /// 26 | /// The name of the user to log. 27 | /// 28 | public LogManager(string loggerSettingsFile, string loggerLogFile, string userName) 29 | { 30 | FileInfo loggerConfigFile = new FileInfo(loggerSettingsFile); 31 | GlobalContext.Properties["LogFileName"] = loggerLogFile; 32 | GlobalContext.Properties["CurrentUser"] = userName; 33 | GlobalContext.Properties["AppStart"] = new MilestoneHelper(); 34 | GlobalContext.Properties["Milestone"] = new MilestoneHelper(); 35 | log4net.Config.XmlConfigurator.Configure(loggerConfigFile); 36 | } 37 | 38 | /// 39 | /// Sets the value of a named property to the specified value (if the property doesn't exists, it's created). 40 | /// 41 | /// 42 | /// The name of the property. 43 | /// 44 | /// 45 | /// The value for the property. 46 | /// 47 | public void SetProperty(string name, string value) 48 | { 49 | GlobalContext.Properties[name] = value; 50 | } 51 | 52 | /// 53 | /// Ensure log4net is shut down properly. 54 | /// 55 | public void Shutdown() 56 | { 57 | log4net.LogManager.Shutdown(); 58 | } 59 | 60 | /// 61 | /// Start a milestone. 62 | /// 63 | public void StartMilestone() 64 | { 65 | ((MilestoneHelper)GlobalContext.Properties["Milestone"]).Start(); 66 | } 67 | 68 | /// 69 | /// Get a logger object for the specified name. 70 | /// 71 | /// 72 | /// The name of the logger. 73 | /// 74 | /// 75 | /// The desired logger object. 76 | /// 77 | public ILog GetLogger(string name) 78 | { 79 | return log4net.LogManager.GetLogger(name); 80 | } 81 | } 82 | 83 | /// 84 | /// This class provides a milestone. 85 | /// 86 | public class MilestoneHelper 87 | { 88 | /// 89 | /// The timestamp for the start of the milestone. 90 | /// 91 | private DateTime _start; 92 | 93 | /// 94 | /// The constructor. 95 | /// 96 | public MilestoneHelper() 97 | { 98 | Start(); 99 | } 100 | 101 | /// 102 | /// Start the milestone. 103 | /// 104 | public void Start() 105 | { 106 | _start = DateTime.Now; 107 | } 108 | 109 | /// 110 | /// Get the number of seconds since the milestone started. 111 | /// 112 | /// 113 | /// The number of seconds since the milestone started. 114 | /// 115 | public override string ToString() 116 | { 117 | return (DateTime.Now - _start).TotalSeconds.ToString(); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Log4VFP 2 | 3 | Log4NET is a powerful diagnostic logging library for .NET applications. Log4VFP provides a VFP wrapper for Log4NET, allowing you to add advanced logging features to your VFP applications. 4 | 5 | Documentation for Log4NET can be found at [https://logging.apache.org/log4net](https://logging.apache.org/log4net). I won't reproduce the documentation here but will just highlight some ways you can use Log4VFP. 6 | 7 | Thanks to Rick Strahl for creating the VFP wrapper class. 8 | 9 | ## Installing Log4VFP 10 | 11 | You can install Log4VFP one of two ways: 12 | 13 | * Click the Clone or Download button and choose Download ZIP to download the source code for Log4VFP. 14 | 15 | * Click the Clone or Download button, copy the URL displayed in the dialog, and use that to clone the repository using Git on your system. 16 | 17 | **Important**: Log4VFP requires wwDotNetBridge by Rick Strahl, which isn't included in the repository or downloads. If you already have wwDotNetBridge, adjust the sample code included with this project to include the path to wwDotNetBridge's files. If you do not have wwDotNetBridge, you can get it from its repository at [https://github.com/RickStrahl/wwDotnetBridge](https://github.com/RickStrahl/wwDotnetBridge). 18 | 19 | If you want to modify the source code for the C# Log4VFP wrapper, you will also need to retrieve the Log4NET package. The easiest way to do that is to open the Log4VFP solution (Log4VFP.sln in the Log4VFPSource subdirectory) in Visual Studio and choose Rebuild Solution from the Build menu. 20 | 21 | ## Log4VFP components 22 | 23 | Log4VFP consists of three files: 24 | 25 | * Log4VFP.dll, a C# wrapper DLL built from the C# solution in the Log4VFP folder 26 | 27 | * Log4NET.dll, the Log4NET assembly 28 | 29 | * Log4VFP.prg, which contains a VFP class that wraps the functionality of the C# classes 30 | 31 | Include the PRG in your project so it's built into the EXE and deploy the two DLLs with your application; they do not require any registration on the user's system. 32 | 33 | In addition, as noted above, Log4VFP uses wwDotNetBridge, so you'll need to add wwDotNetBridge.prg to your project plus deploy ClrHost.dll and wwDotNetBridge.dll with your application; neither of these files need registration either. 34 | 35 | Finally, you'll need a configuration file, named anything you wish, which contains XML that tells Log4NET how to perform diagnostic logging. Log4VFP includes three sample configuration files: 36 | 37 | * compact.config: logs to a text file in a compact format 38 | 39 | * verbose.config: logs to a text file in a verbose format 40 | 41 | * database.config: logs to a SQL Server database 42 | 43 | The VFP Log4VFP wrapper class creates a configuration file named log4net.config (similar to compact.config) automatically if you don't specify one and it doesn't already exist. 44 | 45 | ## Starting Log4VFP 46 | 47 | Start by instantiating the Log4VFP class: 48 | 49 | ```Fox 50 | loLogger = newobject('Log4VFP', 'Log4VFP.prg') 51 | ``` 52 | 53 | If you run Log4VFP first, you can use CREATEOBJECT instead of NEWOBJECT because the program uses SET PROCEDURE TO Log4VFP ADDITIVE: 54 | 55 | ```Fox 56 | do Log4VFP 57 | loLogger = createobject('Log4VFP') 58 | ``` 59 | 60 | Next call the Open method, specifying the name and path of the file to log to: 61 | 62 | ```Fox 63 | lcLogFile = lower(fullpath('applog.txt')) 64 | loLogger.Open(lcLogFile) 65 | ``` 66 | 67 | By default, Open uses log4net.config in the current folder as its configuration file and the name of the Windows user running the application as the user name. If you want to change those, set the cConfigurationFile and cUser properties as necessary before calling Open: 68 | 69 | ```Fox 70 | lcConfigFile = fullpath('verbose.config') 71 | lcUser = 'DHENNIG' 72 | loLogger = newobject('Log4VFP', 'Log4VFP.prg') 73 | loLogger.cConfigurationFile = lcConfigFile 74 | loLogger.cUser = lcUser 75 | loLogger.Open(lcLogFile) 76 | ``` 77 | 78 | ## Using Log4VFP 79 | 80 | Now that you have a logger object, you can write to the log file using these methods: 81 | 82 | * LogInfo: writes an INFO message to the log 83 | 84 | * LogDebug: writes a DEBUG message to the log 85 | 86 | * LogWarn: writes a WARN message to the log 87 | 88 | * LogError: writes an ERROR message to the log 89 | 90 | * LogFatal: writes a FATAL message to the log 91 | 92 | INFO, DEBUG, WARN, ERROR, and FATAL are different levels of logging. You can filter Log4NET to only write certain levels to the log, allowing you to determine how verbose the log is for a particular run of the application (recording all messages or only errors, for example). You decide which things are INFO, which are DEBUG, and so on by calling the appropriate method in your code. 93 | 94 | Pass these methods a single parameter: the message to write to the log. You don't have to worry about the user name, the application name, or the date/time of the message; the logging pattern described in the configuration file (discussed later) determines what's written to the log. Here's an example: 95 | 96 | ```Fox 97 | loLogger.LogInfo('=================> App started') 98 | ``` 99 | 100 | Depending on what the logging pattern is in the configuration file, the log file entry for this message may look like this: 101 | 102 | ``` 103 | 2018-10-03 09:30:17,971 =================> App started 104 | 0.0659599 seconds since previous milestone 105 | Total time to run: 0.0679591 seconds 106 | ``` 107 | 108 | or like this: 109 | 110 | ``` 111 | 2018-09-24 12:17:38,497 (0.0069945 seconds since app started, 0.0069945 seconds since last milestone) DHENNIG INFO - =================> App started 112 | ``` 113 | 114 | or something else. 115 | 116 | You can also pass these methods a message string with placeholders ({0} for the first parameter, {1} for the second, and so on) followed by up to 10 parameters that are inserted into the placeholders. For example: 117 | 118 | ```Fox 119 | loLogger.LogInfo('Using {0} build {1} {2}', os(1), os(5), os(7)) 120 | ``` 121 | 122 | displays this message on my system: 123 | 124 | ``` 125 | Using Windows 6.02 build 9200 126 | ``` 127 | 128 | ## Configuring Log4VFP 129 | 130 | Log4NET uses a configuration file to determine how, where, and when to log. I won't go into detail on this because it's discussed at great length in the Log4NET documentation. We'll just look at some common use cases. 131 | 132 | Here's the content of compact.config that comes with Log4VFP (this is also the content of log4net.config used by the wrapper class if it creates that file): 133 | 134 | ``` 135 | 136 | 137 |
138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | ``` 158 | 159 | This specifies the following: 160 | 161 | * Output goes to a text file (the "type" attribute in the "appender" element) 162 | 163 | * The size of the text file is limited to 10 MB (the "maximumFileSize" element), after which up to five backups are created (the "maxSizeRollBackups" element). 164 | 165 | * Log entries display the date/time of the entry, the number of seconds since the application started, the number of seconds since the last milestone (milestones are discussed later), the name of the current user, the level of the message (INFO, DEBUG, etc.), the message, and a carriage return. See [https://logging.apache.org/log4net/release/sdk/html/T_log4net_Layout_PatternLayout.htm](https://logging.apache.org/log4net/release/sdk/html/T_log4net_Layout_PatternLayout.htm) for documentation on layout patterns. Here's an example: 166 | 167 | ``` 168 | 2018-09-24 12:17:38,502 (0.0089932 seconds since app started, 0.0089932 seconds since last milestone) DHENNIG ERROR - Error 12 occurred: Variable 'Y' is not found. 169 | ``` 170 | 171 | verbose.config also specifies a text file but formats the log entries differently: 172 | 173 | ``` 174 | 175 | ``` 176 | 177 | In this case, they appear as the date/time of the entry, the message, a carriage return, the number of seconds since the previous milestone, a carriage return, and the number of seconds since the application started. Here's an example: 178 | 179 | ``` 180 | 2018-10-03 09:30:17,981 Error 12 occurred: Variable 'Y' is not found. 181 | 0.0669598 seconds since previous milestone 182 | Total time to run: 0.0841964 seconds 183 | ``` 184 | 185 | database.config specifies logging to a table named Log in a SQL Server database named ErrorLog on my server: 186 | 187 | ``` 188 | 189 | 190 |
191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | ``` 249 | 250 | Set the "connectionString" element to the desired connection string. Adjust the "commandText" and "parameter" elements as necessary, depending on the name and structure of your log table. Here's the structure of the Log table expected by this sample config file: 251 | 252 | ```SQL 253 | CREATE TABLE [dbo].[Log] ( 254 | [Id] [int] IDENTITY (1, 1) NOT NULL, 255 | [Date] [datetime] NOT NULL, 256 | [Thread] [varchar] (255) NOT NULL, 257 | [Level] [varchar] (50) NOT NULL, 258 | [Logger] [varchar] (255) NOT NULL, 259 | [Message] [varchar] (4000) NOT NULL, 260 | [Exception] [varchar] (2000) NULL 261 | ) 262 | ``` 263 | 264 | ## Custom properties 265 | 266 | You can add and set the values of custom properties to Log4VFP using the SetProperty method. Here's an example: 267 | 268 | ``` 269 | loLogger.SetProperty('MyCustomProperty', 'SomeValue') 270 | ``` 271 | 272 | If the specified property name doesn't exist, SetProperty creates it. Note that values are automatically converted to strings if necessary. 273 | 274 | To log a custom property, specify it as "%property{*PropertyName*}" in a log pattern. For example: 275 | 276 | ``` 277 | 278 | ``` 279 | 280 | ## Milestones 281 | 282 | You can record the start and end of certain processes in your application by starting "milestones". To start a milestone, call the StartMilestone method of the logger object, optionally passing it a message to log. For example: 283 | 284 | ```Fox 285 | loLogger.StartMilestone('=================> Started process') 286 | * some code here that takes a while to run 287 | loLogger.LogInfo('Process done') 288 | ``` 289 | 290 | The following is logged (using compact.config): 291 | 292 | ``` 293 | 2018-09-24 12:44:43,922 (2.3954147 seconds since app started, 0.0009998 seconds since last milestone) DHENNIG INFO - =================> Started process 294 | 2018-09-24 12:44:48,922 (7.3955336 seconds since app started, 5.0011187 seconds since last milestone) DHENNIG INFO - Process done 295 | ``` 296 | 297 | ## Helping with this project 298 | 299 | See [How to contribute to Log4VFP](.github/CONTRIBUTING.md) for details on how to help with this project. 300 | 301 | ## Releases 302 | 303 | ### 2021-06-16 304 | 305 | * Added SetProperty method which allows creating custom properties. 306 | 307 | ### 2021-01-30 308 | 309 | * Updated to Log4Net 2.0.12. -------------------------------------------------------------------------------- /log4vfp.prg: -------------------------------------------------------------------------------- 1 | SET PROCEDURE TO Log4Vfp ADDITIVE 2 | RETURN 3 | 4 | ************************************************************* 5 | DEFINE CLASS Log4Vfp AS Custom 6 | ************************************************************* 7 | *: Author: Rick Strahl 8 | *: (c) West Wind Technologies, 2018 9 | *:Contact: http://www.west-wind.com 10 | *:Created: 09/29/18 11 | ************************************************************* 12 | #IF .F. 13 | *:Help Documentation 14 | *:Topic: 15 | Class Log4Vfp 16 | 17 | *:Description: 18 | 19 | *:Example: 20 | 21 | *:Remarks: 22 | 23 | *:SeeAlso: 24 | 25 | 26 | *:ENDHELP 27 | #ENDIF 28 | 29 | oBridge = null 30 | oLogger = null 31 | oLogManager = null 32 | 33 | cConfigurationFile = LOWER(FULLPATH("Log4Net.config")) 34 | cUser = SUBSTR(SYS(0),AT("#",SYS(0)) + 2) 35 | 36 | 37 | ************************************************************************ 38 | * Init 39 | **************************************** 40 | *** Function: 41 | *** Assume: 42 | *** Pass: 43 | *** Return: 44 | ************************************************************************ 45 | FUNCTION Init() 46 | 47 | *** wwDotnetBridge dependency 48 | DO wwDotnetBridge 49 | 50 | this.oBridge = GetwwDotnetBridge() 51 | IF VARTYPE(this.oBridge) # "O" 52 | ERROR "Failed to load wwDotnetBridge." 53 | ENDIF 54 | 55 | IF(!THIS.oBridge.LoadAssembly('Log4VFP.dll')) 56 | ERROR "Failed to load Log4VFP assembly." 57 | ENDIF 58 | 59 | THIS.cUser = SUBSTR(SYS(0),AT("#",SYS(0)) + 2) 60 | 61 | ENDFUNC 62 | * Init 63 | 64 | ************************************************************************ 65 | * Open 66 | **************************************** 67 | *** Function: 68 | *** Assume: 69 | *** Pass: 70 | *** Return: 71 | ************************************************************************ 72 | FUNCTION Open(lcLogFile, lcName) 73 | LOCAL loLogManager, loLogger 74 | 75 | IF(!FILE(this.cConfigurationFile)) 76 | THIS.CreateConfigurationFile() 77 | ENDIF 78 | 79 | IF EMPTY(lcLogFile) 80 | lcLogFile = '' 81 | ENDIF 82 | 83 | IF EMPTY(lcName) 84 | lcName = SYS(2015) 85 | ENDIF 86 | 87 | this.oLogManager = THIS.oBridge.CreateInstance('Log4VFP.LogManager', ; 88 | THIS.cConfigurationFile, ; 89 | lcLogFile,; 90 | this.cUser) 91 | IF VARTYPE(THIS.oLogManager) # "O" 92 | ERROR "Unable to create Log Manager: " + this.oBridge.cErrorMsg 93 | ENDIF 94 | 95 | This.oLogger = THIS.oLogManager.GetLogger(lcName) 96 | RETURN This.oLogger 97 | ENDFUNC 98 | * Open 99 | 100 | ************************************************************************ 101 | * SetProperty 102 | **************************************** 103 | *** Function: Sets the value of a named property to the specified value (if the property doesn't exists, it's created). 104 | *** Assume: 105 | *** Pass: tcName - the name of the property, tuValue - the value of the property 106 | *** Return: 107 | ************************************************************************ 108 | function SetProperty(tcName, tuValue) 109 | This.oLogManager.SetProperty(tcName, transform(tuValue)) 110 | endfunc 111 | 112 | ************************************************************************ 113 | * CreateLogConfiguration 114 | **************************************** 115 | *** Function: Creates a new configuration file as a template 116 | *** Assume: 117 | *** Pass: lcFilename - 118 | *** Return: 119 | ************************************************************************ 120 | FUNCTION CreateConfigurationFile(lcFileName) 121 | LOCAL lcConfig 122 | 123 | IF(EMPTY(lcFilename)) 124 | lcFilename = this.cConfigurationFile 125 | ENDIF 126 | 127 | TEXT TO lcConfig NOSHOW 128 | 129 | 130 |
131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | ENDTEXT 151 | 152 | TRY 153 | STRTOFILE(STRCONV(lcConfig,9),lcFilename, 4) 154 | CATCH 155 | ENDTRY 156 | 157 | RETURN FILE(lcFilename) 158 | ENDFUNC 159 | * CreateLogConfiguration 160 | 161 | 162 | ************************************************************************ 163 | * StartMileStone 164 | **************************************** 165 | *** Function: 166 | *** Assume: 167 | *** Pass: 168 | *** Return: 169 | ************************************************************************ 170 | FUNCTION StartMileStone(tcMessage) 171 | 172 | IF VARTYPE(this.oLogManager) = "O" 173 | this.oLogManager.StartMileStone() 174 | if vartype(tcMessage) = 'C' and not empty(tcMessage) 175 | This.LogInfo(tcMessage) 176 | endif 177 | RETURN .T. 178 | ENDIF 179 | 180 | RETURN .F. 181 | ENDFUNC 182 | * StartMileStone 183 | 184 | ************************************************************************ 185 | * Shutdown 186 | **************************************** 187 | *** Function: Shuts down the log Session 188 | *** Assume: 189 | *** Pass: 190 | *** Return: 191 | ************************************************************************ 192 | FUNCTION Shutdown() 193 | 194 | IF VARTYPE(this.oLogManager) = "O" 195 | this.oLogManager.Shutdown() 196 | THIS.oLogManager = null 197 | ENDIF 198 | This.oLogger = .NULL. 199 | 200 | 201 | ENDFUNC 202 | * Shutdown 203 | 204 | ************************************************************************ 205 | * Destroy 206 | **************************************** 207 | *** Function: Shuts down the log Session 208 | *** Assume: 209 | *** Pass: 210 | *** Return: 211 | ************************************************************************ 212 | 213 | function Destroy 214 | This.Shutdown() 215 | endfunc 216 | 217 | ************************************************************************ 218 | * LogInfo 219 | **************************************** 220 | *** Function: Logs an INFO message 221 | *** Assume: 222 | *** Pass: The message to log and up to 10 parameters to be inserted 223 | *** into placeholders in the message 224 | *** Return: 225 | ************************************************************************ 226 | function LogInfo(tcMessage, tuParam1, tuParam2, tuParam3, tuParam4, ; 227 | tuParam5, tuParam6, tuParam7, tuParam8, tuParam9, tuParam10) 228 | local lnParams 229 | lnParams = pcount() 230 | do case 231 | case vartype(This.oLogger) <> 'O' 232 | return .F. 233 | case lnParams = 1 234 | This.oLogger.Info(tcMessage) 235 | case lnParams = 2 236 | This.oLogger.InfoFormat(tcMessage, tuParam1) 237 | case lnParams = 3 238 | This.oLogger.InfoFormat(tcMessage, tuParam1, tuParam2) 239 | case lnParams = 4 240 | This.oLogger.InfoFormat(tcMessage, tuParam1, tuParam2, tuParam3) 241 | case lnParams = 5 242 | This.oLogger.InfoFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 243 | tuParam4) 244 | case lnParams = 6 245 | This.oLogger.InfoFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 246 | tuParam4, tuParam5) 247 | case lnParams = 7 248 | This.oLogger.InfoFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 249 | tuParam4, tuParam5, tuParam6) 250 | case lnParams = 8 251 | This.oLogger.InfoFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 252 | tuParam4, tuParam5, tuParam6, tuParam7) 253 | case lnParams = 9 254 | This.oLogger.InfoFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 255 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8) 256 | case lnParams = 10 257 | This.oLogger.InfoFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 258 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8, tuParam9) 259 | case lnParams = 11 260 | This.oLogger.InfoFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 261 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8, tuParam9, ; 262 | tuParam10) 263 | endcase 264 | return .T. 265 | endfunc 266 | 267 | ************************************************************************ 268 | * LogError 269 | **************************************** 270 | *** Function: Logs an ERROR message 271 | *** Assume: 272 | *** Pass: The message to log and up to 10 parameters to be inserted 273 | *** into placeholders in the message 274 | *** Return: 275 | ************************************************************************ 276 | function LogError(tcMessage, tuParam1, tuParam2, tuParam3, tuParam4, ; 277 | tuParam5, tuParam6, tuParam7, tuParam8, tuParam9, tuParam10) 278 | local lnParams 279 | lnParams = pcount() 280 | do case 281 | case vartype(This.oLogger) <> 'O' 282 | return .F. 283 | case lnParams = 1 284 | This.oLogger.Error(tcMessage) 285 | case lnParams = 2 286 | This.oLogger.ErrorFormat(tcMessage, tuParam1) 287 | case lnParams = 3 288 | This.oLogger.ErrorFormat(tcMessage, tuParam1, tuParam2) 289 | case lnParams = 4 290 | This.oLogger.ErrorFormat(tcMessage, tuParam1, tuParam2, tuParam3) 291 | case lnParams = 5 292 | This.oLogger.ErrorFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 293 | tuParam4) 294 | case lnParams = 6 295 | This.oLogger.ErrorFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 296 | tuParam4, tuParam5) 297 | case lnParams = 7 298 | This.oLogger.ErrorFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 299 | tuParam4, tuParam5, tuParam6) 300 | case lnParams = 8 301 | This.oLogger.ErrorFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 302 | tuParam4, tuParam5, tuParam6, tuParam7) 303 | case lnParams = 9 304 | This.oLogger.ErrorFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 305 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8) 306 | case lnParams = 10 307 | This.oLogger.ErrorFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 308 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8, tuParam9) 309 | case lnParams = 11 310 | This.oLogger.ErrorFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 311 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8, tuParam9, ; 312 | tuParam10) 313 | endcase 314 | return .T. 315 | endfunc 316 | 317 | ************************************************************************ 318 | * LogWarn 319 | **************************************** 320 | *** Function: Logs a WARN message 321 | *** Assume: 322 | *** Pass: The message to log and up to 10 parameters to be inserted 323 | *** into placeholders in the message 324 | *** Return: 325 | ************************************************************************ 326 | function LogWarn(tcMessage, tuParam1, tuParam2, tuParam3, tuParam4, ; 327 | tuParam5, tuParam6, tuParam7, tuParam8, tuParam9, tuParam10) 328 | local lnParams 329 | lnParams = pcount() 330 | do case 331 | case vartype(This.oLogger) <> 'O' 332 | return .F. 333 | case lnParams = 1 334 | This.oLogger.Warn(tcMessage) 335 | case lnParams = 2 336 | This.oLogger.WarnFormat(tcMessage, tuParam1) 337 | case lnParams = 3 338 | This.oLogger.WarnFormat(tcMessage, tuParam1, tuParam2) 339 | case lnParams = 4 340 | This.oLogger.WarnFormat(tcMessage, tuParam1, tuParam2, tuParam3) 341 | case lnParams = 5 342 | This.oLogger.WarnFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 343 | tuParam4) 344 | case lnParams = 6 345 | This.oLogger.WarnFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 346 | tuParam4, tuParam5) 347 | case lnParams = 7 348 | This.oLogger.WarnFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 349 | tuParam4, tuParam5, tuParam6) 350 | case lnParams = 8 351 | This.oLogger.WarnFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 352 | tuParam4, tuParam5, tuParam6, tuParam7) 353 | case lnParams = 9 354 | This.oLogger.WarnFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 355 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8) 356 | case lnParams = 10 357 | This.oLogger.WarnFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 358 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8, tuParam9) 359 | case lnParams = 11 360 | This.oLogger.WarnFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 361 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8, tuParam9, ; 362 | tuParam10) 363 | endcase 364 | return .T. 365 | endfunc 366 | 367 | ************************************************************************ 368 | * LogDebug 369 | **************************************** 370 | *** Function: Logs a DEBUG message 371 | *** Assume: 372 | *** Pass: The message to log and up to 10 parameters to be inserted 373 | *** into placeholders in the message 374 | *** Return: 375 | ************************************************************************ 376 | function LogDebug(tcMessage, tuParam1, tuParam2, tuParam3, tuParam4, ; 377 | tuParam5, tuParam6, tuParam7, tuParam8, tuParam9, tuParam10) 378 | local lnParams 379 | lnParams = pcount() 380 | do case 381 | case vartype(This.oLogger) <> 'O' 382 | return .F. 383 | case lnParams = 1 384 | This.oLogger.Debug(tcMessage) 385 | case lnParams = 2 386 | This.oLogger.DebugFormat(tcMessage, tuParam1) 387 | case lnParams = 3 388 | This.oLogger.DebugFormat(tcMessage, tuParam1, tuParam2) 389 | case lnParams = 4 390 | This.oLogger.DebugFormat(tcMessage, tuParam1, tuParam2, tuParam3) 391 | case lnParams = 5 392 | This.oLogger.DebugFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 393 | tuParam4) 394 | case lnParams = 6 395 | This.oLogger.DebugFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 396 | tuParam4, tuParam5) 397 | case lnParams = 7 398 | This.oLogger.DebugFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 399 | tuParam4, tuParam5, tuParam6) 400 | case lnParams = 8 401 | This.oLogger.DebugFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 402 | tuParam4, tuParam5, tuParam6, tuParam7) 403 | case lnParams = 9 404 | This.oLogger.DebugFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 405 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8) 406 | case lnParams = 10 407 | This.oLogger.DebugFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 408 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8, tuParam9) 409 | case lnParams = 11 410 | This.oLogger.DebugFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 411 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8, tuParam9, ; 412 | tuParam10) 413 | endcase 414 | return .T. 415 | endfunc 416 | 417 | ************************************************************************ 418 | * LogFatal 419 | **************************************** 420 | *** Function: Logs a FATAL message 421 | *** Assume: 422 | *** Pass: The message to log and up to 10 parameters to be inserted 423 | *** into placeholders in the message 424 | *** Return: 425 | ************************************************************************ 426 | function LogFatal(tcMessage, tuParam1, tuParam2, tuParam3, tuParam4, ; 427 | tuParam5, tuParam6, tuParam7, tuParam8, tuParam9, tuParam10) 428 | local lnParams 429 | lnParams = pcount() 430 | do case 431 | case vartype(This.oLogger) <> 'O' 432 | return .F. 433 | case lnParams = 1 434 | This.oLogger.Fatal(tcMessage) 435 | case lnParams = 2 436 | This.oLogger.FatalFormat(tcMessage, tuParam1) 437 | case lnParams = 3 438 | This.oLogger.FatalFormat(tcMessage, tuParam1, tuParam2) 439 | case lnParams = 4 440 | This.oLogger.FatalFormat(tcMessage, tuParam1, tuParam2, tuParam3) 441 | case lnParams = 5 442 | This.oLogger.FatalFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 443 | tuParam4) 444 | case lnParams = 6 445 | This.oLogger.FatalFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 446 | tuParam4, tuParam5) 447 | case lnParams = 7 448 | This.oLogger.FatalFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 449 | tuParam4, tuParam5, tuParam6) 450 | case lnParams = 8 451 | This.oLogger.FatalFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 452 | tuParam4, tuParam5, tuParam6, tuParam7) 453 | case lnParams = 9 454 | This.oLogger.FatalFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 455 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8) 456 | case lnParams = 10 457 | This.oLogger.FatalFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 458 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8, tuParam9) 459 | case lnParams = 11 460 | This.oLogger.FatalFormat(tcMessage, tuParam1, tuParam2, tuParam3, ; 461 | tuParam4, tuParam5, tuParam6, tuParam7, tuParam8, tuParam9, ; 462 | tuParam10) 463 | endcase 464 | return .T. 465 | endfunc 466 | 467 | ENDDEFINE 468 | *EOC Log4Vfp --------------------------------------------------------------------------------