This I have written as part of my LLD Practice. Please let me know further imporvements that can be done.
Also current design is Strong/Average/Lean Hire
/*
Requirements -
1. Support multiple log levels – e.g., Debug, Info, Warn, Error, with priority control.
2. Flexible output destinations – logs can be sent to console, files, databases, or other custom targets.
3. Pluggable formatters – support different formats such as plain text, JSON, XML, etc.
4. Configurable routing – control which log levels go to which appenders.
5. Priority-based filtering with default fallback – ignore messages below the configured level and ensure a default appender always handles logs.
*/
import sun.rmi.runtime.Log;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
enum LogLevel{
Debug(1), Info(2), Warn(3), Error(4);
int value;
LogLevel(int val){
this.value=val;
}
}
class LogMessage{
LogLevel logLevel;
String message;
String source;
LocalDateTime timeStamp;
}
interface LogFormatter{
String format(LogMessage logMessage);
}
class TextFormatter implements LogFormatter{
@Override
public String format(LogMessage logMessage){
}
}
interface LogAppender{
void append(LogMessage logMessage);
}
class ConsoleAppender implements LogAppender{
LogFormatter logFormatter;
ConsoleAppender(LogFormatter formatter){
this.logFormatter=formatter;
}
@Override
public void append(LogMessage logMessage){
System.out.println(logFormatter.format(logMessage));
}
}
class FileAppender implements LogAppender{
LogFormatter logFormatter;
String path;
FileAppender(LogFormatter formatter, String path){
this.logFormatter=formatter;
this.path=path;
}
@Override
public void append(LogMessage logMessage){
System.out.println(logFormatter.format(logMessage));
}
}
class LoggerConfig{
LogLevel logLevel;
Map<LogLevel,List<LogAppender>> appenderMap=new HashMap<>();
LogAppender defaultAppender;
LoggerConfig(LogLevel minLevel, LogAppender defaultAppender){
this.logLevel=minLevel;
this.defaultAppender=defaultAppender;
}
void setLogLevel(LogLevel minLevel){
this.logLevel=minLevel;
}
void addLogAppender(LogLevel level, LogAppender logAppender){
appenderMap.get(level).add(logAppender);
}
List<LogAppender> getLogAppender(LogLevel level){
return appenderMap.getOrDefault(level, Arrays.asList(defaultAppender));
}
}
class Logger{
LoggerConfig config;
Logger(LoggerConfig config){
this.config=config;
}
void log(LogLevel logLevel, String message){
if(logLevel.value<config.logLevel.value) return;
LogMessage logMessage=new LogMessage();
for(LogAppender app:config.getLogAppender(logLevel)){
app.append(logMessage);
}
}
void debug(String message){
log(LogLevel.Debug,message);
}
void info(String message){
log(LogLevel.Info,message);
}
void warn(String message){
log(LogLevel.Warn,message);
}
void error(String message){
log(LogLevel.Error,message);
}
}
public class LoggingFramework {
public static void main(String[] args) {
LoggerConfig config=new LoggerConfig(LogLevel.Info,new ConsoleAppender(new TextFormatter()));
config.addLogAppender(LogLevel.Info, new ConsoleAppender(new TextFormatter()));
config.addLogAppender(LogLevel.Error, new ConsoleAppender(new TextFormatter()));
config.addLogAppender(LogLevel.Error, new FileAppender(new TextFormatter(),"error.txt"));
Logger logger=new Logger(config);
logger.debug("Debugg"); //ignored
logger.info("Info"); // console
logger.warn("Warning"); // default -> console
logger.error("Error"); // console+file
}
}