Convert Legacy to OOP. Logging. Open/Closed principle.

I'm trying to convert some Legacy code to OOP, and I'm wondering how to deal with Logging.

I have a Main routine, which establishes a Log file.

Then I have some classes to instantiate, and code in those classes would like to write to the Log file.

The two choices I see are:
1. Pass something about the log file to the classes as a parameter
2. Have a global somewhere which everyone tries to write to

The first option leads to endlessly passing a log file parameter around.

The second option makes the code dependent on external environment. (The code is no longer encapsulated, loosely coupled.)

What's the third option I'm missing?

To further complicate the issue, I'm trying to merge 8 nearly identical but subtly different copies of the code (yes over time they made 8 different versions). My idea is to move the duplicate parts into a common Library.

I've examined other projects. I see one which uses a static Logger class (all the methods are static). It looks like I might be able to reuse it.

The other thing I would like is to print log messages both to the console and also to the log file, so somewhere I need a tee function. Where would such a function belong? The current Logging module I'm looking at appears to use the Strategy pattern, so you can log to a file, OR to the console, but not both.

How would I convert this to have the option to send log messages to more than one place, without making a bunch of modifications to existing code (the Open/Closed principle)?
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Depending on the class you wind up with, the simplest change to the Strategy (T) pattern would be to parameterize the destination choice(s).

If you create a factory for your log class, your code can request a logging object and receive the current log that was created by the application's initialization code.  You won't have to pass a log object as a parameter.
Ray PaseurCommented:
I'm thinking about the intent of the logging here.

Are you logging to create a permanent record of data and events, or logging to provide insight into program behavior (without the need for the log, except in program development)?

Factory patterns suffer from the same kinds of issues as Global or Singleton - you can't readily mock them for testing.  But if your needs for the logger do not extend beyond the development cycle, a configuration variable could tell the logger where to send its information.  Being unable to test the logger seems like a fairly small price to pay when compared to the debugging value of the logs.

If the logs are permanent records, then the logger must exist at all times.  In this case, a log-analysis tool might be helpful.  Something that could answer the question, "What was logged in the last 3 minutes?" or something like that.

HTH, ~Ray
David L. HansenProgrammer AnalystCommented:
I'm approaching this from Ray's second scenario (the permanent record situation).

I sometimes fall into the trap of thinking that a function (small or large) should expect to receive small bits of data as parameters (like the location of the log file). However (IMHO) the most elegant and powerful solutions seem to have small functions which receive large objects as parameters (like a calendar being passed into a function which checks for a specific appointment as opposed to passing a bunch of single appointments into it).

If you have a logging class (let's say it's a singleton) which assumes the responsibility of all things dealing with logging, then you can build the following. A function that receives that logging-singleton as an input for its first parameter, and a destination flag for its second. This destination flag (perhaps an integer) would represent one of three states: the console, the log-file, or both the console and the log-file.

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Ray PaseurCommented:
Further to David's point about passing objects as parameters (an excellent design idea), this article might be interesting.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic.NET

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.