Fix conflict merging iwasakims readme fix
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
index f918879..a8b7df3 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,8 @@
 HTrace is a tracing framework intended for use with distributed systems written in java.  
 
 The project is hosted at http://github.com/cloudera/htrace.  
-The project is available in Maven Central with groupId: org.cloudera.htrace, and name: htrace.  
+The project is available in Maven Central with groupId: org.htrace, and name: htrace.  
+(It was formally at groupId: org.cloudera.htrace, and name: htrace).  
 
 API
 ---
@@ -44,11 +45,18 @@
 HTrace stores span information in java's ThreadLocals, which causes
 the trace to be "lost" on thread changes. The only way to prevent
 this is to "wrap" your thread changes. For example, if your code looks
-like this:  
-`Thread t1 = new Thread(new MyRunnable());`  
-...  
+like this:
+
+````java
+    Thread t1 = new Thread(new MyRunnable());
+    ...  
+````
+
 Just change it to look this:  
-`Thread t1 = new Thread(Trace.wrap(new MyRunnable()));`
+
+````java
+    Thread t1 = new Thread(Trace.wrap(new MyRunnable()));
+````
 
 That's it! `Trace.wrap()` takes a single argument (a runnable or a
 callable) and if the current thread is a part of a trace, returns a
@@ -78,20 +86,24 @@
 see on your traces.  In this case, you could start a new span before
 the computation that you then stop after the computation has
 finished. It might look like this:  
-<br>
+
+````java
     Span computationSpan = Trace.startSpan("Expensive computation.");  
     try {  
         //expensive computation here  
     } finally {  
         computationSpan.stop();  
     }  
-<br>
+````
+
 HTrace also supports key-value annotations on a per-trace basis.  
 <br>
-Example:  
-`Trace.currentTrace().addAnnotation("faultyRecordCounter".getBytes(),
-"1".getBytes());`  
-<br>
+Example:
+
+````java
+    Trace.currentTrace().addAnnotation("faultyRecordCounter".getBytes(), "1".getBytes());
+````
+
 `Trace.currentTrace()` will not return `null` if the current thread is
 not tracing, but instead it will return a `NullSpan`, which does
 nothing on any of its method calls. The takeaway here is you can call
@@ -99,7 +111,11 @@
 
 ###Samplers  
 `Sampler` is an interface that defines one function:  
-`boolean next(T info);`  
+
+````java
+    boolean next(T info);
+````
+
 All of the `Trace.startSpan()` methods can take an optional sampler.  
 A new span is only created if the sampler's next function returns
 true.  If the Sampler returns false, the `NullSpan` is returned from
@@ -154,3 +170,8 @@
 -------------------------------
 
 The test that creates a sample trace (TestHTrace) takes a command line argument telling it where to write span information. Run mvn test -DargLine="-DspanFile=FILE\_PATH" to write span information to FILE_PATH. If no file is specified, span information will be written to standard out. If span information is written to a file, you can use the included graphDrawer python script in tools/ to create a simple visualization of the trace. Or you could write some javascript to make a better visualization, and send a pull request if you do :). 
+
+Publishing to Maven Central
+-------------------------------
+See [OSSRH-8896](https://issues.sonatype.org/browse/OSSRH-8896)
+for repository vitals.
diff --git a/bin/gen_thrift.sh b/bin/gen_thrift.sh
new file mode 100755
index 0000000..05fdd91
--- /dev/null
+++ b/bin/gen_thrift.sh
@@ -0,0 +1,30 @@
+#
+#/**
+# * Copyright 2007 The Apache Software Foundation
+# *
+# * Licensed to the Apache Software Foundation (ASF) under one
+# * or more contributor license agreements.  See the NOTICE file
+# * distributed with this work for additional information
+# * regarding copyright ownership.  The ASF licenses this file
+# * to you under the Apache License, Version 2.0 (the
+# * "License"); you may not use this file except in compliance
+# * with the License.  You may obtain a copy of the License at
+# *
+# *     http://www.apache.org/licenses/LICENSE-2.0
+# *
+# * Unless required by applicable law or agreed to in writing, software
+# * distributed under the License is distributed on an "AS IS" BASIS,
+# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# * See the License for the specific language governing permissions and
+# * limitations under the License.
+# */
+
+SOURCE="${BASH_SOURCE[0]}"
+DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+
+cd $DIR/../
+FILES=htrace-zipkin/src/main/thrift/*
+for FILE in $FILES
+do
+  thrift --gen java -out htrace-zipkin/src/main/java $FILE
+done
diff --git a/htrace-core/pom.xml b/htrace-core/pom.xml
new file mode 100644
index 0000000..cb46348
--- /dev/null
+++ b/htrace-core/pom.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+license agreements. See the NOTICE file distributed with this work for additional
+information regarding copyright ownership. The ASF licenses this file to
+You under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of
+the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+by applicable law or agreed to in writing, software distributed under the
+License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+OF ANY KIND, either express or implied. See the License for the specific
+language governing permissions and limitations under the License. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>htrace-core</artifactId>
+  <packaging>jar</packaging>
+
+  <parent>
+    <artifactId>htrace</artifactId>
+    <groupId>org.htrace</groupId>
+    <version>3.0-SNAPSHOT</version>
+  </parent>
+
+  <name>htrace-core</name>
+  <url>https://github.com/cloudera/htrace</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-javadoc-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-gpg-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <!-- explicitly define maven-deploy-plugin after other to force exec order -->
+        <artifactId>maven-deploy-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <!-- Global deps. -->
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <!-- core specific deps. -->
+    <dependency>
+      <groupId>org.mortbay.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>6.1.26</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/src/main/java/org/cloudera/htrace/HTraceConfiguration.java b/htrace-core/src/main/java/org/htrace/HTraceConfiguration.java
similarity index 69%
rename from src/main/java/org/cloudera/htrace/HTraceConfiguration.java
rename to htrace-core/src/main/java/org/htrace/HTraceConfiguration.java
index f887dac..7ad608f 100644
--- a/src/main/java/org/cloudera/htrace/HTraceConfiguration.java
+++ b/htrace-core/src/main/java/org/htrace/HTraceConfiguration.java
@@ -14,7 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -24,12 +27,30 @@
  * to provide tracing configuration.
  */
 public abstract class HTraceConfiguration {
-  public abstract String get(String key);
+
+  private static final Log LOG = LogFactory.getLog(HTraceConfiguration.class);
 
   public static HTraceConfiguration fromMap(Map<String, String> conf) {
     return new MapConf(conf);
   }
 
+  public abstract String get(String key);
+
+  public abstract String get(String key, String defaultValue);
+
+  public boolean getBoolean(String key, boolean defaultValue) {
+    String value = get(key, String.valueOf(defaultValue)).trim().toLowerCase();
+
+    if ("true".equals(value)) {
+      return true;
+    } else if ("false".equals(value)) {
+      return false;
+    }
+
+    LOG.warn("Expected boolean for key [" + key + "] instead got [" + value + "].");
+    return defaultValue;
+  }
+
   public int getInt(String key, int defaultVal) {
     String val = get(key);
     if (val == null || val.trim().isEmpty()) {
@@ -41,7 +62,7 @@
       throw new IllegalArgumentException("Bad value for '" + key + "': should be int");
     }
   }
-  
+
   private static class MapConf extends HTraceConfiguration {
     private final Map<String, String> conf;
 
@@ -53,5 +74,11 @@
     public String get(String key) {
       return conf.get(key);
     }
+
+    @Override
+    public String get(String key, String defaultValue) {
+      String value = get(key);
+      return value == null ? defaultValue : value;
+    }
   }
 }
diff --git a/src/main/java/org/cloudera/htrace/Sampler.java b/htrace-core/src/main/java/org/htrace/Sampler.java
similarity index 80%
rename from src/main/java/org/cloudera/htrace/Sampler.java
rename to htrace-core/src/main/java/org/htrace/Sampler.java
index 501b2ac..08c520d 100644
--- a/src/main/java/org/cloudera/htrace/Sampler.java
+++ b/htrace-core/src/main/java/org/htrace/Sampler.java
@@ -14,33 +14,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
 
-import org.cloudera.htrace.impl.AlwaysSampler;
-import org.cloudera.htrace.impl.NeverSampler;
+import org.htrace.impl.AlwaysSampler;
+import org.htrace.impl.NeverSampler;
 
 /**
  * Extremely simple callback to determine the frequency that an action should be
  * performed.
- * 
+ * <p/>
  * 'T' is the object type you require to create a more advanced sampling
  * function. For example if there is some RPC information in a 'Call' object,
  * you might implement Sampler<Call>. Then when the RPC is received you can call
  * one of the Trace.java functions that takes the extra 'info' parameter, which
  * will be passed into the next function you implemented.
- * 
+ * <p/>
  * For the example above, the next(T info) function may look like this
- * 
+ * <p/>
  * public boolean next(T info){
- *    if (info == null) {
- *      return false;
- *    } else if (info.getName().equals("get")) {
- *        return Math.random() > 0.5;
- *    } else if (info.getName().equals("put")) {
- *        return Math.random() > 0.25;
- *    } else {
- *      return false;
- *    }
+ * if (info == null) {
+ * return false;
+ * } else if (info.getName().equals("get")) {
+ * return Math.random() > 0.5;
+ * } else if (info.getName().equals("put")) {
+ * return Math.random() > 0.25;
+ * } else {
+ * return false;
+ * }
  * }
  * This would trace 50% of all gets, 75% of all puts and would not trace any other requests.
  */
diff --git a/src/main/java/org/cloudera/htrace/Span.java b/htrace-core/src/main/java/org/htrace/Span.java
similarity index 74%
rename from src/main/java/org/cloudera/htrace/Span.java
rename to htrace-core/src/main/java/org/htrace/Span.java
index c5d6e63..d7d0d9a 100644
--- a/src/main/java/org/cloudera/htrace/Span.java
+++ b/htrace-core/src/main/java/org/htrace/Span.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
 
 import java.util.List;
 import java.util.Map;
@@ -23,20 +23,26 @@
 /**
  * Base interface for gathering and reporting statistics about a block of
  * execution.
- * 
+ * <p/>
  * Spans form a tree structure with the parent relationship. The first span in a
  * trace has no parent span.
  */
 public interface Span {
   public static final long ROOT_SPAN_ID = 0x74ace;
 
-  /** The block has completed, stop the clock */
+  /**
+   * The block has completed, stop the clock
+   */
   void stop();
 
-  /** Get the start time, in milliseconds */
+  /**
+   * Get the start time, in milliseconds
+   */
   long getStartTimeMillis();
 
-  /** Get the stop time, in milliseconds */
+  /**
+   * Get the stop time, in milliseconds
+   */
   long getStopTimeMillis();
 
   /**
@@ -45,13 +51,19 @@
    */
   long getAccumulatedMillis();
 
-  /** Has the span been started and not yet stopped? */
+  /**
+   * Has the span been started and not yet stopped?
+   */
   boolean isRunning();
 
-  /** Return a textual description of this span */
+  /**
+   * Return a textual description of this span
+   */
   String getDescription();
 
-  /** A pseudo-unique (random) number assigned to this span instance */
+  /**
+   * A pseudo-unique (random) number assigned to this span instance
+   */
   long getSpanId();
 
   /**
@@ -60,7 +72,9 @@
    */
   long getTraceId();
 
-  /** Create a child span of this span with the given description */
+  /**
+   * Create a child span of this span with the given description
+   */
   Span child(String description);
 
   @Override
@@ -72,22 +86,30 @@
    */
   long getParentId();
 
-  /** Add a data annotation associated with this span */
+  /**
+   * Add a data annotation associated with this span
+   */
   void addKVAnnotation(byte[] key, byte[] value);
 
-  /** Add a timeline annotation associated with this span */
+  /**
+   * Add a timeline annotation associated with this span
+   */
   void addTimelineAnnotation(String msg);
 
-  /** Get data associated with this span (read only) */
+  /**
+   * Get data associated with this span (read only)
+   */
   Map<byte[], byte[]> getKVAnnotations();
-  
-  /** Get any timeline annotations (read only) */
+
+  /**
+   * Get any timeline annotations (read only)
+   */
   List<TimelineAnnotation> getTimelineAnnotations();
 
   /**
    * Return a unique id for the node or process from which this Span originated.
    * IP address is a reasonable choice.
-   * 
+   *
    * @return
    */
   String getProcessId();
diff --git a/src/main/java/org/cloudera/htrace/SpanReceiver.java b/htrace-core/src/main/java/org/htrace/SpanReceiver.java
similarity index 96%
rename from src/main/java/org/cloudera/htrace/SpanReceiver.java
rename to htrace-core/src/main/java/org/htrace/SpanReceiver.java
index e5d1cbb..65cfb45 100644
--- a/src/main/java/org/cloudera/htrace/SpanReceiver.java
+++ b/htrace-core/src/main/java/org/htrace/SpanReceiver.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
 
 
 import java.io.Closeable;
@@ -31,10 +31,10 @@
    * configuration to SpanReceivers after creating them.
    */
   public void configure(HTraceConfiguration conf);
-  
+
   /**
    * Called when a Span is stopped and can now be stored.
-   * 
+   *
    * @param span
    */
   public void receiveSpan(Span span);
diff --git a/src/main/java/org/cloudera/htrace/TimelineAnnotation.java b/htrace-core/src/main/java/org/htrace/TimelineAnnotation.java
similarity index 96%
rename from src/main/java/org/cloudera/htrace/TimelineAnnotation.java
rename to htrace-core/src/main/java/org/htrace/TimelineAnnotation.java
index 17c9a29..265d209 100644
--- a/src/main/java/org/cloudera/htrace/TimelineAnnotation.java
+++ b/htrace-core/src/main/java/org/htrace/TimelineAnnotation.java
@@ -14,12 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
 
 public class TimelineAnnotation {
   private final long time;
   private final String msg;
-  
+
   public TimelineAnnotation(long time, String msg) {
     this.time = time;
     this.msg = msg;
@@ -32,7 +32,7 @@
   public String getMessage() {
     return msg;
   }
-  
+
   @Override
   public String toString() {
     return "@" + time + ": " + msg;
diff --git a/src/main/java/org/cloudera/htrace/Trace.java b/htrace-core/src/main/java/org/htrace/Trace.java
similarity index 79%
rename from src/main/java/org/cloudera/htrace/Trace.java
rename to htrace-core/src/main/java/org/htrace/Trace.java
index 18aa8fb..4c14b14 100644
--- a/src/main/java/org/cloudera/htrace/Trace.java
+++ b/htrace-core/src/main/java/org/htrace/Trace.java
@@ -14,17 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
+
+import org.htrace.impl.MilliSpan;
+import org.htrace.impl.TrueIfTracingSampler;
+import org.htrace.wrappers.TraceCallable;
+import org.htrace.wrappers.TraceRunnable;
 
 import java.security.SecureRandom;
 import java.util.Random;
 import java.util.concurrent.Callable;
 
-import org.cloudera.htrace.impl.MilliSpan;
-import org.cloudera.htrace.impl.TrueIfTracingSampler;
-import org.cloudera.htrace.wrappers.TraceCallable;
-import org.cloudera.htrace.wrappers.TraceRunnable;
-
 /**
  * The primary way to interact with the library. Provides methods to start
  * spans, as well as set necessary tracing information.
@@ -36,9 +36,8 @@
    * Starts and returns a new span as the child of the current span if the
    * default sampler (TrueIfTracingSampler) returns true, otherwise returns the
    * NullSpan.
-   * 
-   * @param description
-   *          Description of the span to be created.
+   *
+   * @param description Description of the span to be created.
    * @return
    */
   public static TraceScope startSpan(String description) {
@@ -49,15 +48,14 @@
    * Starts and returns a new span as the child of the parameter 'parent'. This
    * will always return a new span, even if tracing wasn't previously enabled for
    * this thread.
-   * 
-   * @param description
-   *          Description of the span to be created.
-   * @param parent
-   *          The parent that should be used to create the child span that is to
-   *          be returned.
+   *
+   * @param description Description of the span to be created.
+   * @param parent      The parent that should be used to create the child span that is to
+   *                    be returned.
    * @return
    */
   public static TraceScope startSpan(String description, Span parent) {
+    if (parent == null) return startSpan(description);
     return continueSpan(parent.child(description));
   }
 
@@ -67,11 +65,20 @@
         random.nextLong(), Tracer.getProcessId());
     return continueSpan(newSpan);
   }
-  
+
   public static <T> TraceScope startSpan(String description, Sampler<T> s) {
     return startSpan(description, s, null);
   }
 
+  public static TraceScope startSpan(String description, Sampler<TraceInfo> s, TraceInfo tinfo) {
+    Span span = null;
+    if (isTracing() || s.next(tinfo)) {
+      span = new MilliSpan(description, tinfo.traceId, tinfo.spanId,
+          random.nextLong(), Tracer.getProcessId());
+    }
+    return continueSpan(span);
+  }
+
   public static <T> TraceScope startSpan(String description, Sampler<T> s, T info) {
     Span span = null;
     if (isTracing() || s.next(info)) {
@@ -79,7 +86,7 @@
     }
     return continueSpan(span);
   }
-  
+
   /**
    * Pick up an existing span from another thread.
    */
@@ -89,12 +96,12 @@
     if (s == null) return new TraceScope(null, null);
     return Tracer.getInstance().continueSpan(s);
   }
-  
+
   /**
    * Set the processId to be used for all Spans created by this Tracer.
    *
-   * @see Span.java
    * @param processId
+   * @see Span.java
    */
   public static void setProcessId(String processId) {
     Tracer.processId = processId;
@@ -102,7 +109,7 @@
 
   /**
    * Removes the given SpanReceiver from the list of SpanReceivers.
-   * 
+   *
    * @param rcvr
    */
   public static void removeReceiver(SpanReceiver rcvr) {
@@ -128,7 +135,7 @@
       s.addKVAnnotation(key, value);
     }
   }
-  
+
   /**
    * Annotate the current span with the given message.
    */
@@ -184,4 +191,19 @@
       return runnable;
     }
   }
+
+  /**
+   * Wrap the runnable in a TraceRunnable, if tracing
+   *
+   * @param description name of the span to be created.
+   * @param runnable
+   * @return The runnable provided, wrapped if tracing, 'runnable' if not.
+   */
+  public static Runnable wrap(String description, Runnable runnable) {
+    if (isTracing()) {
+      return new TraceRunnable(Trace.currentSpan(), runnable, description);
+    } else {
+      return runnable;
+    }
+  }
 }
