Joris Mathijssen
Joris Mathijssen

Reputation: 669

Preventing multiple switch statements

I'm currently writing a custom logging method for my Web API where users can purchase items. This log method will log all the steps the users takes while following a purchase process so support can later track the steps. There are multiple steps like:

This method will return one 'Event' object where all the necessary log information is combined, for example the LogLevel, Message, UserId and more, and write this to a database.

Before i can reach this point, i have to create the very useful log message. The message is based on these two enums (explanation is a bit simplified):

  1. ActionName - At which step in my process is this log event called
  2. ActionOrigin - Is the recorded log event from my front end or backend system...

It is also based on a object where the necessary log values, like order id for example, are provided.

The log class where the log event method is defined is a scoped injected class so i can log events every where critical in my code.

The first thing that came into my mind was creating a switch statement and create the messages based on the correct case. But this would combine 2 switch statements and quickly started to look like a mess.

I did some research and found the strategy pattern. I'm not completely sure if this can help me? Are there any other ideas or examples?

Upvotes: 0

Views: 130

Answers (3)

John Wu
John Wu

Reputation: 52290

Whenever you are working on an object model and find yourself writing a ton of switch statements, it usually means you've put the class-specific logic in the wrong place. You should put it with the class itself, not the class that consumes it.

To put it another way, your logger should not know how to log each and every type of event. That would be a maintenance nightmare. Instead, it should know how to log a common object (e.g. a string), and each event should itself know how to create that common object, via a common method that the logger knows about. That is the only thing it needs to know.

Here is a simple example. In this case, the logger accepts any type of LoggableEvent and calls its Serialize() method to figure out how it gets added to the common log. The event itself is responsible for knowing how to serialize itself.

abstract class LoggableEventBase
{
    public string ActionName { get; }

    public string ActionOrigin { get; }

    public LoggableEventBase(string actionName, string actionOrigin)
    {
        ActionName = actionName;
        ActionOrigin = actionOrigin;
    }

    public virtual string Serialize()
    {
        return string.Format("{0} {1}", ActionName, ActionOrigin);
    }
}

class CreateOrderEvent : LoggableEventBase
{
    protected readonly List<Item> _items;
    protected readonly int _orderId;

    public CreateOrderEvent(string origin, int orderID, List<Item> items) : base("CreateOrder", origin)
    {
        _orderId = orderID;
        _items = items;
    }

    public override string Serialize()
    {
        return base.Serialize() + string.Format(" {0} {1}", _orderId, string.Join(",", _items.Select(item => item.SKU)));
    }
}

Now the actual logging logic is rather simple-- no switch statements or anything else that needs to know what the event is:

class Logger : ILogger
{
    public void Log(LoggableEventBase eventToLog)
    {
        Write(eventToLog.Serialize());
    }

    protected virtual void Write(string message)
    {
        //Write the message to a log file
    }
}

To add additional event types, you just need to define the new class (and override Serialize()). You never have to go back and modify the Logger class. This is more consistent with the Open-Closed Principle than your existing solution.

Upvotes: 2

eriel marimon
eriel marimon

Reputation: 1380

This is a design pattern question. You might want to read on different patterns used for the language/framework you are using. It seems like you are trying to avoid writing your logs in line. One way of doing it would be to define the format for your different messages in a constant and use string interpolation (or simple concatenation) to build the message with a log() method.

Example (I'll do my best to write proper C#, please edit any mistakes or inadequacies):

class Logger {
    // If you want personalized messages for different actions or origins, define their template constants and create different methods for building them. 
    public const string ORDER_PROGRESS_MSG_TMPL = "Action:{0}, Origin:{1}, OrderId:{3}";
    void log_order_progress(string actionName, sting actionOrigin, string orderId){
        Console.WriteLine(
            ORDER_PROGRESS_MSG_TMPL, actionName, actionOrigin, orderId
        );
    }
}

Order

class Order {
    ...
    void create(int orederId){
        Logger.log_order_progress(ActionEnum.CREATING, OriginEnum.BACK_END, orderId)
        // Do some stuff here to create order
        Logger.log_order_progress(ActionEnum.UPDATING, OriginEnum.BACK_END, orderId)
        // etc
    }
}

This is a way of doing it, you could modularize it more by having templates in their own class. Also you could create (or better: use an existing logging framework) to differentiate level of logging (debug, info, error) as @Sandeep Sharma described.

Upvotes: 1

Sandeep Sharma
Sandeep Sharma

Reputation: 1

You can create multiple methods in your Logger class, each for specific scenario. The methods can be :

  • info() = for logging some information.
  • debug() = for debugging.
  • error() = for logging an error event.

Let's say you want to log an event of purchasing an item , and when user does buy action, you can pass information to the logger.info() method. If you encounter an error, or a certain action or condition was not fulfilled , you can pass data to the method error() , which will log error in your case. For messages : 1. Action Name - You can pass the method name or route path that was called by action of an user. 2. Action Origin - Provide details like user name , full path , action type etc. You can also maintain fields like 'timestamp' and some 'unique-identifier' for better logging of events.

Upvotes: 0

Related Questions