SRP Violation – Is Logging Next to Implementation a Violation?

decoratordesign-patternsloggingsingle-responsibility

When thinking of agile software development and all the principles (SRP, OCP, …) I ask myself how to treat logging.

Is logging next to an implementation a SRP violation?

I would say yes because the implementation should be also able to run without logging. So how can I implement logging in a better way? I've checked some patterns and came to a conclusion that the best way not to violate the principles in a user-defined way, but to use any pattern which is known to violate a principle is to use a decorator pattern.

Let's say we have a bunch of components completely without SRP violation and then we want to add logging.

  • component A
  • component B uses A

We want logging for A, so we create another component D decorated with A both implementing an interface I.

  • interface I
  • component L (logging component of the system)
  • component A implements I
  • component D implements I, decorates/uses A, uses L for logging
  • component B uses an I

Advantages:
– I can use A without logging
– testing A means I don't need any logging mocks
– tests are simpler

Disadvantage:
– more components and more tests

I know this seem to be another open discussion question, but I actually want to know if someone uses better logging strategies than a decorator or SRP violation. What about static singleton logger which are as default NullLogger and if syslog-logging is wanted, one change the implementation object at runtime?

Best Answer

Yes it is a violation of SRP as logging is a cross cutting concern.

The correct way is to delegate logging to a logger class (Interception) which sole purpose is to log - abiding by the SRP.

See this link for a good example: https://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

Here is a short example:

public interface ITenantStore
{
    Tenant GetTenant(string tenant);
    void SaveTenant(Tenant tenant);
}

public class TenantStore : ITenantStore
{
    public Tenant GetTenant(string tenant)
    {....}

    public void SaveTenant(Tenant tenant)
    {....}
} 

public class TenantStoreLogger : ITenantStore
{
    private readonly ILogger _logger; //dep inj
    private readonly ITenantStore _tenantStore;

    public TenantStoreLogger(ITenantStore tenantStore)
    {
        _tenantStore = tenantStore;
    }

    public Tenant GetTenant(string tenant)
    {
        _logger.Log("reading tenant " + tenant.id);
        return _tenantStore.GetTenant(tenant);
    }

    public void SaveTenant(Tenant tenant)
    {
        _tenantStore.SaveTenant(tenant);
        _logger.Log("saving tenant " + tenant.id);
    }
}

Benefits include

  • You can test this without logging - true unit testing
  • you can easily toggle logging on / off - even at runtime
  • you can substitute logging for other forms of logging, without ever having to change the TenantStore file.
Related Topic