33

I'm writing a tool that's going to be check the health of workstations across a network, and will fix according to the issues it finds. I want to create a log file as the app is running through its tasks / checks on each machine. I just want to get this working on a single machine for now, but in time it will be scanning 100+ machines in one go (Threaded out).

What is the best way to create a log file?

I was thinking of using a List<string> to build up the log file in memory and then output it to a file once it had finished.

I'm just thinking there may be a better way of doing this?

JYelton
  • 35,664
  • 27
  • 132
  • 191
Derek
  • 8,300
  • 12
  • 56
  • 88
  • 1
    Is there only one application running that's scanning all machines remotely or will your app be deployed on each machine individually? – Wouter de Kort Apr 20 '12 at 07:36
  • you want to create one log file or per machine? – Renatas M. Apr 20 '12 at 07:39
  • 6
    log4net might be worth having a look at: http://logging.apache.org/log4net/release/features.html – Anders Lindahl Apr 20 '12 at 07:39
  • I will be accessing the machines remotely and I'm looking to create one log file per machine. Ideally, those that i have fixed will go to a specific folder, those that fail are dumped into a different folder. I'm just unsure of teh best method for doing this? thanks guys – Derek Apr 20 '12 at 07:42

12 Answers12

13

I would recommend log4net.

You would need multiple log files. So multiple file appenders. Plus you can create the file appenders dynamically.

Sample Code:

using log4net;
using log4net.Appender;
using log4net.Layout;
using log4net.Repository.Hierarchy;

// Set the level for a named logger
public static void SetLevel(string loggerName, string levelName)
{
    ILog log = LogManager.GetLogger(loggerName);
    Logger l = (Logger)log.Logger;

    l.Level = l.Hierarchy.LevelMap[levelName];
    }

// Add an appender to a logger
public static void AddAppender(string loggerName, IAppender appender)
{
    ILog log = LogManager.GetLogger(loggerName);
    Logger l = (Logger)log.Logger;

    l.AddAppender(appender);
}

// Create a new file appender
public static IAppender CreateFileAppender(string name, string fileName)
{
    FileAppender appender = new
        FileAppender();
    appender.Name = name;
    appender.File = fileName;
    appender.AppendToFile = true;

    PatternLayout layout = new PatternLayout();
    layout.ConversionPattern = "%d [%t] %-5p %c [%x] - %m%n";
    layout.ActivateOptions();

    appender.Layout = layout;
    appender.ActivateOptions();

    return appender;
}

// In order to set the level for a logger and add an appender reference you
// can then use the following calls:
SetLevel("Log4net.MainForm", "ALL");
AddAppender("Log4net.MainForm", CreateFileAppender("appenderName", "fileName.log"));

// repeat as desired

Sources/Good links:

Log4Net: Programmatically specify multiple loggers (with multiple file appenders)

Adding appenders programmatically

How to configure log4net programmatically from scratch (no config)

Plus the log4net also allows to write into event log as well. Everything is configuration based, and the configuration can be loaded dynamically from xml at runtime as well.

Edit 2:

One way to switch log files on the fly: Log4Net configuration file supports environment variables:

Environment.SetEnvironmentVariable("log4netFileName", "MyApp.log");

and in the log4net config:

<param name="File" value="${log4netFileName}".log/>
Community
  • 1
  • 1
A G
  • 21,087
  • 11
  • 87
  • 112
8

I would not use third party libraries, I would log to an xml file.

This is a code sample that do logging to a xml file from different threads:

private static readonly object Locker = new object();
private static XmlDocument _doc = new XmlDocument();

static void Main(string[] args)
{
    if (File.Exists("logs.txt"))
        _doc.Load("logs.txt");
    else
    {
        var root = _doc.CreateElement("hosts");
        _doc.AppendChild(root);
    }

    for (int i = 0; i < 100; i++)
    {
        new Thread(new ThreadStart(DoSomeWork)).Start();
    }
}

static void DoSomeWork()
{
    /*
     * Here you will build log messages
     */
    Log("192.168.1.15", "alive");
}

