diff options
Diffstat (limited to 'src/main/java')
| -rw-r--r-- | src/main/java/org/mamago/logging/Logger.java | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/src/main/java/org/mamago/logging/Logger.java b/src/main/java/org/mamago/logging/Logger.java new file mode 100644 index 0000000..6610da8 --- /dev/null +++ b/src/main/java/org/mamago/logging/Logger.java @@ -0,0 +1,213 @@ +package org.mamago.logging; + +import sun.misc.HexDumpEncoder; + +import java.nio.ByteBuffer; + +import static java.lang.Math.min; + +public class Logger { + public enum Level {TRACE, DEBUG, INFO, WARN, ERROR} + private enum TypeMarker {BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, CHAR_SEQUENCE, FORMATTABLE} + + private final LogBuilder builder; + + // Factory methods ------------------------------------ + + public static Logger createLogger() { + return new Logger(getCallerClass()); + } + + public static Logger createLogger(CharSequence name) { + return new Logger(name); + } + + // Instance methods ----------------------------------- + + private Logger(CharSequence name) { + builder = new LogBuilder(name.toString()); + } + + public LogBuilder log(Level level, CharSequence formatSpec) { + return builder.reset(level, formatSpec); + } + + // Helper methods -------------------------------------- + + private static String getCallerClass() { + return Thread.currentThread().getStackTrace()[3].getClassName(); + } + + private static byte b(int i) { + // @Fixme[MA]: check whether casting give out-of-bounds check, otherwise add in for safety + return (byte) i; + } + + // Helper classes -------------------------------------- + + public interface Formattable { + void formatTo(ByteBuffer b); + } + + public static class LogBuilder implements Appendable { + // @Fixme[MA]: buffer needs to be auto-expandable + private final ByteBuffer headerBuf = ByteBuffer.allocate(256); + private final Header header; + // @Fixme[MA]: buffer needs to be auto-expandable + private final ByteBuffer buffer = ByteBuffer.allocate(256); + private Level level; + + private LogBuilder(CharSequence loggerName) { + // @Optimisation[MA]: Write logger name to the header buffer once. Should never have to be written again, + // as LogBuilders are not shareable between loggers. In the event of seeing corruption of header buffer + // data in the wild can remove this mini-optimisation and just write logger name every time in log() call. + // (Though the real question would be: how if the buffer getting corrupted?) + header = new Header(loggerName); + } + + @Override + public LogBuilder append(CharSequence csq) { + append(csq, 0, csq.length()); + return this; + } + + @Override + public LogBuilder append(CharSequence csq, int start, int end) { + buffer.put(b(TypeMarker.CHAR_SEQUENCE.ordinal())); + buffer.putInt(end - start); // length + for (int i = start; i < end; i++) { + append(csq.charAt(i)); + } + return this; + } + + @Override + public LogBuilder append(char c) { + buffer.put(b(TypeMarker.CHAR.ordinal())); + buffer.put(b(c)); + return this; + } + + @Deprecated // @Fixme[MA]: really want an annotation to create a compiler warning. + public LogBuilder append(String s) { + append((CharSequence) s); + return this; + } + + public LogBuilder append(Formattable f) { + buffer.put(b(TypeMarker.FORMATTABLE.ordinal())); + // @Fixme[MA]: Implement this! + throw new RuntimeException("Not yet implemented"); +// return this; + } + + public LogBuilder append(byte b) { + buffer.put(b(TypeMarker.BYTE.ordinal())); + buffer.put(b); + return this; + } + + public LogBuilder append(short s) { + buffer.put(b(TypeMarker.SHORT.ordinal())); + buffer.putShort(s); + return this; + } + + public LogBuilder append(int i) { + buffer.put(b(TypeMarker.INT.ordinal())); + buffer.putInt(i); + return this; + } + + public LogBuilder append(long l) { + buffer.put(b(TypeMarker.LONG.ordinal())); + buffer.putLong(l); + return this; + } + + public LogBuilder append(float f) { + buffer.put(b(TypeMarker.FLOAT.ordinal())); + buffer.putFloat(f); + return this; + } + + public LogBuilder append(double d) { + buffer.put(b(TypeMarker.DOUBLE.ordinal())); + buffer.putDouble(d); + return this; + } + + // @Fixme[MA]: should generate a compiler warning if a log statement is missing the .log() call + public void log() { + Thread ct = Thread.currentThread(); + header.timestamp(System.currentTimeMillis()) // @Fixme[MA] Use high precision wall clock + .level(level) + .threadId(ct.getId()).threadName(ct.getName()); +// logRingBuffer.write(headerBuf, buffer); + HexDumpEncoder encoder = new HexDumpEncoder(); + System.out.println("Header:"); + headerBuf.position(0).limit(headerBuf.get(0)); + System.out.println(encoder.encodeBuffer(headerBuf)); + System.out.println("Args:"); + System.out.println(encoder.encodeBuffer((ByteBuffer) buffer.flip())); + } + + public LogBuilder reset(Level level, CharSequence formatSpec) { + this.level = level; + buffer.clear(); + append(formatSpec); + return this; + } + + // Helper classes ---------------------------------- + + // @Speedup[MA]: this implementation just packs everything tight in the header. + // It is possible that alignment optimisations may make this faster. + private class Header { + private final int tsOffset; + private final int levelOffset; + private final int tidOffset; + private final int tNameOffset; + + Header(CharSequence loggerName) { + tsOffset = put(1, loggerName); + levelOffset = tsOffset + 8; + tidOffset = levelOffset + 1; + tNameOffset = tidOffset + 8; + } + + Header timestamp(long ts) { + System.out.println("ts = " + ts); + headerBuf.putLong(tsOffset, ts); + return this; + } + + Header level(Level level) { + headerBuf.put(levelOffset, b(level.ordinal())); + return this; + } + + Header threadId(long tid) { + headerBuf.putLong(tidOffset, tid); + return this; + } + + // Does not return this reference, as this is the terminator for building the header buffer + void threadName(CharSequence tName) { + // Write header size to first byte + headerBuf.put(0, b(put(tNameOffset, tName))); + } + + private int put(int offset, CharSequence csq) { + // @Optimisation[MA]: CharSequence length as a byte, as we chop header char sequences short (we don't + // expect long thread or logger names). + int length = min(csq.length(), 50); + headerBuf.put(offset++, b(length)); + for (int i = 0; i < length; i++) { + headerBuf.put(offset++, (byte) csq.charAt(i)); + } + return offset; + } + } + } +} |
