Skip to content

Commit

Permalink
Added support for different log formats in logcat, and updated the us…
Browse files Browse the repository at this point in the history
…ability of it by adding --help etc.
  • Loading branch information
Anders Storsveen committed Nov 29, 2012
1 parent e73a529 commit 75534e0
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 6 deletions.
95 changes: 95 additions & 0 deletions log/src/main/java/org/cloudname/log/format/FullFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.cloudname.log.format;

import org.cloudname.log.pb.Timber;

/**
* Format Timber.LogEvent messages to a single line using NL to
* separate log records and TAB to separate the fields. The fields
* are in order:
*
* <ul>
* <li> timestamp in ISO8601 format
* <li> hostname
* <li> process id/thread id
* <li> service name
* <li> class
* <li> type of log message
* <li> log level
* <li> consistency level
* <li> log message payload
* </ul>
*
* This formatter is quite a bit slower than the SingleLineFormatter,
* but it is perhaps more pleasing to look at.
*
* @author borud
*/
public class FullFormatter implements LogEventFormatter {
@Override
public String format(Timber.LogEvent logEvent) {
StringBuilder buff = new StringBuilder(200);
Util.formatTimeISO(logEvent.getTimestamp(), buff);

buff.append('\t')
.append(logEvent.getHost())
.append('\t')

// Add process- and thread id if applicable
.append((logEvent.hasPid() ? logEvent.getPid() : "-"))
.append("/")
.append((logEvent.hasTid() ? logEvent.getTid() : "-"))
.append('\t')

// Add service name
.append(logEvent.getServiceName())
.append('\t')

// Add source
.append(classFromSource(logEvent.getSource()))
.append('\t')

// Type of log message
.append(logEvent.getType())
.append('\t')

// Level of log message
.append(Util.logLevelNameForValue(logEvent.getLevel()))
.append('\t')

// Add consistency level
.append(logEvent.getConsistencyLevel().toString().substring(0,2))
.append('\t');

// Add the payloads
boolean first = true;
for (Timber.Payload payload : logEvent.getPayloadList()) {
String s = payload.getPayload().toStringUtf8();

buff.append((first?"":" | "))
.append(payload.getName())
.append(": ")
.append(Util.escape(s));

first = false;
}

return buff.toString();
}

/**
* Given a fully qualified class name, return just the class name
* without the package name.
*
* @param source A (possibly) fully qualified class name
* @return the class name without the package.
*/
private String classFromSource(String source) {
int last = source.lastIndexOf('.');
if (-1 == last) {
return source;
}

return source.substring(last + 1);
}

}
58 changes: 52 additions & 6 deletions log/src/main/java/org/cloudname/log/logcat/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import org.cloudname.log.format.CompactFormatter;
import org.cloudname.flags.Flag;
import org.cloudname.flags.Flags;
import org.cloudname.log.format.FullFormatter;

import java.io.FileInputStream;
import java.util.List;

