Design and Implementation of a Custom Java Logging Framework
This article walks through building a complete Java logging framework from scratch, covering the core LoggingEvent class, Appender abstraction, Level enumeration, hierarchical Logger design, context management, factory creation, and XML/YAML configuration parsing.
Java offers several logging frameworks (Log4j, Logback, etc.) with similar core structures; this article walks through building a custom logging framework from scratch.
LoggingEvent class encapsulates timestamp, thread information, logger name, level, and message.
public class LoggingEvent {
public long timestamp;
private Level level;
private Object message;
private String threadName;
private long threadId;
private String loggerName;
// getters and setters...
}Appender interface defines an append(LoggingEvent event) method, with a ConsoleAppender implementation that writes to standard output.
public interface Appender {
void append(LoggingEvent event);
} public class ConsoleAppender implements Appender {
private OutputStream out = System.out;
private OutputStream out_err = System.err;
@Override
public void append(LoggingEvent event) {
try {
out.write(event.toString().getBytes(encoding));
} catch (IOException e) {
e.printStackTrace();
}
}
}The Level enum defines ERROR, WARN, INFO, DEBUG, TRACE with integer values and utility methods.
public enum Level {
ERROR(40000, "ERROR"),
WARN(30000, "WARN"),
INFO(20000, "INFO"),
DEBUG(10000, "DEBUG"),
TRACE(5000, "TRACE");
private int levelInt;
private String levelStr;
Level(int i, String s) { levelInt = i; levelStr = s; }
public static Level parse(String level) { return valueOf(level.toUpperCase()); }
public int toInt() { return levelInt; }
public String toString() { return levelStr; }
public boolean isGreaterOrEqual(Level level) { return levelInt >= level.toInt(); }
}A Logger interface provides methods for trace, debug, info, warn, error, and a name getter. LogcLogger implements this interface, handling level filtering and delegating to an Appender.
public class LogcLogger implements Logger {
private String name;
private Appender appender;
private Level level = Level.TRACE; // default lowest
private int effectiveLevelInt;
// ... getters and setters ...
@Override
public void trace(String msg) { filterAndLog(Level.TRACE, msg); }
@Override
public void debug(String msg) { filterAndLog(Level.DEBUG, msg); }
@Override
public void info(String msg) { filterAndLog(Level.INFO, msg); }
@Override
public void warn(String msg) { filterAndLog(Level.WARN, msg); }
@Override
public void error(String msg) { filterAndLog(Level.ERROR, msg); }
private void filterAndLog(Level level, String msg) {
LoggingEvent e = new LoggingEvent(level, msg, getName());
if (level.toInt() >= effectiveLevelInt && appender != null) {
appender.append(e);
}
}
@Override
public String getName() { return name; }
}To support hierarchical loggers, each logger can have a parent; the filter method walks up the hierarchy until a suitable appender is found.
private LogcLogger parent;
...
for (LogcLogger l = this; l != null; l = l.parent) {
if (l.appender == null) continue;
if (level.toInt() >= l.effectiveLevelInt) {
l.appender.append(e);
}
break;
}LoggerContext stores the root logger and a cache of named loggers.
public class LoggerContext {
private Logger root;
private Map<String, Logger> loggerCache = new HashMap<>();
public void addLogger(String name, Logger logger) { loggerCache.put(name, logger); }
public void addLogger(Logger logger) { loggerCache.put(logger.getName(), logger); }
// getters and setters ...
}ILoggerFactory and StaticLoggerFactory create or retrieve loggers, automatically initializing the context via ContextInitializer.
public interface ILoggerFactory {
Logger getLogger(Class clazz);
Logger getLogger(String name);
Logger newLogger(String name);
} public class StaticLoggerFactory implements ILoggerFactory {
private LoggerContext loggerContext;
public StaticLoggerFactory() {
ContextInitializer.autoconfig();
loggerContext = ContextInitializer.getDefautLoggerContext();
}
// implementation of getLogger and newLogger ...
}The LoggerFactory class offers static access to the factory.
public class LoggerFactory {
private static ILoggerFactory loggerFactory = new StaticLoggerFactory();
public static Logger getLogger(Class clazz) { return loggerFactory.getLogger(clazz); }
public static Logger getLogger(String name) { return loggerFactory.getLogger(name); }
}Configuration is handled by a Configurator interface; an XMLConfigurator parses XML files to set up appenders, loggers, and the root logger.
public interface Configurator { void doConfigure(); } public class XMLConfigurator implements Configurator {
private final LoggerContext loggerContext;
public XMLConfigurator(URL url, LoggerContext loggerContext) { /* ... */ }
@Override
public void doConfigure() { /* parse XML and populate loggerContext */ }
}The ContextInitializer locates a default configuration file (logc.xml or logc.yml), selects the appropriate configurator, and builds the global LoggerContext.
public class ContextInitializer {
public static final String AUTOCONFIG_FILE = "logc.xml";
public static final String YAML_FILE = "logc.yml";
private static final LoggerContext DEFAULT_LOGGER_CONTEXT = new LoggerContext();
public static void autoconfig() {
URL url = getConfigURL();
if (url == null) {
System.err.println("config[logc.xml or logc.yml] file not found!");
return;
}
Configurator configurator = url.toString().endsWith("xml") ? new XMLConfigurator(url, DEFAULT_LOGGER_CONTEXT) : new YAMLConfigurator(url, DEFAULT_LOGGER_CONTEXT);
configurator.doConfigure();
}
private static URL getConfigURL() { /* search classpath for config files */ }
public static LoggerContext getDefautLoggerContext() { return DEFAULT_LOGGER_CONTEXT; }
}With these components, a minimal yet functional logging framework is assembled, ready for further extensions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
IT Architects Alliance
Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