static void Log(string hostname, string state)
{
    lock (Locker)
    {
        var el = (XmlElement)_doc.DocumentElement.AppendChild(_doc.CreateElement("host"));
        el.SetAttribute("Hostname", hostname);
        el.AppendChild(_doc.CreateElement("State")).InnerText = state;
        _doc.Save("logs.txt");
    }
}
JHobern
  • 866
  • 1
  • 13
  • 20
croisharp
  • 1,926
  • 5
  • 25
  • 40
  • Normally I'd disagree and say use log4net or Enterprise Library... why reinvent the wheel. But this solution is simple and appropriate to the situation, and if you haven't used those solutions before, this is probably quicker than reading the docs and getting them configured. Well done! – bopapa_1979 Apr 20 '12 at 15:23
  • 3
    -1 - I'm no .Net expert but it looks like you are saving the whole log file to disk for each call to `Log(string,string)`, not only that you are maintaining a copy of the log file in memory in `_doc` that surely isn't a good idea. – Gareth Davis Jun 18 '12 at 10:59
  • 4
    Gareth, if there is a crash in application, process is killed, or computer restarts and the logs aren't saved for each call, then we really are in trouble. About memory i think that it will cost much more memory to load xml document for every Log(string, string) call (think about 100 threads). – croisharp Jun 18 '12 at 14:06
  • 2
    Whatever happened to just using `File.AppendAllLines`, dump it in a simple separated format and move on. By outputting flat text there's no need to keep anything in memory, or re-read the file, just append some lines to the end of the file and move on. – Seph Dec 05 '13 at 11:41
  • 3
    This is not a good idea at all. If the application crashes right in the middle then you will end up with no lock file or a lock file with truncated data in. – akd Jun 18 '15 at 13:58
  • 1
    -1 - the main problem I see, as already posted, is the continuos saving of xml file. It is LOT better a normal file where every log is appended, so you will not have to rewrite 5Mb of file every time but only to append 200 bytes. For reading the log file you can read it with notepad or better create an application to read an interpret it (sort by severity, by host and so on) when you need it – Matteo TeoMan Mangano Nov 17 '16 at 10:40
5

You might want to use the Event Log ! Here's how to access it from C# http://support.microsoft.com/kb/307024/en

But whatever is the method that you will use, I'd recommend to output to a file every time something is appended to the log rather than when your process exits, so you won't lose data in the case of crash or if your process is killed.

Julien
  • 1,181
  • 10
  • 31
5

Use the Nlog http://nlog-project.org/. It is free and allows to write to file, database, event log and other 20+ targets. The other logging framework is log4net - http://logging.apache.org/log4net/ (ported from java Log4j project). Its also free.

Best practices are to use common logging - http://commons.apache.org/logging/ So you can later change NLog or log4net to other logging framework.

Alex Blokha
  • 1,141
  • 2
  • 17
  • 30
5

We did a lot of research into logging, and decided that NLog was the best one to use.

See http://nlog-project.org/

Also see log4net vs. Nlog and http://www.dotnetlogging.com/comparison/

Community
  • 1
  • 1
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
4

You could use the Apache log4net library:

using System;
using log4net;
using log4net.Config;


public class MyApp
{
    // Define a static logger variable so that it references the
    // Logger instance named "MyApp".
    private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
    static void Main(string[] args)
    {
        XmlConfigurator.Configure(new System.IO.FileInfo(@"..\..\resources\log4net.config"));
        log.Info("Entering application.");
        Console.WriteLine("starting.........");
        log.Info("Entering application.");
        log.Error("Exiting application.");
        Console.WriteLine("starting.........");
    }
}
PLNech
  • 3,087
  • 1
  • 23
  • 52
Narottam Goyal
  • 3,534
  • 27
  • 26
4

add this config file


*************************************************************************************
<!--Configuration for file appender-->

<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>
  <log4net>
    <appender name="FileAppender" type="log4net.Appender.FileAppender">
      <file value="logfile.txt" />
      <appendToFile value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%d [%t] %-5p [%logger] - %m%n" />
      </layout>
    </appender>
    <root>
      <level value="DEBUG" />
      <appender-ref ref="FileAppender" />
    </root>
  </log4net>
