diff options
Diffstat (limited to 'src/main/java/org/mamago/logging/Logger.java')
| -rw-r--r-- | src/main/java/org/mamago/logging/Logger.java | 153 |
1 files changed, 58 insertions, 95 deletions
diff --git a/src/main/java/org/mamago/logging/Logger.java b/src/main/java/org/mamago/logging/Logger.java index 6610da8..0c6d06a 100644 --- a/src/main/java/org/mamago/logging/Logger.java +++ b/src/main/java/org/mamago/logging/Logger.java @@ -1,16 +1,22 @@ package org.mamago.logging; -import sun.misc.HexDumpEncoder; - import java.nio.ByteBuffer; -import static java.lang.Math.min; +import static org.mamago.util.Cast.asByte; +import static org.mamago.util.Cast.asShort; +// @Speedup[MA]: Increased performance can be gained by providing our own buffer for CharSequence types that gives +// direct access to the byte array for use with System.arrayCopy(). +//@NotThreadSafe @Fixme[MA] public class Logger { public enum Level {TRACE, DEBUG, INFO, WARN, ERROR} - private enum TypeMarker {BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, CHAR_SEQUENCE, FORMATTABLE} + enum TypeMarker {BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, CHAR_SEQUENCE, FORMATTABLE} private final LogBuilder builder; + // @Fixme[MA]: Extract to a ring buffer registry, or similar. Also allow custom ring buffer to be passed in to + // Logger for complete flexible configuration. + final ByteBuffer logRingBuffer = ByteBuffer.allocateDirect(4 * 1024 * 1024); // 4 MiB + // Factory methods ------------------------------------ @@ -22,47 +28,44 @@ public class Logger { return new Logger(name); } + // Instance methods ----------------------------------- private Logger(CharSequence name) { - builder = new LogBuilder(name.toString()); + builder = new LogBuilder(name.toString(), logRingBuffer); } 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 static final short DUMMY = 0; + + // @Fixme[MA]: handle log message overflowing message limit + private final ByteBuffer buffer = ByteBuffer.allocate(32 * 1024); // 32 KiB limit per log message + private final ThrowableFormatterFlyweight throwableFormatterFlyweight = new ThrowableFormatterFlyweight(); + private final ByteBuffer logRingBuffer; 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) { + private LogBuilder(CharSequence loggerName, ByteBuffer logRingBuffer) { + this.logRingBuffer = logRingBuffer; // @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); + header = new Header(loggerName, buffer); } @Override @@ -73,141 +76,101 @@ public class Logger { @Override public LogBuilder append(CharSequence csq, int start, int end) { - buffer.put(b(TypeMarker.CHAR_SEQUENCE.ordinal())); + buffer.put(asByte(TypeMarker.CHAR_SEQUENCE.ordinal())); buffer.putInt(end - start); // length for (int i = start; i < end; i++) { - append(csq.charAt(i)); + buffer.put(asByte(csq.charAt(i))); } return this; } @Override public LogBuilder append(char c) { - buffer.put(b(TypeMarker.CHAR.ordinal())); - buffer.put(b(c)); + buffer.put(asByte(TypeMarker.CHAR.ordinal())); + buffer.put(asByte(c)); return this; } - @Deprecated // @Fixme[MA]: really want an annotation to create a compiler warning. + @Deprecated // @Fixme[MA]: really want my own 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; + buffer.put(asByte(TypeMarker.FORMATTABLE.ordinal())); + int lengthPos = buffer.position(); + // @Optimisation[MA]: would we ever add an argument whose length is longer than max short? + // @Fixme[MA]: Handle short length overflow by setting the high bit to indicate to the log writer that + // a value was truncated. The log writer can then log an appropriate warning. + buffer.putShort(DUMMY); + int length = f.formatTo(buffer); + // Set the position, as can't depend on formatter implementations to do so + buffer.position(lengthPos + length); + // Update the formatter length field + buffer.putShort(lengthPos, asShort(length)); + return this; } public LogBuilder append(byte b) { - buffer.put(b(TypeMarker.BYTE.ordinal())); + buffer.put(asByte(TypeMarker.BYTE.ordinal())); buffer.put(b); return this; } public LogBuilder append(short s) { - buffer.put(b(TypeMarker.SHORT.ordinal())); + buffer.put(asByte(TypeMarker.SHORT.ordinal())); buffer.putShort(s); return this; } public LogBuilder append(int i) { - buffer.put(b(TypeMarker.INT.ordinal())); + buffer.put(asByte(TypeMarker.INT.ordinal())); buffer.putInt(i); return this; } public LogBuilder append(long l) { - buffer.put(b(TypeMarker.LONG.ordinal())); + buffer.put(asByte(TypeMarker.LONG.ordinal())); buffer.putLong(l); return this; } public LogBuilder append(float f) { - buffer.put(b(TypeMarker.FLOAT.ordinal())); + buffer.put(asByte(TypeMarker.FLOAT.ordinal())); buffer.putFloat(f); return this; } public LogBuilder append(double d) { - buffer.put(b(TypeMarker.DOUBLE.ordinal())); + buffer.put(asByte(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 LogBuilder append(Throwable t) { + append(throwableFormatterFlyweight.wrap(t)); + throwableFormatterFlyweight.unwrap(); + return this; + } + + // @Fixme[MA]: should generate a compiler warning if a client 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())); + .threadId(ct.getId()) + .threadName(ct.getName()) + .bodySize(buffer.position() - Header.HEADER_SIZE); + buffer.flip(); + logRingBuffer.put(buffer); } public LogBuilder reset(Level level, CharSequence formatSpec) { this.level = level; - buffer.clear(); + buffer.clear().position(Header.HEADER_SIZE); 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; - } - } } } |
