Main Site Documentation

Logger code, please review


#1

Hi guys,

I needed to log data to SD card and wrote the following class. It simply logs a text message to a file in the form of date,message;.

Please feel free to comment.

SDLogger.cs:


using System;
using System.Collections;
using System.IO;
using System.Threading;

using Microsoft.SPOT;

using GHIElectronics.NETMF.IO;

  public class SDLogger
  {
    private string LogPath = @ "\SD\log.txt";
    private PersistentStorage sdPS;
    private ArrayList LogItems;

    public SDLogger()
    {
      sdPS = new PersistentStorage("SD");
      sdPS.MountFileSystem();
      Thread.Sleep(2000);
    }

    public void Write(string Message)
    {
      Thread logWriteThread = new Thread(delegate() { WriteToLog(Message); });
      logWriteThread.Start();
    }

    public ArrayList Read()
    {
      Thread logReadThread = new Thread(delegate() { ReadFromLog(); });
      logReadThread.Start();
      while (logReadThread.ThreadState == ThreadState.Running) { }
      return LogItems;
    }

    public void Delete()
    {
      Thread logDeleteThread = new Thread(delegate() { DeleteLogFile(); });
      logDeleteThread.Start();
    }

    public void EjectSD()
    {
      Thread logEjectThread = new Thread(delegate() { EjectSDCard(); });
      logEjectThread.Start();
    }

    private void WriteToLog(string Message)
    {
      WriteLogLine(Helpers.date() + Message + ";");
    }

    private void WriteLogLine(string LogLine)
    {
      using (StreamWriter sw = new StreamWriter(LogPath, true))
      {
        if (sw == null)
        {
          Debug.Print("can't find log file");
        }
        else
        {
          try
          {
            sw.WriteLine(LogLine);
          }
          catch (Exception ex)
          {
            Debug.Print(ex.Message);
          }
        }
      }
    }   
    
    private ArrayList ReadFromLog()
    {
      if (File.Exists(LogPath))
      {
        LogItems = new ArrayList();
        using (StreamReader SR = new StreamReader(LogPath))
        {
          string Line;
          while ((Line = SR.ReadLine()) != string.Empty)
          {
            LogItems.Add(Line);
          }
        }
      }
      return LogItems;
    }

    private void DeleteLogFile()
    {
      try
      {
        if (File.Exists(LogPath))
        {
          File.Delete(LogPath);
        }
      }
      catch (Exception e)
      {
        Debug.Print(e.ToString());
      }
    }

    private void EjectSDCard()
    {
      sdPS.UnmountFileSystem();
      sdPS.Dispose();
    }
  }

Usage example using Fez Cobra:


using System;
using System.Threading;
using System.Collections;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

using GHIElectronics.NETMF.FEZ;

  public class Program
  {

    static int x = 0;
    static InterruptPort up;
    static InterruptPort down;
    static InterruptPort select;
    static SDLogger log = new SDLogger();

    public static DateTime when { get; set; }

    public static void Main()
    {
      Cpu.GlitchFilterTime = new TimeSpan(0, 0, 0, 0, 900);

      up = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.ButtonUp, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
      down = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.ButtonDown, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
      select = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.ButtonSelect, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);

      //Up button writes 1 line to logfile
      up.OnInterrupt += new NativeEventHandler(up_OnInterrupt);
      //Down button reads all lines from logfile
      down.OnInterrupt += new NativeEventHandler(down_OnInterrupt);
      //Select button deletes the logfile
      select.OnInterrupt += new NativeEventHandler(select_OnInterrupt);

      Thread.Sleep(Timeout.Infinite);
    }

    static void select_OnInterrupt(uint data1, uint data2, DateTime time)
    {
      deletefile();
    }

    static void down_OnInterrupt(uint data1, uint data2, DateTime time)
    {
      readdata();
    }

    static void up_OnInterrupt(uint data1, uint data2, DateTime time)
    {
      writedata();      
    }

    private static void writedata()
    {
      log.Write("test:" + x++.ToString());
    }

    private static void readdata()
    {
      ArrayList x = log.Read();
      Debug.Print("--Array Start log items--");
      foreach (string y in x)
      {
        Debug.Print(y);
      }
      Debug.Print("--Array End log items--");
    }

    private static void deletefile()
    {
      log.Delete();
    }
  }