</configuration>

*************************************************************************************

<!--Configuration for console appender-->


<configuration>

  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,
        log4net" />
  </configSections>

  <log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%d [%t] %-5p [%logger] - %m%n" />
      </layout>
    </appender>
   <root>
      <level value="ALL" />
      <appender-ref ref="ConsoleAppender" />
    </root>
  </log4net>
</configuration>
Thiem Nguyen
  • 6,345
  • 7
  • 30
  • 50
Narottam Goyal
  • 3,534
  • 27
  • 26
3

You can also take a look at the built-in .NET tracing facilities too. There's a set of trace listeners that allow you to output to a log file, but you can configure it to log into the Event viewer, or to a database (or all of them simultaneously).

http://www.codeguru.com/csharp/.net/net_debugging/tracing/article.php/c5919/NET-Tracing-Tutorial.htm

David Z.
  • 5,621
  • 2
  • 20
  • 13
2

You can use http://logging.apache.org/ library and use a database appender to collect all your log info together.

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
Chris Li
  • 3,715
  • 3
  • 29
  • 31
1

I'm using thread safe static class. Main idea is to queue the message on list and then save to log file each period of time, or each counter limit.

Important: You should force save file ( DirectLog.SaveToFile(); ) when you exit the program. (in case that there are still some items on the list)

The use is very simple: DirectLog.Log("MyLogMessage", 5);

This is my code:

using System;
using System.IO;
using System.Collections.Generic;

namespace Mendi
{

    /// <summary>
    /// class used for logging misc information to log file
    /// written by Mendi Barel
    /// </summary>
    static class DirectLog
    {
        readonly static int SAVE_PERIOD = 10 * 1000;// period=10 seconds
        readonly static int SAVE_COUNTER = 1000;// save after 1000 messages
        readonly static int MIN_IMPORTANCE = 0;// log only messages with importance value >=MIN_IMPORTANCE

        readonly static string DIR_LOG_FILES = @"z:\MyFolder\";

        static string _filename = DIR_LOG_FILES + @"Log." + DateTime.Now.ToString("yyMMdd.HHmm") + @".txt";

        readonly static List<string> _list_log = new List<string>();
        readonly static object _locker = new object();
        static int _counter = 0;
        static DateTime _last_save = DateTime.Now;

        public static void NewFile()
        {//new file is created because filename changed
            SaveToFile();
            lock (_locker)
            {

                _filename = DIR_LOG_FILES + @"Log." + DateTime.Now.ToString("yyMMdd.HHmm") + @".txt";
                _counter = 0;
            }
        }
        public static void Log(string LogMessage, int Importance)
        {
            if (Importance < MIN_IMPORTANCE) return;
            lock (_locker)
            {
                _list_log.Add(String.Format("{0:HH:mm:ss.ffff},{1},{2}", DateTime.Now, LogMessage, Importance));
                _counter++;
            }
            TimeSpan timeDiff = DateTime.Now - _last_save;

            if (_counter > SAVE_COUNTER || timeDiff.TotalMilliseconds > SAVE_PERIOD)
                SaveToFile();
        }

        public static void SaveToFile()
        {
            lock (_locker)
                if (_list_log.Count == 0)
                {
                    _last_save = _last_save = DateTime.Now;
                    return;
                }
            lock (_locker)
            {
                using (StreamWriter logfile = File.AppendText(_filename))
                {

                    foreach (string s in _list_log) logfile.WriteLine(s);
                    logfile.Flush();
                    logfile.Close();
                }

                _list_log.Clear();
                _counter = 0;
                _last_save = DateTime.Now;
            }
        }


        public static void ReadLog(string logfile)
        {
            using (StreamReader r = File.OpenText(logfile))
            {
                string line;
                while ((line = r.ReadLine()) != null)
                {
                    Console.WriteLine(line);
                }
                r.Close();
            }
        }
    }
}
Mendi Barel
  • 3,350
  • 1
  • 23
  • 24
  • Locking on a file isn't necessary. Thread.Sleep(1)! Lock on the fact that you can't open the file for writing. The file will be your lock. This is cross thread, process, application, server, ... safe. Locking on an object is only process safe. – TamusJRoyce Apr 24 '14 at 19:38
