Features Required
Logging Levels:
Logging should support multiple levels likeDEBUG
,INFO
,WARN
,ERROR
, andFATAL
.Helps filter messages based on severity.
Ensures flexibility to control verbosity.
High Performance:
- Must handle 1 million concurrent logs per second, making asynchronous logging and buffering mandatory.
Appenders:
ConsoleAppender: Logs to the console.
FileAppender: Logs to a file.
RollingFileAppender: Supports log rotation based on size or time.
Thread-Safety:
Logs should not get mixed up in a multi-threaded environment.Asynchronous Logging:
Use a queue to buffer logs.
Worker threads consume from the queue for actual writing.
Formatter/Layouts:
Support customizable message formats like:
[Timestamp] [LogLevel] [Thread] - Message
Configuration:
Provide configuration via a JSON file.
Dynamically update logging behavior during runtime.
Filters:
Allow conditional logging (e.g., only log messages containing specific keywords).
Design Patterns Involved
Singleton Pattern:
Ensure only one instance of the
LogManager
exists throughout the application.Centralized logger configuration and management.
Factory Pattern:
- Dynamically create different types of
Appender
(e.g.,ConsoleAppender
,FileAppender
).
- Dynamically create different types of
Observer Pattern:
- Allow
Appenders
to observe log events fromLogger
.
- Allow
Builder Pattern:
- Configure the
Logger
and itsAppenders
in a flexible and readable way.
- Configure the
Multiple Algorithms Involved
Covered in Course
Solution (Java)
public enum LogLevel {
DEBUG, INFO, WARN, ERROR, FATAL;
}
import java.util.ArrayList;
import java.util.List;
public class Logger {
private String name;
private LogLevel level;
private List<Appender> appenders = new ArrayList<>();
public Logger(String name, LogLevel level) {
this.name = name;
this.level = level;
}
public void log(LogLevel level, String message) {
if (level.ordinal() >= this.level.ordinal()) {
for (Appender appender : appenders) {
appender.append(formatMessage(level, message));
}
}
}
public void debug(String message) {
log(LogLevel.DEBUG, message);
}
public void info(String message) {
log(LogLevel.INFO, message);
}
public void warn(String message) {
log(LogLevel.WARN, message);
}
public void error(String message) {
log(LogLevel.ERROR, message);
}
public void fatal(String message) {
log(LogLevel.FATAL, message);
}
private String formatMessage(LogLevel level, String message) {
return String.format("[%s] [%s] [%s] %s",
new java.util.Date(), level, Thread.currentThread().getName(), message);
}
public void addAppender(Appender appender) {
appenders.add(appender);
}
}
public interface Appender {
void append(String message);
}
public class ConsoleAppender implements Appender {
@Override
public void append(String message) {
System.out.println(message);
}
}
import java.io.FileWriter;
import java.io.IOException;
public class FileAppender implements Appender {
private FileWriter writer;
public FileAppender(String filePath) throws IOException {
this.writer = new FileWriter(filePath, true);
}
@Override
public void append(String message) {
try {
writer.write(message + "\n");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.util.HashMap;
import java.util.Map;
public class LogManager {
private static LogManager instance;
private Map<String, Logger> loggers = new HashMap<>();
private LogManager() {}
public static synchronized LogManager getInstance() {
if (instance == null) {
instance = new LogManager();
}
return instance;
}
public Logger getLogger(String name) {
return loggers.computeIfAbsent(name, key -> new Logger(name, LogLevel.INFO));
}
}
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class AsyncAppender implements Appender {
// will be covered in our course with video explanation
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.List;
public class ConfigLoader {
// will be covered in our course with video explanation
}
public class AppenderFactory {
public static Appender createAppender(AppenderConfig config) {
switch (config.getType()) {
case "ConsoleAppender":
return new ConsoleAppender();
case "FileAppender":
try {
return new FileAppender(config.getFilePath());
} catch (IOException e) {
e.printStackTrace();
}
default:
throw new IllegalArgumentException("Unknown Appender type: " + config.getType());
}
}
}
public class Main {
public static void main(String[] args) {
LogManager logManager = LogManager.getInstance();
Logger logger = logManager.getLogger("MainLogger");
logger.addAppender(new ConsoleAppender());
try {
logger.addAppender(new FileAppender("application.log"));
} catch (IOException e) {
e.printStackTrace();
}
logger.info("This is an info message");
logger.error("This is an error message");
}
}