#2

This is probably a stupid question but I’m not a C# expert :slight_smile:
It seems to me that the class methods all run in new threads, why is this?


#3

I don’t want the logger to block any other code running in the Cobra.


#4

I don’t know the Cobra so it probably makes sense
I’m just trying to learn :slight_smile:

So, on the Cobra system you can have several applications running, I didnt know that. But wouldnt your log application run in its own thread and thereby not block other code running on the Cobra?

On the select interrupt function you use a deletefile(); function that just contains the Log.Delete() method. Why not just invoke the method directly?


#5

Starting a new thread for each operation of the logger is not a good way of doing it. It takes time and resources. I would rather have one thread and use synchronization objects.


#6

Architect,

can you point me to an example that uses synchronization objects?


#7

Look for AutoResetEvent and ManualResetEvent objects.
Use separate event object for each operation (Read,Write,etc.)
Wait for any of these events in the logger thread using WaitHandle.WaitAny.

WaitAny will return index of the event,that way you can distinguish what operation was requested. You can have another event object to signal the main thread when read operation is completed.

Let me know if you need more information.


#8

Yes please, this is new stuff for me to learn.


#9

Eric,

I can put a small example later tonight if you can wait.


#10

I can wait :wink:


#11

This kind of think is perfect for a blocking queue. A blocking queue codifies the consumer/producer pattern. A blocking queue (don’t let the name fool you), will block on empty for reader(s) and optionally block on some max number for producer thread(s). So it is perfect for a logger. I updated fezzer http://www.fezzer.com/project/107/blocking-queue/. You can grab BlockingQueue class there as this sample logger. Add your file write. Logger uses a single thread to grab new items from the queue and write. Note how simple the logic becomes because most the redundant grunt work and thread safety is done in the BlockingQueue class.

using System.Threading; 
using MF.Collections; 
using Microsoft.SPOT; 
  
namespace MyApp 
{ 
    public class Logger 
    { 
        private BlockingQueue q; 
  
        public Logger(int maxItems=1000) 
        { 
            q = new BlockingQueue(maxItems); 
            new Thread(Writer).Start(); 
        } 
  
        public void Write(string logEntry) 
        { 
            q.Add(logEntry); 
        } 
  
        private void Writer() 
        { 
            while (!q.IsCompleted) 
            { 
                string item = q.Take() as string; 
                Debug.Print(item); // And/or write to file here. 
            } 
            Debug.Print("Logger writer thread exit."); 
        } 
  
        public bool IsOpen 
        { 
            get
            { 
                return !q.IsAddingCompleted; 
            } 
        } 
  
        public void Close() 
        { 
            // Set q to complete. Allow it to drain, but no more items can be added. 
            q.CompleteAdding(); 
        } 
    } 
} 
  
// Test Logger. 
public static void TestLogger() 
{ 
    Logger l = new Logger(); 
    for (int i = 0; i < 50; i++) 
    { 
        l.Write("Logging" + i.ToString()); 
    } 
    l.Close(); // Let drain. 
}

#12

Eric,

Something like this:


using System;
using Microsoft.SPOT;
using GHIElectronics.NETMF.IO;
using System.Collections;
using System.Threading;
using System.IO;

namespace LoggerExample
{
    public class SDLogger
    {
        private string LogPath = @ "\SD\log.txt";
        private PersistentStorage sdPS;
        private ArrayList _logItems;
        private string _writeString;

        private AutoResetEvent _readEvent;
        private AutoResetEvent _writeEvent;
        private AutoResetEvent _deleteEvent;

        private AutoResetEvent _readCompleteEvent;

        Thread _loggerThread;

        public SDLogger()
        {
            sdPS = new PersistentStorage("SD");
            sdPS.MountFileSystem();
            Thread.Sleep(2000);

            _readEvent = new AutoResetEvent(false);
            _writeEvent = new AutoResetEvent(false);
            _deleteEvent = new AutoResetEvent(false);

            _readCompleteEvent = new AutoResetEvent(false);
            StartLoggerThread();
        }

