Designing a Logging Framework in Java


Requirements:
Design componenets:
Code Implementation:
// Enum representing log levels
enum LogLevel {
    DEBUG, INFO, WARNING, ERROR, FATAL
}
// Class representing a log message
class LogMessage {
    private LocalDateTime timestamp;
    private LogLevel level;
    private String message;

    public LogMessage(LogLevel level, String message) {
        this.timestamp = LocalDateTime.now();
        this.level = level;
        this.message = message;
    }

    @Override
    public String toString() {
        return "[" + timestamp + "] [" + level + "] " + message;
    }
}
// Interface for log appenders
interface LogAppender {
    void append(LogMessage message);
}
// Console Appender
class ConsoleAppender implements LogAppender {
    @Override
    public void append(LogMessage message) {
        System.out.println(message.toString());
    }
}
// File Appender
class FileAppender implements LogAppender {
    private String filePath;

    public FileAppender(String filePath) {
        this.filePath = filePath;
    }

    @Override
    public void append(LogMessage message) {
        try (FileWriter writer = new FileWriter(filePath, true)) {
            writer.write(message.toString() + "\n");
        } catch (IOException e) {
            System.err.println("Failed to write to log file: " + e.getMessage());
        }
    }
}
// Database Appender
class DatabaseAppender implements LogAppender {
    private String connectionString;
    private String insertQuery = "INSERT INTO logs (timestamp, level, message) VALUES (?, ?, ?)";

    public DatabaseAppender(String connectionString) {
        this.connectionString = connectionString;
    }

    @Override
    public void append(LogMessage message) {
        try (Connection conn = DriverManager.getConnection(connectionString);
             PreparedStatement stmt = conn.prepareStatement(insertQuery)) {
            stmt.setString(1, message.toString());
            stmt.executeUpdate();
        } catch (Exception e) {
            System.err.println("Failed to write to database: " + e.getMessage());
        }
    }
}
// Logger Configuration
class LoggerConfig {
    private LogLevel logLevel;
    private LogAppender appender;

    public LoggerConfig(LogLevel logLevel, LogAppender appender) {
        this.logLevel = logLevel;
        this.appender = appender;
    }

    public LogLevel getLogLevel() {
        return logLevel;
    }

    public LogAppender getAppender() {
        return appender;
    }
}
// Logger Singleton
class Logger {
    private static Logger instance;
    private LoggerConfig config;
    private final Lock lock = new ReentrantLock();

    private Logger() {}

    public static Logger getInstance() {
        if (instance == null) {
            synchronized (Logger.class) {
                if (instance == null) {
                    instance = new Logger();
                }
            }
        }
        return instance;
    }

    public void setConfig(LoggerConfig config) {
        lock.lock();
        try {
            this.config = config;
        } finally {
            lock.unlock();
        }
    }

    private void log(LogLevel level, String message) {
        lock.lock();
        try {
            if (config != null && level.ordinal() >= config.getLogLevel().ordinal()) {
                LogMessage logMessage = new LogMessage(level, message);
                config.getAppender().append(logMessage);
            }
        } finally {
            lock.unlock();
        }
    }

    public void debug(String message) { log(LogLevel.DEBUG, message); }
    public void info(String message) { log(LogLevel.INFO, message); }
    public void warning(String message) { log(LogLevel.WARNING, message); }
    public void error(String message) { log(LogLevel.ERROR, message); }
    public void fatal(String message) { log(LogLevel.FATAL, message); }
}
// Example Usage
public class LoggingExample {
    public static void main(String[] args) {
        Logger logger = Logger.getInstance();

        // Set Console Appender
        logger.setConfig(new LoggerConfig(LogLevel.INFO, new ConsoleAppender()));

        // Log messages
        logger.debug("This is a DEBUG message");
        logger.info("This is an INFO message");
        logger.error("This is an ERROR message");

        // Change to File Appender
        logger.setConfig(new LoggerConfig(LogLevel.DEBUG, new FileAppender("logs.txt")));
        logger.warning("This is a WARNING message");
        logger.fatal("This is a FATAL message");
    }
}

Input:

Output:


Explanation of the Code:
Design Considerations:
Improvements & Extensions: