3
\$\begingroup\$

Interface for logger:

 public interface ILogger
        {
            void Log(Exception ex, string contextualMessage = "");

            void Log(string message);
        }

Interface for web job logger:

public interface IJobLogger : ILogger
{
    void SetWriter(TextWriter logWriter);
}

Web job logger:

 public class WebJobLogger : IJobLogger
    {
        private TextWriter writer;

        public WebJobLogger()
        {
            writer = TextWriter.Null;
        }

        public void SetWriter(TextWriter logWriter)
        {
            writer = logWriter;
        }

        public void Log(Exception ex, string contextualMessage = "")
        {
            writer.WriteLine("Logger: Exception thrown with message {0}, exception : {1}", contextualMessage, ex);
        }

        public void Log(string message)
        {
            writer.WriteLine("Logger: {0}", message);
        }
    }

Usage:

public async Task TestJob(
    [QueueTrigger(QueueName.StorageExcelImport)] StartImportCommand blobInfo,
    [Blob(Global.BlobContainerName + BlobReferenceName, FileAccess.Read)] Stream input,
    TextWriter logWriter)
{
    await logWriter.WriteLineAsync("Start");
    try
    {
        //Implemetation
    }
    catch (Exception ex)
    {
        //Set writer
        log.SetWriter(logWriter);
        //Log exception
        log.Log(ex);
    }
    await logWriter.WriteLineAsync(string.Format("File: '{0}' successfuly processed from blob to the database. ({1})", blobInfo.OriginalFileName, DateTime.UtcNow));
}

Dependency injection:

container.Register<ILogger, WebJobLogger>();

The problem with this is that I always have to call log.SetWriter(logWriter); before call log.Log(ex);. If I forget that, then will not work. The whole point of this is to mock logger in unit test. What do you think?

\$\endgroup\$
4
  • \$\begingroup\$ "Whole point of this is to mock logger in unit test" Could you explain that a little, please? \$\endgroup\$ Commented Jul 28, 2015 at 11:42
  • \$\begingroup\$ I want to write unit test for TestJob method. I can't do that if method is static. Thats why I create non-static methot in web job and I inject everything, including logger in constructor. With this solution I can mock logger in unit test. \$\endgroup\$ Commented Jul 28, 2015 at 11:48
  • \$\begingroup\$ Rather than using a TextWriter, you may want to look into TraceSource and TextWriterTraceListener. \$\endgroup\$
    – Dan Lyons
    Commented Jul 28, 2015 at 17:45
  • \$\begingroup\$ Should not ILogger have async methods, and TestJob have ILogger as the third parameter rather than TextWriter? Then you just mock the ILogger passed in. \$\endgroup\$
    – hocho
    Commented Jul 28, 2015 at 22:42

1 Answer 1

1
\$\begingroup\$
void Log(Exception ex, string contextualMessage = "");

This isn't bad, but seems perhaps a bit redundant, since exceptions already have messages. A more idiomatic option might be to wrap the exception in another, with the custom message you want. However, if a "contextual message" conceptually serves a different purpose than the message on an exception, what you have may be fine.

void SetWriter(TextWriter logWriter);

You're right that this is problematic. What it means is that your class can be in a "not fully initialized" state. Like you mentioned, that means you have to remember to initialize it. It also means that if you ever pass it around, you can't rely on your configuration staying the same- some other code might change the logWriter without you realizing.

There's a pretty common solution to this- just pass it into the constructor. That way a WebJobLogger always has one TextWriter for its entire lifetime, which won't be changed. I'm not immediately sure which IoC container you're using, but any mainstream one will have support for constructors with parameters- you just need to make sure the dependencies are bound too.

await logWriter.WriteLineAsync("Start");

Shouldn't you be using the ILogger.Log method for this too? It's confusing to access the underlying writer from the same different place at two different abstraction levels.

\$\endgroup\$
2
  • \$\begingroup\$ How to inject TextWriter to constructor? How to register this? I am using Simple injector. \$\endgroup\$ Commented Jul 28, 2015 at 12:24
  • \$\begingroup\$ @merso0027 Sorry, I don't know that one, but I'm sure it's doable. Usually for IOC containers, if you try to get an instance of A and A's constructor needs an instance of B, and your container has both A and B registered, it will automatically create a B then inject it into the A to create it. \$\endgroup\$ Commented Jul 28, 2015 at 13:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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