Expand All @@ -14,15 +16,43 @@
public class Main {
private static final int DEFAULT_TAIL_DELAY_MS = 50;

@Flag(name = "follow", description = "Follow log file")
public enum LogFormatter {
COMPACT,
FULL,
NONE
}

@Flag(name = "follow", description = "Follow log file (tail). Only supports one file at a time.")
private static String tailFile = null;

public static void main(String[] args) throws Exception {
Flags flags = new Flags()
@Flag(name = "format", description = "The type of formatting to use (compact/full).")
private static String formatType = "compact";

public static void main(final String[] args) throws Exception {
final Flags flags = new Flags()
.loadOpts(Main.class)
.parse(args);
List<String> files = flags.getNonOptionArguments();
LogCat cat = new LogCat(new CompactFormatter());
final List<String> files = flags.getNonOptionArguments();

if (flags.helpFlagged() || args.length == 0) {
System.out.print("\nUsage 'java -jar <jarfile> <options> <filename(s)>");

flags.printHelp(System.out);
return;
}

final LogCat cat;
switch (toLogFormatter(formatType)) {
case COMPACT:
cat = new LogCat(new CompactFormatter());
break;
case FULL:
cat = new LogCat(new FullFormatter());
break;
default:
System.out.println("Unknown log formatter. Exiting...");
return;
}

if (null != tailFile) {
cat.catStream(new TailInputStream(tailFile, DEFAULT_TAIL_DELAY_MS));
Expand All @@ -31,9 +61,25 @@ else if (0 == files.size()) {
cat.catStream(System.in);
}
else {
for (String filename : files) {
for (final String filename : files) {
cat.catStream(new FileInputStream(filename));
}
}
}

/**
* Helper method to uppercase and catch nullpointers.
*
* @param str The string to match.
* @return returns the matching enum.
*/
public static LogFormatter toLogFormatter(final String str)
{
try {
return LogFormatter.valueOf(str.toUpperCase());
}
catch (Exception ex) {
return LogFormatter.NONE;
}
}
}
140 changes: 140 additions & 0 deletions log/src/test/java/org/cloudname/log/format/FullFormatterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package org.cloudname.log.format;

import com.google.protobuf.ByteString;
import org.cloudname.log.pb.Timber;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringStartsWith.startsWith;
import static org.junit.Assert.assertEquals;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Unit test for FullFormatter.
*
* @author storsveen
*/
public class FullFormatterTest {
private static final Logger log = Logger.getLogger(CompactFormatterTest.class.getName());

private static final String eventString
= "2011-11-28T16:46:22.123\texample.com\t0/1\tmyservice\tSingleLineFormatter\tT\tINFO\tBE\tmsg: this is a test";

private static final String eventStringWithException
= "2011-11-28T16:46:22.123\texample.com\t0/1\tmyservice\tSingleLineFormatter\tT\tWARNING" +
"\tBE\tmsg: this is a test with an exception | exception: java.lang.RuntimeException: " +
"Testing\\n\\tat";

private static Timber.LogEvent event;
private static Timber.LogEvent eventWithException;

private static long instant = 1322498782123L;
private static RuntimeException runtimeException = new RuntimeException("Testing");

@BeforeClass
public static void setUp() {
event = Timber.LogEvent.newBuilder()
.setTimestamp(instant)
.setConsistencyLevel(Timber.ConsistencyLevel.BESTEFFORT)
.setLevel(Level.INFO.intValue())
.setHost("example.com")
.setServiceName("myservice")
.setSource(SingleLineFormatter.class.getName())
.setPid(0)
.setTid((int) Thread.currentThread().getId())
.setType("T")
.addPayload(
Timber.Payload.newBuilder()
.setName("msg")
.setPayload(ByteString.copyFromUtf8("this is a test")))
.build();

ByteArrayOutputStream os = new ByteArrayOutputStream();
runtimeException.printStackTrace(new PrintStream(os));

eventWithException = Timber.LogEvent.newBuilder()
.setTimestamp(instant)
.setConsistencyLevel(Timber.ConsistencyLevel.BESTEFFORT)
.setLevel(Level.WARNING.intValue())
.setHost("example.com")
.setServiceName("myservice")
.setSource(SingleLineFormatter.class.getName())
.setPid(0)
.setTid((int) Thread.currentThread().getId())
.setType("T")
.addPayload(
Timber.Payload.newBuilder()
.setName("msg")
.setPayload(ByteString.copyFromUtf8("this is a test with an exception")))
.addPayload(
Timber.Payload.newBuilder()
.setName("exception")
.setContentType("application/java-exception")
.setPayload(ByteString.copyFrom(os.toByteArray())))
.build();
}

/**
* Tests that a simple event is as expected.
*
* @throws Exception
*/
@Test
public void simpleTest() throws Exception {
FullFormatter form = new FullFormatter();
assertEquals(eventString, form.format(event));
}

/**
* Tests that a event with exception is as expected.
*
* @throws Exception
*/
@Test
public void exceptionTest() throws Exception {
FullFormatter form = new FullFormatter();
assertThat(form.format(eventWithException), startsWith(eventStringWithException));
}

/**
* A micro benchmark.
*/
@Test (timeout = 1000)
public void microBenchmarkTest() {
FullFormatter form = new FullFormatter();
int numIterations = 1000;

long start = System.currentTimeMillis();
for (int i = 0; i < numIterations; i++) {
form.format(event);
}
long duration = System.currentTimeMillis() - start;
double formatsPerSecond = numIterations / ((double) duration / 1000.0);

log.info("event formats per second: " + formatsPerSecond
+ " (" + numIterations + " iterations took " + duration + " milliseconds)");
}

/**
* A micro benchmark with exceptions.
*/
@Test (timeout = 1000)
public void microBenchmarkWithExceptionTest() {
FullFormatter form = new FullFormatter();
int numIterations = 1000;

long start = System.currentTimeMillis();
for (int i = 0; i < numIterations; i++) {
form.format(eventWithException);
}
long duration = System.currentTimeMillis() - start;
double formatsPerSecond = numIterations / ((double) duration / 1000.0);

log.info("event + exception formats per second: " + formatsPerSecond
+ " (" + numIterations + " iterations took " + duration + " milliseconds)");
}
}

0 comments on commit 75534e0

Please sign in to comment.