summaryrefslogtreecommitdiff
path: root/src/main/java/org
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org')
-rw-r--r--src/main/java/org/mamago/logging/Logger.java213
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;
+ }
+ }
+ }
+}