1

I found the SimpleLogger from heiswayi on GitHub good.

Tim
  • 1,045
  • 14
  • 23
0

Instead of using log4net which is an external library I have created my own simple class, highly customizable and easy to use (edit YOURNAMESPACEHERE with the namespace that you need).

CONSOLE APP

using System;
using System.IO;

namespace YOURNAMESPACEHERE
{
    enum LogEvent
    {
        Info = 0,
        Success = 1,
        Warning = 2,
        Error = 3
    }

    internal static class Log
    {
        private static readonly string LogSession = DateTime.Now.ToLocalTime().ToString("ddMMyyyy_HHmmss");
        private static readonly string LogPath = AppDomain.CurrentDomain.BaseDirectory + "logs";

        internal static void Write(LogEvent Level, string Message, bool ShowConsole = true, bool WritelogFile = true)
        {
            string Event = string.Empty;
            ConsoleColor ColorEvent = Console.ForegroundColor;

            switch (Level)
            {
                case LogEvent.Info:
                    Event = "INFO";
                    ColorEvent = ConsoleColor.White;
                    break;
                case LogEvent.Success:
                    Event = "SUCCESS";
                    ColorEvent = ConsoleColor.Green;
                    break;
                case LogEvent.Warning:
                    Event = "WARNING";
                    ColorEvent = ConsoleColor.Yellow;
                    break;
                case LogEvent.Error:
                    Event = "ERROR";
                    ColorEvent = ConsoleColor.Red;
                    break;
            }

            if (ShowConsole)
            {
                Console.ForegroundColor = ColorEvent;
                Console.WriteLine(" [{0}] => {1}", DateTime.Now.ToString("HH:mm:ss"), Message);
                Console.ResetColor();
            }

            if (WritelogFile)
            {
                if (!Directory.Exists(LogPath))
                    Directory.CreateDirectory(LogPath);

                File.AppendAllText(LogPath + @"\" + LogSession + ".log", string.Format("[{0}] => {1}: {2}\n", DateTime.Now.ToString("HH:mm:ss"), Event, Message));
            }
        }
    }
}

NO CONSOLE APP (ONLY LOG)

using System;
using System.IO;

namespace YOURNAMESPACEHERE
{
    enum LogEvent
    {
        Info = 0,
        Success = 1,
        Warning = 2,
        Error = 3
    }

internal static class Log
{
    private static readonly string LogSession = DateTime.Now.ToLocalTime().ToString("ddMMyyyy_HHmmss");
    private static readonly string LogPath = AppDomain.CurrentDomain.BaseDirectory + "logs";

    internal static void Write(LogEvent Level, string Message)
    {
        string Event = string.Empty;

        switch (Level)
        {
            case LogEvent.Info:
                Event = "INFO";
                break;
            case LogEvent.Success:
                Event = "SUCCESS";
                break;
            case LogEvent.Warning:
                Event = "WARNING";
                break;
            case LogEvent.Error:
                Event = "ERROR";
                break;
        }

        if (!Directory.Exists(LogPath))
            Directory.CreateDirectory(LogPath);

        File.AppendAllText(LogPath + @"\" + LogSession + ".log", string.Format("[{0}] => {1}: {2}\n", DateTime.Now.ToString("HH:mm:ss"), Event, Message));
    }
}

Usage:

CONSOLE APP

Log.Write(LogEvent.Info, "Test message"); // It will print an info in your console, also will save a copy of this print in a .log file.
Log.Write(LogEvent.Warning, "Test message", false); // It will save the print as warning only in your .log file.
Log.Write(LogEvent.Error, "Test message", true, false); // It will print an error only in your console.

NO CONSOLE APP (ONLY LOG)

Log.Write(LogEvent.Info, "Test message"); // It will print an info in your .log file.
Marco Concas
  • 1,665
  • 20
  • 25