2

Background

I often write software for systems that are responsible for testing manufactured products. For every product that gets tested, we have to generate a report for the test-results. The way that we do it now is that we create a .txt file with the test-results and write that file into some folder on our network drive. Ultimately, I am looking to move toward a database solution such as SQL.

Since I know that we will eventually be moving towards some kind of database solution, I would like to segregate the current functionality (writing the .txt file to the network drive) by an interface. Eventually I will be able to write a library to control the logging of the test-results to some database. When that happens I would like to simply implement the same interface that the current test-result logging solution will use.

As for the interface, I was thinking to have some simple methods that I know I'll need such as:

LogResults(string operator, string product, string serialNumber, string Results)
Connect(string pathToDriveLocation)

The only thing is, I've never done a database before and I'm not sure if I can adapt the two different ways of performing the logging to the same interface.

Question

Am I thinking about this correctly? Does this seem like a sound approach, or does it need some work?

  • Yes, your are in the right way. However, there are libs for logging that already do what you are planning to do. What's your programming language? – Laiv Oct 6 '16 at 17:30
  • @Laiv Visual C# for Windows/.NET. – Snoop Oct 6 '16 at 18:08
  • 1
    Then I guess you know log4net. I have used its Java version almost in every project I have worked on. Take a look to post liks this Log4net. What you are trying to do is an Appender (in terms of log4net). – Laiv Oct 6 '16 at 18:50
  • @Laiv that's for error logging, I've used Log4net extensively. I even have my own custom version that I modified and we build every day into some of our stuff. Thank you though. – Snoop Oct 6 '16 at 18:52
  • 1
    For error or for whatever you need. The basic Appender may implement a datamodem that does not met your needs. But you can customize it as you pointed – Laiv Oct 6 '16 at 18:54
0

I'd like to take @TulainsCordova's answer one step further.

First, define the interface to be the minimal behavior required to perform logging (I'll use C# as an example):

public interface ILogger
{
    void LogResults(string operatorName, string product, string serialNumber, string Results);
}

That's all you need is a way to log stuff. Anything more and your interface gets polluted with implementation details. Any information that is implementation-specific should be handled as a constructor argument to the concrete class that implements this interface. For instance, TextFileLogger:

public class TextFileLogger : ILogger
{
    public TextFileLogger(string fileName)
    {
        this.fileName = fileName;
    }

    private string fileName;

    public void LogResults(string operatorName, string product, string serialNumber, string Results)
    {
        string message = "...";

        File.WriteAllText(fileName, message, Encoding.UTF8);
    }
}

Later on you decide to log to a database, so you can add another class:

public class SqlServerDatabaseLogger : ILogger
{
    public SqlServerDatabaseLogger(string connectionString)
    {
        this.connectionString = connectionString;
    }

    public void LogResults(string operatorName, string product, string serialNumber, string Results)
    {
        string message = "...";

        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            // INSERT into database
        }
    }
}

In the rest of your application can be decoupled from the kind of logger by utilizing a factory class (and accompanying enum for the logger type):

public enum LoggerType
{
    TextFile = 0,
    SqlServer = 1
}

public class LoggerFactory
{
    public LoggerFactory(LoggerType loggerType)
    {
        this.loggerType = loggerType;
    }

    private ILogger logger;
    private LoggerType loggerType;

    public ILogger Logger
    {
        get
        {
            if (logger == null)
                logger = CreateLogger(loggerType);

            return logger;
        }
    }

    private static ILogger CreateLogger(LoggerType loggerType)
    {
        switch (loggerType)
        {
            case LoggerType.TextFile:
                return new TextFileLogger("path/to/filename");
            case LoggerType.SqlServer:
                return new SqlServerDatabaseLogger("connection string");
            default:
                throw new ArgumentOutOfRangeException("loggerType");
        }
    }
}

So create the logger factory once:

LoggerFactory factory = new LoggerFactory(LoggerType.TextFile);
ILogger logger = factory.Logger;

Pass the logger or factory around to any number of other classes. When switching to a new logging type, just change a single line of code:

LoggerFactory factory = new LoggerFactory(LoggerType.SqlServer);

And you're done.

  • Something like this is exactly what I was thinking. Thank you for pointing out how some of the implementation detail should be passed as constructor arguments, that's useful as well. I probably would have passed that right in through a property sitting on the interface, probably a little cleaner your way. – Snoop Oct 6 '16 at 18:06
  • Wouldn't the logger-factory need to have references to multiple concrete classes in order to achieve this? I think I can pull off the same thing by just instantiating at the entry point of the application, and then using constructor injection to anyone who needs the logger... Right? – Snoop Oct 7 '16 at 20:22
0

Your approach is good indeed. There should be no conceptual difference (interface-wise) between logging to a text file, to a database table or to a printer (or screen).

In the connect method I suggest you change signature to something more neutral, like:

openDevice(String deviceString);

A devide string cuold be a path to a file...

connect("/var/logs/log001.txt");

or a database connection string...

connect("user/password@10.72.2.10:1021/database");

In the case of file logs you simply validate whether the file exists and you have permission.

You can choose between adding a "throws CouldNotOpenDeviceException" or returning an error code.

void openDevice(String deviceString) throws CouldNotOpenDeviceException;

vs

int openDevice(String deviceString);
  • This is pretty good, could work for both a text-logger and a database probably just fine. – Snoop Oct 6 '16 at 18:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.