diff --git a/src/main/java/org/cloudera/htrace/TraceInfo.java b/htrace-core/src/main/java/org/htrace/TraceInfo.java
similarity index 97%
rename from src/main/java/org/cloudera/htrace/TraceInfo.java
rename to htrace-core/src/main/java/org/htrace/TraceInfo.java
index 0b90ac2..438289d 100644
--- a/src/main/java/org/cloudera/htrace/TraceInfo.java
+++ b/htrace-core/src/main/java/org/htrace/TraceInfo.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
 
 
 public class TraceInfo {
diff --git a/htrace-core/src/main/java/org/htrace/TraceScope.java b/htrace-core/src/main/java/org/htrace/TraceScope.java
new file mode 100644
index 0000000..07b7929
--- /dev/null
+++ b/htrace-core/src/main/java/org/htrace/TraceScope.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.htrace;
+
+import java.io.Closeable;
+
+public class TraceScope implements Closeable {
+
+  /**
+   * the span for this scope
+   */
+  private final Span span;
+
+  /**
+   * the span that was "current" before this scope was entered
+   */
+  private final Span savedSpan;
+
+  private boolean detached = false;
+
+  TraceScope(Span span, Span saved) {
+    this.span = span;
+    this.savedSpan = saved;
+  }
+
+  public Span getSpan() {
+    return span;
+  }
+
+  /**
+   * Remove this span as the current thread, but don't stop it yet or
+   * send it for collection. This is useful if the span object is then
+   * passed to another thread for use with Trace.continueTrace().
+   *
+   * @return the same Span object
+   */
+  public Span detach() {
+    detached = true;
+
+    Span cur = Tracer.getInstance().currentSpan();
+    if (cur != span) {
+      Tracer.LOG.debug("Closing trace span " + span + " but " +
+          cur + " was top-of-stack");
+    } else {
+      Tracer.getInstance().setCurrentSpan(savedSpan);
+    }
+    return span;
+  }
+
+  @Override
+  public void close() {
+    if (span == null) return;
+
+    if (!detached) {
+      // The span is done
+      span.stop();
+      detach();
+    }
+  }
+}
diff --git a/src/main/java/org/cloudera/htrace/TraceTree.java b/htrace-core/src/main/java/org/htrace/TraceTree.java
similarity index 80%
rename from src/main/java/org/cloudera/htrace/TraceTree.java
rename to htrace-core/src/main/java/org/htrace/TraceTree.java
index 783e86c..fadcf2f 100644
--- a/src/main/java/org/cloudera/htrace/TraceTree.java
+++ b/htrace-core/src/main/java/org/htrace/TraceTree.java
@@ -14,18 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
 
-import java.util.Collection;
-
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
+import java.util.Collection;
 
 /**
- * Used to create the graph formed by spans.  
+ * Used to create the graph formed by spans.
  */
 public class TraceTree {
   public static final Log LOG = LogFactory.getLog(Tracer.class);
@@ -35,18 +35,17 @@
 
   /**
    * Create a new TraceTree
-   * 
-   * @param spans
-   *          The collection of spans to use to create this TraceTree. Should
-   *          have at least one root span (span with parentId =
-   *          Span.ROOT_SPAN_ID
+   *
+   * @param spans The collection of spans to use to create this TraceTree. Should
+   *              have at least one root span (span with parentId =
+   *              Span.ROOT_SPAN_ID
    */
   public TraceTree(Collection<Span> spans) {
-    this.spans = spans;
-    this.spansByParentID = HashMultimap.<Long, Span> create();
-    this.spansByPid = HashMultimap.<String, Span> create();
+    this.spans = ImmutableList.copyOf(spans);
+    this.spansByParentID = HashMultimap.<Long, Span>create();
+    this.spansByPid = HashMultimap.<String, Span>create();
 
-    for (Span s : spans) {
+    for (Span s : this.spans) {
       if (s.getProcessId() != null) {
         spansByPid.put(s.getProcessId(), s);
       } else {
@@ -69,7 +68,7 @@
    *         that ID.
    */
   public Multimap<Long, Span> getSpansByParentIdMap() {
-    return HashMultimap.<Long, Span> create(spansByParentID);
+    return HashMultimap.<Long, Span>create(spansByParentID);
   }
 
   /**
@@ -80,7 +79,7 @@
     Collection<Span> roots = spansByParentID.get(Span.ROOT_SPAN_ID);
     if (roots != null) {
       return roots;
-    } 
+    }
     throw new IllegalStateException(
         "TraceTree is not correctly formed - there are no root spans in the collection provided at construction.");
   }
@@ -91,6 +90,6 @@
    *         have empty string process IDs.
    */
   public Multimap<String, Span> getSpansByPidMap() {
-    return HashMultimap.<String, Span> create(spansByPid);
+    return HashMultimap.<String, Span>create(spansByPid);
   }
 }
\ No newline at end of file
diff --git a/src/main/java/org/cloudera/htrace/Tracer.java b/htrace-core/src/main/java/org/htrace/Tracer.java
similarity index 86%
rename from src/main/java/org/cloudera/htrace/Tracer.java
rename to htrace-core/src/main/java/org/htrace/Tracer.java
index 9efe627..bf69afc 100644
--- a/src/main/java/org/cloudera/htrace/Tracer.java
+++ b/htrace-core/src/main/java/org/htrace/Tracer.java
@@ -14,18 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
 
-import java.lang.management.ManagementFactory;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.htrace.impl.MilliSpan;
+
 import java.security.SecureRandom;
 import java.util.List;
 import java.util.Random;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.cloudera.htrace.impl.MilliSpan;
-
 /**
  * A Tracer provides the implementation for collecting and distributing Spans
  * within a process.
@@ -43,13 +42,17 @@
   public static final TraceInfo DONT_TRACE = new TraceInfo(-1, -1);
   protected static String processId = null;
 
-  private static Tracer instance = null;
+  /**
+   * Internal class for defered singleton idiom.
+   * <p/>
+   * https://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom
+   */
+  private static class TracerHolder {
+    private static final Tracer INSTANCE = new Tracer();
+  }
 
-  synchronized protected static Tracer getInstance() {
-    if (instance == null) {
-      instance = new Tracer();
-    }
-    return instance;
+  public static Tracer getInstance() {
+    return TracerHolder.INSTANCE;
   }
 
   protected Span createNew(String description) {
@@ -73,7 +76,7 @@
     return currentSpan.get();
   }
 
-  protected void deliver(Span span) {
+  public void deliver(Span span) {
     for (SpanReceiver receiver : receivers) {
       receiver.receiveSpan(span);
     }
@@ -91,7 +94,7 @@
     currentSpan.set(span);
     return span;
   }
-  
+
 
   public TraceScope continueSpan(Span s) {
     Span oldCurrent = currentSpan();
@@ -105,7 +108,6 @@
 
   static String getProcessId() {
     if (processId == null) {
-      String mxBeanName = ManagementFactory.getRuntimeMXBean().getName();
       String cmdLine = System.getProperty("sun.java.command");
       if (cmdLine != null && !cmdLine.isEmpty()) {
         String fullClassName = cmdLine.split("\\s+")[0];
@@ -113,7 +115,7 @@
         cmdLine = classParts[classParts.length - 1];
       }
 
-      processId = cmdLine + " (" + mxBeanName + ")";
+      processId = (cmdLine == null || cmdLine.isEmpty()) ? "Unknown" : cmdLine;
     }
     return processId;
   }
diff --git a/src/main/java/org/cloudera/htrace/impl/AlwaysSampler.java b/htrace-core/src/main/java/org/htrace/impl/AlwaysSampler.java
similarity index 93%
rename from src/main/java/org/cloudera/htrace/impl/AlwaysSampler.java
rename to htrace-core/src/main/java/org/htrace/impl/AlwaysSampler.java
index f1446dc..c8d53e8 100644
--- a/src/main/java/org/cloudera/htrace/impl/AlwaysSampler.java
+++ b/htrace-core/src/main/java/org/htrace/impl/AlwaysSampler.java
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.impl;
+package org.htrace.impl;
 
-import org.cloudera.htrace.Sampler;
+import org.htrace.Sampler;
 
 public final class AlwaysSampler implements Sampler<Object> {
 
diff --git a/src/main/java/org/cloudera/htrace/impl/CountSampler.java b/htrace-core/src/main/java/org/htrace/impl/CountSampler.java
similarity index 93%
rename from src/main/java/org/cloudera/htrace/impl/CountSampler.java
rename to htrace-core/src/main/java/org/htrace/impl/CountSampler.java
index 2af4623..8afcd8e 100644
--- a/src/main/java/org/cloudera/htrace/impl/CountSampler.java
+++ b/htrace-core/src/main/java/org/htrace/impl/CountSampler.java
@@ -14,15 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.impl;
+package org.htrace.impl;
+
+import org.htrace.Sampler;
 
 import java.util.Random;
 
-import org.cloudera.htrace.Sampler;
-
 /**
  * Sampler that returns true every N calls.
- * 
  */
 public class CountSampler implements Sampler<Object> {
 
diff --git a/src/main/java/org/cloudera/htrace/impl/LocalFileSpanReceiver.java b/htrace-core/src/main/java/org/htrace/impl/LocalFileSpanReceiver.java
similarity index 96%
rename from src/main/java/org/cloudera/htrace/impl/LocalFileSpanReceiver.java
rename to htrace-core/src/main/java/org/htrace/impl/LocalFileSpanReceiver.java
index 97aac7a..631bb97 100644
--- a/src/main/java/org/cloudera/htrace/impl/LocalFileSpanReceiver.java
+++ b/htrace-core/src/main/java/org/htrace/impl/LocalFileSpanReceiver.java
@@ -14,7 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.impl;
+package org.htrace.impl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.htrace.HTraceConfiguration;
+import org.htrace.Span;
+import org.htrace.SpanReceiver;
+import org.mortbay.util.ajax.JSON;
 
 import java.io.BufferedWriter;
 import java.io.FileWriter;
@@ -26,13 +33,6 @@
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.cloudera.htrace.HTraceConfiguration;
-import org.cloudera.htrace.Span;
-import org.cloudera.htrace.SpanReceiver;
-import org.mortbay.util.ajax.JSON;
-
 /**
  * Writes the spans it receives to a local file. For now I am ignoring the data
  * (annotations) portion of the spans. A production LocalFileSpanReceiver should
@@ -55,8 +55,8 @@
 
   public LocalFileSpanReceiver() {
   }
-  
-  
+
+
   @Override
   public void configure(HTraceConfiguration conf) {
     this.executorTerminationTimeoutDuration = DEFAULT_EXECUTOR_TERMINATION_TIMEOUT_DURATION;
diff --git a/src/main/java/org/cloudera/htrace/impl/MilliSpan.java b/htrace-core/src/main/java/org/htrace/impl/MilliSpan.java
similarity index 89%
rename from src/main/java/org/cloudera/htrace/impl/MilliSpan.java
rename to htrace-core/src/main/java/org/htrace/impl/MilliSpan.java
index 396627e..0d47a40 100644
--- a/src/main/java/org/cloudera/htrace/impl/MilliSpan.java
+++ b/htrace-core/src/main/java/org/htrace/impl/MilliSpan.java
@@ -14,7 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.impl;
+package org.htrace.impl;
+
+import org.htrace.Span;
+import org.htrace.TimelineAnnotation;
+import org.htrace.Tracer;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -23,9 +27,6 @@
 import java.util.Map;
 import java.util.Random;
 
-import org.cloudera.htrace.Span;
-import org.cloudera.htrace.TimelineAnnotation;
-
 /**
  * A Span implementation that stores its information in milliseconds since the
  * epoch.
@@ -33,7 +34,7 @@
 public class MilliSpan implements Span {
 
   private static Random rand = new Random();
-  
+
   private long start;
   private long stop;
   private final String description;
@@ -61,12 +62,15 @@
 
   @Override
   public synchronized void stop() {
-    if (start == 0)
-      throw new IllegalStateException("Span for " + description
-          + " has not been started");
-    stop = System.currentTimeMillis();
+    if (stop == 0) {
+      if (start == 0)
+        throw new IllegalStateException("Span for " + description
+            + " has not been started");
+      stop = System.currentTimeMillis();
+      Tracer.getInstance().deliver(this);
+    }
   }
-  
+
   protected long currentTimeMillis() {
     return System.currentTimeMillis();
   }
@@ -88,7 +92,7 @@
   @Override
   public String toString() {
     return "start=" + start + "\nstop=" + stop + "\nparentId=" + parentSpanId
-        + "\ndescription=" + description + "\nspanId=" + spanId
+        + "\ndescription=" + description + "\nspanId=" + spanId + "\ntraceId=" + traceId
         + "\ntraceInfo=" + traceInfo + "\nprocessId=" + processId
         + "\ntimeline=" + timeline;
   }
@@ -129,7 +133,7 @@
       traceInfo = new HashMap<byte[], byte[]>();
     traceInfo.put(key, value);
   }
-  
+
   @Override
   public void addTimelineAnnotation(String msg) {
     if (timeline == null) {
@@ -144,7 +148,7 @@
       return Collections.emptyMap();
     return Collections.unmodifiableMap(traceInfo);
   }
-  
+
   @Override
   public List<TimelineAnnotation> getTimelineAnnotations() {
     if (timeline == null) {
diff --git a/src/main/java/org/cloudera/htrace/impl/NeverSampler.java b/htrace-core/src/main/java/org/htrace/impl/NeverSampler.java
similarity index 93%
rename from src/main/java/org/cloudera/htrace/impl/NeverSampler.java
rename to htrace-core/src/main/java/org/htrace/impl/NeverSampler.java
index 157bea4..38b193a 100644
--- a/src/main/java/org/cloudera/htrace/impl/NeverSampler.java
+++ b/htrace-core/src/main/java/org/htrace/impl/NeverSampler.java
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.impl;
+package org.htrace.impl;
 
-import org.cloudera.htrace.Sampler;
+import org.htrace.Sampler;
 
 public final class NeverSampler implements Sampler<Object> {
 
diff --git a/htrace-core/src/main/java/org/htrace/impl/POJOSpanReceiver.java b/htrace-core/src/main/java/org/htrace/impl/POJOSpanReceiver.java
new file mode 100644
index 0000000..54874e6
--- /dev/null
+++ b/htrace-core/src/main/java/org/htrace/impl/POJOSpanReceiver.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.htrace.impl;
+
+import org.htrace.HTraceConfiguration;
+import org.htrace.Span;
+import org.htrace.SpanReceiver;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * SpanReceiver for testing only that just collects the Span objects it
+ * receives. The spans it receives can be accessed with getSpans();
+ */
+public class POJOSpanReceiver implements SpanReceiver {
+  private final Collection<Span> spans;
+
+  @Override
+  public void configure(HTraceConfiguration conf) {
+  }
+
+  /**
+   * @return The spans this POJOSpanReceiver has received.
+   */
+  public Collection<Span> getSpans() {
+    return spans;
+  }
+
+  public POJOSpanReceiver() {
+    this.spans = new HashSet<Span>();
+  }
+
+  @Override
+  public void close() throws IOException {
+  }
+
+  @Override
+  public void receiveSpan(Span span) {
+    spans.add(span);
+  }
+
+}
diff --git a/src/main/java/org/cloudera/htrace/impl/ProbabilitySampler.java b/htrace-core/src/main/java/org/htrace/impl/ProbabilitySampler.java
similarity index 93%
rename from src/main/java/org/cloudera/htrace/impl/ProbabilitySampler.java
rename to htrace-core/src/main/java/org/htrace/impl/ProbabilitySampler.java
index 4a0ffe4..1ac1f73 100644
--- a/src/main/java/org/cloudera/htrace/impl/ProbabilitySampler.java
+++ b/htrace-core/src/main/java/org/htrace/impl/ProbabilitySampler.java
@@ -14,12 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.impl;
+package org.htrace.impl;
+
+import org.htrace.Sampler;
 
 import java.util.Random;
 
-import org.cloudera.htrace.Sampler;
-
 public class ProbabilitySampler implements Sampler<Object> {
   public final double threshold;
   private Random random;
diff --git a/src/main/java/org/cloudera/htrace/impl/CountSampler.java b/htrace-core/src/main/java/org/htrace/impl/StandardOutSpanReceiver.java
similarity index 63%
copy from src/main/java/org/cloudera/htrace/impl/CountSampler.java
copy to htrace-core/src/main/java/org/htrace/impl/StandardOutSpanReceiver.java
index 2af4623..f1ebada 100644
--- a/src/main/java/org/cloudera/htrace/impl/CountSampler.java
+++ b/htrace-core/src/main/java/org/htrace/impl/StandardOutSpanReceiver.java
@@ -14,29 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.impl;
+package org.htrace.impl;
 
-import java.util.Random;
+import org.htrace.HTraceConfiguration;
+import org.htrace.Span;
+import org.htrace.SpanReceiver;
 
-import org.cloudera.htrace.Sampler;
+import java.io.IOException;
 
 /**
- * Sampler that returns true every N calls.
- * 
+ * Used for testing. Simply prints to standard out any spans it receives.
  */
-public class CountSampler implements Sampler<Object> {
+public class StandardOutSpanReceiver implements SpanReceiver {
 
-  final static Random random = new Random();
-
-  final long frequency;
-  long count = random.nextLong();
-
-  public CountSampler(long frequency) {
-    this.frequency = frequency;
+  @Override
+  public void configure(HTraceConfiguration conf) {
   }
 
   @Override
-  public boolean next(Object info) {
-    return (count++ % frequency) == 0;
+  public void receiveSpan(Span span) {
+    System.out.println(span);
+  }
+
+  @Override
+  public void close() throws IOException {
   }
 }
diff --git a/src/main/java/org/cloudera/htrace/impl/AlwaysSampler.java b/htrace-core/src/main/java/org/htrace/impl/TrueIfTracingSampler.java
similarity index 69%
copy from src/main/java/org/cloudera/htrace/impl/AlwaysSampler.java
copy to htrace-core/src/main/java/org/htrace/impl/TrueIfTracingSampler.java
index f1446dc..100650e 100644
--- a/src/main/java/org/cloudera/htrace/impl/AlwaysSampler.java
+++ b/htrace-core/src/main/java/org/htrace/impl/TrueIfTracingSampler.java
@@ -14,19 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.impl;
+package org.htrace.impl;
 
-import org.cloudera.htrace.Sampler;
+import org.htrace.Sampler;
+import org.htrace.Trace;
 
-public final class AlwaysSampler implements Sampler<Object> {
+/**
+ * A Sampler that returns true if and only if tracing is on the current thread.
+ */
+public class TrueIfTracingSampler implements Sampler<Object> {
 
-  public static final AlwaysSampler INSTANCE = new AlwaysSampler();
+  public static final TrueIfTracingSampler INSTANCE = new TrueIfTracingSampler();
 
-  private AlwaysSampler() {
+  private TrueIfTracingSampler() {
   }
 
   @Override
   public boolean next(Object info) {
-    return true;
+    return Trace.isTracing();
   }
+
 }
diff --git a/src/main/java/org/cloudera/htrace/wrappers/TraceCallable.java b/htrace-core/src/main/java/org/htrace/wrappers/TraceCallable.java
similarity index 75%
rename from src/main/java/org/cloudera/htrace/wrappers/TraceCallable.java
rename to htrace-core/src/main/java/org/htrace/wrappers/TraceCallable.java
index 5a58e74..5b25893 100644
--- a/src/main/java/org/cloudera/htrace/wrappers/TraceCallable.java
+++ b/htrace-core/src/main/java/org/htrace/wrappers/TraceCallable.java
@@ -14,35 +14,40 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.wrappers;
+package org.htrace.wrappers;
+
+import org.htrace.Span;
+import org.htrace.Trace;
+import org.htrace.TraceScope;
 
 import java.util.concurrent.Callable;
 
-import org.cloudera.htrace.Span;
-import org.cloudera.htrace.Trace;
-import org.cloudera.htrace.TraceScope;
-
 /**
  * Wrap a Callable with a Span that survives a change in threads.
- * 
  */
 public class TraceCallable<V> implements Callable<V> {
   private final Callable<V> impl;
   private final Span parent;
+  private final String description;
 
   public TraceCallable(Callable<V> impl) {
     this(Trace.currentSpan(), impl);
   }
 
   public TraceCallable(Span parent, Callable<V> impl) {
+    this(parent, impl, null);
+  }
+
+  public TraceCallable(Span parent, Callable<V> impl, String description) {
     this.impl = impl;
     this.parent = parent;
+    this.description = description;
   }
 
   @Override
   public V call() throws Exception {
     if (parent != null) {
-      TraceScope chunk = Trace.startSpan(Thread.currentThread().getName(), parent);
+      TraceScope chunk = Trace.startSpan(getDescription(), parent);
 
       try {
         return impl.call();
@@ -57,4 +62,8 @@
   public Callable<V> getImpl() {
     return impl;
   }
+
+  private String getDescription() {
+    return this.description == null ? Thread.currentThread().getName() : description;
+  }
 }
diff --git a/src/main/java/org/cloudera/htrace/wrappers/TraceExecutorService.java b/htrace-core/src/main/java/org/htrace/wrappers/TraceExecutorService.java
similarity index 93%
rename from src/main/java/org/cloudera/htrace/wrappers/TraceExecutorService.java
rename to htrace-core/src/main/java/org/htrace/wrappers/TraceExecutorService.java
index ac31981..95ae88a 100644
--- a/src/main/java/org/cloudera/htrace/wrappers/TraceExecutorService.java
+++ b/htrace-core/src/main/java/org/htrace/wrappers/TraceExecutorService.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.wrappers;
+package org.htrace.wrappers;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -98,7 +98,7 @@
 
   @Override
   public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
-      long timeout, TimeUnit unit) throws InterruptedException {
+                                       long timeout, TimeUnit unit) throws InterruptedException {
     return impl.invokeAll(wrapCollection(tasks), timeout, unit);
   }
 
@@ -110,7 +110,7 @@
 
   @Override
   public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout,
-      TimeUnit unit) throws InterruptedException, ExecutionException,
+                         TimeUnit unit) throws InterruptedException, ExecutionException,
       TimeoutException {
     return impl.invokeAny(wrapCollection(tasks), timeout, unit);
   }
diff --git a/src/main/java/org/cloudera/htrace/wrappers/TraceProxy.java b/htrace-core/src/main/java/org/htrace/wrappers/TraceProxy.java
similarity index 91%
rename from src/main/java/org/cloudera/htrace/wrappers/TraceProxy.java
rename to htrace-core/src/main/java/org/htrace/wrappers/TraceProxy.java
index 92118b3..42cdd5a 100644
--- a/src/main/java/org/cloudera/htrace/wrappers/TraceProxy.java
+++ b/htrace-core/src/main/java/org/htrace/wrappers/TraceProxy.java
@@ -14,21 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.wrappers;
+package org.htrace.wrappers;
+
+import org.htrace.Sampler;
+import org.htrace.Trace;
+import org.htrace.TraceScope;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 
-import org.cloudera.htrace.Sampler;
-import org.cloudera.htrace.Span;
-import org.cloudera.htrace.Trace;
-import org.cloudera.htrace.TraceScope;
-
 public class TraceProxy {
   /**
    * Returns an object that will trace all calls to itself.
-   * 
+   *
    * @param instance
    * @return
    */
@@ -38,9 +37,8 @@
 
   /**
    * Returns an object that will trace all calls to itself.
-   * 
+   *
    * @param <V>
-   * 
    * @param instance
    * @param sampler
    * @return
diff --git a/src/main/java/org/cloudera/htrace/wrappers/TraceRunnable.java b/htrace-core/src/main/java/org/htrace/wrappers/TraceRunnable.java
similarity index 73%
rename from src/main/java/org/cloudera/htrace/wrappers/TraceRunnable.java
rename to htrace-core/src/main/java/org/htrace/wrappers/TraceRunnable.java
index fdfa895..9f7ea1e 100644
--- a/src/main/java/org/cloudera/htrace/wrappers/TraceRunnable.java
+++ b/htrace-core/src/main/java/org/htrace/wrappers/TraceRunnable.java
@@ -14,35 +14,39 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace.wrappers;
+package org.htrace.wrappers;
 
-import org.cloudera.htrace.Sampler;
-import org.cloudera.htrace.Span;
-import org.cloudera.htrace.Trace;
-import org.cloudera.htrace.TraceScope;
+import org.htrace.Span;
+import org.htrace.Trace;
+import org.htrace.TraceScope;
 
 /**
  * Wrap a Runnable with a Span that survives a change in threads.
- * 
  */
 public class TraceRunnable implements Runnable {
 
   private final Span parent;
   private final Runnable runnable;
+  private final String description;
 
   public TraceRunnable(Runnable runnable) {
     this(Trace.currentSpan(), runnable);
   }
 
   public TraceRunnable(Span parent, Runnable runnable) {
+    this(parent, runnable, null);
+  }
+
+  public TraceRunnable(Span parent, Runnable runnable, String description) {
     this.parent = parent;
     this.runnable = runnable;
+    this.description = description;
   }
 
   @Override
   public void run() {
     if (parent != null) {
-      TraceScope chunk = Trace.startSpan(Thread.currentThread().getName(), parent);
+      TraceScope chunk = Trace.startSpan(getDescription(), parent);
 
       try {
         runnable.run();
@@ -53,4 +57,8 @@
       runnable.run();
     }
   }
+
+  private String getDescription() {
+    return this.description == null ? Thread.currentThread().getName() : description;
+  }
 }
diff --git a/src/test/java/org/cloudera/htrace/TestCountSampler.java b/htrace-core/src/test/java/org/htrace/TestCountSampler.java
similarity index 85%
rename from src/test/java/org/cloudera/htrace/TestCountSampler.java
rename to htrace-core/src/test/java/org/htrace/TestCountSampler.java
index 80d072e..25a0d9e 100644
--- a/src/test/java/org/cloudera/htrace/TestCountSampler.java
+++ b/htrace-core/src/test/java/org/htrace/TestCountSampler.java
@@ -14,15 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
 
-import static org.junit.Assert.assertEquals;
-
-import org.cloudera.htrace.impl.CountSampler;
+import org.htrace.impl.CountSampler;
+import org.junit.Assert;
 import org.junit.Test;
 
 public class TestCountSampler {
-  
+
   @Test
   public void testNext() {
     CountSampler half = new CountSampler(2);
@@ -35,7 +34,7 @@
       if (hundred.next(null))
         hundredCount++;
     }
-    assertEquals(2, hundredCount);
-    assertEquals(100, halfCount);
+    Assert.assertEquals(2, hundredCount);
+    Assert.assertEquals(100, halfCount);
   }
 }
diff --git a/src/test/java/org/cloudera/htrace/TestHTrace.java b/htrace-core/src/test/java/org/htrace/TestHTrace.java
similarity index 78%
rename from src/test/java/org/cloudera/htrace/TestHTrace.java
rename to htrace-core/src/test/java/org/htrace/TestHTrace.java
index aac0a76..f613755 100644
--- a/src/test/java/org/cloudera/htrace/TestHTrace.java
+++ b/htrace-core/src/test/java/org/htrace/TestHTrace.java
@@ -14,10 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import com.google.common.collect.Multimap;
+import org.htrace.impl.LocalFileSpanReceiver;
+import org.htrace.impl.POJOSpanReceiver;
+import org.htrace.impl.StandardOutSpanReceiver;
+import org.junit.Assert;
+import org.junit.Test;
 
 import java.io.File;
 import java.util.Collection;
@@ -25,20 +29,13 @@
 import java.util.HashSet;
 import java.util.Map;
 
-import org.cloudera.htrace.impl.LocalFileSpanReceiver;
-import org.cloudera.htrace.impl.POJOSpanReceiver;
-import org.cloudera.htrace.impl.StandardOutSpanReceiver;
-import org.junit.Test;
-
-import com.google.common.collect.Multimap;
-
 public class TestHTrace {
 
   public static final String SPAN_FILE_FLAG = "spanFile";
 
   /**
    * Basic system test of HTrace.
-   * 
+   *
    * @throws Exception
    */
   @Test
@@ -77,35 +74,35 @@
     Collection<Span> spans = psr.getSpans();
     TraceTree traceTree = new TraceTree(spans);
     Collection<Span> roots = traceTree.getRoots();
-    assertEquals(numTraces, roots.size());
+    Assert.assertEquals(numTraces, roots.size());
 
-    Map<String,Span> descriptionToRootSpan = new HashMap<String,Span>();
+    Map<String, Span> descriptionToRootSpan = new HashMap<String, Span>();
     for (Span root : roots) {
       descriptionToRootSpan.put(root.getDescription(), root);
     }
-    
-    assertTrue(descriptionToRootSpan.keySet().contains(
+
+    Assert.assertTrue(descriptionToRootSpan.keySet().contains(
         TraceCreator.RPC_TRACE_ROOT));
-    assertTrue(descriptionToRootSpan.keySet().contains(
+    Assert.assertTrue(descriptionToRootSpan.keySet().contains(
         TraceCreator.SIMPLE_TRACE_ROOT));
-    assertTrue(descriptionToRootSpan.keySet().contains(
+    Assert.assertTrue(descriptionToRootSpan.keySet().contains(
         TraceCreator.THREADED_TRACE_ROOT));
-    
+
     Multimap<Long, Span> spansByParentId = traceTree.getSpansByParentIdMap();
     Span rpcTraceRoot = descriptionToRootSpan.get(TraceCreator.RPC_TRACE_ROOT);
-    assertEquals(1, spansByParentId.get(rpcTraceRoot.getSpanId()).size());
+    Assert.assertEquals(1, spansByParentId.get(rpcTraceRoot.getSpanId()).size());
 
     Span rpcTraceChild1 = spansByParentId.get(rpcTraceRoot.getSpanId())
         .iterator().next();
-    assertEquals(1, spansByParentId.get(rpcTraceChild1.getSpanId()).size());
+    Assert.assertEquals(1, spansByParentId.get(rpcTraceChild1.getSpanId()).size());
 
     Span rpcTraceChild2 = spansByParentId.get(rpcTraceChild1.getSpanId())
         .iterator().next();
-    assertEquals(1, spansByParentId.get(rpcTraceChild2.getSpanId()).size());
+    Assert.assertEquals(1, spansByParentId.get(rpcTraceChild2.getSpanId()).size());
 
     Span rpcTraceChild3 = spansByParentId.get(rpcTraceChild2.getSpanId())
         .iterator().next();
-    assertEquals(0, spansByParentId.get(rpcTraceChild3.getSpanId()).size());
+    Assert.assertEquals(0, spansByParentId.get(rpcTraceChild3.getSpanId()).size());
   }
 
   private void runTraceCreatorTraces(TraceCreator tc) {
diff --git a/htrace-core/src/test/java/org/htrace/TestHTraceConfiguration.java b/htrace-core/src/test/java/org/htrace/TestHTraceConfiguration.java
new file mode 100644
index 0000000..5506ddc
--- /dev/null
+++ b/htrace-core/src/test/java/org/htrace/TestHTraceConfiguration.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.htrace;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class TestHTraceConfiguration {
+  @Test
+  public void testGetBoolean() throws Exception {
+
+    Map<String, String> m = new HashMap<String, String>();
+    m.put("testTrue", " True");
+    m.put("testFalse", "falsE ");
+    HTraceConfiguration configuration = HTraceConfiguration.fromMap(m);
+
+    // Tests for value being there
+    assertTrue(configuration.getBoolean("testTrue", false));
+    assertFalse(configuration.getBoolean("testFalse", true));
+
+    // Test for absent
+    assertTrue(configuration.getBoolean("absent", true));
+    assertFalse(configuration.getBoolean("absent", false));
+  }
+
+  @Test
+  public void testGetInt() throws Exception {
+    Map<String, String> m = new HashMap<String, String>();
+    m.put("a", "100");
+    m.put("b", "0");
+    m.put("c", "-100");
+    m.put("d", "5");
+
+    HTraceConfiguration configuration = HTraceConfiguration.fromMap(m);
+    assertEquals(100, configuration.getInt("a", -999));
+    assertEquals(0, configuration.getInt("b", -999));
+    assertEquals(-100, configuration.getInt("c", -999));
+    assertEquals(5, configuration.getInt("d", -999));
+    assertEquals(-999, configuration.getInt("absent", -999));
+  }
+}
diff --git a/htrace-core/src/test/java/org/htrace/TestSampler.java b/htrace-core/src/test/java/org/htrace/TestSampler.java
new file mode 100644
index 0000000..9bb2e8b
--- /dev/null
+++ b/htrace-core/src/test/java/org/htrace/TestSampler.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.htrace;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestSampler {
+  @Test
+  public void testParamterizedSampler() {
+    TestParamSampler sampler = new TestParamSampler();
+    TraceScope s = Trace.startSpan("test", sampler, 1);
+    Assert.assertNotNull(s.getSpan());
+    s.close();
+    s = Trace.startSpan("test", sampler, -1);
+    Assert.assertNull(s.getSpan());
+    s.close();
+  }
+
+  @Test
+  public void testAlwaysSampler() {
+    TraceScope cur = Trace.startSpan("test", new TraceInfo(0, 0));
+    Assert.assertNotNull(cur);
+    cur.close();
+  }
+
+  private class TestParamSampler implements Sampler<Integer> {
+
+    @Override
+    public boolean next(Integer info) {
+      return info > 0;
+    }
+
+  }
+}
diff --git a/src/test/java/org/cloudera/htrace/TraceCreator.java b/htrace-core/src/test/java/org/htrace/TraceCreator.java
similarity index 98%
rename from src/test/java/org/cloudera/htrace/TraceCreator.java
rename to htrace-core/src/test/java/org/htrace/TraceCreator.java
index 34c38d1..7a034b8 100644
--- a/src/test/java/org/cloudera/htrace/TraceCreator.java
+++ b/htrace-core/src/test/java/org/htrace/TraceCreator.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.cloudera.htrace;
+package org.htrace;
 
 import java.util.Collection;
 import java.util.Random;
@@ -31,7 +31,7 @@
   /**
    * Takes as input the SpanReceiver that should used as the sink for Spans when
    * createDemoTrace() is called.
-   * 
+   *
    * @param receiver
    */
   public TraceCreator(SpanReceiver receiver) {
@@ -41,7 +41,7 @@
   /**
    * Takes as input the SpanReceivers that should used as the sink for Spans
    * when createDemoTrace() is called.
-   * 
+   *
    * @param receivers
    */
   public TraceCreator(Collection<SpanReceiver> receivers) {
diff --git a/htrace-zipkin/pom.xml b/htrace-zipkin/pom.xml
new file mode 100644
index 0000000..b61f7d2
--- /dev/null
+++ b/htrace-zipkin/pom.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+license agreements. See the NOTICE file distributed with this work for additional
+information regarding copyright ownership. The ASF licenses this file to
+You under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of
+the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+by applicable law or agreed to in writing, software distributed under the
+License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+OF ANY KIND, either express or implied. See the License for the specific
+language governing permissions and limitations under the License. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>htrace-zipkin</artifactId>
+  <packaging>jar</packaging>
+
+  <parent>
+    <artifactId>htrace</artifactId>
+    <groupId>org.htrace</groupId>
+    <version>3.0-SNAPSHOT</version>
+  </parent>
+
+  <name>htrace-zipkin</name>
+  <url>https://github.com/cloudera/htrace</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-javadoc-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-gpg-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <!-- explicitly define maven-deploy-plugin after other to force exec order -->
+        <artifactId>maven-deploy-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <!-- Module deps. -->
+    <dependency>
+      <groupId>org.htrace</groupId>
+      <artifactId>htrace-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <!-- Global deps. -->
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <!-- Zipkin specific deps. -->
+    <dependency>
+      <groupId>org.apache.thrift</groupId>
+      <artifactId>libthrift</artifactId>
+      <version>0.9.0</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <version>1.7</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Annotation.java b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Annotation.java
new file mode 100644
index 0000000..70cb0b6
--- /dev/null
+++ b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Annotation.java
@@ -0,0 +1,687 @@
+/**
+ * Autogenerated by Thrift Compiler (0.9.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package com.twitter.zipkin.gen;
+
+import org.apache.thrift.scheme.IScheme;
+import org.apache.thrift.scheme.SchemeFactory;
+import org.apache.thrift.scheme.StandardScheme;
+
+import org.apache.thrift.scheme.TupleScheme;
+import org.apache.thrift.protocol.TTupleProtocol;
+import org.apache.thrift.protocol.TProtocolException;
+import org.apache.thrift.EncodingUtils;
+import org.apache.thrift.TException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Annotation implements org.apache.thrift.TBase<Annotation, Annotation._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Annotation");
+
+  private static final org.apache.thrift.protocol.TField TIMESTAMP_FIELD_DESC = new org.apache.thrift.protocol.TField("timestamp", org.apache.thrift.protocol.TType.I64, (short)1);
+  private static final org.apache.thrift.protocol.TField VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("value", org.apache.thrift.protocol.TType.STRING, (short)2);
+  private static final org.apache.thrift.protocol.TField HOST_FIELD_DESC = new org.apache.thrift.protocol.TField("host", org.apache.thrift.protocol.TType.STRUCT, (short)3);
+  private static final org.apache.thrift.protocol.TField DURATION_FIELD_DESC = new org.apache.thrift.protocol.TField("duration", org.apache.thrift.protocol.TType.I32, (short)4);
+
+  private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
+  static {
+    schemes.put(StandardScheme.class, new AnnotationStandardSchemeFactory());
+    schemes.put(TupleScheme.class, new AnnotationTupleSchemeFactory());
+  }
+
+  public long timestamp; // required
+  public String value; // required
+  public Endpoint host; // optional
+  public int duration; // optional
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    TIMESTAMP((short)1, "timestamp"),
+    VALUE((short)2, "value"),
+    HOST((short)3, "host"),
+    DURATION((short)4, "duration");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // TIMESTAMP
+          return TIMESTAMP;
+        case 2: // VALUE
+          return VALUE;
+        case 3: // HOST
+          return HOST;
+        case 4: // DURATION
+          return DURATION;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __TIMESTAMP_ISSET_ID = 0;
+  private static final int __DURATION_ISSET_ID = 1;
+  private byte __isset_bitfield = 0;
+  private _Fields optionals[] = {_Fields.HOST,_Fields.DURATION};
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.TIMESTAMP, new org.apache.thrift.meta_data.FieldMetaData("timestamp", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
+    tmpMap.put(_Fields.VALUE, new org.apache.thrift.meta_data.FieldMetaData("value", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.HOST, new org.apache.thrift.meta_data.FieldMetaData("host", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Endpoint.class)));
+    tmpMap.put(_Fields.DURATION, new org.apache.thrift.meta_data.FieldMetaData("duration", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Annotation.class, metaDataMap);
+  }
+
+  public Annotation() {
+  }
+
+  public Annotation(
+    long timestamp,
+    String value)
+  {
+    this();
+    this.timestamp = timestamp;
+    setTimestampIsSet(true);
+    this.value = value;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public Annotation(Annotation other) {
+    __isset_bitfield = other.__isset_bitfield;
+    this.timestamp = other.timestamp;
+    if (other.isSetValue()) {
+      this.value = other.value;
+    }
+    if (other.isSetHost()) {
+      this.host = new Endpoint(other.host);
+    }
+    this.duration = other.duration;
+  }
+
+  public Annotation deepCopy() {
+    return new Annotation(this);
+  }
+
+  @Override
+  public void clear() {
+    setTimestampIsSet(false);
+    this.timestamp = 0;
+    this.value = null;
+    this.host = null;
+    setDurationIsSet(false);
+    this.duration = 0;
+  }
+
+  public long getTimestamp() {
+    return this.timestamp;
+  }
+
+  public Annotation setTimestamp(long timestamp) {
+    this.timestamp = timestamp;
+    setTimestampIsSet(true);
+    return this;
+  }
+
+  public void unsetTimestamp() {
+    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __TIMESTAMP_ISSET_ID);
+  }
+
+  /** Returns true if field timestamp is set (has been assigned a value) and false otherwise */
+  public boolean isSetTimestamp() {
+    return EncodingUtils.testBit(__isset_bitfield, __TIMESTAMP_ISSET_ID);
+  }
+
+  public void setTimestampIsSet(boolean value) {
+    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __TIMESTAMP_ISSET_ID, value);
+  }
+
+  public String getValue() {
+    return this.value;
+  }
+
+  public Annotation setValue(String value) {
+    this.value = value;
+    return this;
+  }
+
+  public void unsetValue() {
+    this.value = null;
+  }
+
+  /** Returns true if field value is set (has been assigned a value) and false otherwise */
+  public boolean isSetValue() {
+    return this.value != null;
+  }
+
+  public void setValueIsSet(boolean value) {
+    if (!value) {
+      this.value = null;
+    }
+  }
+
+  public Endpoint getHost() {
+    return this.host;
+  }
+
+  public Annotation setHost(Endpoint host) {
+    this.host = host;
+    return this;
+  }
+
+  public void unsetHost() {
+    this.host = null;
+  }
+
+  /** Returns true if field host is set (has been assigned a value) and false otherwise */
+  public boolean isSetHost() {
+    return this.host != null;
+  }
+
+  public void setHostIsSet(boolean value) {
+    if (!value) {
+      this.host = null;
+    }
+  }
+
+  public int getDuration() {
+    return this.duration;
+  }
+
+  public Annotation setDuration(int duration) {
+    this.duration = duration;
+    setDurationIsSet(true);
+    return this;
+  }
+
+  public void unsetDuration() {
+    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __DURATION_ISSET_ID);
+  }
+
+  /** Returns true if field duration is set (has been assigned a value) and false otherwise */
+  public boolean isSetDuration() {
+    return EncodingUtils.testBit(__isset_bitfield, __DURATION_ISSET_ID);
+  }
+
+  public void setDurationIsSet(boolean value) {
+    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __DURATION_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case TIMESTAMP:
+      if (value == null) {
+        unsetTimestamp();
+      } else {
+        setTimestamp((Long)value);
+      }
+      break;
+
+    case VALUE:
+      if (value == null) {
+        unsetValue();
+      } else {
+        setValue((String)value);
+      }
+      break;
+
+    case HOST:
+      if (value == null) {
+        unsetHost();
+      } else {
+        setHost((Endpoint)value);
+      }
+      break;
+
+    case DURATION:
+      if (value == null) {
+        unsetDuration();
+      } else {
+        setDuration((Integer)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case TIMESTAMP:
+      return Long.valueOf(getTimestamp());
+
+    case VALUE:
+      return getValue();
+
+    case HOST:
+      return getHost();
+
+    case DURATION:
+      return Integer.valueOf(getDuration());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case TIMESTAMP:
+      return isSetTimestamp();
+    case VALUE:
+      return isSetValue();
+    case HOST:
+      return isSetHost();
+    case DURATION:
+      return isSetDuration();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof Annotation)
+      return this.equals((Annotation)that);
+    return false;
+  }
+
+  public boolean equals(Annotation that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_timestamp = true;
+    boolean that_present_timestamp = true;
+    if (this_present_timestamp || that_present_timestamp) {
+      if (!(this_present_timestamp && that_present_timestamp))
+        return false;
+      if (this.timestamp != that.timestamp)
+        return false;
+    }
+
+    boolean this_present_value = true && this.isSetValue();
+    boolean that_present_value = true && that.isSetValue();
+    if (this_present_value || that_present_value) {
+      if (!(this_present_value && that_present_value))
+        return false;
+      if (!this.value.equals(that.value))
+        return false;
+    }
+
+    boolean this_present_host = true && this.isSetHost();
+    boolean that_present_host = true && that.isSetHost();
+    if (this_present_host || that_present_host) {
+      if (!(this_present_host && that_present_host))
+        return false;
+      if (!this.host.equals(that.host))
+        return false;
+    }
+
+    boolean this_present_duration = true && this.isSetDuration();
+    boolean that_present_duration = true && that.isSetDuration();
+    if (this_present_duration || that_present_duration) {
+      if (!(this_present_duration && that_present_duration))
+        return false;
+      if (this.duration != that.duration)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(Annotation other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    Annotation typedOther = (Annotation)other;
+
+    lastComparison = Boolean.valueOf(isSetTimestamp()).compareTo(typedOther.isSetTimestamp());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetTimestamp()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.timestamp, typedOther.timestamp);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetValue()).compareTo(typedOther.isSetValue());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetValue()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.value, typedOther.value);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetHost()).compareTo(typedOther.isSetHost());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHost()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.host, typedOther.host);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetDuration()).compareTo(typedOther.isSetDuration());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetDuration()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.duration, typedOther.duration);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("Annotation(");
+    boolean first = true;
+
+    sb.append("timestamp:");
+    sb.append(this.timestamp);
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("value:");
+    if (this.value == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.value);
+    }
+    first = false;
+    if (isSetHost()) {
+      if (!first) sb.append(", ");
+      sb.append("host:");
+      if (this.host == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.host);
+      }
+      first = false;
+    }
+    if (isSetDuration()) {
+      if (!first) sb.append(", ");
+      sb.append("duration:");
+      sb.append(this.duration);
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    // check for sub-struct validity
+    if (host != null) {
+      host.validate();
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bitfield = 0;
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private static class AnnotationStandardSchemeFactory implements SchemeFactory {
+    public AnnotationStandardScheme getScheme() {
+      return new AnnotationStandardScheme();
+    }
+  }
+
+  private static class AnnotationStandardScheme extends StandardScheme<Annotation> {
+
+    public void read(org.apache.thrift.protocol.TProtocol iprot, Annotation struct) throws org.apache.thrift.TException {
+      org.apache.thrift.protocol.TField schemeField;
+      iprot.readStructBegin();
+      while (true)
+      {
+        schemeField = iprot.readFieldBegin();
+        if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
+          break;
+        }
+        switch (schemeField.id) {
+          case 1: // TIMESTAMP
+            if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
+              struct.timestamp = iprot.readI64();
+              struct.setTimestampIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 2: // VALUE
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
+              struct.value = iprot.readString();
+              struct.setValueIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 3: // HOST
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) {
+              struct.host = new Endpoint();
+              struct.host.read(iprot);
+              struct.setHostIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 4: // DURATION
+            if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
+              struct.duration = iprot.readI32();
+              struct.setDurationIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          default:
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+        }
+        iprot.readFieldEnd();
+      }
+      iprot.readStructEnd();
+
+      // check for required fields of primitive type, which can't be checked in the validate method
+      struct.validate();
+    }
+
+    public void write(org.apache.thrift.protocol.TProtocol oprot, Annotation struct) throws org.apache.thrift.TException {
+      struct.validate();
+
+      oprot.writeStructBegin(STRUCT_DESC);
+      oprot.writeFieldBegin(TIMESTAMP_FIELD_DESC);
+      oprot.writeI64(struct.timestamp);
+      oprot.writeFieldEnd();
+      if (struct.value != null) {
+        oprot.writeFieldBegin(VALUE_FIELD_DESC);
+        oprot.writeString(struct.value);
+        oprot.writeFieldEnd();
+      }
+      if (struct.host != null) {
+        if (struct.isSetHost()) {
+          oprot.writeFieldBegin(HOST_FIELD_DESC);
+          struct.host.write(oprot);
+          oprot.writeFieldEnd();
+        }
+      }
+      if (struct.isSetDuration()) {
+        oprot.writeFieldBegin(DURATION_FIELD_DESC);
+        oprot.writeI32(struct.duration);
+        oprot.writeFieldEnd();
+      }
+      oprot.writeFieldStop();
+      oprot.writeStructEnd();
+    }
+
+  }
+
+  private static class AnnotationTupleSchemeFactory implements SchemeFactory {
+    public AnnotationTupleScheme getScheme() {
+      return new AnnotationTupleScheme();
+    }
+  }
+
+  private static class AnnotationTupleScheme extends TupleScheme<Annotation> {
+
+    @Override
+    public void write(org.apache.thrift.protocol.TProtocol prot, Annotation struct) throws org.apache.thrift.TException {
+      TTupleProtocol oprot = (TTupleProtocol) prot;
+      BitSet optionals = new BitSet();
+      if (struct.isSetTimestamp()) {
+        optionals.set(0);
+      }
+      if (struct.isSetValue()) {
+        optionals.set(1);
+      }
+      if (struct.isSetHost()) {
+        optionals.set(2);
+      }
+      if (struct.isSetDuration()) {
+        optionals.set(3);
+      }
+      oprot.writeBitSet(optionals, 4);
+      if (struct.isSetTimestamp()) {
+        oprot.writeI64(struct.timestamp);
+      }
+      if (struct.isSetValue()) {
+        oprot.writeString(struct.value);
+      }
+      if (struct.isSetHost()) {
+        struct.host.write(oprot);
+      }
+      if (struct.isSetDuration()) {
+        oprot.writeI32(struct.duration);
+      }
+    }
+
+    @Override
+    public void read(org.apache.thrift.protocol.TProtocol prot, Annotation struct) throws org.apache.thrift.TException {
+      TTupleProtocol iprot = (TTupleProtocol) prot;
+      BitSet incoming = iprot.readBitSet(4);
+      if (incoming.get(0)) {
+        struct.timestamp = iprot.readI64();
+        struct.setTimestampIsSet(true);
+      }
+      if (incoming.get(1)) {
+        struct.value = iprot.readString();
+        struct.setValueIsSet(true);
+      }
+      if (incoming.get(2)) {
+        struct.host = new Endpoint();
+        struct.host.read(iprot);
+        struct.setHostIsSet(true);
+      }
+      if (incoming.get(3)) {
+        struct.duration = iprot.readI32();
+        struct.setDurationIsSet(true);
+      }
+    }
+  }
+
+}
+
diff --git a/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/AnnotationType.java b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/AnnotationType.java
new file mode 100644
index 0000000..b66df34
--- /dev/null
+++ b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/AnnotationType.java
@@ -0,0 +1,60 @@
+/**
+ * Autogenerated by Thrift Compiler (0.9.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package com.twitter.zipkin.gen;
+
+
+import java.util.Map;
+import java.util.HashMap;
+import org.apache.thrift.TEnum;
+
+public enum AnnotationType implements org.apache.thrift.TEnum {
+  BOOL(0),
+  BYTES(1),
+  I16(2),
+  I32(3),
+  I64(4),
+  DOUBLE(5),
+  STRING(6);
+
+  private final int value;
+
+  private AnnotationType(int value) {
+    this.value = value;
+  }
+
+  /**
+   * Get the integer value of this enum value, as defined in the Thrift IDL.
+   */
+  public int getValue() {
+    return value;
+  }
+
+  /**
+   * Find a the enum type by its integer value, as defined in the Thrift IDL.
+   * @return null if the value is not found.
+   */
+  public static AnnotationType findByValue(int value) { 
+    switch (value) {
+      case 0:
+        return BOOL;
+      case 1:
+        return BYTES;
+      case 2:
+        return I16;
+      case 3:
+        return I32;
+      case 4:
+        return I64;
+      case 5:
+        return DOUBLE;
+      case 6:
+        return STRING;
+      default:
+        return null;
+    }
+  }
+}
diff --git a/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/BinaryAnnotation.java b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/BinaryAnnotation.java
new file mode 100644
index 0000000..f762f62
--- /dev/null
+++ b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/BinaryAnnotation.java
@@ -0,0 +1,721 @@
+/**
+ * Autogenerated by Thrift Compiler (0.9.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package com.twitter.zipkin.gen;
+
+import org.apache.thrift.scheme.IScheme;
+import org.apache.thrift.scheme.SchemeFactory;
+import org.apache.thrift.scheme.StandardScheme;
+
+import org.apache.thrift.scheme.TupleScheme;
+import org.apache.thrift.protocol.TTupleProtocol;
+import org.apache.thrift.protocol.TProtocolException;
+import org.apache.thrift.EncodingUtils;
+import org.apache.thrift.TException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BinaryAnnotation implements org.apache.thrift.TBase<BinaryAnnotation, BinaryAnnotation._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("BinaryAnnotation");
+
+  private static final org.apache.thrift.protocol.TField KEY_FIELD_DESC = new org.apache.thrift.protocol.TField("key", org.apache.thrift.protocol.TType.STRING, (short)1);
+  private static final org.apache.thrift.protocol.TField VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("value", org.apache.thrift.protocol.TType.STRING, (short)2);
+  private static final org.apache.thrift.protocol.TField ANNOTATION_TYPE_FIELD_DESC = new org.apache.thrift.protocol.TField("annotation_type", org.apache.thrift.protocol.TType.I32, (short)3);
+  private static final org.apache.thrift.protocol.TField HOST_FIELD_DESC = new org.apache.thrift.protocol.TField("host", org.apache.thrift.protocol.TType.STRUCT, (short)4);
+
+  private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
+  static {
+    schemes.put(StandardScheme.class, new BinaryAnnotationStandardSchemeFactory());
+    schemes.put(TupleScheme.class, new BinaryAnnotationTupleSchemeFactory());
+  }
+
+  public String key; // required
+  public ByteBuffer value; // required
+  /**
+   * 
+   * @see AnnotationType
+   */
+  public AnnotationType annotation_type; // required
+  public Endpoint host; // optional
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    KEY((short)1, "key"),
+    VALUE((short)2, "value"),
+    /**
+     * 
+     * @see AnnotationType
+     */
+    ANNOTATION_TYPE((short)3, "annotation_type"),
+    HOST((short)4, "host");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // KEY
+          return KEY;
+        case 2: // VALUE
+          return VALUE;
+        case 3: // ANNOTATION_TYPE
+          return ANNOTATION_TYPE;
+        case 4: // HOST
+          return HOST;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private _Fields optionals[] = {_Fields.HOST};
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.KEY, new org.apache.thrift.meta_data.FieldMetaData("key", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.VALUE, new org.apache.thrift.meta_data.FieldMetaData("value", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING        , true)));
+    tmpMap.put(_Fields.ANNOTATION_TYPE, new org.apache.thrift.meta_data.FieldMetaData("annotation_type", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.EnumMetaData(org.apache.thrift.protocol.TType.ENUM, AnnotationType.class)));
+    tmpMap.put(_Fields.HOST, new org.apache.thrift.meta_data.FieldMetaData("host", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Endpoint.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(BinaryAnnotation.class, metaDataMap);
+  }
+
+  public BinaryAnnotation() {
+  }
+
+  public BinaryAnnotation(
+    String key,
+    ByteBuffer value,
+    AnnotationType annotation_type)
+  {
+    this();
+    this.key = key;
+    this.value = value;
+    this.annotation_type = annotation_type;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public BinaryAnnotation(BinaryAnnotation other) {
+    if (other.isSetKey()) {
+      this.key = other.key;
+    }
+    if (other.isSetValue()) {
+      this.value = org.apache.thrift.TBaseHelper.copyBinary(other.value);
+;
+    }
+    if (other.isSetAnnotation_type()) {
+      this.annotation_type = other.annotation_type;
+    }
+    if (other.isSetHost()) {
+      this.host = new Endpoint(other.host);
+    }
+  }
+
+  public BinaryAnnotation deepCopy() {
+    return new BinaryAnnotation(this);
+  }
+
+  @Override
+  public void clear() {
+    this.key = null;
+    this.value = null;
+    this.annotation_type = null;
+    this.host = null;
+  }
+
+  public String getKey() {
+    return this.key;
+  }
+
+  public BinaryAnnotation setKey(String key) {
+    this.key = key;
+    return this;
+  }
+
+  public void unsetKey() {
+    this.key = null;
+  }
+
+  /** Returns true if field key is set (has been assigned a value) and false otherwise */
+  public boolean isSetKey() {
+    return this.key != null;
+  }
+
+  public void setKeyIsSet(boolean value) {
+    if (!value) {
+      this.key = null;
+    }
+  }
+
+  public byte[] getValue() {
+    setValue(org.apache.thrift.TBaseHelper.rightSize(value));
+    return value == null ? null : value.array();
+  }
+
+  public ByteBuffer bufferForValue() {
+    return value;
+  }
+
+  public BinaryAnnotation setValue(byte[] value) {
+    setValue(value == null ? (ByteBuffer)null : ByteBuffer.wrap(value));
+    return this;
+  }
+
+  public BinaryAnnotation setValue(ByteBuffer value) {
+    this.value = value;
+    return this;
+  }
+
+  public void unsetValue() {
+    this.value = null;
+  }
+
+  /** Returns true if field value is set (has been assigned a value) and false otherwise */
+  public boolean isSetValue() {
+    return this.value != null;
+  }
+
+  public void setValueIsSet(boolean value) {
+    if (!value) {
+      this.value = null;
+    }
+  }
+
+  /**
+   * 
+   * @see AnnotationType
+   */
+  public AnnotationType getAnnotation_type() {
+    return this.annotation_type;
+  }
+
+  /**
+   * 
+   * @see AnnotationType
+   */
+  public BinaryAnnotation setAnnotation_type(AnnotationType annotation_type) {
+    this.annotation_type = annotation_type;
+    return this;
+  }
+
+  public void unsetAnnotation_type() {
+    this.annotation_type = null;
+  }
+
+  /** Returns true if field annotation_type is set (has been assigned a value) and false otherwise */
+  public boolean isSetAnnotation_type() {
+    return this.annotation_type != null;
+  }
+
+  public void setAnnotation_typeIsSet(boolean value) {
+    if (!value) {
+      this.annotation_type = null;
+    }
+  }
+
+  public Endpoint getHost() {
+    return this.host;
+  }
+
+  public BinaryAnnotation setHost(Endpoint host) {
+    this.host = host;
+    return this;
+  }
+
+  public void unsetHost() {
+    this.host = null;
+  }
+
+  /** Returns true if field host is set (has been assigned a value) and false otherwise */
+  public boolean isSetHost() {
+    return this.host != null;
+  }
+
+  public void setHostIsSet(boolean value) {
+    if (!value) {
+      this.host = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case KEY:
+      if (value == null) {
+        unsetKey();
+      } else {
+        setKey((String)value);
+      }
+      break;
+
+    case VALUE:
+      if (value == null) {
+        unsetValue();
+      } else {
+        setValue((ByteBuffer)value);
+      }
+      break;
+
+    case ANNOTATION_TYPE:
+      if (value == null) {
+        unsetAnnotation_type();
+      } else {
+        setAnnotation_type((AnnotationType)value);
+      }
+      break;
+
+    case HOST:
+      if (value == null) {
+        unsetHost();
+      } else {
+        setHost((Endpoint)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case KEY:
+      return getKey();
+
+    case VALUE:
+      return getValue();
+
+    case ANNOTATION_TYPE:
+      return getAnnotation_type();
+
+    case HOST:
+      return getHost();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case KEY:
+      return isSetKey();
+    case VALUE:
+      return isSetValue();
+    case ANNOTATION_TYPE:
+      return isSetAnnotation_type();
+    case HOST:
+      return isSetHost();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof BinaryAnnotation)
+      return this.equals((BinaryAnnotation)that);
+    return false;
+  }
+
+  public boolean equals(BinaryAnnotation that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_key = true && this.isSetKey();
+    boolean that_present_key = true && that.isSetKey();
+    if (this_present_key || that_present_key) {
+      if (!(this_present_key && that_present_key))
+        return false;
+      if (!this.key.equals(that.key))
+        return false;
+    }
+
+    boolean this_present_value = true && this.isSetValue();
+    boolean that_present_value = true && that.isSetValue();
+    if (this_present_value || that_present_value) {
+      if (!(this_present_value && that_present_value))
+        return false;
+      if (!this.value.equals(that.value))
+        return false;
+    }
+
+    boolean this_present_annotation_type = true && this.isSetAnnotation_type();
+    boolean that_present_annotation_type = true && that.isSetAnnotation_type();
+    if (this_present_annotation_type || that_present_annotation_type) {
+      if (!(this_present_annotation_type && that_present_annotation_type))
+        return false;
+      if (!this.annotation_type.equals(that.annotation_type))
+        return false;
+    }
+
+    boolean this_present_host = true && this.isSetHost();
+    boolean that_present_host = true && that.isSetHost();
+    if (this_present_host || that_present_host) {
+      if (!(this_present_host && that_present_host))
+        return false;
+      if (!this.host.equals(that.host))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(BinaryAnnotation other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    BinaryAnnotation typedOther = (BinaryAnnotation)other;
+
+    lastComparison = Boolean.valueOf(isSetKey()).compareTo(typedOther.isSetKey());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetKey()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.key, typedOther.key);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetValue()).compareTo(typedOther.isSetValue());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetValue()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.value, typedOther.value);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetAnnotation_type()).compareTo(typedOther.isSetAnnotation_type());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetAnnotation_type()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.annotation_type, typedOther.annotation_type);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetHost()).compareTo(typedOther.isSetHost());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHost()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.host, typedOther.host);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("BinaryAnnotation(");
+    boolean first = true;
+
+    sb.append("key:");
+    if (this.key == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.key);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("value:");
+    if (this.value == null) {
+      sb.append("null");
+    } else {
+      org.apache.thrift.TBaseHelper.toString(this.value, sb);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("annotation_type:");
+    if (this.annotation_type == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.annotation_type);
+    }
+    first = false;
+    if (isSetHost()) {
+      if (!first) sb.append(", ");
+      sb.append("host:");
+      if (this.host == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.host);
+      }
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    // check for sub-struct validity
+    if (host != null) {
+      host.validate();
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private static class BinaryAnnotationStandardSchemeFactory implements SchemeFactory {
+    public BinaryAnnotationStandardScheme getScheme() {
+      return new BinaryAnnotationStandardScheme();
+    }
+  }
+
+  private static class BinaryAnnotationStandardScheme extends StandardScheme<BinaryAnnotation> {
+
+    public void read(org.apache.thrift.protocol.TProtocol iprot, BinaryAnnotation struct) throws org.apache.thrift.TException {
+      org.apache.thrift.protocol.TField schemeField;
+      iprot.readStructBegin();
+      while (true)
+      {
+        schemeField = iprot.readFieldBegin();
+        if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
+          break;
+        }
+        switch (schemeField.id) {
+          case 1: // KEY
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
+              struct.key = iprot.readString();
+              struct.setKeyIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 2: // VALUE
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
+              struct.value = iprot.readBinary();
+              struct.setValueIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 3: // ANNOTATION_TYPE
+            if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
+              struct.annotation_type = AnnotationType.findByValue(iprot.readI32());
+              struct.setAnnotation_typeIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 4: // HOST
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) {
+              struct.host = new Endpoint();
+              struct.host.read(iprot);
+              struct.setHostIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          default:
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+        }
+        iprot.readFieldEnd();
+      }
+      iprot.readStructEnd();
+
+      // check for required fields of primitive type, which can't be checked in the validate method
+      struct.validate();
+    }
+
+    public void write(org.apache.thrift.protocol.TProtocol oprot, BinaryAnnotation struct) throws org.apache.thrift.TException {
+      struct.validate();
+
+      oprot.writeStructBegin(STRUCT_DESC);
+      if (struct.key != null) {
+        oprot.writeFieldBegin(KEY_FIELD_DESC);
+        oprot.writeString(struct.key);
+        oprot.writeFieldEnd();
+      }
+      if (struct.value != null) {
+        oprot.writeFieldBegin(VALUE_FIELD_DESC);
+        oprot.writeBinary(struct.value);
+        oprot.writeFieldEnd();
+      }
+      if (struct.annotation_type != null) {
+        oprot.writeFieldBegin(ANNOTATION_TYPE_FIELD_DESC);
+        oprot.writeI32(struct.annotation_type.getValue());
+        oprot.writeFieldEnd();
+      }
+      if (struct.host != null) {
+        if (struct.isSetHost()) {
+          oprot.writeFieldBegin(HOST_FIELD_DESC);
+          struct.host.write(oprot);
+          oprot.writeFieldEnd();
+        }
+      }
+      oprot.writeFieldStop();
+      oprot.writeStructEnd();
+    }
+
+  }
+
+  private static class BinaryAnnotationTupleSchemeFactory implements SchemeFactory {
+    public BinaryAnnotationTupleScheme getScheme() {
+      return new BinaryAnnotationTupleScheme();
+    }
+  }
+
+  private static class BinaryAnnotationTupleScheme extends TupleScheme<BinaryAnnotation> {
+
+    @Override
+    public void write(org.apache.thrift.protocol.TProtocol prot, BinaryAnnotation struct) throws org.apache.thrift.TException {
+      TTupleProtocol oprot = (TTupleProtocol) prot;
+      BitSet optionals = new BitSet();
+      if (struct.isSetKey()) {
+        optionals.set(0);
+      }
+      if (struct.isSetValue()) {
+        optionals.set(1);
+      }
+      if (struct.isSetAnnotation_type()) {
+        optionals.set(2);
+      }
+      if (struct.isSetHost()) {
+        optionals.set(3);
+      }
+      oprot.writeBitSet(optionals, 4);
+      if (struct.isSetKey()) {
+        oprot.writeString(struct.key);
+      }
+      if (struct.isSetValue()) {
+        oprot.writeBinary(struct.value);
+      }
+      if (struct.isSetAnnotation_type()) {
+        oprot.writeI32(struct.annotation_type.getValue());
+      }
+      if (struct.isSetHost()) {
+        struct.host.write(oprot);
+      }
+    }
+
+    @Override
+    public void read(org.apache.thrift.protocol.TProtocol prot, BinaryAnnotation struct) throws org.apache.thrift.TException {
+      TTupleProtocol iprot = (TTupleProtocol) prot;
+      BitSet incoming = iprot.readBitSet(4);
+      if (incoming.get(0)) {
+        struct.key = iprot.readString();
+        struct.setKeyIsSet(true);
+      }
+      if (incoming.get(1)) {
+        struct.value = iprot.readBinary();
+        struct.setValueIsSet(true);
+      }
+      if (incoming.get(2)) {
+        struct.annotation_type = AnnotationType.findByValue(iprot.readI32());
+        struct.setAnnotation_typeIsSet(true);
+      }
+      if (incoming.get(3)) {
+        struct.host = new Endpoint();
+        struct.host.read(iprot);
+        struct.setHostIsSet(true);
+      }
+    }
+  }
+
+}
+
diff --git a/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Endpoint.java b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Endpoint.java
new file mode 100644
index 0000000..9877509
--- /dev/null
+++ b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Endpoint.java
@@ -0,0 +1,578 @@
+/**
+ * Autogenerated by Thrift Compiler (0.9.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package com.twitter.zipkin.gen;
+
+import org.apache.thrift.scheme.IScheme;
+import org.apache.thrift.scheme.SchemeFactory;
+import org.apache.thrift.scheme.StandardScheme;
+
+import org.apache.thrift.scheme.TupleScheme;
+import org.apache.thrift.protocol.TTupleProtocol;
+import org.apache.thrift.protocol.TProtocolException;
+import org.apache.thrift.EncodingUtils;
+import org.apache.thrift.TException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Endpoint implements org.apache.thrift.TBase<Endpoint, Endpoint._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Endpoint");
+
+  private static final org.apache.thrift.protocol.TField IPV4_FIELD_DESC = new org.apache.thrift.protocol.TField("ipv4", org.apache.thrift.protocol.TType.I32, (short)1);
+  private static final org.apache.thrift.protocol.TField PORT_FIELD_DESC = new org.apache.thrift.protocol.TField("port", org.apache.thrift.protocol.TType.I16, (short)2);
+  private static final org.apache.thrift.protocol.TField SERVICE_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("service_name", org.apache.thrift.protocol.TType.STRING, (short)3);
+
+  private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
+  static {
+    schemes.put(StandardScheme.class, new EndpointStandardSchemeFactory());
+    schemes.put(TupleScheme.class, new EndpointTupleSchemeFactory());
+  }
+
+  public int ipv4; // required
+  public short port; // required
+  public String service_name; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    IPV4((short)1, "ipv4"),
+    PORT((short)2, "port"),
+    SERVICE_NAME((short)3, "service_name");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // IPV4
+          return IPV4;
+        case 2: // PORT
+          return PORT;
+        case 3: // SERVICE_NAME
+          return SERVICE_NAME;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __IPV4_ISSET_ID = 0;
+  private static final int __PORT_ISSET_ID = 1;
+  private byte __isset_bitfield = 0;
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.IPV4, new org.apache.thrift.meta_data.FieldMetaData("ipv4", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
+    tmpMap.put(_Fields.PORT, new org.apache.thrift.meta_data.FieldMetaData("port", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I16)));
+    tmpMap.put(_Fields.SERVICE_NAME, new org.apache.thrift.meta_data.FieldMetaData("service_name", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Endpoint.class, metaDataMap);
+  }
+
+  public Endpoint() {
+  }
+
+  public Endpoint(
+    int ipv4,
+    short port,
+    String service_name)
+  {
+    this();
+    this.ipv4 = ipv4;
+    setIpv4IsSet(true);
+    this.port = port;
+    setPortIsSet(true);
+    this.service_name = service_name;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public Endpoint(Endpoint other) {
+    __isset_bitfield = other.__isset_bitfield;
+    this.ipv4 = other.ipv4;
+    this.port = other.port;
+    if (other.isSetService_name()) {
+      this.service_name = other.service_name;
+    }
+  }
+
+  public Endpoint deepCopy() {
+    return new Endpoint(this);
+  }
+
+  @Override
+  public void clear() {
+    setIpv4IsSet(false);
+    this.ipv4 = 0;
+    setPortIsSet(false);
+    this.port = 0;
+    this.service_name = null;
+  }
+
+  public int getIpv4() {
+    return this.ipv4;
+  }
+
+  public Endpoint setIpv4(int ipv4) {
+    this.ipv4 = ipv4;
+    setIpv4IsSet(true);
+    return this;
+  }
+
+  public void unsetIpv4() {
+    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __IPV4_ISSET_ID);
+  }
+
+  /** Returns true if field ipv4 is set (has been assigned a value) and false otherwise */
+  public boolean isSetIpv4() {
+    return EncodingUtils.testBit(__isset_bitfield, __IPV4_ISSET_ID);
+  }
+
+  public void setIpv4IsSet(boolean value) {
+    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __IPV4_ISSET_ID, value);
+  }
+
+  public short getPort() {
+    return this.port;
+  }
+
+  public Endpoint setPort(short port) {
+    this.port = port;
+    setPortIsSet(true);
+    return this;
+  }
+
+  public void unsetPort() {
+    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __PORT_ISSET_ID);
+  }
+
+  /** Returns true if field port is set (has been assigned a value) and false otherwise */
+  public boolean isSetPort() {
+    return EncodingUtils.testBit(__isset_bitfield, __PORT_ISSET_ID);
+  }
+
+  public void setPortIsSet(boolean value) {
+    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __PORT_ISSET_ID, value);
+  }
+
+  public String getService_name() {
+    return this.service_name;
+  }
+
+  public Endpoint setService_name(String service_name) {
+    this.service_name = service_name;
+    return this;
+  }
+
+  public void unsetService_name() {
+    this.service_name = null;
+  }
+
+  /** Returns true if field service_name is set (has been assigned a value) and false otherwise */
+  public boolean isSetService_name() {
+    return this.service_name != null;
+  }
+
+  public void setService_nameIsSet(boolean value) {
+    if (!value) {
+      this.service_name = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case IPV4:
+      if (value == null) {
+        unsetIpv4();
+      } else {
+        setIpv4((Integer)value);
+      }
+      break;
+
+    case PORT:
+      if (value == null) {
+        unsetPort();
+      } else {
+        setPort((Short)value);
+      }
+      break;
+
+    case SERVICE_NAME:
+      if (value == null) {
+        unsetService_name();
+      } else {
+        setService_name((String)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case IPV4:
+      return Integer.valueOf(getIpv4());
+
+    case PORT:
+      return Short.valueOf(getPort());
+
+    case SERVICE_NAME:
+      return getService_name();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case IPV4:
+      return isSetIpv4();
+    case PORT:
+      return isSetPort();
+    case SERVICE_NAME:
+      return isSetService_name();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof Endpoint)
+      return this.equals((Endpoint)that);
+    return false;
+  }
+
+  public boolean equals(Endpoint that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_ipv4 = true;
+    boolean that_present_ipv4 = true;
+    if (this_present_ipv4 || that_present_ipv4) {
+      if (!(this_present_ipv4 && that_present_ipv4))
+        return false;
+      if (this.ipv4 != that.ipv4)
+        return false;
+    }
+
+    boolean this_present_port = true;
+    boolean that_present_port = true;
+    if (this_present_port || that_present_port) {
+      if (!(this_present_port && that_present_port))
+        return false;
+      if (this.port != that.port)
+        return false;
+    }
+
+    boolean this_present_service_name = true && this.isSetService_name();
+    boolean that_present_service_name = true && that.isSetService_name();
+    if (this_present_service_name || that_present_service_name) {
+      if (!(this_present_service_name && that_present_service_name))
+        return false;
+      if (!this.service_name.equals(that.service_name))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(Endpoint other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    Endpoint typedOther = (Endpoint)other;
+
+    lastComparison = Boolean.valueOf(isSetIpv4()).compareTo(typedOther.isSetIpv4());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetIpv4()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.ipv4, typedOther.ipv4);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetPort()).compareTo(typedOther.isSetPort());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetPort()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.port, typedOther.port);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetService_name()).compareTo(typedOther.isSetService_name());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetService_name()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.service_name, typedOther.service_name);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("Endpoint(");
+    boolean first = true;
+
+    sb.append("ipv4:");
+    sb.append(this.ipv4);
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("port:");
+    sb.append(this.port);
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("service_name:");
+    if (this.service_name == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.service_name);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    // check for sub-struct validity
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bitfield = 0;
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private static class EndpointStandardSchemeFactory implements SchemeFactory {
+    public EndpointStandardScheme getScheme() {
+      return new EndpointStandardScheme();
+    }
+  }
+
+  private static class EndpointStandardScheme extends StandardScheme<Endpoint> {
+
+    public void read(org.apache.thrift.protocol.TProtocol iprot, Endpoint struct) throws org.apache.thrift.TException {
+      org.apache.thrift.protocol.TField schemeField;
+      iprot.readStructBegin();
+      while (true)
+      {
+        schemeField = iprot.readFieldBegin();
+        if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
+          break;
+        }
+        switch (schemeField.id) {
+          case 1: // IPV4
+            if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
+              struct.ipv4 = iprot.readI32();
+              struct.setIpv4IsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 2: // PORT
+            if (schemeField.type == org.apache.thrift.protocol.TType.I16) {
+              struct.port = iprot.readI16();
+              struct.setPortIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 3: // SERVICE_NAME
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
+              struct.service_name = iprot.readString();
+              struct.setService_nameIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          default:
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+        }
+        iprot.readFieldEnd();
+      }
+      iprot.readStructEnd();
+
+      // check for required fields of primitive type, which can't be checked in the validate method
+      struct.validate();
+    }
+
+    public void write(org.apache.thrift.protocol.TProtocol oprot, Endpoint struct) throws org.apache.thrift.TException {
+      struct.validate();
+
+      oprot.writeStructBegin(STRUCT_DESC);
+      oprot.writeFieldBegin(IPV4_FIELD_DESC);
+      oprot.writeI32(struct.ipv4);
+      oprot.writeFieldEnd();
+      oprot.writeFieldBegin(PORT_FIELD_DESC);
+      oprot.writeI16(struct.port);
+      oprot.writeFieldEnd();
+      if (struct.service_name != null) {
+        oprot.writeFieldBegin(SERVICE_NAME_FIELD_DESC);
+        oprot.writeString(struct.service_name);
+        oprot.writeFieldEnd();
+      }
+      oprot.writeFieldStop();
+      oprot.writeStructEnd();
+    }
+
+  }
+
+  private static class EndpointTupleSchemeFactory implements SchemeFactory {
+    public EndpointTupleScheme getScheme() {
+      return new EndpointTupleScheme();
+    }
+  }
+
+  private static class EndpointTupleScheme extends TupleScheme<Endpoint> {
+
+    @Override
+    public void write(org.apache.thrift.protocol.TProtocol prot, Endpoint struct) throws org.apache.thrift.TException {
+      TTupleProtocol oprot = (TTupleProtocol) prot;
+      BitSet optionals = new BitSet();
+      if (struct.isSetIpv4()) {
+        optionals.set(0);
+      }
+      if (struct.isSetPort()) {
+        optionals.set(1);
+      }
+      if (struct.isSetService_name()) {
+        optionals.set(2);
+      }
+      oprot.writeBitSet(optionals, 3);
+      if (struct.isSetIpv4()) {
+        oprot.writeI32(struct.ipv4);
+      }
+      if (struct.isSetPort()) {
+        oprot.writeI16(struct.port);
+      }
+      if (struct.isSetService_name()) {
+        oprot.writeString(struct.service_name);
+      }
+    }
+
+    @Override
+    public void read(org.apache.thrift.protocol.TProtocol prot, Endpoint struct) throws org.apache.thrift.TException {
+      TTupleProtocol iprot = (TTupleProtocol) prot;
+      BitSet incoming = iprot.readBitSet(3);
+      if (incoming.get(0)) {
+        struct.ipv4 = iprot.readI32();
+        struct.setIpv4IsSet(true);
+      }
+      if (incoming.get(1)) {
+        struct.port = iprot.readI16();
+        struct.setPortIsSet(true);
+      }
+      if (incoming.get(2)) {
+        struct.service_name = iprot.readString();
+        struct.setService_nameIsSet(true);
+      }
+    }
+  }
+
+}
+
diff --git a/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/LogEntry.java b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/LogEntry.java
new file mode 100644
index 0000000..2ea3c10
--- /dev/null
+++ b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/LogEntry.java
@@ -0,0 +1,486 @@
+/**
+ * Autogenerated by Thrift Compiler (0.9.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package com.twitter.zipkin.gen;
+
+import org.apache.thrift.scheme.IScheme;
+import org.apache.thrift.scheme.SchemeFactory;
+import org.apache.thrift.scheme.StandardScheme;
+
+import org.apache.thrift.scheme.TupleScheme;
+import org.apache.thrift.protocol.TTupleProtocol;
+import org.apache.thrift.protocol.TProtocolException;
+import org.apache.thrift.EncodingUtils;
+import org.apache.thrift.TException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LogEntry implements org.apache.thrift.TBase<LogEntry, LogEntry._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("LogEntry");
+
+  private static final org.apache.thrift.protocol.TField CATEGORY_FIELD_DESC = new org.apache.thrift.protocol.TField("category", org.apache.thrift.protocol.TType.STRING, (short)1);
+  private static final org.apache.thrift.protocol.TField MESSAGE_FIELD_DESC = new org.apache.thrift.protocol.TField("message", org.apache.thrift.protocol.TType.STRING, (short)2);
+
+  private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
+  static {
+    schemes.put(StandardScheme.class, new LogEntryStandardSchemeFactory());
+    schemes.put(TupleScheme.class, new LogEntryTupleSchemeFactory());
+  }
+
+  public String category; // required
+  public String message; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    CATEGORY((short)1, "category"),
+    MESSAGE((short)2, "message");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // CATEGORY
+          return CATEGORY;
+        case 2: // MESSAGE
+          return MESSAGE;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.CATEGORY, new org.apache.thrift.meta_data.FieldMetaData("category", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.MESSAGE, new org.apache.thrift.meta_data.FieldMetaData("message", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(LogEntry.class, metaDataMap);
+  }
+
+  public LogEntry() {
+  }
+
+  public LogEntry(
+    String category,
+    String message)
+  {
+    this();
+    this.category = category;
+    this.message = message;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public LogEntry(LogEntry other) {
+    if (other.isSetCategory()) {
+      this.category = other.category;
+    }
+    if (other.isSetMessage()) {
+      this.message = other.message;
+    }
+  }
+
+  public LogEntry deepCopy() {
+    return new LogEntry(this);
+  }
+
+  @Override
+  public void clear() {
+    this.category = null;
+    this.message = null;
+  }
+
+  public String getCategory() {
+    return this.category;
+  }
+
+  public LogEntry setCategory(String category) {
+    this.category = category;
+    return this;
+  }
+
+  public void unsetCategory() {
+    this.category = null;
+  }
+
+  /** Returns true if field category is set (has been assigned a value) and false otherwise */
+  public boolean isSetCategory() {
+    return this.category != null;
+  }
+
+  public void setCategoryIsSet(boolean value) {
+    if (!value) {
+      this.category = null;
+    }
+  }
+
+  public String getMessage() {
+    return this.message;
+  }
+
+  public LogEntry setMessage(String message) {
+    this.message = message;
+    return this;
+  }
+
+  public void unsetMessage() {
+    this.message = null;
+  }
+
+  /** Returns true if field message is set (has been assigned a value) and false otherwise */
+  public boolean isSetMessage() {
+    return this.message != null;
+  }
+
+  public void setMessageIsSet(boolean value) {
+    if (!value) {
+      this.message = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case CATEGORY:
+      if (value == null) {
+        unsetCategory();
+      } else {
+        setCategory((String)value);
+      }
+      break;
+
+    case MESSAGE:
+      if (value == null) {
+        unsetMessage();
+      } else {
+        setMessage((String)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case CATEGORY:
+      return getCategory();
+
+    case MESSAGE:
+      return getMessage();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case CATEGORY:
+      return isSetCategory();
+    case MESSAGE:
+      return isSetMessage();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof LogEntry)
+      return this.equals((LogEntry)that);
+    return false;
+  }
+
+  public boolean equals(LogEntry that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_category = true && this.isSetCategory();
+    boolean that_present_category = true && that.isSetCategory();
+    if (this_present_category || that_present_category) {
+      if (!(this_present_category && that_present_category))
+        return false;
+      if (!this.category.equals(that.category))
+        return false;
+    }
+
+    boolean this_present_message = true && this.isSetMessage();
+    boolean that_present_message = true && that.isSetMessage();
+    if (this_present_message || that_present_message) {
+      if (!(this_present_message && that_present_message))
+        return false;
+      if (!this.message.equals(that.message))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(LogEntry other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    LogEntry typedOther = (LogEntry)other;
+
+    lastComparison = Boolean.valueOf(isSetCategory()).compareTo(typedOther.isSetCategory());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetCategory()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.category, typedOther.category);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetMessage()).compareTo(typedOther.isSetMessage());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetMessage()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.message, typedOther.message);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("LogEntry(");
+    boolean first = true;
+
+    sb.append("category:");
+    if (this.category == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.category);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("message:");
+    if (this.message == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.message);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    // check for sub-struct validity
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private static class LogEntryStandardSchemeFactory implements SchemeFactory {
+    public LogEntryStandardScheme getScheme() {
+      return new LogEntryStandardScheme();
+    }
+  }
+
+  private static class LogEntryStandardScheme extends StandardScheme<LogEntry> {
+
+    public void read(org.apache.thrift.protocol.TProtocol iprot, LogEntry struct) throws org.apache.thrift.TException {
+      org.apache.thrift.protocol.TField schemeField;
+      iprot.readStructBegin();
+      while (true)
+      {
+        schemeField = iprot.readFieldBegin();
+        if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
+          break;
+        }
+        switch (schemeField.id) {
+          case 1: // CATEGORY
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
+              struct.category = iprot.readString();
+              struct.setCategoryIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 2: // MESSAGE
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
+              struct.message = iprot.readString();
+              struct.setMessageIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          default:
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+        }
+        iprot.readFieldEnd();
+      }
+      iprot.readStructEnd();
+
+      // check for required fields of primitive type, which can't be checked in the validate method
+      struct.validate();
+    }
+
+    public void write(org.apache.thrift.protocol.TProtocol oprot, LogEntry struct) throws org.apache.thrift.TException {
+      struct.validate();
+
+      oprot.writeStructBegin(STRUCT_DESC);
+      if (struct.category != null) {
+        oprot.writeFieldBegin(CATEGORY_FIELD_DESC);
+        oprot.writeString(struct.category);
+        oprot.writeFieldEnd();
+      }
+      if (struct.message != null) {
+        oprot.writeFieldBegin(MESSAGE_FIELD_DESC);
+        oprot.writeString(struct.message);
+        oprot.writeFieldEnd();
+      }
+      oprot.writeFieldStop();
+      oprot.writeStructEnd();
+    }
+
+  }
+
+  private static class LogEntryTupleSchemeFactory implements SchemeFactory {
+    public LogEntryTupleScheme getScheme() {
+      return new LogEntryTupleScheme();
+    }
+  }
+
+  private static class LogEntryTupleScheme extends TupleScheme<LogEntry> {
+
+    @Override
+    public void write(org.apache.thrift.protocol.TProtocol prot, LogEntry struct) throws org.apache.thrift.TException {
+      TTupleProtocol oprot = (TTupleProtocol) prot;
+      BitSet optionals = new BitSet();
+      if (struct.isSetCategory()) {
+        optionals.set(0);
+      }
+      if (struct.isSetMessage()) {
+        optionals.set(1);
+      }
+      oprot.writeBitSet(optionals, 2);
+      if (struct.isSetCategory()) {
+        oprot.writeString(struct.category);
+      }
+      if (struct.isSetMessage()) {
+        oprot.writeString(struct.message);
+      }
+    }
+
+    @Override
+    public void read(org.apache.thrift.protocol.TProtocol prot, LogEntry struct) throws org.apache.thrift.TException {
+      TTupleProtocol iprot = (TTupleProtocol) prot;
+      BitSet incoming = iprot.readBitSet(2);
+      if (incoming.get(0)) {
+        struct.category = iprot.readString();
+        struct.setCategoryIsSet(true);
+      }
+      if (incoming.get(1)) {
+        struct.message = iprot.readString();
+        struct.setMessageIsSet(true);
+      }
+    }
+  }
+
+}
+
diff --git a/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/ResultCode.java b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/ResultCode.java
new file mode 100644
index 0000000..d0fc5ae
--- /dev/null
+++ b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/ResultCode.java
@@ -0,0 +1,45 @@
+/**
+ * Autogenerated by Thrift Compiler (0.9.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package com.twitter.zipkin.gen;
+
+
+import java.util.Map;
+import java.util.HashMap;
+import org.apache.thrift.TEnum;
+
+public enum ResultCode implements org.apache.thrift.TEnum {
+  OK(0),
+  TRY_LATER(1);
+
+  private final int value;
+
+  private ResultCode(int value) {
+    this.value = value;
+  }
+
+  /**
+   * Get the integer value of this enum value, as defined in the Thrift IDL.
+   */
+  public int getValue() {
+    return value;
+  }
+
+  /**
+   * Find a the enum type by its integer value, as defined in the Thrift IDL.
+   * @return null if the value is not found.
+   */
+  public static ResultCode findByValue(int value) { 
+    switch (value) {
+      case 0:
+        return OK;
+      case 1:
+        return TRY_LATER;
+      default:
+        return null;
+    }
+  }
+}
diff --git a/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Scribe.java b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Scribe.java
new file mode 100644
index 0000000..7e51998
--- /dev/null
+++ b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Scribe.java
@@ -0,0 +1,957 @@
+/**
+ * Autogenerated by Thrift Compiler (0.9.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package com.twitter.zipkin.gen;
+
+import org.apache.thrift.scheme.IScheme;
+import org.apache.thrift.scheme.SchemeFactory;
+import org.apache.thrift.scheme.StandardScheme;
+
+import org.apache.thrift.scheme.TupleScheme;
+import org.apache.thrift.protocol.TTupleProtocol;
+import org.apache.thrift.protocol.TProtocolException;
+import org.apache.thrift.EncodingUtils;
+import org.apache.thrift.TException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Scribe {
+
+  public interface Iface {
+
+    public ResultCode Log(List<LogEntry> messages) throws org.apache.thrift.TException;
+
+  }
+
+  public interface AsyncIface {
+
+    public void Log(List<LogEntry> messages, org.apache.thrift.async.AsyncMethodCallback<AsyncClient.Log_call> resultHandler) throws org.apache.thrift.TException;
+
+  }
+
+  public static class Client extends org.apache.thrift.TServiceClient implements Iface {
+    public static class Factory implements org.apache.thrift.TServiceClientFactory<Client> {
+      public Factory() {}
+      public Client getClient(org.apache.thrift.protocol.TProtocol prot) {
+        return new Client(prot);
+      }
+      public Client getClient(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
+        return new Client(iprot, oprot);
+      }
+    }
+
+    public Client(org.apache.thrift.protocol.TProtocol prot)
+    {
+      super(prot, prot);
+    }
+
+    public Client(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
+      super(iprot, oprot);
+    }
+
+    public ResultCode Log(List<LogEntry> messages) throws org.apache.thrift.TException
+    {
+      send_Log(messages);
+      return recv_Log();
+    }
+
+    public void send_Log(List<LogEntry> messages) throws org.apache.thrift.TException
+    {
+      Log_args args = new Log_args();
+      args.setMessages(messages);
+      sendBase("Log", args);
+    }
+
+    public ResultCode recv_Log() throws org.apache.thrift.TException
+    {
+      Log_result result = new Log_result();
+      receiveBase(result, "Log");
+      if (result.isSetSuccess()) {
+        return result.success;
+      }
+      throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "Log failed: unknown result");
+    }
+
+  }
+  public static class AsyncClient extends org.apache.thrift.async.TAsyncClient implements AsyncIface {
+    public static class Factory implements org.apache.thrift.async.TAsyncClientFactory<AsyncClient> {
+      private org.apache.thrift.async.TAsyncClientManager clientManager;
+      private org.apache.thrift.protocol.TProtocolFactory protocolFactory;
+      public Factory(org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.protocol.TProtocolFactory protocolFactory) {
+        this.clientManager = clientManager;
+        this.protocolFactory = protocolFactory;
+      }
+      public AsyncClient getAsyncClient(org.apache.thrift.transport.TNonblockingTransport transport) {
+        return new AsyncClient(protocolFactory, clientManager, transport);
+      }
+    }
+
+    public AsyncClient(org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.transport.TNonblockingTransport transport) {
+      super(protocolFactory, clientManager, transport);
+    }
+
+    public void Log(List<LogEntry> messages, org.apache.thrift.async.AsyncMethodCallback<Log_call> resultHandler) throws org.apache.thrift.TException {
+      checkReady();
+      Log_call method_call = new Log_call(messages, resultHandler, this, ___protocolFactory, ___transport);
+      this.___currentMethod = method_call;
+      ___manager.call(method_call);
+    }
+
+    public static class Log_call extends org.apache.thrift.async.TAsyncMethodCall {
+      private List<LogEntry> messages;
+      public Log_call(List<LogEntry> messages, org.apache.thrift.async.AsyncMethodCallback<Log_call> resultHandler, org.apache.thrift.async.TAsyncClient client, org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.transport.TNonblockingTransport transport) throws org.apache.thrift.TException {
+        super(client, protocolFactory, transport, resultHandler, false);
+        this.messages = messages;
+      }
+
+      public void write_args(org.apache.thrift.protocol.TProtocol prot) throws org.apache.thrift.TException {
+        prot.writeMessageBegin(new org.apache.thrift.protocol.TMessage("Log", org.apache.thrift.protocol.TMessageType.CALL, 0));
+        Log_args args = new Log_args();
+        args.setMessages(messages);
+        args.write(prot);
+        prot.writeMessageEnd();
+      }
+
+      public ResultCode getResult() throws org.apache.thrift.TException {
+        if (getState() != org.apache.thrift.async.TAsyncMethodCall.State.RESPONSE_READ) {
+          throw new IllegalStateException("Method call not finished!");
+        }
+        org.apache.thrift.transport.TMemoryInputTransport memoryTransport = new org.apache.thrift.transport.TMemoryInputTransport(getFrameBuffer().array());
+        org.apache.thrift.protocol.TProtocol prot = client.getProtocolFactory().getProtocol(memoryTransport);
+        return (new Client(prot)).recv_Log();
+      }
+    }
+
+  }
+
+  public static class Processor<I extends Iface> extends org.apache.thrift.TBaseProcessor<I> implements org.apache.thrift.TProcessor {
+    private static final Logger LOGGER = LoggerFactory.getLogger(Processor.class.getName());
+    public Processor(I iface) {
+      super(iface, getProcessMap(new HashMap<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>>()));
+    }
+
+    protected Processor(I iface, Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> processMap) {
+      super(iface, getProcessMap(processMap));
+    }
+
+    private static <I extends Iface> Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> getProcessMap(Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> processMap) {
+      processMap.put("Log", new Log());
+      return processMap;
+    }
+
+    public static class Log<I extends Iface> extends org.apache.thrift.ProcessFunction<I, Log_args> {
+      public Log() {
+        super("Log");
+      }
+
+      public Log_args getEmptyArgsInstance() {
+        return new Log_args();
+      }
+
+      protected boolean isOneway() {
+        return false;
+      }
+
+      public Log_result getResult(I iface, Log_args args) throws org.apache.thrift.TException {
+        Log_result result = new Log_result();
+        result.success = iface.Log(args.messages);
+        return result;
+      }
+    }
+
+  }
+
+  public static class Log_args implements org.apache.thrift.TBase<Log_args, Log_args._Fields>, java.io.Serializable, Cloneable   {
+    private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Log_args");
+
+    private static final org.apache.thrift.protocol.TField MESSAGES_FIELD_DESC = new org.apache.thrift.protocol.TField("messages", org.apache.thrift.protocol.TType.LIST, (short)1);
+
+    private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
+    static {
+      schemes.put(StandardScheme.class, new Log_argsStandardSchemeFactory());
+      schemes.put(TupleScheme.class, new Log_argsTupleSchemeFactory());
+    }
+
+    public List<LogEntry> messages; // required
+
+    /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+    public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+      MESSAGES((short)1, "messages");
+
+      private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+      static {
+        for (_Fields field : EnumSet.allOf(_Fields.class)) {
+          byName.put(field.getFieldName(), field);
+        }
+      }
+
+      /**
+       * Find the _Fields constant that matches fieldId, or null if its not found.
+       */
+      public static _Fields findByThriftId(int fieldId) {
+        switch(fieldId) {
+          case 1: // MESSAGES
+            return MESSAGES;
+          default:
+            return null;
+        }
+      }
+
+      /**
+       * Find the _Fields constant that matches fieldId, throwing an exception
+       * if it is not found.
+       */
+      public static _Fields findByThriftIdOrThrow(int fieldId) {
+        _Fields fields = findByThriftId(fieldId);
+        if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+        return fields;
+      }
+
+      /**
+       * Find the _Fields constant that matches name, or null if its not found.
+       */
+      public static _Fields findByName(String name) {
+        return byName.get(name);
+      }
+
+      private final short _thriftId;
+      private final String _fieldName;
+
+      _Fields(short thriftId, String fieldName) {
+        _thriftId = thriftId;
+        _fieldName = fieldName;
+      }
+
+      public short getThriftFieldId() {
+        return _thriftId;
+      }
+
+      public String getFieldName() {
+        return _fieldName;
+      }
+    }
+
+    // isset id assignments
+    public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+    static {
+      Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+      tmpMap.put(_Fields.MESSAGES, new org.apache.thrift.meta_data.FieldMetaData("messages", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+          new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+              new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, LogEntry.class))));
+      metaDataMap = Collections.unmodifiableMap(tmpMap);
+      org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Log_args.class, metaDataMap);
+    }
+
+    public Log_args() {
+    }
+
+    public Log_args(
+      List<LogEntry> messages)
+    {
+      this();
+      this.messages = messages;
+    }
+
+    /**
+     * Performs a deep copy on <i>other</i>.
+     */
+    public Log_args(Log_args other) {
+      if (other.isSetMessages()) {
+        List<LogEntry> __this__messages = new ArrayList<LogEntry>();
+        for (LogEntry other_element : other.messages) {
+          __this__messages.add(new LogEntry(other_element));
+        }
+        this.messages = __this__messages;
+      }
+    }
+
+    public Log_args deepCopy() {
+      return new Log_args(this);
+    }
+
+    @Override
+    public void clear() {
+      this.messages = null;
+    }
+
+    public int getMessagesSize() {
+      return (this.messages == null) ? 0 : this.messages.size();
+    }
+
+    public java.util.Iterator<LogEntry> getMessagesIterator() {
+      return (this.messages == null) ? null : this.messages.iterator();
+    }
+
+    public void addToMessages(LogEntry elem) {
+      if (this.messages == null) {
+        this.messages = new ArrayList<LogEntry>();
+      }
+      this.messages.add(elem);
+    }
+
+    public List<LogEntry> getMessages() {
+      return this.messages;
+    }
+
+    public Log_args setMessages(List<LogEntry> messages) {
+      this.messages = messages;
+      return this;
+    }
+
+    public void unsetMessages() {
+      this.messages = null;
+    }
+
+    /** Returns true if field messages is set (has been assigned a value) and false otherwise */
+    public boolean isSetMessages() {
+      return this.messages != null;
+    }
+
+    public void setMessagesIsSet(boolean value) {
+      if (!value) {
+        this.messages = null;
+      }
+    }
+
+    public void setFieldValue(_Fields field, Object value) {
+      switch (field) {
+      case MESSAGES:
+        if (value == null) {
+          unsetMessages();
+        } else {
+          setMessages((List<LogEntry>)value);
+        }
+        break;
+
+      }
+    }
+
+    public Object getFieldValue(_Fields field) {
+      switch (field) {
+      case MESSAGES:
+        return getMessages();
+
+      }
+      throw new IllegalStateException();
+    }
+
+    /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+    public boolean isSet(_Fields field) {
+      if (field == null) {
+        throw new IllegalArgumentException();
+      }
+
+      switch (field) {
+      case MESSAGES:
+        return isSetMessages();
+      }
+      throw new IllegalStateException();
+    }
+
+    @Override
+    public boolean equals(Object that) {
+      if (that == null)
+        return false;
+      if (that instanceof Log_args)
+        return this.equals((Log_args)that);
+      return false;
+    }
+
+    public boolean equals(Log_args that) {
+      if (that == null)
+        return false;
+
+      boolean this_present_messages = true && this.isSetMessages();
+      boolean that_present_messages = true && that.isSetMessages();
+      if (this_present_messages || that_present_messages) {
+        if (!(this_present_messages && that_present_messages))
+          return false;
+        if (!this.messages.equals(that.messages))
+          return false;
+      }
+
+      return true;
+    }
+
+    @Override
+    public int hashCode() {
+      return 0;
+    }
+
+    public int compareTo(Log_args other) {
+      if (!getClass().equals(other.getClass())) {
+        return getClass().getName().compareTo(other.getClass().getName());
+      }
+
+      int lastComparison = 0;
+      Log_args typedOther = (Log_args)other;
+
+      lastComparison = Boolean.valueOf(isSetMessages()).compareTo(typedOther.isSetMessages());
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+      if (isSetMessages()) {
+        lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.messages, typedOther.messages);
+        if (lastComparison != 0) {
+          return lastComparison;
+        }
+      }
+      return 0;
+    }
+
+    public _Fields fieldForId(int fieldId) {
+      return _Fields.findByThriftId(fieldId);
+    }
+
+    public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+      schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
+    }
+
+    public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+      schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder("Log_args(");
+      boolean first = true;
+
+      sb.append("messages:");
+      if (this.messages == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.messages);
+      }
+      first = false;
+      sb.append(")");
+      return sb.toString();
+    }
+
+    public void validate() throws org.apache.thrift.TException {
+      // check for required fields
+      // check for sub-struct validity
+    }
+
+    private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+      try {
+        write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+      } catch (org.apache.thrift.TException te) {
+        throw new java.io.IOException(te);
+      }
+    }
+
+    private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+      try {
+        read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+      } catch (org.apache.thrift.TException te) {
+        throw new java.io.IOException(te);
+      }
+    }
+
+    private static class Log_argsStandardSchemeFactory implements SchemeFactory {
+      public Log_argsStandardScheme getScheme() {
+        return new Log_argsStandardScheme();
+      }
+    }
+
+    private static class Log_argsStandardScheme extends StandardScheme<Log_args> {
+
+      public void read(org.apache.thrift.protocol.TProtocol iprot, Log_args struct) throws org.apache.thrift.TException {
+        org.apache.thrift.protocol.TField schemeField;
+        iprot.readStructBegin();
+        while (true)
+        {
+          schemeField = iprot.readFieldBegin();
+          if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
+            break;
+          }
+          switch (schemeField.id) {
+            case 1: // MESSAGES
+              if (schemeField.type == org.apache.thrift.protocol.TType.LIST) {
+                {
+                  org.apache.thrift.protocol.TList _list0 = iprot.readListBegin();
+                  struct.messages = new ArrayList<LogEntry>(_list0.size);
+                  for (int _i1 = 0; _i1 < _list0.size; ++_i1)
+                  {
+                    LogEntry _elem2; // required
+                    _elem2 = new LogEntry();
+                    _elem2.read(iprot);
+                    struct.messages.add(_elem2);
+                  }
+                  iprot.readListEnd();
+                }
+                struct.setMessagesIsSet(true);
+              } else { 
+                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+              }
+              break;
+            default:
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+          }
+          iprot.readFieldEnd();
+        }
+        iprot.readStructEnd();
+
+        // check for required fields of primitive type, which can't be checked in the validate method
+        struct.validate();
+      }
+
+      public void write(org.apache.thrift.protocol.TProtocol oprot, Log_args struct) throws org.apache.thrift.TException {
+        struct.validate();
+
+        oprot.writeStructBegin(STRUCT_DESC);
+        if (struct.messages != null) {
+          oprot.writeFieldBegin(MESSAGES_FIELD_DESC);
+          {
+            oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, struct.messages.size()));
+            for (LogEntry _iter3 : struct.messages)
+            {
+              _iter3.write(oprot);
+            }
+            oprot.writeListEnd();
+          }
+          oprot.writeFieldEnd();
+        }
+        oprot.writeFieldStop();
+        oprot.writeStructEnd();
+      }
+
+    }
+
+    private static class Log_argsTupleSchemeFactory implements SchemeFactory {
+      public Log_argsTupleScheme getScheme() {
+        return new Log_argsTupleScheme();
+      }
+    }
+
+    private static class Log_argsTupleScheme extends TupleScheme<Log_args> {
+
+      @Override
+      public void write(org.apache.thrift.protocol.TProtocol prot, Log_args struct) throws org.apache.thrift.TException {
+        TTupleProtocol oprot = (TTupleProtocol) prot;
+        BitSet optionals = new BitSet();
+        if (struct.isSetMessages()) {
+          optionals.set(0);
+        }
+        oprot.writeBitSet(optionals, 1);
+        if (struct.isSetMessages()) {
+          {
+            oprot.writeI32(struct.messages.size());
+            for (LogEntry _iter4 : struct.messages)
+            {
+              _iter4.write(oprot);
+            }
+          }
+        }
+      }
+
+      @Override
+      public void read(org.apache.thrift.protocol.TProtocol prot, Log_args struct) throws org.apache.thrift.TException {
+        TTupleProtocol iprot = (TTupleProtocol) prot;
+        BitSet incoming = iprot.readBitSet(1);
+        if (incoming.get(0)) {
+          {
+            org.apache.thrift.protocol.TList _list5 = new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, iprot.readI32());
+            struct.messages = new ArrayList<LogEntry>(_list5.size);
+            for (int _i6 = 0; _i6 < _list5.size; ++_i6)
+            {
+              LogEntry _elem7; // required
+              _elem7 = new LogEntry();
+              _elem7.read(iprot);
+              struct.messages.add(_elem7);
+            }
+          }
+          struct.setMessagesIsSet(true);
+        }
+      }
+    }
+
+  }
+
+  public static class Log_result implements org.apache.thrift.TBase<Log_result, Log_result._Fields>, java.io.Serializable, Cloneable   {
+    private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Log_result");
+
+    private static final org.apache.thrift.protocol.TField SUCCESS_FIELD_DESC = new org.apache.thrift.protocol.TField("success", org.apache.thrift.protocol.TType.I32, (short)0);
+
+    private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
+    static {
+      schemes.put(StandardScheme.class, new Log_resultStandardSchemeFactory());
+      schemes.put(TupleScheme.class, new Log_resultTupleSchemeFactory());
+    }
+
+    /**
+     * 
+     * @see ResultCode
+     */
+    public ResultCode success; // required
+
+    /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+    public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+      /**
+       * 
+       * @see ResultCode
+       */
+      SUCCESS((short)0, "success");
+
+      private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+      static {
+        for (_Fields field : EnumSet.allOf(_Fields.class)) {
+          byName.put(field.getFieldName(), field);
+        }
+      }
+
+      /**
+       * Find the _Fields constant that matches fieldId, or null if its not found.
+       */
+      public static _Fields findByThriftId(int fieldId) {
+        switch(fieldId) {
+          case 0: // SUCCESS
+            return SUCCESS;
+          default:
+            return null;
+        }
+      }
+
+      /**
+       * Find the _Fields constant that matches fieldId, throwing an exception
+       * if it is not found.
+       */
+      public static _Fields findByThriftIdOrThrow(int fieldId) {
+        _Fields fields = findByThriftId(fieldId);
+        if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+        return fields;
+      }
+
+      /**
+       * Find the _Fields constant that matches name, or null if its not found.
+       */
+      public static _Fields findByName(String name) {
+        return byName.get(name);
+      }
+
+      private final short _thriftId;
+      private final String _fieldName;
+
+      _Fields(short thriftId, String fieldName) {
+        _thriftId = thriftId;
+        _fieldName = fieldName;
+      }
+
+      public short getThriftFieldId() {
+        return _thriftId;
+      }
+
+      public String getFieldName() {
+        return _fieldName;
+      }
+    }
+
+    // isset id assignments
+    public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+    static {
+      Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+      tmpMap.put(_Fields.SUCCESS, new org.apache.thrift.meta_data.FieldMetaData("success", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+          new org.apache.thrift.meta_data.EnumMetaData(org.apache.thrift.protocol.TType.ENUM, ResultCode.class)));
+      metaDataMap = Collections.unmodifiableMap(tmpMap);
+      org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Log_result.class, metaDataMap);
+    }
+
+    public Log_result() {
+    }
+
+    public Log_result(
+      ResultCode success)
+    {
+      this();
+      this.success = success;
+    }
+
+    /**
+     * Performs a deep copy on <i>other</i>.
+     */
+    public Log_result(Log_result other) {
+      if (other.isSetSuccess()) {
+        this.success = other.success;
+      }
+    }
+
+    public Log_result deepCopy() {
+      return new Log_result(this);
+    }
+
+    @Override
+    public void clear() {
+      this.success = null;
+    }
+
+    /**
+     * 
+     * @see ResultCode
+     */
+    public ResultCode getSuccess() {
+      return this.success;
+    }
+
+    /**
+     * 
+     * @see ResultCode
+     */
+    public Log_result setSuccess(ResultCode success) {
+      this.success = success;
+      return this;
+    }
+
+    public void unsetSuccess() {
+      this.success = null;
+    }
+
+    /** Returns true if field success is set (has been assigned a value) and false otherwise */
+    public boolean isSetSuccess() {
+      return this.success != null;
+    }
+
+    public void setSuccessIsSet(boolean value) {
+      if (!value) {
+        this.success = null;
+      }
+    }
+
+    public void setFieldValue(_Fields field, Object value) {
+      switch (field) {
+      case SUCCESS:
+        if (value == null) {
+          unsetSuccess();
+        } else {
+          setSuccess((ResultCode)value);
+        }
+        break;
+
+      }
+    }
+
+    public Object getFieldValue(_Fields field) {
+      switch (field) {
+      case SUCCESS:
+        return getSuccess();
+
+      }
+      throw new IllegalStateException();
+    }
+
+    /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+    public boolean isSet(_Fields field) {
+      if (field == null) {
+        throw new IllegalArgumentException();
+      }
+
+      switch (field) {
+      case SUCCESS:
+        return isSetSuccess();
+      }
+      throw new IllegalStateException();
+    }
+
+    @Override
+    public boolean equals(Object that) {
+      if (that == null)
+        return false;
+      if (that instanceof Log_result)
+        return this.equals((Log_result)that);
+      return false;
+    }
+
+    public boolean equals(Log_result that) {
+      if (that == null)
+        return false;
+
+      boolean this_present_success = true && this.isSetSuccess();
+      boolean that_present_success = true && that.isSetSuccess();
+      if (this_present_success || that_present_success) {
+        if (!(this_present_success && that_present_success))
+          return false;
+        if (!this.success.equals(that.success))
+          return false;
+      }
+
+      return true;
+    }
+
+    @Override
+    public int hashCode() {
+      return 0;
+    }
+
+    public int compareTo(Log_result other) {
+      if (!getClass().equals(other.getClass())) {
+        return getClass().getName().compareTo(other.getClass().getName());
+      }
+
+      int lastComparison = 0;
+      Log_result typedOther = (Log_result)other;
+
+      lastComparison = Boolean.valueOf(isSetSuccess()).compareTo(typedOther.isSetSuccess());
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+      if (isSetSuccess()) {
+        lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.success, typedOther.success);
+        if (lastComparison != 0) {
+          return lastComparison;
+        }
+      }
+      return 0;
+    }
+
+    public _Fields fieldForId(int fieldId) {
+      return _Fields.findByThriftId(fieldId);
+    }
+
+    public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+      schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
+    }
+
+    public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+      schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
+      }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder("Log_result(");
+      boolean first = true;
+
+      sb.append("success:");
+      if (this.success == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.success);
+      }
+      first = false;
+      sb.append(")");
+      return sb.toString();
+    }
+
+    public void validate() throws org.apache.thrift.TException {
+      // check for required fields
+      // check for sub-struct validity
+    }
+
+    private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+      try {
+        write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+      } catch (org.apache.thrift.TException te) {
+        throw new java.io.IOException(te);
+      }
+    }
+
+    private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+      try {
+        read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+      } catch (org.apache.thrift.TException te) {
+        throw new java.io.IOException(te);
+      }
+    }
+
+    private static class Log_resultStandardSchemeFactory implements SchemeFactory {
+      public Log_resultStandardScheme getScheme() {
+        return new Log_resultStandardScheme();
+      }
+    }
+
+    private static class Log_resultStandardScheme extends StandardScheme<Log_result> {
+
+      public void read(org.apache.thrift.protocol.TProtocol iprot, Log_result struct) throws org.apache.thrift.TException {
+        org.apache.thrift.protocol.TField schemeField;
+        iprot.readStructBegin();
+        while (true)
+        {
+          schemeField = iprot.readFieldBegin();
+          if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
+            break;
+          }
+          switch (schemeField.id) {
+            case 0: // SUCCESS
+              if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
+                struct.success = ResultCode.findByValue(iprot.readI32());
+                struct.setSuccessIsSet(true);
+              } else { 
+                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+              }
+              break;
+            default:
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+          }
+          iprot.readFieldEnd();
+        }
+        iprot.readStructEnd();
+
+        // check for required fields of primitive type, which can't be checked in the validate method
+        struct.validate();
+      }
+
+      public void write(org.apache.thrift.protocol.TProtocol oprot, Log_result struct) throws org.apache.thrift.TException {
+        struct.validate();
+
+        oprot.writeStructBegin(STRUCT_DESC);
+        if (struct.success != null) {
+          oprot.writeFieldBegin(SUCCESS_FIELD_DESC);
+          oprot.writeI32(struct.success.getValue());
+          oprot.writeFieldEnd();
+        }
+        oprot.writeFieldStop();
+        oprot.writeStructEnd();
+      }
+
+    }
+
+    private static class Log_resultTupleSchemeFactory implements SchemeFactory {
+      public Log_resultTupleScheme getScheme() {
+        return new Log_resultTupleScheme();
+      }
+    }
+
+    private static class Log_resultTupleScheme extends TupleScheme<Log_result> {
+
+      @Override
+      public void write(org.apache.thrift.protocol.TProtocol prot, Log_result struct) throws org.apache.thrift.TException {
+        TTupleProtocol oprot = (TTupleProtocol) prot;
+        BitSet optionals = new BitSet();
+        if (struct.isSetSuccess()) {
+          optionals.set(0);
+        }
+        oprot.writeBitSet(optionals, 1);
+        if (struct.isSetSuccess()) {
+          oprot.writeI32(struct.success.getValue());
+        }
+      }
+
+      @Override
+      public void read(org.apache.thrift.protocol.TProtocol prot, Log_result struct) throws org.apache.thrift.TException {
+        TTupleProtocol iprot = (TTupleProtocol) prot;
+        BitSet incoming = iprot.readBitSet(1);
+        if (incoming.get(0)) {
+          struct.success = ResultCode.findByValue(iprot.readI32());
+          struct.setSuccessIsSet(true);
+        }
+      }
+    }
+
+  }
+
+}
diff --git a/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Span.java b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Span.java
new file mode 100644
index 0000000..ef6ae04
--- /dev/null
+++ b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/Span.java
@@ -0,0 +1,1079 @@
+/**
+ * Autogenerated by Thrift Compiler (0.9.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package com.twitter.zipkin.gen;
+
+import org.apache.thrift.scheme.IScheme;
+import org.apache.thrift.scheme.SchemeFactory;
+import org.apache.thrift.scheme.StandardScheme;
+
+import org.apache.thrift.scheme.TupleScheme;
+import org.apache.thrift.protocol.TTupleProtocol;
+import org.apache.thrift.protocol.TProtocolException;
+import org.apache.thrift.EncodingUtils;
+import org.apache.thrift.TException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Span implements org.apache.thrift.TBase<Span, Span._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Span");
+
+  private static final org.apache.thrift.protocol.TField TRACE_ID_FIELD_DESC = new org.apache.thrift.protocol.TField("trace_id", org.apache.thrift.protocol.TType.I64, (short)1);
+  private static final org.apache.thrift.protocol.TField NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("name", org.apache.thrift.protocol.TType.STRING, (short)3);
+  private static final org.apache.thrift.protocol.TField ID_FIELD_DESC = new org.apache.thrift.protocol.TField("id", org.apache.thrift.protocol.TType.I64, (short)4);
+  private static final org.apache.thrift.protocol.TField PARENT_ID_FIELD_DESC = new org.apache.thrift.protocol.TField("parent_id", org.apache.thrift.protocol.TType.I64, (short)5);
+  private static final org.apache.thrift.protocol.TField ANNOTATIONS_FIELD_DESC = new org.apache.thrift.protocol.TField("annotations", org.apache.thrift.protocol.TType.LIST, (short)6);
+  private static final org.apache.thrift.protocol.TField BINARY_ANNOTATIONS_FIELD_DESC = new org.apache.thrift.protocol.TField("binary_annotations", org.apache.thrift.protocol.TType.LIST, (short)8);
+  private static final org.apache.thrift.protocol.TField DEBUG_FIELD_DESC = new org.apache.thrift.protocol.TField("debug", org.apache.thrift.protocol.TType.BOOL, (short)9);
+
+  private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
+  static {
+    schemes.put(StandardScheme.class, new SpanStandardSchemeFactory());
+    schemes.put(TupleScheme.class, new SpanTupleSchemeFactory());
+  }
+
+  public long trace_id; // required
+  public String name; // required
+  public long id; // required
+  public long parent_id; // optional
+  public List<Annotation> annotations; // required
+  public List<BinaryAnnotation> binary_annotations; // required
+  public boolean debug; // optional
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    TRACE_ID((short)1, "trace_id"),
+    NAME((short)3, "name"),
+    ID((short)4, "id"),
+    PARENT_ID((short)5, "parent_id"),
+    ANNOTATIONS((short)6, "annotations"),
+    BINARY_ANNOTATIONS((short)8, "binary_annotations"),
+    DEBUG((short)9, "debug");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // TRACE_ID
+          return TRACE_ID;
+        case 3: // NAME
+          return NAME;
+        case 4: // ID
+          return ID;
+        case 5: // PARENT_ID
+          return PARENT_ID;
+        case 6: // ANNOTATIONS
+          return ANNOTATIONS;
+        case 8: // BINARY_ANNOTATIONS
+          return BINARY_ANNOTATIONS;
+        case 9: // DEBUG
+          return DEBUG;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __TRACE_ID_ISSET_ID = 0;
+  private static final int __ID_ISSET_ID = 1;
+  private static final int __PARENT_ID_ISSET_ID = 2;
+  private static final int __DEBUG_ISSET_ID = 3;
+  private byte __isset_bitfield = 0;
+  private _Fields optionals[] = {_Fields.PARENT_ID,_Fields.DEBUG};
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.TRACE_ID, new org.apache.thrift.meta_data.FieldMetaData("trace_id", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
+    tmpMap.put(_Fields.NAME, new org.apache.thrift.meta_data.FieldMetaData("name", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.ID, new org.apache.thrift.meta_data.FieldMetaData("id", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
+    tmpMap.put(_Fields.PARENT_ID, new org.apache.thrift.meta_data.FieldMetaData("parent_id", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
+    tmpMap.put(_Fields.ANNOTATIONS, new org.apache.thrift.meta_data.FieldMetaData("annotations", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+            new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Annotation.class))));
+    tmpMap.put(_Fields.BINARY_ANNOTATIONS, new org.apache.thrift.meta_data.FieldMetaData("binary_annotations", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+            new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, BinaryAnnotation.class))));
+    tmpMap.put(_Fields.DEBUG, new org.apache.thrift.meta_data.FieldMetaData("debug", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.BOOL)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Span.class, metaDataMap);
+  }
+
+  public Span() {
+    this.debug = false;
+
+  }
+
+  public Span(
+    long trace_id,
+    String name,
+    long id,
+    List<Annotation> annotations,
+    List<BinaryAnnotation> binary_annotations)
+  {
+    this();
+    this.trace_id = trace_id;
+    setTrace_idIsSet(true);
+    this.name = name;
+    this.id = id;
+    setIdIsSet(true);
+    this.annotations = annotations;
+    this.binary_annotations = binary_annotations;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public Span(Span other) {
+    __isset_bitfield = other.__isset_bitfield;
+    this.trace_id = other.trace_id;
+    if (other.isSetName()) {
+      this.name = other.name;
+    }
+    this.id = other.id;
+    this.parent_id = other.parent_id;
+    if (other.isSetAnnotations()) {
+      List<Annotation> __this__annotations = new ArrayList<Annotation>();
+      for (Annotation other_element : other.annotations) {
+        __this__annotations.add(new Annotation(other_element));
+      }
+      this.annotations = __this__annotations;
+    }
+    if (other.isSetBinary_annotations()) {
+      List<BinaryAnnotation> __this__binary_annotations = new ArrayList<BinaryAnnotation>();
+      for (BinaryAnnotation other_element : other.binary_annotations) {
+        __this__binary_annotations.add(new BinaryAnnotation(other_element));
+      }
+      this.binary_annotations = __this__binary_annotations;
+    }
+    this.debug = other.debug;
+  }
+
+  public Span deepCopy() {
+    return new Span(this);
+  }
+
+  @Override
+  public void clear() {
+    setTrace_idIsSet(false);
+    this.trace_id = 0;
+    this.name = null;
+    setIdIsSet(false);
+    this.id = 0;
+    setParent_idIsSet(false);
+    this.parent_id = 0;
+    this.annotations = null;
+    this.binary_annotations = null;
+    this.debug = false;
+
+  }
+
+  public long getTrace_id() {
+    return this.trace_id;
+  }
+
+  public Span setTrace_id(long trace_id) {
+    this.trace_id = trace_id;
+    setTrace_idIsSet(true);
+    return this;
+  }
+
+  public void unsetTrace_id() {
+    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __TRACE_ID_ISSET_ID);
+  }
+
+  /** Returns true if field trace_id is set (has been assigned a value) and false otherwise */
+  public boolean isSetTrace_id() {
+    return EncodingUtils.testBit(__isset_bitfield, __TRACE_ID_ISSET_ID);
+  }
+
+  public void setTrace_idIsSet(boolean value) {
+    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __TRACE_ID_ISSET_ID, value);
+  }
+
+  public String getName() {
+    return this.name;
+  }
+
+  public Span setName(String name) {
+    this.name = name;
+    return this;
+  }
+
+  public void unsetName() {
+    this.name = null;
+  }
+
+  /** Returns true if field name is set (has been assigned a value) and false otherwise */
+  public boolean isSetName() {
+    return this.name != null;
+  }
+
+  public void setNameIsSet(boolean value) {
+    if (!value) {
+      this.name = null;
+    }
+  }
+
+  public long getId() {
+    return this.id;
+  }
+
+  public Span setId(long id) {
+    this.id = id;
+    setIdIsSet(true);
+    return this;
+  }
+
+  public void unsetId() {
+    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __ID_ISSET_ID);
+  }
+
+  /** Returns true if field id is set (has been assigned a value) and false otherwise */
+  public boolean isSetId() {
+    return EncodingUtils.testBit(__isset_bitfield, __ID_ISSET_ID);
+  }
+
+  public void setIdIsSet(boolean value) {
+    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __ID_ISSET_ID, value);
+  }
+
+  public long getParent_id() {
+    return this.parent_id;
+  }
+
+  public Span setParent_id(long parent_id) {
+    this.parent_id = parent_id;
+    setParent_idIsSet(true);
+    return this;
+  }
+
+  public void unsetParent_id() {
+    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __PARENT_ID_ISSET_ID);
+  }
+
+  /** Returns true if field parent_id is set (has been assigned a value) and false otherwise */
+  public boolean isSetParent_id() {
+    return EncodingUtils.testBit(__isset_bitfield, __PARENT_ID_ISSET_ID);
+  }
+
+  public void setParent_idIsSet(boolean value) {
+    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __PARENT_ID_ISSET_ID, value);
+  }
+
+  public int getAnnotationsSize() {
+    return (this.annotations == null) ? 0 : this.annotations.size();
+  }
+
+  public java.util.Iterator<Annotation> getAnnotationsIterator() {
+    return (this.annotations == null) ? null : this.annotations.iterator();
+  }
+
+  public void addToAnnotations(Annotation elem) {
+    if (this.annotations == null) {
+      this.annotations = new ArrayList<Annotation>();
+    }
+    this.annotations.add(elem);
+  }
+
+  public List<Annotation> getAnnotations() {
+    return this.annotations;
+  }
+
+  public Span setAnnotations(List<Annotation> annotations) {
+    this.annotations = annotations;
+    return this;
+  }
+
+  public void unsetAnnotations() {
+    this.annotations = null;
+  }
+
+  /** Returns true if field annotations is set (has been assigned a value) and false otherwise */
+  public boolean isSetAnnotations() {
+    return this.annotations != null;
+  }
+
+  public void setAnnotationsIsSet(boolean value) {
+    if (!value) {
+      this.annotations = null;
+    }
+  }
+
+  public int getBinary_annotationsSize() {
+    return (this.binary_annotations == null) ? 0 : this.binary_annotations.size();
+  }
+
+  public java.util.Iterator<BinaryAnnotation> getBinary_annotationsIterator() {
+    return (this.binary_annotations == null) ? null : this.binary_annotations.iterator();
+  }
+
+  public void addToBinary_annotations(BinaryAnnotation elem) {
+    if (this.binary_annotations == null) {
+      this.binary_annotations = new ArrayList<BinaryAnnotation>();
+    }
+    this.binary_annotations.add(elem);
+  }
+
+  public List<BinaryAnnotation> getBinary_annotations() {
+    return this.binary_annotations;
+  }
+
+  public Span setBinary_annotations(List<BinaryAnnotation> binary_annotations) {
+    this.binary_annotations = binary_annotations;
+    return this;
+  }
+
+  public void unsetBinary_annotations() {
+    this.binary_annotations = null;
+  }
+
+  /** Returns true if field binary_annotations is set (has been assigned a value) and false otherwise */
+  public boolean isSetBinary_annotations() {
+    return this.binary_annotations != null;
+  }
+
+  public void setBinary_annotationsIsSet(boolean value) {
+    if (!value) {
+      this.binary_annotations = null;
+    }
+  }
+
+  public boolean isDebug() {
+    return this.debug;
+  }
+
+  public Span setDebug(boolean debug) {
+    this.debug = debug;
+    setDebugIsSet(true);
+    return this;
+  }
+
+  public void unsetDebug() {
+    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __DEBUG_ISSET_ID);
+  }
+
+  /** Returns true if field debug is set (has been assigned a value) and false otherwise */
+  public boolean isSetDebug() {
+    return EncodingUtils.testBit(__isset_bitfield, __DEBUG_ISSET_ID);
+  }
+
+  public void setDebugIsSet(boolean value) {
+    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __DEBUG_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case TRACE_ID:
+      if (value == null) {
+        unsetTrace_id();
+      } else {
+        setTrace_id((Long)value);
+      }
+      break;
+
+    case NAME:
+      if (value == null) {
+        unsetName();
+      } else {
+        setName((String)value);
+      }
+      break;
+
+    case ID:
+      if (value == null) {
+        unsetId();
+      } else {
+        setId((Long)value);
+      }
+      break;
+
+    case PARENT_ID:
+      if (value == null) {
+        unsetParent_id();
+      } else {
+        setParent_id((Long)value);
+      }
+      break;
+
+    case ANNOTATIONS:
+      if (value == null) {
+        unsetAnnotations();
+      } else {
+        setAnnotations((List<Annotation>)value);
+      }
+      break;
+
+    case BINARY_ANNOTATIONS:
+      if (value == null) {
+        unsetBinary_annotations();
+      } else {
+        setBinary_annotations((List<BinaryAnnotation>)value);
+      }
+      break;
+
+    case DEBUG:
+      if (value == null) {
+        unsetDebug();
+      } else {
+        setDebug((Boolean)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case TRACE_ID:
+      return Long.valueOf(getTrace_id());
+
+    case NAME:
+      return getName();
+
+    case ID:
+      return Long.valueOf(getId());
+
+    case PARENT_ID:
+      return Long.valueOf(getParent_id());
+
+    case ANNOTATIONS:
+      return getAnnotations();
+
+    case BINARY_ANNOTATIONS:
+      return getBinary_annotations();
+
+    case DEBUG:
+      return Boolean.valueOf(isDebug());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case TRACE_ID:
+      return isSetTrace_id();
+    case NAME:
+      return isSetName();
+    case ID:
+      return isSetId();
+    case PARENT_ID:
+      return isSetParent_id();
+    case ANNOTATIONS:
+      return isSetAnnotations();
+    case BINARY_ANNOTATIONS:
+      return isSetBinary_annotations();
+    case DEBUG:
+      return isSetDebug();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof Span)
+      return this.equals((Span)that);
+    return false;
+  }
+
+  public boolean equals(Span that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_trace_id = true;
+    boolean that_present_trace_id = true;
+    if (this_present_trace_id || that_present_trace_id) {
+      if (!(this_present_trace_id && that_present_trace_id))
+        return false;
+      if (this.trace_id != that.trace_id)
+        return false;
+    }
+
+    boolean this_present_name = true && this.isSetName();
+    boolean that_present_name = true && that.isSetName();
+    if (this_present_name || that_present_name) {
+      if (!(this_present_name && that_present_name))
+        return false;
+      if (!this.name.equals(that.name))
+        return false;
+    }
+
+    boolean this_present_id = true;
+    boolean that_present_id = true;
+    if (this_present_id || that_present_id) {
+      if (!(this_present_id && that_present_id))
+        return false;
+      if (this.id != that.id)
+        return false;
+    }
+
+    boolean this_present_parent_id = true && this.isSetParent_id();
+    boolean that_present_parent_id = true && that.isSetParent_id();
+    if (this_present_parent_id || that_present_parent_id) {
+      if (!(this_present_parent_id && that_present_parent_id))
+        return false;
+      if (this.parent_id != that.parent_id)
+        return false;
+    }
+
+    boolean this_present_annotations = true && this.isSetAnnotations();
+    boolean that_present_annotations = true && that.isSetAnnotations();
+    if (this_present_annotations || that_present_annotations) {
+      if (!(this_present_annotations && that_present_annotations))
+        return false;
+      if (!this.annotations.equals(that.annotations))
+        return false;
+    }
+
+    boolean this_present_binary_annotations = true && this.isSetBinary_annotations();
+    boolean that_present_binary_annotations = true && that.isSetBinary_annotations();
+    if (this_present_binary_annotations || that_present_binary_annotations) {
+      if (!(this_present_binary_annotations && that_present_binary_annotations))
+        return false;
+      if (!this.binary_annotations.equals(that.binary_annotations))
+        return false;
+    }
+
+    boolean this_present_debug = true && this.isSetDebug();
+    boolean that_present_debug = true && that.isSetDebug();
+    if (this_present_debug || that_present_debug) {
+      if (!(this_present_debug && that_present_debug))
+        return false;
+      if (this.debug != that.debug)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(Span other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    Span typedOther = (Span)other;
+
+    lastComparison = Boolean.valueOf(isSetTrace_id()).compareTo(typedOther.isSetTrace_id());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetTrace_id()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.trace_id, typedOther.trace_id);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetName()).compareTo(typedOther.isSetName());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetName()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.name, typedOther.name);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetId()).compareTo(typedOther.isSetId());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetId()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.id, typedOther.id);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetParent_id()).compareTo(typedOther.isSetParent_id());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetParent_id()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.parent_id, typedOther.parent_id);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetAnnotations()).compareTo(typedOther.isSetAnnotations());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetAnnotations()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.annotations, typedOther.annotations);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetBinary_annotations()).compareTo(typedOther.isSetBinary_annotations());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetBinary_annotations()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.binary_annotations, typedOther.binary_annotations);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetDebug()).compareTo(typedOther.isSetDebug());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetDebug()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.debug, typedOther.debug);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("Span(");
+    boolean first = true;
+
+    sb.append("trace_id:");
+    sb.append(this.trace_id);
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("name:");
+    if (this.name == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.name);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("id:");
+    sb.append(this.id);
+    first = false;
+    if (isSetParent_id()) {
+      if (!first) sb.append(", ");
+      sb.append("parent_id:");
+      sb.append(this.parent_id);
+      first = false;
+    }
+    if (!first) sb.append(", ");
+    sb.append("annotations:");
+    if (this.annotations == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.annotations);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("binary_annotations:");
+    if (this.binary_annotations == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.binary_annotations);
+    }
+    first = false;
+    if (isSetDebug()) {
+      if (!first) sb.append(", ");
+      sb.append("debug:");
+      sb.append(this.debug);
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    // check for sub-struct validity
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bitfield = 0;
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private static class SpanStandardSchemeFactory implements SchemeFactory {
+    public SpanStandardScheme getScheme() {
+      return new SpanStandardScheme();
+    }
+  }
+
+  private static class SpanStandardScheme extends StandardScheme<Span> {
+
+    public void read(org.apache.thrift.protocol.TProtocol iprot, Span struct) throws org.apache.thrift.TException {
+      org.apache.thrift.protocol.TField schemeField;
+      iprot.readStructBegin();
+      while (true)
+      {
+        schemeField = iprot.readFieldBegin();
+        if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
+          break;
+        }
+        switch (schemeField.id) {
+          case 1: // TRACE_ID
+            if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
+              struct.trace_id = iprot.readI64();
+              struct.setTrace_idIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 3: // NAME
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
+              struct.name = iprot.readString();
+              struct.setNameIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 4: // ID
+            if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
+              struct.id = iprot.readI64();
+              struct.setIdIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 5: // PARENT_ID
+            if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
+              struct.parent_id = iprot.readI64();
+              struct.setParent_idIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 6: // ANNOTATIONS
+            if (schemeField.type == org.apache.thrift.protocol.TType.LIST) {
+              {
+                org.apache.thrift.protocol.TList _list0 = iprot.readListBegin();
+                struct.annotations = new ArrayList<Annotation>(_list0.size);
+                for (int _i1 = 0; _i1 < _list0.size; ++_i1)
+                {
+                  Annotation _elem2; // required
+                  _elem2 = new Annotation();
+                  _elem2.read(iprot);
+                  struct.annotations.add(_elem2);
+                }
+                iprot.readListEnd();
+              }
+              struct.setAnnotationsIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 8: // BINARY_ANNOTATIONS
+            if (schemeField.type == org.apache.thrift.protocol.TType.LIST) {
+              {
+                org.apache.thrift.protocol.TList _list3 = iprot.readListBegin();
+                struct.binary_annotations = new ArrayList<BinaryAnnotation>(_list3.size);
+                for (int _i4 = 0; _i4 < _list3.size; ++_i4)
+                {
+                  BinaryAnnotation _elem5; // required
+                  _elem5 = new BinaryAnnotation();
+                  _elem5.read(iprot);
+                  struct.binary_annotations.add(_elem5);
+                }
+                iprot.readListEnd();
+              }
+              struct.setBinary_annotationsIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 9: // DEBUG
+            if (schemeField.type == org.apache.thrift.protocol.TType.BOOL) {
+              struct.debug = iprot.readBool();
+              struct.setDebugIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          default:
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+        }
+        iprot.readFieldEnd();
+      }
+      iprot.readStructEnd();
+
+      // check for required fields of primitive type, which can't be checked in the validate method
+      struct.validate();
+    }
+
+    public void write(org.apache.thrift.protocol.TProtocol oprot, Span struct) throws org.apache.thrift.TException {
+      struct.validate();
+
+      oprot.writeStructBegin(STRUCT_DESC);
+      oprot.writeFieldBegin(TRACE_ID_FIELD_DESC);
+      oprot.writeI64(struct.trace_id);
+      oprot.writeFieldEnd();
+      if (struct.name != null) {
+        oprot.writeFieldBegin(NAME_FIELD_DESC);
+        oprot.writeString(struct.name);
+        oprot.writeFieldEnd();
+      }
+      oprot.writeFieldBegin(ID_FIELD_DESC);
+      oprot.writeI64(struct.id);
+      oprot.writeFieldEnd();
+      if (struct.isSetParent_id()) {
+        oprot.writeFieldBegin(PARENT_ID_FIELD_DESC);
+        oprot.writeI64(struct.parent_id);
+        oprot.writeFieldEnd();
+      }
+      if (struct.annotations != null) {
+        oprot.writeFieldBegin(ANNOTATIONS_FIELD_DESC);
+        {
+          oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, struct.annotations.size()));
+          for (Annotation _iter6 : struct.annotations)
+          {
+            _iter6.write(oprot);
+          }
+          oprot.writeListEnd();
+        }
+        oprot.writeFieldEnd();
+      }
+      if (struct.binary_annotations != null) {
+        oprot.writeFieldBegin(BINARY_ANNOTATIONS_FIELD_DESC);
+        {
+          oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, struct.binary_annotations.size()));
+          for (BinaryAnnotation _iter7 : struct.binary_annotations)
+          {
+            _iter7.write(oprot);
+          }
+          oprot.writeListEnd();
+        }
+        oprot.writeFieldEnd();
+      }
+      if (struct.isSetDebug()) {
+        oprot.writeFieldBegin(DEBUG_FIELD_DESC);
+        oprot.writeBool(struct.debug);
+        oprot.writeFieldEnd();
+      }
+      oprot.writeFieldStop();
+      oprot.writeStructEnd();
+    }
+
+  }
+
+  private static class SpanTupleSchemeFactory implements SchemeFactory {
+    public SpanTupleScheme getScheme() {
+      return new SpanTupleScheme();
+    }
+  }
+
+  private static class SpanTupleScheme extends TupleScheme<Span> {
+
+    @Override
+    public void write(org.apache.thrift.protocol.TProtocol prot, Span struct) throws org.apache.thrift.TException {
+      TTupleProtocol oprot = (TTupleProtocol) prot;
+      BitSet optionals = new BitSet();
+      if (struct.isSetTrace_id()) {
+        optionals.set(0);
+      }
+      if (struct.isSetName()) {
+        optionals.set(1);
+      }
+      if (struct.isSetId()) {
+        optionals.set(2);
+      }
+      if (struct.isSetParent_id()) {
+        optionals.set(3);
+      }
+      if (struct.isSetAnnotations()) {
+        optionals.set(4);
+      }
+      if (struct.isSetBinary_annotations()) {
+        optionals.set(5);
+      }
+      if (struct.isSetDebug()) {
+        optionals.set(6);
+      }
+      oprot.writeBitSet(optionals, 7);
+      if (struct.isSetTrace_id()) {
+        oprot.writeI64(struct.trace_id);
+      }
+      if (struct.isSetName()) {
+        oprot.writeString(struct.name);
+      }
+      if (struct.isSetId()) {
+        oprot.writeI64(struct.id);
+      }
+      if (struct.isSetParent_id()) {
+        oprot.writeI64(struct.parent_id);
+      }
+      if (struct.isSetAnnotations()) {
+        {
+          oprot.writeI32(struct.annotations.size());
+          for (Annotation _iter8 : struct.annotations)
+          {
+            _iter8.write(oprot);
+          }
+        }
+      }
+      if (struct.isSetBinary_annotations()) {
+        {
+          oprot.writeI32(struct.binary_annotations.size());
+          for (BinaryAnnotation _iter9 : struct.binary_annotations)
+          {
+            _iter9.write(oprot);
+          }
+        }
+      }
+      if (struct.isSetDebug()) {
+        oprot.writeBool(struct.debug);
+      }
+    }
+
+    @Override
+    public void read(org.apache.thrift.protocol.TProtocol prot, Span struct) throws org.apache.thrift.TException {
+      TTupleProtocol iprot = (TTupleProtocol) prot;
+      BitSet incoming = iprot.readBitSet(7);
+      if (incoming.get(0)) {
+        struct.trace_id = iprot.readI64();
+        struct.setTrace_idIsSet(true);
+      }
+      if (incoming.get(1)) {
+        struct.name = iprot.readString();
+        struct.setNameIsSet(true);
+      }
+      if (incoming.get(2)) {
+        struct.id = iprot.readI64();
+        struct.setIdIsSet(true);
+      }
+      if (incoming.get(3)) {
+        struct.parent_id = iprot.readI64();
+        struct.setParent_idIsSet(true);
+      }
+      if (incoming.get(4)) {
+        {
+          org.apache.thrift.protocol.TList _list10 = new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, iprot.readI32());
+          struct.annotations = new ArrayList<Annotation>(_list10.size);
+          for (int _i11 = 0; _i11 < _list10.size; ++_i11)
+          {
+            Annotation _elem12; // required
+            _elem12 = new Annotation();
+            _elem12.read(iprot);
+            struct.annotations.add(_elem12);
+          }
+        }
+        struct.setAnnotationsIsSet(true);
+      }
+      if (incoming.get(5)) {
+        {
+          org.apache.thrift.protocol.TList _list13 = new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, iprot.readI32());
+          struct.binary_annotations = new ArrayList<BinaryAnnotation>(_list13.size);
+          for (int _i14 = 0; _i14 < _list13.size; ++_i14)
+          {
+            BinaryAnnotation _elem15; // required
+            _elem15 = new BinaryAnnotation();
+            _elem15.read(iprot);
+            struct.binary_annotations.add(_elem15);
+          }
+        }
+        struct.setBinary_annotationsIsSet(true);
+      }
+      if (incoming.get(6)) {
+        struct.debug = iprot.readBool();
+        struct.setDebugIsSet(true);
+      }
+    }
+  }
+
+}
+
diff --git a/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/zipkinCoreConstants.java b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/zipkinCoreConstants.java
new file mode 100644
index 0000000..f3e1cde
--- /dev/null
+++ b/htrace-zipkin/src/main/java/com/twitter/zipkin/gen/zipkinCoreConstants.java
@@ -0,0 +1,43 @@
+/**
+ * Autogenerated by Thrift Compiler (0.9.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package com.twitter.zipkin.gen;
+
+import org.apache.thrift.scheme.IScheme;
+import org.apache.thrift.scheme.SchemeFactory;
+import org.apache.thrift.scheme.StandardScheme;
+
+import org.apache.thrift.scheme.TupleScheme;
+import org.apache.thrift.protocol.TTupleProtocol;
+import org.apache.thrift.protocol.TProtocolException;
+import org.apache.thrift.EncodingUtils;
+import org.apache.thrift.TException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class zipkinCoreConstants {
+
+  public static final String CLIENT_SEND = "cs";
+
+  public static final String CLIENT_RECV = "cr";
+
+  public static final String SERVER_SEND = "ss";
+
+  public static final String SERVER_RECV = "sr";
+
+}
diff --git a/htrace-zipkin/src/main/java/org/htrace/impl/ZipkinSpanReceiver.java b/htrace-zipkin/src/main/java/org/htrace/impl/ZipkinSpanReceiver.java
new file mode 100644
index 0000000..198b654
--- /dev/null
+++ b/htrace-zipkin/src/main/java/org/htrace/impl/ZipkinSpanReceiver.java
@@ -0,0 +1,362 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.htrace.impl;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.twitter.zipkin.gen.LogEntry;
+import com.twitter.zipkin.gen.Scribe;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TProtocol;
+import org.apache.thrift.protocol.TProtocolFactory;
+import org.apache.thrift.transport.TFramedTransport;
+import org.apache.thrift.transport.TIOStreamTransport;
+import org.apache.thrift.transport.TSocket;
+import org.apache.thrift.transport.TTransport;
+import org.apache.thrift.transport.TTransportException;
+import org.htrace.HTraceConfiguration;
+import org.htrace.Span;
+import org.htrace.SpanReceiver;
+import org.htrace.zipkin.HTraceToZipkinConverter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Zipkin is an open source tracing library. This span receiver acts as a bridge between HTrace and
+ * Zipkin, that converts HTrace Span objects into Zipkin Span objects.
+ * <p/>
+ * HTrace spans are queued into a blocking queue.  From there background worker threads will
+ * batch the spans together and then send them through to a Zipkin collector.
+ */
+public class ZipkinSpanReceiver implements SpanReceiver {
+  private static final Log LOG = LogFactory.getLog(ZipkinSpanReceiver.class);
+
+  /**
+   * Default hostname to fall back on.
+   */
+  private static final String DEFAULT_COLLECTOR_HOSTNAME = "localhost";
+
+  /**
+   * Default collector port.
+   */
+  private static final int DEFAULT_COLLECTOR_PORT = 9410; // trace collector default port.
+
+  /**
+   * this is used to tell scribe that the entries are for zipkin..
+   */
+  private static final String CATEGORY = "zipkin";
+
+  /**
+   * Whether the service which is traced is in client or a server mode. It is used while creating
+   * the Endpoint.
+   */
+  private static final boolean DEFAULT_IN_CLIENT_MODE = false;
+
+  /**
+   * How long this receiver will try and wait for all threads to shutdown.
+   */
+  private static final int SHUTDOWN_TIMEOUT = 30;
+
+  /**
+   * How many spans this receiver will try and send in one batch.
+   */
+  private static final int MAX_SPAN_BATCH_SIZE = 100;
+
+  /**
+   * How many errors in a row before we start dropping traces on the floor.
+   */
+  private static final int MAX_ERRORS = 10;
+
+  /**
+   * The queue that will get all HTrace spans that are to be sent.
+   */
+  private final BlockingQueue<Span> queue;
+
+  /**
+   * Factory used to encode a Zipkin Span to bytes.
+   */
+  private final TProtocolFactory protocolFactory;
+
+  /**
+   * Boolean used to signal that the threads should end.
+   */
+  private final AtomicBoolean running = new AtomicBoolean(true);
+
+  /**
+   * The thread factory used to create new ExecutorService.
+   * <p/>
+   * This will be the same factory for the lifetime of this object so that
+   * no thread names will ever be duplicated.
+   */
+  private final ThreadFactory tf;
+
+  ////////////////////
+  /// Variables that will change on each call to configure()
+  ///////////////////
+  private HTraceToZipkinConverter converter;
+  private ExecutorService service;
+  private HTraceConfiguration conf;
+  private String collectorHostname;
+  private int collectorPort;
+
+  public ZipkinSpanReceiver() {
+    this.queue = new ArrayBlockingQueue<Span>(1000);
+    this.protocolFactory = new TBinaryProtocol.Factory();
+
+    tf = new ThreadFactoryBuilder().setDaemon(true)
+        .setNameFormat("zipkinSpanReceiver-%d")
+        .build();
+  }
+
+  @Override
+  public void configure(HTraceConfiguration conf) {
+    this.conf = conf;
+
+    this.collectorHostname = conf.get("zipkin.collector-hostname",
+        DEFAULT_COLLECTOR_HOSTNAME);
+    this.collectorPort = conf.getInt("zipkin.collector-port",
+        DEFAULT_COLLECTOR_PORT);
+
+    // initialize the endpoint. This endpoint is used while writing the Span.
+    initConverter();
+
+    int numThreads = conf.getInt("zipkin.num-threads", 1);
+
+    // If there are already threads runnnig tear them down.
+    if (this.service != null) {
+      this.service.shutdownNow();
+      this.service = null;
+    }
+
+    this.service = Executors.newFixedThreadPool(numThreads, tf);
+
+    for (int i = 0; i < numThreads; i++) {
+      this.service.submit(new WriteSpanRunnable());
+    }
+  }
+
+  /**
+   * Set up the HTrace to Zipkin converter.
+   */
+  private void initConverter() {
+    InetAddress tracedServiceHostname = null;
+    // Try and get the hostname.  If it's not configured try and get the local hostname.
+    try {
+      String host = conf.get("zipkin.traced-service-hostname",
+          InetAddress.getLocalHost().getHostAddress());
+
+      tracedServiceHostname = InetAddress.getByName(host);
+    } catch (UnknownHostException e) {
+      LOG.error("Couldn't get the localHost address", e);
+    }
+    short tracedServicePort = (short) conf.getInt("zipkin.traced-service-port", -1);
+    byte[] address = tracedServiceHostname != null
+        ? tracedServiceHostname.getAddress() : DEFAULT_COLLECTOR_HOSTNAME.getBytes();
+    int ipv4 = ByteBuffer.wrap(address).getInt();
+    this.converter = new HTraceToZipkinConverter(ipv4, tracedServicePort);
+  }
+
+
+  private class WriteSpanRunnable implements Runnable {
+    /**
+     * scribe client to push zipkin spans
+     */
+    private Scribe.Client scribeClient = null;
+    private final ByteArrayOutputStream baos;
+    private final TProtocol streamProtocol;
+
+    public WriteSpanRunnable() {
+      baos = new ByteArrayOutputStream();
+      streamProtocol = protocolFactory.getProtocol(new TIOStreamTransport(baos));
+    }
+
+    /**
+     * This runnable converts a HTrace span to a Zipkin span and sends it across the zipkin
+     * collector as a thrift object. The scribe client which is used for rpc writes a list of
+     * LogEntry objects, so the span objects are first transformed into LogEntry objects before
+     * sending to the zipkin-collector.
+     * <p/>
+     * Here is a little ascii art which shows the above transformation:
+     * <pre>
+     *  +------------+   +------------+   +------------+              +-----------------+
+     *  | HTrace Span|-->|Zipkin Span |-->| (LogEntry) | ===========> | Zipkin Collector|
+     *  +------------+   +------------+   +------------+ (Scribe rpc) +-----------------+
+     *  </pre>
+     */
+    @Override
+    public void run() {
+
+      List<Span> dequeuedSpans = new ArrayList<Span>(MAX_SPAN_BATCH_SIZE);
+
+      long errorCount = 0;
+
+      while (running.get() || queue.size() > 0) {
+        Span firstSpan = null;
+        try {
+          // Block for up to a second. to try and get a span.
+          // We only block for a little bit in order to notice if the running value has changed
+          firstSpan = queue.poll(1, TimeUnit.SECONDS);
+
+          // If the poll was successful then it's possible that there
+          // will be other spans to get. Try and get them.
+          if (firstSpan != null) {
+            // Add the first one that we got
+            dequeuedSpans.add(firstSpan);
+            // Try and get up to 100 queues
+            queue.drainTo(dequeuedSpans, MAX_SPAN_BATCH_SIZE - 1);
+          }
+
+        } catch (InterruptedException ie) {
+          // Ignored.
+        }
+
+        if (dequeuedSpans.isEmpty()) continue;
+
+        // If this is the first time through or there was an error re-connect
+        if (scribeClient == null) {
+          startClient();
+        }
+        // Create a new list every time through so that the list doesn't change underneath
+        // thrift as it's sending.
+        List<LogEntry> entries = new ArrayList<LogEntry>(dequeuedSpans.size());
+        try {
+          // Convert every de-queued span
+          for (Span htraceSpan : dequeuedSpans) {
+            // convert the HTrace span to Zipkin span
+            com.twitter.zipkin.gen.Span zipkinSpan = converter.convert(htraceSpan);
+            // Clear any old data.
+            baos.reset();
+            // Write the span to a BAOS
+            zipkinSpan.write(streamProtocol);
+
+            // Do Base64 encoding and put the string into a log entry.
+            LogEntry logEntry =
+                new LogEntry(CATEGORY, Base64.encodeBase64String(baos.toByteArray()));
+            entries.add(logEntry);
+          }
+
+          // Send the entries
+          scribeClient.Log(entries);
+          // clear the list for the next time through.
+          dequeuedSpans.clear();
+          // reset the error counter.
+          errorCount = 0;
+        } catch (Exception e) {
+          LOG.error("Error when writing to the zipkin collector: " +
+              collectorHostname + ":" + collectorPort);
+
+          errorCount += 1;
+          // If there have been ten errors in a row start dropping things.
+          if (errorCount < MAX_ERRORS) {
+            try {
+              queue.addAll(dequeuedSpans);
+            } catch (IllegalStateException ex) {
+              LOG.error("Drop " + dequeuedSpans.size() + " span(s) because queue is full");
+            }
+          }
+
+          closeClient();
+          try {
+            // Since there was an error sleep just a little bit to try and allow the
+            // zipkin collector some time to recover.
+            Thread.sleep(500);
+          } catch (InterruptedException e1) {
+            // Ignored
+          }
+        }
+      }
+      closeClient();
+    }
+
+    /**
+     * Close out the connection.
+     */
+    private void closeClient() {
+      // close out the transport.
+      if (scribeClient != null) {
+        scribeClient.getInputProtocol().getTransport().close();
+        scribeClient = null;
+      }
+    }
+
+    /**
+     * Re-connect to Zipkin.
+     */
+    private void startClient() {
+      if (this.scribeClient == null) {
+        TTransport transport = new TFramedTransport(new TSocket(collectorHostname, collectorPort));
+        try {
+          transport.open();
+        } catch (TTransportException e) {
+          e.printStackTrace();
+        }
+        TProtocol protocol = protocolFactory.getProtocol(transport);
+        this.scribeClient = new Scribe.Client(protocol);
+      }
+    }
+  }
+
+  /**
+   * Close the receiver.
+   * <p/>
+   * This tries to shut
+   *
+   * @throws IOException
+   */
+  @Override
+  public void close() throws IOException {
+    running.set(false);
+    service.shutdown();
+    try {
+      if (!service.awaitTermination(SHUTDOWN_TIMEOUT, TimeUnit.SECONDS)) {
+        LOG.error("Was not able to process all remaining spans to write upon closing in: " +
+            SHUTDOWN_TIMEOUT + " " + TimeUnit.SECONDS + ". There could be un-sent spans still left." +
+            "  They have been dropped.");
+      }
+    } catch (InterruptedException e1) {
+      LOG.warn("Thread interrupted when terminating executor.", e1);
+    }
+  }
+
+  @Override
+  public void receiveSpan(Span span) {
+    if (running.get()) {
+      try {
+        this.queue.add(span);
+      } catch (IllegalStateException e) {
+        LOG.error("Error trying to append span (" + span.getDescription() + ") to the queue."
+            + "  Blocking Queue was full.");
+      }
+    }
+  }
+}
diff --git a/htrace-zipkin/src/main/java/org/htrace/zipkin/HTraceToZipkinConverter.java b/htrace-zipkin/src/main/java/org/htrace/zipkin/HTraceToZipkinConverter.java
new file mode 100644
index 0000000..0a3a60a
--- /dev/null
+++ b/htrace-zipkin/src/main/java/org/htrace/zipkin/HTraceToZipkinConverter.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.htrace.zipkin;
+
+import com.twitter.zipkin.gen.Annotation;
+import com.twitter.zipkin.gen.AnnotationType;
+import com.twitter.zipkin.gen.BinaryAnnotation;
+import com.twitter.zipkin.gen.Endpoint;
+import com.twitter.zipkin.gen.Span;
+import com.twitter.zipkin.gen.zipkinCoreConstants;
+import org.htrace.TimelineAnnotation;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class is responsible for converting a HTrace.Span to a Zipkin.Span object. To use the Zipkin
+ * infrastructure (collector, front end), we need to store the Span information in a zipkin specific
+ * format. This class transforms a HTrace:Span object to a Zipkin:Span object.
+ * <p/>
+ * This is how both Span objects are related:
+ * <table>
+ * <col width="50%"/> <col width="50%"/> <thead>
+ * <tr>
+ * <th>HTrace:Span</th>
+ * <th>Zipkin:Span</th>
+ * </tr>
+ * <thead> <tbody>
+ * <tr>
+ * <td>TraceId</td>
+ * <td>TraceId</td>
+ * </tr>
+ * <tr>
+ * <td>ParentId</td>
+ * <td>ParentId</td>
+ * </tr>
+ * <tr>
+ * <td>SpanId</td>
+ * <td>id</td>
+ * </tr>
+ * <tr>
+ * <td>Description</td>
+ * <td>Name</td>
+ * </tr>
+ * <tr>
+ * <td>startTime, stopTime</td>
+ * <td>Annotations (cs, cr, sr, ss)</td>
+ * </tr>
+ * <tr>
+ * <td>Other annotations</td>
+ * <td>Annotations</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p/>
+ */
+public class HTraceToZipkinConverter {
+
+  private final int ipv4Address;
+  private final short port;
+
+
+  private static final Map<String, Integer> DEFAULT_PORTS = new HashMap<String, Integer>();
+
+  static {
+    DEFAULT_PORTS.put("hmaster", 60000);
+    DEFAULT_PORTS.put("hregionserver",  60020);
+    DEFAULT_PORTS.put("namenode", 8020);
+    DEFAULT_PORTS.put("datanode", 50010);
+  }
+
+  public HTraceToZipkinConverter(int ipv4Address, short port) {
+    this.ipv4Address = ipv4Address;
+    this.port = port;
+  }
+
+  /**
+   * Converts a given HTrace span to a Zipkin Span.
+   * <ul>
+   * <li>First set the start annotation. [CS, SR], depending whether it is a client service or not.
+   * <li>Set other id's, etc [TraceId's etc]
+   * <li>Create binary annotations based on data from HTrace Span object.
+   * <li>Set the last annotation. [SS, CR]
+   * </ul>
+   */
+  public Span convert(org.htrace.Span hTraceSpan) {
+    Span zipkinSpan = new Span();
+    String serviceName = hTraceSpan.getProcessId().toLowerCase();
+    Endpoint ep = new Endpoint(ipv4Address, (short) getPort(serviceName), serviceName);
+    List<Annotation> annotationList = createZipkinAnnotations(hTraceSpan, ep);
+    List<BinaryAnnotation> binaryAnnotationList = createZipkinBinaryAnnotations(hTraceSpan, ep);
+    zipkinSpan.setTrace_id(hTraceSpan.getTraceId());
+    if (hTraceSpan.getParentId() != org.htrace.Span.ROOT_SPAN_ID) {
+      zipkinSpan.setParent_id(hTraceSpan.getParentId());
+    }
+    zipkinSpan.setId(hTraceSpan.getSpanId());
+    zipkinSpan.setName(hTraceSpan.getDescription());
+    zipkinSpan.setAnnotations(annotationList);
+    zipkinSpan.setBinary_annotations(binaryAnnotationList);
+    return zipkinSpan;
+  }
+
+  /**
+   * Add annotations from the htrace Span.
+   */
+  private List<Annotation> createZipkinAnnotations(org.htrace.Span hTraceSpan,
+                                                   Endpoint ep) {
+    List<Annotation> annotationList = new ArrayList<Annotation>();
+
+    // add first zipkin  annotation.
+    annotationList.add(createZipkinAnnotation(zipkinCoreConstants.CLIENT_SEND, hTraceSpan.getStartTimeMillis(), ep, true));
+    annotationList.add(createZipkinAnnotation(zipkinCoreConstants.SERVER_RECV, hTraceSpan.getStartTimeMillis(), ep, true));
+    // add HTrace time annotation
+    for (TimelineAnnotation ta : hTraceSpan.getTimelineAnnotations()) {
+      annotationList.add(createZipkinAnnotation(ta.getMessage(), ta.getTime(), ep, true));
+    }
+    // add last zipkin annotation
+    annotationList.add(createZipkinAnnotation(zipkinCoreConstants.SERVER_SEND, hTraceSpan.getStopTimeMillis(), ep, false));
+    annotationList.add(createZipkinAnnotation(zipkinCoreConstants.CLIENT_RECV, hTraceSpan.getStopTimeMillis(), ep, false));
+    return annotationList;
+  }
+
+  /**
+   * Creates a list of Annotations that are present in HTrace Span object.
+   *
+   * @return list of Annotations that could be added to Zipkin Span.
+   */
+  private List<BinaryAnnotation> createZipkinBinaryAnnotations(org.htrace.Span span,
+                                                               Endpoint ep) {
+    List<BinaryAnnotation> l = new ArrayList<BinaryAnnotation>();
+    for (Map.Entry<byte[], byte[]> e : span.getKVAnnotations().entrySet()) {
+      BinaryAnnotation binaryAnn = new BinaryAnnotation();
+      binaryAnn.setAnnotation_type(AnnotationType.BYTES);
+      binaryAnn.setKey(new String(e.getKey()));
+      binaryAnn.setValue(e.getValue());
+      binaryAnn.setHost(ep);
+      l.add(binaryAnn);
+    }
+    return l;
+  }
+
+  /**
+   * Create an annotation with the correct times and endpoint.
+   *
+   * @param value       Annotation value
+   * @param time        timestamp will be extracted
+   * @param ep          the endopint this annotation will be associated with.
+   * @param sendRequest use the first or last timestamp.
+   */
+  private static Annotation createZipkinAnnotation(String value, long time,
+                                                   Endpoint ep, boolean sendRequest) {
+    Annotation annotation = new Annotation();
+    annotation.setHost(ep);
+
+    // Zipkin is in microseconds
+    if (sendRequest) {
+      annotation.setTimestamp(time * 1000);
+    } else {
+      annotation.setTimestamp(time * 1000);
+    }
+
+    annotation.setDuration(1);
+    annotation.setValue(value);
+    return annotation;
+  }
+
+  private int getPort(String serviceName) {
+    if (port != -1) {
+      return port;
+    }
+
+    Integer p = DEFAULT_PORTS.get(serviceName);
+    if (p != null) {
+      return p;
+    }
+    return 80;
+  }
+}
diff --git a/htrace-zipkin/src/main/thrift/scribe.thrift b/htrace-zipkin/src/main/thrift/scribe.thrift
new file mode 100644
index 0000000..1976396
--- /dev/null
+++ b/htrace-zipkin/src/main/thrift/scribe.thrift
@@ -0,0 +1,31 @@
+# Copyright 2012 Twitter Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+namespace java com.twitter.zipkin.gen
+
+enum ResultCode
+{
+  OK,
+  TRY_LATER
+}
+
+struct LogEntry
+{
+  1:  string category,
+  2:  string message
+}
+
+service Scribe
+{
+  ResultCode Log(1: list<LogEntry> messages);
+}
diff --git a/htrace-zipkin/src/main/thrift/zipkinCore.thrift b/htrace-zipkin/src/main/thrift/zipkinCore.thrift
new file mode 100644
index 0000000..a14b888
--- /dev/null
+++ b/htrace-zipkin/src/main/thrift/zipkinCore.thrift
@@ -0,0 +1,58 @@
+# Copyright 2012 Twitter Inc.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#      http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+namespace java com.twitter.zipkin.gen
+namespace rb Zipkin
+
+//************** Collection related structs **************
+
+// these are the annotations we always expect to find in a span
+const string CLIENT_SEND = "cs"
+const string CLIENT_RECV = "cr"
+const string SERVER_SEND = "ss"
+const string SERVER_RECV = "sr"
+
+// this represents a host and port in a network
+struct Endpoint {
+  1: i32 ipv4,
+  2: i16 port                      // beware that this will give us negative ports. some conversion needed
+  3: string service_name           // which service did this operation happen on?
+}
+
+// some event took place, either one by the framework or by the user
+struct Annotation {
+  1: i64 timestamp                 // microseconds from epoch
+  2: string value                  // what happened at the timestamp?
+  3: optional Endpoint host        // host this happened on
+  4: optional i32 duration         // how long did the operation take? microseconds
+}
+
+enum AnnotationType { BOOL, BYTES, I16, I32, I64, DOUBLE, STRING }
+
+struct BinaryAnnotation {
+  1: string key,
+  2: binary value,
+  3: AnnotationType annotation_type,
+  4: optional Endpoint host
+}
+
+struct Span {
+  1: i64 trace_id                  // unique trace id, use for all spans in trace
+  3: string name,                  // span name, rpc method for example
+  4: i64 id,                       // unique span id, only used for this span
+  5: optional i64 parent_id,                // parent span id
+  6: list<Annotation> annotations, // list of all annotations/events that occured
+  8: list<BinaryAnnotation> binary_annotations // any binary annotations
+  9: optional bool debug = 0       // if true, we DEMAND that this span passes all samplers
+}
+
diff --git a/htrace-zipkin/src/test/java/org/htrace/TestHTraceSpanToZipkinSpan.java b/htrace-zipkin/src/test/java/org/htrace/TestHTraceSpanToZipkinSpan.java
new file mode 100644
index 0000000..80cc2bc
--- /dev/null
+++ b/htrace-zipkin/src/test/java/org/htrace/TestHTraceSpanToZipkinSpan.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.htrace;
+
+import com.twitter.zipkin.gen.zipkinCoreConstants;
+import org.htrace.impl.MilliSpan;
+import org.htrace.zipkin.HTraceToZipkinConverter;
+import org.htrace.impl.POJOSpanReceiver;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Creates HTrace and then convert it to Zipkin trace and checks whether it is a valid span or not.
+ */
+public class TestHTraceSpanToZipkinSpan {
+
+  private static final String ROOT_SPAN_DESC = "ROOT";
+
+  @Test
+  public void testHTraceToZipkin() throws IOException {
+    POJOSpanReceiver psr = new POJOSpanReceiver();
+    Trace.addReceiver(psr);
+
+    Span rootSpan = new MilliSpan(ROOT_SPAN_DESC, 1, Span.ROOT_SPAN_ID, 100, "test");
+    Span innerOne = rootSpan.child("Some good work");
+    Span innerTwo = innerOne.child("Some more good work");
+    innerTwo.stop();
+    innerOne.stop();
+    rootSpan.addKVAnnotation("foo".getBytes(), "bar".getBytes());
+    rootSpan.addTimelineAnnotation("timeline");
+    rootSpan.stop();
+
+    for (Span s : new Span[] {rootSpan, innerOne, innerTwo}) {
+      com.twitter.zipkin.gen.Span zs =
+          new HTraceToZipkinConverter(12345, (short) 12).convert(s);
+      assertSpansAreEquivalent(s, zs);
+    }
+  }
+
+  @Test
+  public void testHTraceAnnotationTimestamp() throws IOException, InterruptedException {
+
+    String traceName = "testHTraceAnnotationTimestamp";
+    long startTime = System.currentTimeMillis() * 1000;
+    Span ms = new MilliSpan(traceName, 1, Span.ROOT_SPAN_ID, 2, traceName);
+
+    Thread.sleep(500);
+    long annoStartTime = System.currentTimeMillis() * 1000;
+    Thread.sleep(500);
+    ms.addTimelineAnnotation("anno");
+    Thread.sleep(500);
+    long annoEndTime = System.currentTimeMillis() * 1000;
+    Thread.sleep(500);
+    ms.stop();
+    long endTime = System.currentTimeMillis() * 1000;
+
+
+
+    com.twitter.zipkin.gen.Span zs = new HTraceToZipkinConverter(12345, (short) -1).convert(ms);
+
+    // Check to make sure that all times are in the proper order.
+    for (com.twitter.zipkin.gen.Annotation annotation : zs.getAnnotations()) {
+      // CS and SR should be before the annotation
+      // the annotation should be in between annotationStart and annotationEnd times
+      // SS and CR should be after annotationEnd and before endtime.
+      if (annotation.getValue().equals(zipkinCoreConstants.CLIENT_SEND)
+          || annotation.getValue().equals(zipkinCoreConstants.SERVER_RECV)) {
+        assertTrue(startTime <= annotation.getTimestamp());
+        assertTrue(annotation.getTimestamp() <= annoStartTime);
+      } else if (annotation.getValue().equals(zipkinCoreConstants.CLIENT_RECV)
+          || annotation.getValue().equals(zipkinCoreConstants.SERVER_SEND)) {
+        assertTrue(annoEndTime <= annotation.getTimestamp());
+        assertTrue(annotation.getTimestamp() <= endTime);
+      } else {
+        assertTrue(annoStartTime <= annotation.getTimestamp());
+        assertTrue(annotation.getTimestamp() <= annoEndTime);
+        assertTrue(annotation.getTimestamp() <= endTime);
+      }
+    }
+  }
+
+  @Test
+  public void testHTraceDefaultPort() throws IOException {
+    MilliSpan ms = new MilliSpan("test", 1, 2, 3, "hmaster");
+    com.twitter.zipkin.gen.Span zs = new HTraceToZipkinConverter(12345, (short) -1).convert(ms);
+    for (com.twitter.zipkin.gen.Annotation annotation:zs.getAnnotations()) {
+      assertEquals((short)60000, annotation.getHost().getPort());
+    }
+
+    ms = new MilliSpan("test", 1, 2, 3, "HregIonServer");   // make sure it's all lower cased
+    zs = new HTraceToZipkinConverter(12345, (short) -1).convert(ms);
+    for (com.twitter.zipkin.gen.Annotation annotation:zs.getAnnotations()) {
+      assertEquals((short)60020, annotation.getHost().getPort());
+    }
+  }
+
+  private void assertSpansAreEquivalent(Span s, com.twitter.zipkin.gen.Span zs) {
+    assertEquals(s.getTraceId(), zs.getTrace_id());
+    if (s.getParentId() != Span.ROOT_SPAN_ID) {
+      assertEquals(s.getParentId(), zs.getParent_id());
+    }
+    assertEquals(s.getSpanId(), zs.getId());
+    Assert.assertNotNull(zs.getAnnotations());
+    if (ROOT_SPAN_DESC.equals(zs.getName())) {
+      assertEquals(5, zs.getAnnotations().size());// two start, two stop + one timeline annotation
+      assertEquals(1, zs.getBinary_annotations().size());
+    } else {
+      assertEquals(4, zs.getAnnotations().size());
+    }
+  }
+}
diff --git a/pom.xml b/pom.xml
index 16356c7..9977c74 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,146 +10,189 @@
 OF ANY KIND, either express or implied. See the License for the specific
 language governing permissions and limitations under the License. -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-    <groupId>org.cloudera.htrace</groupId>
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>htrace</artifactId>
-    <name>htrace</name>
-    <version>1.51-SNAPSHOT</version>
-    <description>Tracing library</description>
-    <developers>
-        <developer>
-            <id>jon</id>
-            <name>Jonathan Leavitt</name>
-            <email>jonathan.leavitt@cloudera.com</email>
-            <timezone>-7</timezone>
-            <organization>Cloudera</organization>
-            <organizationUrl>http://www.cloudera.com</organizationUrl>
-        </developer>
-    </developers>
-    <profiles>
-        <profile>
-            <id>release-sign-artifacts</id>
-            <activation>
-                <property>
-                    <name>performRelease</name>
-                    <value>true</value>
-                </property>
-            </activation>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.apache.maven.plugins</groupId>
-                        <artifactId>maven-gpg-plugin</artifactId>
-                        <version>1.1</version>
-                        <executions>
-                            <execution>
-                                <id>sign-artifacts</id>
-                                <phase>verify</phase>
-                                <goals>
-                                    <goal>sign</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
-    <parent>
-        <groupId>org.sonatype.oss</groupId>
-        <artifactId>oss-parent</artifactId>
-        <version>7</version>
-    </parent>
-    <packaging>jar</packaging>
-    <url>https://github.com/cloudera/htrace</url>
-    <licenses>
-        <license>
-            <name>The Apache Software License, Version 2.0</name>
-            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
-            <distribution>repo</distribution>
-            <comments>A business-friendly OSS license</comments>
-        </license>
-    </licenses>
-    <scm>
-        <connection>scm:git:git@github.com:cloudera/htrace.git</connection>
-        <developerConnection>scm:git:git@github.com:cloudera/htrace.git</developerConnection>
-        <url>scm:git:git@github.com:cloudera/htrace.git</url>
-    </scm>
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-source-plugin</artifactId>
-                <version>2.1.2</version>
-                <executions>
-                    <execution>
-                        <id>attach-sources</id>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>jar-no-fork</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <artifactId>maven-javadoc-plugin</artifactId>
-                <version>2.8.1</version>
-                <executions>
-                    <execution>
-                        <id>attach-javadocs</id>
-                        <phase>package</phase>
-                        <goals><goal>jar</goal></goals>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>2.5.1</version>
-                <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
-                    <optimize>true</optimize>
-                    <encoding>UTF-8</encoding>
-                </configuration>
-            </plugin>
-            <plugin>
-                <!-- explicitly define maven-deploy-plugin after other to force exec order -->
-                <artifactId>maven-deploy-plugin</artifactId>
-                <version>2.7</version>
-                <executions>
-                    <execution>
-                        <id>deploy</id>
-                        <phase>deploy</phase>
-                        <goals><goal>deploy</goal></goals>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-    </build>
-    <properties>
-        <targetJdk>1.6</targetJdk>
-    </properties>
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.htrace</groupId>
+  <artifactId>htrace</artifactId>
+  <version>3.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <name>htrace</name>
+  <description>Tracing library</description>
+
+  <modules>
+    <module>htrace-core</module>
+    <module>htrace-zipkin</module>
+  </modules>
+
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
+  </parent>
+
+  <developers>
+    <developer>
+      <id>jon</id>
+      <name>Jonathan Leavitt</name>
+      <email>jonathan.leavitt@cloudera.com</email>
+      <timezone>-7</timezone>
+      <organization>Cloudera</organization>
+      <organizationUrl>http://www.cloudera.com</organizationUrl>
+    </developer>
+    <developer>
+      <id>eclark</id>
+      <name>Elliott Clark</name>
+      <email>eclark@apache.org</email>
+      <timezone>-7</timezone>
+      <organization>Cloudera</organization>
+      <organizationUrl>http://www.cloudera.com</organizationUrl>
+    </developer>
+  </developers>
+
+  <url>https://github.com/cloudera/htrace</url>
+  <licenses>
+    <license>
+      <name>The Apache Software License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+      <comments>A business-friendly OSS license</comments>
+    </license>
+  </licenses>
+  <scm>
+    <connection>scm:git:git@github.com:cloudera/htrace.git</connection>
+    <developerConnection>scm:git:git@github.com:cloudera/htrace.git</developerConnection>
+    <url>scm:git:git@github.com:cloudera/htrace.git</url>
+  </scm>
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-source-plugin</artifactId>
+          <version>2.1.2</version>
+          <executions>
+            <execution>
+              <id>attach-sources</id>
+              <phase>package</phase>
+              <goals>
+                <goal>jar-no-fork</goal>
+              </goals>
+            </execution>
+          </executions>
+        </plugin>
+        <plugin>
+          <artifactId>maven-javadoc-plugin</artifactId>
+          <version>2.8.1</version>
+          <executions>
+            <execution>
+              <id>attach-javadocs</id>
+              <phase>package</phase>
+              <goals>
+                <goal>jar</goal>
+              </goals>
+            </execution>
+          </executions>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.rat</groupId>
+          <artifactId>apache-rat-plugin</artifactId>
+          <version>0.10</version>
+          <executions>
+            <execution>
+              <phase>package</phase>
+              <goals>
+                <goal>check</goal>
+              </goals>
+            </execution>
+          </executions>
+          <configuration>
+            <excludes>
+              <!--  exclude source control files -->
+              <exclude>.git/**</exclude>
+              <exclude>.svn/**</exclude>
+              <exclude>**/.settings/**</exclude>
+              <!-- Readme -->
+              <exclue>README.md</exclue>
+            </excludes>
+          </configuration>
+        </plugin>
+        <plugin>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>2.5.1</version>
+          <configuration>
+            <source>1.6</source>
+            <target>1.6</target>
+            <optimize>true</optimize>
+            <encoding>UTF-8</encoding>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-gpg-plugin</artifactId>
+          <version>1.1</version>
+          <executions>
+            <execution>
+              <id>sign-artifacts</id>
+              <phase>deploy</phase>
+              <goals>
+                <goal>sign</goal>
+              </goals>
+            </execution>
+          </executions>
+        </plugin>
+        <plugin>
+          <!-- explicitly define maven-deploy-plugin after other to force exec order -->
+          <artifactId>maven-deploy-plugin</artifactId>
+          <version>2.7</version>
+          <executions>
+            <execution>
+              <id>deploy</id>
+              <phase>deploy</phase>
+              <goals>
+                <goal>deploy</goal>
+              </goals>
+            </execution>
+          </executions>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-gpg-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <!-- explicitly define maven-deploy-plugin after other to force exec order -->
+        <artifactId>maven-deploy-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+  <properties>
+    <targetJdk>1.6</targetJdk>
+  </properties>
+  <dependencyManagement>
     <dependencies>
-        <dependency>
-            <groupId>org.mortbay.jetty</groupId>
-            <artifactId>jetty-util</artifactId>
-            <version>6.1.26</version>
-        </dependency>
-        <dependency>
-            <groupId>commons-logging</groupId>
-            <artifactId>commons-logging</artifactId>
-            <version>1.1.1</version>
-        </dependency>
-        <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava</artifactId>
-            <version>12.0.1</version>
-        </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <version>4.10</version>
-	    <scope>test</scope>
-        </dependency>
+      <dependency>
+        <groupId>commons-logging</groupId>
+        <artifactId>commons-logging</artifactId>
+        <version>1.1.1</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.guava</groupId>
+        <artifactId>guava</artifactId>
+        <version>12.0.1</version>
+      </dependency>
+      <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>4.10</version>
+        <scope>test</scope>
+      </dependency>
     </dependencies>
+  </dependencyManagement>
 </project>
diff --git a/src/main/java/org/cloudera/htrace/TraceScope.java b/src/main/java/org/cloudera/htrace/TraceScope.java
deleted file mode 100644
index 86ad130..0000000
--- a/src/main/java/org/cloudera/htrace/TraceScope.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.cloudera.htrace;
-
-import java.io.Closeable;
-
-public class TraceScope implements Closeable {
-
-  /** the span for this scope */
-  private final Span span;
-
-  /** the span that was "current" before this scope was entered */
-  private final Span savedSpan;
-
-  private boolean detached = false;
-  
-  TraceScope(Span span, Span saved) {
-    this.span = span;
-    this.savedSpan = saved;
-  }
-  
-  public Span getSpan() {
-    return span;
-  }
-  
-  /**
-   * Remove this span as the current thread, but don't stop it yet or
-   * send it for collection. This is useful if the span object is then
-   * passed to another thread for use with Trace.continueTrace().
-   * 
-   * @return the same Span object
-   */
-  public Span detach() {
-    detached = true;
-
-    Span cur = Tracer.getInstance().currentSpan();
-    if (cur != span) {
-      Tracer.LOG.debug("Closing trace span " + span + " but " +
-        cur + " was top-of-stack");
-    } else {
-      Tracer.getInstance().setCurrentSpan(savedSpan);
-    }
-    return span;
-  }
-  
-  @Override
-  public void close() {
-    if (span == null) return;
-    
-    if (!detached) {
-      // The span is done
-      span.stop();
-      Tracer.getInstance().deliver(span);
-      detach();
-    }
-  }
-}
diff --git a/src/main/java/org/cloudera/htrace/impl/POJOSpanReceiver.java b/src/main/java/org/cloudera/htrace/impl/POJOSpanReceiver.java
deleted file mode 100644
index b08afaf..0000000
--- a/src/main/java/org/cloudera/htrace/impl/POJOSpanReceiver.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.cloudera.htrace.impl;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.HashSet;
-
-import org.cloudera.htrace.HTraceConfiguration;
-import org.cloudera.htrace.Span;
-import org.cloudera.htrace.SpanReceiver;
-
-/**
- * SpanReceiver for testing only that just collects the Span objects it
- * receives. The spans it receives can be accessed with getSpans();
- */
-public class POJOSpanReceiver implements SpanReceiver {
-  private final Collection<Span> spans;
-
-  @Override
-  public void configure(HTraceConfiguration conf) {    
-  }
-
-  /**
-   * @return The spans this POJOSpanReceiver has received.
-   */
-  public Collection<Span> getSpans() {
-    return spans;
-  }
-
-  public POJOSpanReceiver() {
-    this.spans = new HashSet<Span>();
-  }
-
-  @Override
-  public void close() throws IOException {
-  }
-
-  @Override
-  public void receiveSpan(Span span) {
-    spans.add(span);
-  }
-
-}
diff --git a/src/main/java/org/cloudera/htrace/impl/StandardOutSpanReceiver.java b/src/main/java/org/cloudera/htrace/impl/StandardOutSpanReceiver.java
deleted file mode 100644
index 7b14169..0000000
--- a/src/main/java/org/cloudera/htrace/impl/StandardOutSpanReceiver.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.cloudera.htrace.impl;
-
-import java.io.IOException;
-
-import org.cloudera.htrace.HTraceConfiguration;
-import org.cloudera.htrace.Span;
-import org.cloudera.htrace.SpanReceiver;
-
-/**
- * Used for testing. Simply prints to standard out any spans it receives.
- */
-public class StandardOutSpanReceiver implements SpanReceiver {
-
-  @Override
-  public void configure(HTraceConfiguration conf) {    
-  }
-
-  @Override
-  public void receiveSpan(Span span) {
-    System.out.println(span);
-  }
-
-  @Override
-  public void close() throws IOException {
-  }
-}
diff --git a/src/main/java/org/cloudera/htrace/impl/TrueIfTracingSampler.java b/src/main/java/org/cloudera/htrace/impl/TrueIfTracingSampler.java
deleted file mode 100644
index aae231c..0000000
--- a/src/main/java/org/cloudera/htrace/impl/TrueIfTracingSampler.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.cloudera.htrace.impl;
-
-import org.cloudera.htrace.Sampler;
-import org.cloudera.htrace.Trace;
-
-/**
- * A Sampler that returns true if and only if tracing is on the current thread.
- */
-public class TrueIfTracingSampler implements Sampler<Object> {
-
-  public static final TrueIfTracingSampler INSTANCE = new TrueIfTracingSampler();
-
-  private TrueIfTracingSampler() {
-  }
-
-  @Override
-  public boolean next(Object info) {
-    return Trace.isTracing();
-  }
-
-}
diff --git a/src/test/java/org/cloudera/htrace/TestSampler.java b/src/test/java/org/cloudera/htrace/TestSampler.java
deleted file mode 100644
index e3b9a7d..0000000
--- a/src/test/java/org/cloudera/htrace/TestSampler.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.cloudera.htrace;
-
-import static org.junit.Assert.*;
-
-import org.junit.Test;
-
-public class TestSampler {
-  @Test
-  public void testParamterizedSampler() {
-    TestParamSampler sampler = new TestParamSampler();
-    TraceScope s = Trace.startSpan("test", sampler, 1);
-    assertNotNull(s.getSpan());
-    s.close();
-    s = Trace.startSpan("test", sampler, -1);
-    assertNull(s.getSpan());
-    s.close();
-  }
-
-  @Test
-  public void testAlwaysSampler() {
-    TraceScope cur = Trace.startSpan("test", new TraceInfo(0, 0));
-    assertNotNull(cur);
-    cur.close();
-  }
-
-  private class TestParamSampler implements Sampler<Integer> {
-
-    @Override
-    public boolean next(Integer info) {
-      return info > 0;
-    }
-
-  }
-}