summaryrefslogtreecommitdiff
path: root/src/main/java/org/mamago
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/mamago')
-rw-r--r--src/main/java/org/mamago/logging/Formattable.java13
-rw-r--r--src/main/java/org/mamago/logging/Header.java82
-rw-r--r--src/main/java/org/mamago/logging/Logger.java153
-rw-r--r--src/main/java/org/mamago/logging/ThrowableFormatterFlyweight.java23
-rw-r--r--src/main/java/org/mamago/util/Bits.java16
-rw-r--r--src/main/java/org/mamago/util/Cast.java16
6 files changed, 208 insertions, 95 deletions
diff --git a/src/main/java/org/mamago/logging/Formattable.java b/src/main/java/org/mamago/logging/Formattable.java
new file mode 100644
index 0000000..2711ea1
--- /dev/null
+++ b/src/main/java/org/mamago/logging/Formattable.java
@@ -0,0 +1,13 @@
+package org.mamago.logging;
+
+import java.nio.ByteBuffer;
+
+public interface Formattable {
+ /**
+ * Format an argument to the logger buffer.
+ *
+ * @param buffer ByteBuffer to format to.
+ * @return length of bytes written to buffer.
+ */
+ int formatTo(ByteBuffer buffer);
+}
diff --git a/src/main/java/org/mamago/logging/Header.java b/src/main/java/org/mamago/logging/Header.java
new file mode 100644
index 0000000..392b328
--- /dev/null
+++ b/src/main/java/org/mamago/logging/Header.java
@@ -0,0 +1,82 @@
+package org.mamago.logging;
+
+import org.mamago.util.Bits;
+
+import java.nio.ByteBuffer;
+
+import static java.lang.Math.min;
+import static org.mamago.util.Cast.asByte;
+import static org.mamago.util.Cast.asShort;
+
+/**
+ * Header fields are as follows:
+ * <table>
+ * <tr><th>Bytes</th><th>Field</th></tr>
+ * <tr><td>8</td><td>Header size</td></tr>
+ * <tr><td>8</td><td>Timestamp</td></tr>
+ * <tr><td>8</td><td>Log level</td></tr>
+ * <tr><td>8</td><td>Thread Id (tid)</td></tr>
+ * <tr><td>130</td><td>Thread name (includes length field - 2 bytes)</td></tr>
+ * <tr><td>130</td><td>Logger name (includes length field - 2 bytes)</td></tr>
+ * <tr><td>?</td><td>Padding to align to long size</td></tr>
+ * <tr><td>8</td><td>Body size</td></tr>
+ * </table>
+ */
+class Header {
+ static final int NAME_SIZE_LIMIT = 128;
+
+ static final int SIZE_OFFSET = 0;
+ static final int TS_OFFSET = SIZE_OFFSET + 8;
+ static final int LEVEL_OFFSET = TS_OFFSET + 8;
+ static final int TID_OFFSET = LEVEL_OFFSET + 8;
+ static final int TNAME_OFFSET = TID_OFFSET + 8;
+ static final int LOGGER_NAME_OFFSET = TNAME_OFFSET + NAME_SIZE_LIMIT + 2;
+ static final int PADDING_OFFSET = LOGGER_NAME_OFFSET + NAME_SIZE_LIMIT + 2;
+ static final int BODY_SIZE_OFFSET = Bits.roundUpToLongSize(PADDING_OFFSET);
+ static final int HEADER_SIZE = BODY_SIZE_OFFSET + 8;
+
+ private final ByteBuffer buffer;
+
+ Header(CharSequence loggerName, ByteBuffer buffer) {
+ this.buffer = buffer;
+ // Write header length at start
+ buffer.putLong(0, HEADER_SIZE);
+ put(LOGGER_NAME_OFFSET, loggerName);
+ }
+
+ Header timestamp(long ts) {
+ buffer.putLong(TS_OFFSET, ts);
+ return this;
+ }
+
+ Header level(Logger.Level level) {
+ buffer.putLong(LEVEL_OFFSET, asByte(level.ordinal()));
+ return this;
+ }
+
+ Header threadId(long tid) {
+ buffer.putLong(TID_OFFSET, tid);
+ return this;
+ }
+
+ Header threadName(CharSequence tName) {
+ put(TNAME_OFFSET, tName);
+ return this;
+ }
+
+ // Does not return this reference, as this is the terminator for building the header buffer
+ void bodySize(long size) {
+ buffer.putLong(BODY_SIZE_OFFSET, size);
+ }
+
+ private void put(int offset, CharSequence csq) {
+ // @Optimisation[MA]: CharSequence length as a short, as we limit header char sequence length
+ // (we don't expect long thread or logger names).
+ int length = min(csq.length(), NAME_SIZE_LIMIT);
+ buffer.putShort(offset++, asShort(length));
+ offset++;
+ for (int i = 0; i < length; i++) {
+ buffer.put(offset++, (byte) csq.charAt(i));
+ }
+ }
+}
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;
- }
- }
}
}
diff --git a/src/main/java/org/mamago/logging/ThrowableFormatterFlyweight.java b/src/main/java/org/mamago/logging/ThrowableFormatterFlyweight.java
new file mode 100644
index 0000000..6761c8c
--- /dev/null
+++ b/src/main/java/org/mamago/logging/ThrowableFormatterFlyweight.java
@@ -0,0 +1,23 @@
+package org.mamago.logging;
+
+import java.nio.ByteBuffer;
+
+class ThrowableFormatterFlyweight implements Formattable {
+ private Throwable throwable;
+
+ ThrowableFormatterFlyweight wrap(Throwable t) {
+ throwable = t;
+ return this;
+ }
+
+ void unwrap() {
+ throwable = null;
+ }
+
+ @Override
+ public int formatTo(ByteBuffer b) {
+ int startPos = b.position();
+ // @Fixme[MA]: Implement this!
+ return b.position() - startPos;
+ }
+}
diff --git a/src/main/java/org/mamago/util/Bits.java b/src/main/java/org/mamago/util/Bits.java
new file mode 100644
index 0000000..4672ea9
--- /dev/null
+++ b/src/main/java/org/mamago/util/Bits.java
@@ -0,0 +1,16 @@
+package org.mamago.util;
+
+public class Bits {
+ // Prevent instantiation
+ private Bits() {}
+
+ public static int roundUpToLongSize(int size) {
+ return roundUpToSize(size, 8);
+ }
+
+ public static int roundUpToSize(int size, int width) {
+ // Formula:
+ // (size + w) / w * w; // Adding w rounds up to the next whole width
+ return ((size + width) / width) * width;
+ }
+}
diff --git a/src/main/java/org/mamago/util/Cast.java b/src/main/java/org/mamago/util/Cast.java
new file mode 100644
index 0000000..4ed3d47
--- /dev/null
+++ b/src/main/java/org/mamago/util/Cast.java
@@ -0,0 +1,16 @@
+package org.mamago.util;
+
+public class Cast {
+ // Prevent instantiation
+ private Cast() {}
+
+ public static byte asByte(int i) {
+ // @Fixme[MA]: check whether casting give out-of-bounds check, otherwise add in for safety
+ return (byte) i;
+ }
+
+ public static short asShort(int i) {
+ // @Fixme[MA]: check whether casting give out-of-bounds check, otherwise add in for safety
+ return (byte) i;
+ }
+}