        private void StartLoggerThread()
        {
            _loggerThread = new Thread(new ThreadStart(this.ThreadFunc));
        }

        private void ThreadFunc()
        {
            WaitHandle[] events = new WaitHandle[] { _readEvent, _writeEvent, _deleteEvent };

            while (true)
            {
                int eventIndex = WaitHandle.WaitAny(events);

                switch (eventIndex)
                {
                    case 0:
                        ReadFromLog();
                        _readCompleteEvent.Set();
                        break;
                    case 1:
                        WriteToLog(_writeString);
                        break;
                    case 2:
                        DeleteLogFile();
                        break;

                }
            }
        }

        public void Write(string Message)
        {
            lock (this)
            {
                _writeString = Message;
                _writeEvent.Set();
            }
        }

        public ArrayList Read()
        {
            lock( this )
            {
                _readEvent.Set();
                _readCompleteEvent.WaitOne();
                return _logItems;
            }
        }

        public void Delete()
        {
            lock(this)
            {
                _deleteEvent.Set();
            }
        }

        public void EjectSD()
        {
            Thread logEjectThread = new Thread(delegate() { EjectSDCard(); });
            logEjectThread.Start();
        }

        private void WriteToLog(string Message)
        {
            WriteLogLine(Helpers.date() + Message + ";");
        }

        private void WriteLogLine(string LogLine)
        {
            using (StreamWriter sw = new StreamWriter(LogPath, true))
            {
                if (sw == null)
                {
                    Debug.Print("can't find log file");
                }
                else
                {
                    try
                    {
                        sw.WriteLine(LogLine);
                    }
                    catch (Exception ex)
                    {
                        Debug.Print(ex.Message);
                    }
                }
            }
        }

        private void ReadFromLog()
        {
            if( _logItems == null )
                _logItems = new ArrayList();

            _logItems.Clear();

            if (File.Exists(LogPath))
            {
                _logItems = new ArrayList();
                using (StreamReader SR = new StreamReader(LogPath))
                {
                    string Line;
                    while ((Line = SR.ReadLine()) != string.Empty)
                    {
                        _logItems.Add(Line);
                    }
                }
            }
        }

        private void DeleteLogFile()
        {
            try
            {
                if (File.Exists(LogPath))
                {
                    File.Delete(LogPath);
                }
            }
            catch (Exception e)
            {
                Debug.Print(e.ToString());
            }
        }

        private void EjectSDCard()
        {
            sdPS.UnmountFileSystem();
            sdPS.Dispose();
        }
    }
}


I made it as close to your original code as possible. This should give you an idea what AutoResetEvent is and how to use it.

I haven’t tested it though, but it should work. Let me know if you need more help.

Cheers


#13

Architect,

thanks for the example. I stepped through the code and the write method seems to work, though can’t verify because the read method hangs on this line:


while( !_readCompleteEvent.WaitOne() )

Because this is all new to me I can’t really figure out why it’s hanging.

Greetings


#14

Ooops! My bad.

It should be:


        public ArrayList Read()
        {
            lock( this )
            {
                _readEvent.Set();
                _readCompleteEvent.WaitOne();
                return _logItems;
            }
        }


#15

Sorry Architect, no joy…same result. It’s as if the cobra goes into an infinite loop. Resetting the cobra is the only way to bring it alive again.


#16
private void WriteToLog(string Message)
        {
            WriteLogLine(Helpers.date() + Message + ";");
        }

What is the References to Helpers.date() in this method?


#17

Sam,

No magic, just formatting the date in the format i want.


  class Helpers
  {

    public static string date()
    {
      return DateTime.Now.ToString("dd-MM-yy hh:mm:ss") + ",";
    }
  }


#18

That’s why I could not find it in the “References” list! :slight_smile:

Thanks, Eric.


#19

Hmmm…

I will test it on my ChipworkX.


#20

I think the lock code might be biting you since you’re calling WaitOne with it held.

It looks like you might also be losing messages if writes are being done faster than the thread can process them. (edit: you could use a queue or something to store up pending writes but even then, if the caller is hammering writes into the system, at some point the code will have to block the caller, lose messages or run out of memory.)