You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@htrace.apache.org by cm...@apache.org on 2015/08/25 04:18:09 UTC

[2/2] incubator-htrace git commit: HTRACE-214. De-globalize Tracer.java (cmccabe)

HTRACE-214. De-globalize Tracer.java (cmccabe)


Project: http://git-wip-us.apache.org/repos/asf/incubator-htrace/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-htrace/commit/7997d208
Tree: http://git-wip-us.apache.org/repos/asf/incubator-htrace/tree/7997d208
Diff: http://git-wip-us.apache.org/repos/asf/incubator-htrace/diff/7997d208

Branch: refs/heads/master
Commit: 7997d208989b5e5bf6734da64ec9249c0226c21f
Parents: 0f873fd
Author: Masatake Iwasaki <iw...@apache.org>
Authored: Fri Aug 21 16:00:36 2015 +0900
Committer: Colin P. Mccabe <cm...@apache.org>
Committed: Mon Aug 24 19:15:35 2015 -0700

----------------------------------------------------------------------
 .../org/apache/htrace/core/AlwaysSampler.java   |   3 +-
 .../org/apache/htrace/core/CountSampler.java    |   2 +-
 .../htrace/core/LocalFileSpanReceiver.java      |  20 +-
 .../java/org/apache/htrace/core/MilliSpan.java  |   1 -
 .../org/apache/htrace/core/NeverSampler.java    |   4 +-
 .../java/org/apache/htrace/core/NullScope.java  |  41 +-
 .../apache/htrace/core/POJOSpanReceiver.java    |   2 +-
 .../apache/htrace/core/ProbabilitySampler.java  |   7 +-
 .../java/org/apache/htrace/core/Sampler.java    |  96 +++-
 .../org/apache/htrace/core/SamplerBuilder.java  |  91 ----
 .../main/java/org/apache/htrace/core/Span.java  |   9 +-
 .../java/org/apache/htrace/core/SpanId.java     |   3 -
 .../org/apache/htrace/core/SpanReceiver.java    | 137 ++++-
 .../apache/htrace/core/SpanReceiverBuilder.java | 138 -----
 .../htrace/core/StandardOutSpanReceiver.java    |   2 +-
 .../main/java/org/apache/htrace/core/Trace.java | 219 --------
 .../org/apache/htrace/core/TraceCallable.java   |  39 +-
 .../htrace/core/TraceExecutorService.java       |  19 +-
 .../java/org/apache/htrace/core/TraceProxy.java |  58 --
 .../org/apache/htrace/core/TraceRunnable.java   |  40 +-
 .../java/org/apache/htrace/core/TraceScope.java | 115 ++--
 .../java/org/apache/htrace/core/Tracer.java     | 528 +++++++++++++++++--
 .../org/apache/htrace/core/TracerBuilder.java   | 144 +++++
 .../java/org/apache/htrace/core/TracerId.java   |  44 +-
 .../java/org/apache/htrace/core/TracerPool.java | 283 ++++++++++
 .../org/apache/htrace/core/TestBadClient.java   | 171 ++++--
 .../java/org/apache/htrace/core/TestHTrace.java | 152 +++---
 .../htrace/core/TestLocalFileSpanReceiver.java  |  35 +-
 .../org/apache/htrace/core/TestNullScope.java   |  25 +-
 .../org/apache/htrace/core/TestSampler.java     |  89 +++-
 .../htrace/core/TestSpanReceiverBuilder.java    | 156 +++---
 .../org/apache/htrace/core/TestTracerId.java    |  21 +-
 .../org/apache/htrace/core/TraceCreator.java    |  58 +-
 .../apache/htrace/impl/FlumeSpanReceiver.java   |   7 +-
 .../htrace/impl/TestFlumeSpanReceiver.java      |  68 +--
 .../apache/htrace/HBaseSpanReceiverHost.java    | 107 ----
 .../apache/htrace/impl/HBaseSpanReceiver.java   |  35 +-
 .../org/apache/htrace/impl/HBaseTestUtil.java   |   4 +-
 .../htrace/impl/TestHBaseSpanReceiver.java      |  40 +-
 .../apache/htrace/impl/HTracedRESTReceiver.java |  10 +-
 .../htrace/impl/TestHTracedRESTReceiver.java    |   2 +
 .../apache/htrace/impl/ZipkinSpanReceiver.java  |   9 +-
 .../htrace/TestHTraceSpanToZipkinSpan.java      |  10 +-
 src/main/site/markdown/index.md                 | 186 ++-----
 44 files changed, 1845 insertions(+), 1385 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/AlwaysSampler.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/AlwaysSampler.java b/htrace-core/src/main/java/org/apache/htrace/core/AlwaysSampler.java
index a9259bd..8d5a296 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/AlwaysSampler.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/AlwaysSampler.java
@@ -19,8 +19,7 @@ package org.apache.htrace.core;
 /**
  * A Sampler that always returns true.
  */
-public final class AlwaysSampler implements Sampler {
-
+public final class AlwaysSampler extends Sampler {
   public static final AlwaysSampler INSTANCE = new AlwaysSampler(null);
 
   public AlwaysSampler(HTraceConfiguration conf) {

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/CountSampler.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/CountSampler.java b/htrace-core/src/main/java/org/apache/htrace/core/CountSampler.java
index 10d5c98..5a838c7 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/CountSampler.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/CountSampler.java
@@ -22,7 +22,7 @@ import java.util.concurrent.ThreadLocalRandom;
  * Sampler that returns true every N calls. Specify the frequency interval by configuring a
  * {@code long} value for {@link #SAMPLER_FREQUENCY_CONF_KEY}.
  */
-public class CountSampler implements Sampler {
+public class CountSampler extends Sampler {
   public final static String SAMPLER_FREQUENCY_CONF_KEY = "sampler.frequency";
 
   final long frequency;

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/LocalFileSpanReceiver.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/LocalFileSpanReceiver.java b/htrace-core/src/main/java/org/apache/htrace/core/LocalFileSpanReceiver.java
index 0aed846..69a43b1 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/LocalFileSpanReceiver.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/LocalFileSpanReceiver.java
@@ -41,10 +41,10 @@ import java.util.concurrent.locks.ReentrantLock;
 /**
  * Writes the spans it receives to a local file.
  */
-public class LocalFileSpanReceiver implements SpanReceiver {
+public class LocalFileSpanReceiver extends SpanReceiver {
   private static final Log LOG = LogFactory.getLog(LocalFileSpanReceiver.class);
-  public static final String PATH_KEY = "local-file-span-receiver.path";
-  public static final String CAPACITY_KEY = "local-file-span-receiver.capacity";
+  public static final String PATH_KEY = "local.file.span.receiver.path";
+  public static final String CAPACITY_KEY = "local.file.span.receiver.capacity";
   public static final int CAPACITY_DEFAULT = 5000;
   private static ObjectWriter JSON_WRITER = new ObjectMapper().writer();
   private final String path;
@@ -56,7 +56,6 @@ public class LocalFileSpanReceiver implements SpanReceiver {
   private final FileOutputStream stream;
   private final FileChannel channel;
   private final ReentrantLock channelLock = new ReentrantLock();
-  private final TracerId tracerId;
 
   public LocalFileSpanReceiver(HTraceConfiguration conf) {
     int capacity = conf.getInt(CAPACITY_KEY, CAPACITY_DEFAULT);
@@ -64,9 +63,11 @@ public class LocalFileSpanReceiver implements SpanReceiver {
       throw new IllegalArgumentException(CAPACITY_KEY + " must not be " +
           "less than 1.");
     }
-    this.path = conf.get(PATH_KEY);
-    if (path == null || path.isEmpty()) {
-      throw new IllegalArgumentException("must configure " + PATH_KEY);
+    String pathStr = conf.get(PATH_KEY);
+    if (pathStr == null || pathStr.isEmpty()) {
+      path = getUniqueLocalTraceFileName();
+    } else {
+      path = pathStr;
     }
     boolean success = false;
     try {
@@ -91,7 +92,6 @@ public class LocalFileSpanReceiver implements SpanReceiver {
       LOG.debug("Created new LocalFileSpanReceiver with path = " + path +
                 ", capacity = " + capacity);
     }
-    this.tracerId = new TracerId(conf);
   }
 
   /**
@@ -134,10 +134,6 @@ public class LocalFileSpanReceiver implements SpanReceiver {
 
   @Override
   public void receiveSpan(Span span) {
-    if (span.getTracerId().isEmpty()) {
-      span.setTracerId(tracerId.get());
-    }
-
     // Serialize the span data into a byte[].  Note that we're not holding the
     // lock here, to improve concurrency.
     byte jsonBuf[] = null;

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/MilliSpan.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/MilliSpan.java b/htrace-core/src/main/java/org/apache/htrace/core/MilliSpan.java
index afef809..5dd6bdb 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/MilliSpan.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/MilliSpan.java
@@ -170,7 +170,6 @@ public class MilliSpan implements Span {
         throw new IllegalStateException("Span for " + description
             + " has not been started");
       end = System.currentTimeMillis();
-      Tracer.getInstance().deliver(this);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/NeverSampler.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/NeverSampler.java b/htrace-core/src/main/java/org/apache/htrace/core/NeverSampler.java
index 65f6087..60cc7d2 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/NeverSampler.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/NeverSampler.java
@@ -19,8 +19,7 @@ package org.apache.htrace.core;
 /**
  * A Sampler that never returns true.
  */
-public final class NeverSampler implements Sampler {
-
+public final class NeverSampler extends Sampler {
   public static final NeverSampler INSTANCE = new NeverSampler(null);
 
   public NeverSampler(HTraceConfiguration conf) {
@@ -30,5 +29,4 @@ public final class NeverSampler implements Sampler {
   public boolean next() {
     return false;
   }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/NullScope.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/NullScope.java b/htrace-core/src/main/java/org/apache/htrace/core/NullScope.java
index e7964cf..fe76e46 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/NullScope.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/NullScope.java
@@ -17,28 +17,53 @@
 package org.apache.htrace.core;
 
 /**
- * Singleton instance representing an empty {@link TraceScope}.
+ * An empty {@link TraceScope}.
  */
-public final class NullScope extends TraceScope {
+class NullScope extends TraceScope {
+  NullScope(Tracer tracer) {
+    super(tracer, null, null);
+  }
 
-  public static final TraceScope INSTANCE = new NullScope();
+  @Override
+  public SpanId getSpanId() {
+    return SpanId.INVALID;
+  }
 
-  private NullScope() {
-    super(null, null);
+  @Override
+  public void detach() {
+    if (detached) {
+      Tracer.throwClientError("Can't detach this TraceScope  because " +
+          "it is already detached.");
+    }
+    detached = true;
   }
 
   @Override
-  public Span detach() {
-    return null;
+  public void reattach() {
+    if (!detached) {
+      Tracer.throwClientError("Can't reattach this TraceScope  because " +
+          "it is not detached.");
+    }
+    detached = false;
   }
 
   @Override
   public void close() {
-    return;
+    tracer.popNullScope();
   }
 
   @Override
   public String toString() {
     return "NullScope";
   }
+
+  @Override
+  public void addKVAnnotation(String key, String value) {
+    // do nothing
+  }
+
+  @Override
+  public void addTimelineAnnotation(String msg) {
+    // do nothing
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/POJOSpanReceiver.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/POJOSpanReceiver.java b/htrace-core/src/main/java/org/apache/htrace/core/POJOSpanReceiver.java
index be782ba..34322fa 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/POJOSpanReceiver.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/POJOSpanReceiver.java
@@ -24,7 +24,7 @@ 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 {
+public class POJOSpanReceiver extends SpanReceiver {
   private final Collection<Span> spans;
 
   public POJOSpanReceiver(HTraceConfiguration conf) {

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/ProbabilitySampler.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/ProbabilitySampler.java b/htrace-core/src/main/java/org/apache/htrace/core/ProbabilitySampler.java
index 5bb0042..c0bb16c 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/ProbabilitySampler.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/ProbabilitySampler.java
@@ -16,17 +16,16 @@
  */
 package org.apache.htrace.core;
 
+import java.util.concurrent.ThreadLocalRandom;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import java.util.Random;
-import java.util.concurrent.ThreadLocalRandom;
-
 /**
  * Sampler that returns true a certain percentage of the time. Specify the frequency interval by
  * configuring a {@code double} value for {@link #SAMPLER_FRACTION_CONF_KEY}.
  */
-public class ProbabilitySampler implements Sampler {
+public class ProbabilitySampler extends Sampler {
   private static final Log LOG = LogFactory.getLog(ProbabilitySampler.class);
   public final double threshold;
   public final static String SAMPLER_FRACTION_CONF_KEY = "sampler.fraction";

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/Sampler.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/Sampler.java b/htrace-core/src/main/java/org/apache/htrace/core/Sampler.java
index 91843f5..af0165c 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/Sampler.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/Sampler.java
@@ -16,6 +16,11 @@
  */
 package org.apache.htrace.core;
 
+import java.lang.reflect.Constructor;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 /**
  * Extremely simple callback to determine the frequency that an action should be
  * performed.
@@ -31,9 +36,96 @@ package org.apache.htrace.core;
  * </pre>
  * This would trace 50% of all gets, 75% of all puts and would not trace any other requests.
  */
-public interface Sampler {
+public abstract class Sampler {
+  /**
+   * A {@link Sampler} builder. It takes a {@link Sampler} class name and
+   * constructs an instance of that class, with the provided configuration.
+   */
+  public static class Builder {
+    private static final Log LOG = LogFactory.getLog(Builder.class);
+
+    private final static String DEFAULT_PACKAGE = "org.apache.htrace.core";
+    private final HTraceConfiguration conf;
+    private String className;
+    private ClassLoader classLoader = Builder.class.getClassLoader();
+
+    public Builder(HTraceConfiguration conf) {
+      this.conf = conf;
+      reset();
+    }
+
+    public Builder reset() {
+      this.className = null;
+      return this;
+    }
+
+    public Builder className(String className) {
+      this.className = className;
+      return this;
+    }
+
+    public Builder classLoader(ClassLoader classLoader) {
+      this.classLoader = classLoader;
+      return this;
+    }
+
+    private void throwError(String errorStr) {
+      LOG.error(errorStr);
+      throw new RuntimeException(errorStr);
+    }
+
+    private void throwError(String errorStr, Throwable e) {
+      LOG.error(errorStr, e);
+      throw new RuntimeException(errorStr, e);
+    }
+
+    public Sampler build() {
+      Sampler sampler = newSampler();
+      if (LOG.isTraceEnabled()) {
+        LOG.trace("Created new sampler of type " +
+            sampler.getClass().getName(), new Exception());
+      }
+      return sampler;
+    }
+
+    private Sampler newSampler() {
+      if (className == null || className.isEmpty()) {
+        throwError("No sampler class specified.");
+      }
+      String str = className;
+      if (!str.contains(".")) {
+        str = DEFAULT_PACKAGE + "." + str;
+      }
+      Class cls = null;
+      try {
+        cls = classLoader.loadClass(str);
+      } catch (ClassNotFoundException e) {
+        throwError("Cannot find Sampler class " + str);
+      }
+      Constructor<Sampler> ctor = null;
+      try {
+        ctor = cls.getConstructor(HTraceConfiguration.class);
+      } catch (NoSuchMethodException e) {
+        throwError("Cannot find a constructor for class " +
+            str + "which takes an HTraceConfiguration.");
+      }
+      Sampler sampler = null;
+      try {
+        LOG.debug("Creating new instance of " + str + "...");
+        sampler = ctor.newInstance(conf);
+      } catch (ReflectiveOperationException e) {
+        throwError("Reflection error when constructing " +
+            str + ".", e);
+      } catch (Throwable t) {
+        throwError("NewInstance error when constructing " +
+            str + ".", t);
+      }
+      return sampler;
+    }
+  }
+
   public static final Sampler ALWAYS = AlwaysSampler.INSTANCE;
   public static final Sampler NEVER = NeverSampler.INSTANCE;
 
-  public boolean next();
+  public abstract boolean next();
 }

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/SamplerBuilder.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/SamplerBuilder.java b/htrace-core/src/main/java/org/apache/htrace/core/SamplerBuilder.java
deleted file mode 100644
index 5b53905..0000000
--- a/htrace-core/src/main/java/org/apache/htrace/core/SamplerBuilder.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.apache.htrace.core;
-
-import java.lang.reflect.Constructor;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * A {@link Sampler} builder. It reads a {@link Sampler} class name from the provided
- * configuration using the {@link #SAMPLER_CONF_KEY} key. Unqualified class names
- * are interpreted as members of the {@code org.apache.htrace.impl} package. The {@link #build()}
- * method constructs an instance of that class, initialized with the same configuration.
- */
-public class SamplerBuilder {
-
-  // TODO: should follow the same API as SpanReceiverBuilder
-
-  public final static String SAMPLER_CONF_KEY = "sampler";
-  private final static String DEFAULT_PACKAGE = "org.apache.htrace.core";
-  private final static ClassLoader classLoader =
-      SamplerBuilder.class.getClassLoader();
-  private final HTraceConfiguration conf;
-  private static final Log LOG = LogFactory.getLog(SamplerBuilder.class);
-
-  public SamplerBuilder(HTraceConfiguration conf) {
-    this.conf = conf;
-  }
-
-  public Sampler build() {
-    Sampler sampler = newSampler();
-    if (LOG.isTraceEnabled()) {
-      LOG.trace("Created new sampler of type " +
-          sampler.getClass().getName(), new Exception());
-    }
-    return sampler;
-  }
-
-  private Sampler newSampler() {
-    String str = conf.get(SAMPLER_CONF_KEY);
-    if (str == null || str.isEmpty()) {
-      return NeverSampler.INSTANCE;
-    }
-    if (!str.contains(".")) {
-      str = DEFAULT_PACKAGE + "." + str;
-    }
-    Class cls = null;
-    try {
-      cls = classLoader.loadClass(str);
-    } catch (ClassNotFoundException e) {
-      LOG.error("SamplerBuilder cannot find sampler class " + str +
-          ": falling back on NeverSampler.");
-      return NeverSampler.INSTANCE;
-    }
-    Constructor<Sampler> ctor = null;
-    try {
-      ctor = cls.getConstructor(HTraceConfiguration.class);
-    } catch (NoSuchMethodException e) {
-      LOG.error("SamplerBuilder cannot find a constructor for class " + str +
-          "which takes an HTraceConfiguration.  Falling back on " +
-          "NeverSampler.");
-      return NeverSampler.INSTANCE;
-    }
-    try {
-      return ctor.newInstance(conf);
-    } catch (ReflectiveOperationException e) {
-      LOG.error("SamplerBuilder reflection error when constructing " + str +
-          ".  Falling back on NeverSampler.", e);
-      return NeverSampler.INSTANCE;
-    } catch (Throwable e) {
-      LOG.error("SamplerBuilder constructor error when constructing " + str +
-          ".  Falling back on NeverSampler.", e);
-      return NeverSampler.INSTANCE;
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/Span.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/Span.java b/htrace-core/src/main/java/org/apache/htrace/core/Span.java
index db1a961..4971983 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/Span.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/Span.java
@@ -16,15 +16,14 @@
  */
 package org.apache.htrace.core;
 
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 /**
  * Base interface for gathering and reporting statistics about a block of

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/SpanId.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/SpanId.java b/htrace-core/src/main/java/org/apache/htrace/core/SpanId.java
index e10f894..ed31ad3 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/SpanId.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/SpanId.java
@@ -16,10 +16,7 @@
  */
 package org.apache.htrace.core;
 
-import java.math.BigInteger;
-import java.lang.Void;
 import java.util.concurrent.ThreadLocalRandom;
-import java.util.Random;
 
 /**
  * Uniquely identifies an HTrace span.

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/SpanReceiver.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/SpanReceiver.java b/htrace-core/src/main/java/org/apache/htrace/core/SpanReceiver.java
index 5547c51..a955ddf 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/SpanReceiver.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/SpanReceiver.java
@@ -16,9 +16,12 @@
  */
 package org.apache.htrace.core;
 
-
 import java.io.Closeable;
+import java.lang.reflect.Constructor;
+import java.util.concurrent.atomic.AtomicLong;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 
 /**
  * The collector within a process that is the destination of Spans when a trace is running.
@@ -27,13 +30,135 @@ import java.io.Closeable;
  * <pre>
  * <code>public SpanReceiverImpl(HTraceConfiguration)</code>
  * </pre>
- * The helper class {@link org.apache.htrace.SpanReceiverBuilder} provides convenient factory
- * methods for creating {@code SpanReceiver} instances from configuration.
- * @see org.apache.htrace.SpanReceiverBuilder
  */
-public interface SpanReceiver extends Closeable {
+public abstract class SpanReceiver implements Closeable {
+  /**
+   * A {@link SpanReceiver} builder. It takes a {@link SpanReceiver} class name
+   * and constructs an instance of that class, with the provided configuration.
+   */
+  public static class Builder {
+    private static final Log LOG = LogFactory.getLog(Builder.class);
+
+    private final static String DEFAULT_PACKAGE = "org.apache.htrace.core";
+    private final HTraceConfiguration conf;
+    private boolean logErrors;
+    private String className;
+    private ClassLoader classLoader = Builder.class.getClassLoader();
+
+    public Builder(HTraceConfiguration conf) {
+      this.conf = conf;
+      reset();
+    }
+
+    /**
+     * Set this builder back to defaults.
+     *
+     * @return this instance.
+     */
+    public Builder reset() {
+      this.logErrors = true;
+      this.className = null;
+      return this;
+    }
+
+    public Builder className(final String className) {
+      this.className = className;
+      return this;
+    }
+
+    /**
+     * Configure whether we should log errors during build().
+     * @return This instance
+     */
+    public Builder logErrors(boolean logErrors) {
+      this.logErrors = logErrors;
+      return this;
+    }
+
+    public Builder classLoader(ClassLoader classLoader) {
+      this.classLoader = classLoader;
+      return this;
+    }
+
+    private void throwError(String errorStr) {
+      if (logErrors) {
+        LOG.error(errorStr);
+      }
+      throw new RuntimeException(errorStr);
+    }
+
+    private void throwError(String errorStr, Throwable e) {
+      if (logErrors) {
+        LOG.error(errorStr, e);
+      }
+      throw new RuntimeException(errorStr, e);
+    }
+
+    public SpanReceiver build() {
+      SpanReceiver spanReceiver = newSpanReceiver();
+      if (LOG.isTraceEnabled()) {
+        LOG.trace("Created new span receiver of type " +
+                  spanReceiver.getClass().getName());
+      }
+      return spanReceiver;
+    }
+
+    private SpanReceiver newSpanReceiver() {
+      if ((className == null) || className.isEmpty()) {
+        throwError("No span receiver class specified.");
+      }
+      String str = className;
+      if (!str.contains(".")) {
+        str = DEFAULT_PACKAGE + "." + str;
+      }
+      Class cls = null;
+      try {
+        cls = classLoader.loadClass(str);
+      } catch (ClassNotFoundException e) {
+        throwError("Cannot find SpanReceiver class " + str);
+      }
+      Constructor<SpanReceiver> ctor = null;
+      try {
+        ctor = cls.getConstructor(HTraceConfiguration.class);
+      } catch (NoSuchMethodException e) {
+        throwError("Cannot find a constructor for class " +
+            str + "which takes an HTraceConfiguration.");
+      }
+      SpanReceiver receiver = null;
+      try {
+        LOG.debug("Creating new instance of " + str + "...");
+        receiver = ctor.newInstance(conf);
+      } catch (ReflectiveOperationException e) {
+        throwError("Reflection error when constructing " +
+            str + ".", e);
+      } catch (Throwable t) {
+        throwError("NewInstance error when constructing " +
+            str + ".", t);
+      }
+      return receiver;
+    }
+  }
+
+  /**
+   * An ID which uniquely identifies this SpanReceiver.
+   */
+  private final long id;
+
+  private static final AtomicLong HIGHEST_SPAN_RECEIVER_ID = new AtomicLong(0);
+
+  /**
+   * Get an ID uniquely identifying this SpanReceiver.
+   */
+  public final long getId() {
+    return id;
+  }
+
+  protected SpanReceiver() {
+    this.id = HIGHEST_SPAN_RECEIVER_ID.incrementAndGet();
+  }
+
   /**
    * Called when a Span is stopped and can now be stored.
    */
-  public void receiveSpan(Span span);
+  public abstract void receiveSpan(Span span);
 }

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/SpanReceiverBuilder.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/SpanReceiverBuilder.java b/htrace-core/src/main/java/org/apache/htrace/core/SpanReceiverBuilder.java
deleted file mode 100644
index 3ab0b07..0000000
--- a/htrace-core/src/main/java/org/apache/htrace/core/SpanReceiverBuilder.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.apache.htrace.core;
-
-import java.lang.reflect.Constructor;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * A {@link SpanReceiver} builder. It reads a {@link SpanReceiver} class name from the provided
- * configuration using the {@link #SPAN_RECEIVER_CONF_KEY} key. Unqualified class names
- * are interpreted as members of the {@code org.apache.htrace.impl} package. The {@link #build()}
- * method constructs an instance of that class, initialized with the same configuration.
- */
-public class SpanReceiverBuilder {
-  private static final Log LOG = LogFactory.getLog(SpanReceiverBuilder.class);
-
-  public final static String SPAN_RECEIVER_CONF_KEY = "span.receiver";
-  private final static String DEFAULT_PACKAGE = "org.apache.htrace.core";
-  private final static ClassLoader classLoader =
-      SpanReceiverBuilder.class.getClassLoader();
-  private final HTraceConfiguration conf;
-  private boolean logErrors;
-  private String spanReceiverClass;
-
-  public SpanReceiverBuilder(HTraceConfiguration conf) {
-    this.conf = conf;
-    reset();
-  }
-
-  /**
-   * Set this builder back to defaults. Any previous calls to {@link #spanReceiverClass(String)}
-   * are overridden by the value provided by configuration.
-   * @return This instance
-   */
-  public SpanReceiverBuilder reset() {
-    this.logErrors = true;
-    this.spanReceiverClass = this.conf.get(SPAN_RECEIVER_CONF_KEY);
-    return this;
-  }
-
-  /**
-   * Override the {@code SpanReceiver} class name provided in configuration with a new value.
-   * @return This instance
-   */
-  public SpanReceiverBuilder spanReceiverClass(final String spanReceiverClass) {
-    this.spanReceiverClass = spanReceiverClass;
-    return this;
-  }
-
-  /**
-   * Configure whether we should log errors during build().
-   * @return This instance
-   */
-  public SpanReceiverBuilder logErrors(boolean logErrors) {
-    this.logErrors = logErrors;
-    return this;
-  }
-
-  private void logError(String errorStr) {
-    if (!logErrors) {
-      return;
-    }
-    LOG.error(errorStr);
-  }
-
-  private void logError(String errorStr, Throwable e) {
-    if (!logErrors) {
-      return;
-    }
-    LOG.error(errorStr, e);
-  }
-
-  public SpanReceiver build() {
-    SpanReceiver spanReceiver = newSpanReceiver();
-    if (LOG.isTraceEnabled()) {
-      LOG.trace("Created new span receiver of type " +
-             ((spanReceiver == null) ? "(none)" :
-               spanReceiver.getClass().getName()));
-    }
-    return spanReceiver;
-  }
-
-  private SpanReceiver newSpanReceiver() {
-    if ((this.spanReceiverClass == null) ||
-        this.spanReceiverClass.isEmpty()) {
-      LOG.debug("No span receiver class specified.");
-      return null;
-    }
-    String str = spanReceiverClass;
-    if (!str.contains(".")) {
-      str = DEFAULT_PACKAGE + "." + str;
-    }
-    Class cls = null;
-    try {
-      cls = classLoader.loadClass(str);
-    } catch (ClassNotFoundException e) {
-      logError("SpanReceiverBuilder cannot find SpanReceiver class " + str +
-          ": disabling span receiver.");
-      return null;
-    }
-    Constructor<SpanReceiver> ctor = null;
-    try {
-      ctor = cls.getConstructor(HTraceConfiguration.class);
-    } catch (NoSuchMethodException e) {
-      logError("SpanReceiverBuilder cannot find a constructor for class " +
-          str + "which takes an HTraceConfiguration.  Disabling span " +
-          "receiver.");
-      return null;
-    }
-    try {
-      LOG.debug("Creating new instance of " + str + "...");
-      return ctor.newInstance(conf);
-    } catch (ReflectiveOperationException e) {
-      logError("SpanReceiverBuilder reflection error when constructing " + str +
-          ".  Disabling span receiver.", e);
-      return null;
-    } catch (Throwable e) {
-      logError("SpanReceiverBuilder constructor error when constructing " + str +
-          ".  Disabling span receiver.", e);
-      return null;
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/StandardOutSpanReceiver.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/StandardOutSpanReceiver.java b/htrace-core/src/main/java/org/apache/htrace/core/StandardOutSpanReceiver.java
index b084046..f443ec6 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/StandardOutSpanReceiver.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/StandardOutSpanReceiver.java
@@ -24,7 +24,7 @@ import java.io.IOException;
 /**
  * Used for testing. Simply prints to standard out any spans it receives.
  */
-public class StandardOutSpanReceiver implements SpanReceiver {
+public class StandardOutSpanReceiver extends SpanReceiver {
   private static final Log LOG = LogFactory.getLog(StandardOutSpanReceiver.class);
 
   public StandardOutSpanReceiver(HTraceConfiguration conf) {

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/Trace.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/Trace.java b/htrace-core/src/main/java/org/apache/htrace/core/Trace.java
deleted file mode 100644
index 9b72afe..0000000
--- a/htrace-core/src/main/java/org/apache/htrace/core/Trace.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * 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.apache.htrace.core;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import java.util.concurrent.Callable;
-
-/**
- * The Trace class is the primary way to interact with the library.  It provides
- * methods to create and manipulate spans.
- *
- * A 'Span' represents a length of time.  It has many other attributes such as a
- * description, ID, and even potentially a set of key/value strings attached to
- * it.
- *
- * Each thread in your application has a single currently active currentSpan
- * associated with it.  When this is non-null, it represents the current
- * operation that the thread is doing.  Spans are NOT thread-safe, and must
- * never be used by multiple threads at once.  With care, it is possible to
- * safely pass a Span object between threads, but in most cases this is not
- * necessary.
- *
- * A 'TraceScope' can either be empty, or contain a Span.  TraceScope objects
- * implement the Java's Closeable interface.  Similar to file descriptors, they
- * must be closed after they are created.  When a TraceScope contains a Span,
- * this span is closed when the scope is closed.
- *
- * The 'startSpan' methods in this class do a few things:
- * <ul>
- *   <li>Create a new Span which has this thread's currentSpan as one of its parents.</li>
- *   <li>Set currentSpan to the new Span.</li>
- *   <li>Create a TraceSpan object to manage the new Span.</li>
- * </ul>
- *
- * Closing a TraceScope does a few things:
- * <ul>
- *   <li>It closes the span which the scope was managing.</li>
- *   <li>Set currentSpan to the previous currentSpan (which may be null).</li>
- * </ul>
- */
-public class Trace {
-  private static final Log LOG = LogFactory.getLog(Trace.class);
-
-  /**
-   * Creates a new trace scope.
-   *
-   * If this thread has a currently active trace span, the trace scope we create
-   * here will contain a new span descending from the currently active span.
-   * If there is no currently active trace span, the trace scope we create will
-   * be empty.
-   *
-   * @param description   The description field for the new span to create.
-   */
-  public static TraceScope startSpan(String description) {
-    return startSpan(description, NeverSampler.INSTANCE);
-  }
-
-  public static TraceScope startSpan(String description, SpanId parentId) {
-    if (parentId == null) {
-      return continueSpan(null);
-    }
-    Span newSpan = new MilliSpan.Builder().
-        begin(System.currentTimeMillis()).
-        end(0).
-        description(description).
-        spanId(parentId.newChildId()).
-        parents(new SpanId[] { parentId }).
-        build();
-    return continueSpan(newSpan);
-  }
-
-  /**
-   * Creates a new trace scope.
-   *
-   * If this thread has a currently active trace span, it must be the 'parent'
-   * span that you pass in here as a parameter.  The trace scope we create here
-   * will contain a new span which is a child of 'parent'.
-   *
-   * @param description   The description field for the new span to create.
-   */
-  public static TraceScope startSpan(String description, Span parent) {
-    if (parent == null) {
-      return startSpan(description);
-    }
-    Span currentSpan = currentSpan();
-    if ((currentSpan != null) && (currentSpan != parent)) {
-      Tracer.clientError("HTrace client error: thread " +
-          Thread.currentThread().getName() + " tried to start a new Span " +
-          "with parent " + parent.toString() + ", but there is already a " +
-          "currentSpan " + currentSpan);
-    }
-    return continueSpan(parent.child(description));
-  }
-
-  public static <T> TraceScope startSpan(String description, Sampler s) {
-    Span span = null;
-    if (isTracing() || s.next()) {
-      span = Tracer.getInstance().createNew(description);
-    }
-    return continueSpan(span);
-  }
-
-  /**
-   * Pick up an existing span from another thread.
-   */
-  public static TraceScope continueSpan(Span s) {
-    // Return an empty TraceScope that does nothing on close
-    if (s == null) return NullScope.INSTANCE;
-    return Tracer.getInstance().continueSpan(s);
-  }
-
-  /**
-   * Removes the given SpanReceiver from the list of SpanReceivers.
-   */
-  public static void removeReceiver(SpanReceiver rcvr) {
-    Tracer.getInstance().removeReceiver(rcvr);
-  }
-
-  /**
-   * Adds the given SpanReceiver to the current Tracer instance's list of
-   * SpanReceivers.
-   */
-  public static void addReceiver(SpanReceiver rcvr) {
-    Tracer.getInstance().addReceiver(rcvr);
-  }
-
-  /**
-   * Adds a data annotation to the current span if tracing is currently on.
-   */
-  public static void addKVAnnotation(String key, String value) {
-    Span s = currentSpan();
-    if (s != null) {
-      s.addKVAnnotation(key, value);
-    }
-  }
-
-  /**
-   * Annotate the current span with the given message.
-   */
-  public static void addTimelineAnnotation(String msg) {
-    Span s = currentSpan();
-    if (s != null) {
-      s.addTimelineAnnotation(msg);
-    }
-  }
-
-  /**
-   * Returns true if the current thread is a part of a trace, false otherwise.
-   */
-  public static boolean isTracing() {
-    return Tracer.getInstance().isTracing();
-  }
-
-  /**
-   * If we are tracing, return the current span, else null
-   *
-   * @return Span representing the current trace, or null if not tracing.
-   */
-  public static Span currentSpan() {
-    return Tracer.getInstance().currentSpan();
-  }
-
-  /**
-   * Wrap the callable in a TraceCallable, if tracing.
-   *
-   * @return The callable provided, wrapped if tracing, 'callable' if not.
-   */
-  public static <V> Callable<V> wrap(Callable<V> callable) {
-    if (isTracing()) {
-      return new TraceCallable<V>(Trace.currentSpan(), callable);
-    } else {
-      return callable;
-    }
-  }
-
-  /**
-   * Wrap the runnable in a TraceRunnable, if tracing
-   *
-   * @return The runnable provided, wrapped if tracing, 'runnable' if not.
-   */
-  public static Runnable wrap(Runnable runnable) {
-    if (isTracing()) {
-      return new TraceRunnable(Trace.currentSpan(), runnable);
-    } else {
-      return runnable;
-    }
-  }
-
-  /**
-   * Wrap the runnable in a TraceRunnable, if tracing
-   *
-   * @param description name of the span to be created.
-   * @param runnable The runnable that will have tracing info associated with it if tracing.
-   * @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;
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/TraceCallable.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/TraceCallable.java b/htrace-core/src/main/java/org/apache/htrace/core/TraceCallable.java
index 08bcace..a0fec17 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/TraceCallable.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/TraceCallable.java
@@ -22,44 +22,35 @@ import java.util.concurrent.Callable;
  * Wrap a Callable with a Span that survives a change in threads.
  */
 public class TraceCallable<V> implements Callable<V> {
+  private final Tracer tracer;
   private final Callable<V> impl;
-  private final Span parent;
+  private final TraceScope 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) {
+  TraceCallable(Tracer tracer, TraceScope parent, Callable<V> impl,
+      String description) {
+    this.tracer = tracer;
     this.impl = impl;
     this.parent = parent;
-    this.description = description;
+    if (description == null) {
+      this.description = Thread.currentThread().getName();
+    } else {
+      this.description = description;
+    }
   }
 
   @Override
   public V call() throws Exception {
-    if (parent != null) {
-      TraceScope chunk = Trace.startSpan(getDescription(), parent);
-
-      try {
-        return impl.call();
-      } finally {
-        chunk.close();
-      }
-    } else {
+    TraceScope chunk = tracer.newScope(description,
+        parent.getSpan().getSpanId());
+    try {
       return impl.call();
+    } finally {
+      chunk.close();
     }
   }
 
   public Callable<V> getImpl() {
     return impl;
   }
-
-  private String getDescription() {
-    return this.description == null ? Thread.currentThread().getName() : description;
-  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/TraceExecutorService.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/TraceExecutorService.java b/htrace-core/src/main/java/org/apache/htrace/core/TraceExecutorService.java
index 8519d04..81e31ea 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/TraceExecutorService.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/TraceExecutorService.java
@@ -26,18 +26,21 @@ import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-
 public class TraceExecutorService implements ExecutorService {
-
+  private final Tracer tracer;
+  private final String scopeName;
   private final ExecutorService impl;
 
-  public TraceExecutorService(ExecutorService impl) {
+  TraceExecutorService(Tracer tracer, String scopeName,
+                       ExecutorService impl) {
+    this.tracer = tracer;
+    this.scopeName = scopeName;
     this.impl = impl;
   }
 
   @Override
   public void execute(Runnable command) {
-    impl.execute(new TraceRunnable(command));
+    impl.execute(tracer.wrap(command, scopeName));
   }
 
   @Override
@@ -68,24 +71,24 @@ public class TraceExecutorService implements ExecutorService {
 
   @Override
   public <T> Future<T> submit(Callable<T> task) {
-    return impl.submit(new TraceCallable<T>(task));
+    return impl.submit(tracer.wrap(task, scopeName));
   }
 
   @Override
   public <T> Future<T> submit(Runnable task, T result) {
-    return impl.submit(new TraceRunnable(task), result);
+    return impl.submit(tracer.wrap(task, scopeName), result);
   }
 
   @Override
   public Future<?> submit(Runnable task) {
-    return impl.submit(new TraceRunnable(task));
+    return impl.submit(tracer.wrap(task, scopeName));
   }
 
   private <T> Collection<? extends Callable<T>> wrapCollection(
       Collection<? extends Callable<T>> tasks) {
     List<Callable<T>> result = new ArrayList<Callable<T>>();
     for (Callable<T> task : tasks) {
-      result.add(new TraceCallable<T>(task));
+      result.add(tracer.wrap(task, scopeName));
     }
     return result;
   }

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/TraceProxy.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/TraceProxy.java b/htrace-core/src/main/java/org/apache/htrace/core/TraceProxy.java
deleted file mode 100644
index de9c980..0000000
--- a/htrace-core/src/main/java/org/apache/htrace/core/TraceProxy.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.apache.htrace.core;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-
-public class TraceProxy {
-  /**
-   * Returns an object that will trace all calls to itself.
-   */
-  public static <T> T trace(T instance) {
-    return trace(instance, Sampler.ALWAYS);
-  }
-
-  /**
-   * Returns an object that will trace all calls to itself.
-   */
-  @SuppressWarnings("unchecked")
-  public static <T, V> T trace(final T instance, final Sampler sampler) {
-    InvocationHandler handler = new InvocationHandler() {
-      @Override
-      public Object invoke(Object obj, Method method, Object[] args)
-          throws Throwable {
-        if (!sampler.next()) {
-          return method.invoke(instance, args);
-        }
-
-        TraceScope scope = Trace.startSpan(method.getName(), Sampler.ALWAYS);
-        try {
-          return method.invoke(instance, args);
-        } catch (Throwable ex) {
-          ex.printStackTrace();
-          throw ex;
-        } finally {
-          scope.close();
-        }
-      }
-    };
-    return (T) Proxy.newProxyInstance(instance.getClass().getClassLoader(),
-        instance.getClass().getInterfaces(), handler);
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/TraceRunnable.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/TraceRunnable.java b/htrace-core/src/main/java/org/apache/htrace/core/TraceRunnable.java
index 6accea9..8f98708 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/TraceRunnable.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/TraceRunnable.java
@@ -20,44 +20,34 @@ package org.apache.htrace.core;
  * Wrap a Runnable with a Span that survives a change in threads.
  */
 public class TraceRunnable implements Runnable {
-
-  private final Span parent;
+  private final Tracer tracer;
+  private final TraceScope 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) {
+  public TraceRunnable(Tracer tracer, TraceScope parent,
+      Runnable runnable, String description) {
+    this.tracer = tracer;
     this.parent = parent;
     this.runnable = runnable;
-    this.description = description;
+    if (description == null) {
+      this.description = Thread.currentThread().getName();
+    } else {
+      this.description = description;
+    }
   }
 
   @Override
   public void run() {
-    if (parent != null) {
-      TraceScope chunk = Trace.startSpan(getDescription(), parent);
-
-      try {
-        runnable.run();
-      } finally {
-        chunk.close();
-      }
-    } else {
+    TraceScope chunk = tracer.newScope(description,
+        parent.getSpan().getSpanId());
+    try {
       runnable.run();
+    } finally {
+      chunk.close();
     }
   }
 
-  private String getDescription() {
-    return this.description == null ? Thread.currentThread().getName() : description;
-  }
-
   public Runnable getRunnable() {
     return runnable;
   }

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/TraceScope.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/TraceScope.java b/htrace-core/src/main/java/org/apache/htrace/core/TraceScope.java
index f41e720..b04d785 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/TraceScope.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/TraceScope.java
@@ -18,6 +18,7 @@ package org.apache.htrace.core;
 
 import java.io.Closeable;
 import java.lang.Thread;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -25,75 +26,107 @@ public class TraceScope implements Closeable {
   private static final Log LOG = LogFactory.getLog(TraceScope.class);
 
   /**
-   * the span for this scope
+   * The tracer to use for this scope.
+   */
+  final Tracer tracer;
+
+  /**
+   * The trace span for this scope, or null if the scope is closed.
+   *
+   * If the scope is closed, it must also be detached.
    */
   private final Span span;
 
   /**
-   * the span that was "current" before this scope was entered
+   * The parent of this trace scope, or null if there is no parent.
    */
-  private final Span savedSpan;
+  private TraceScope parent;
 
-  private boolean detached = false;
+  /**
+   * True if this scope is detached.
+   */
+  boolean detached;
 
-  TraceScope(Span span, Span saved) {
+  TraceScope(Tracer tracer, Span span, TraceScope parent) {
+    this.tracer = tracer;
     this.span = span;
-    this.savedSpan = saved;
+    this.parent = parent;
+    this.detached = false;
   }
 
+  /**
+   * Returns the span which this scope is managing.
+   */
   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().
+   * Returns the span ID which this scope is managing.
+   */
+  public SpanId getSpanId() {
+    return span.getSpanId();
+  }
+
+  TraceScope getParent() {
+    return parent;
+  }
+
+  void setParent(TraceScope parent) {
+    this.parent = parent;
+  }
+
+  /**
+   * Detach this TraceScope from the current thread.
    *
-   * @return the same Span object
+   * It is OK to "leak" TraceScopes which have been detached.  They will not
+   * consume any resources other than a small amount of memory until they are
+   * garbage collected.  On the other hand, trace scopes which are still
+   * attached must never be leaked.
    */
-  public Span detach() {
+  public void detach() {
     if (detached) {
-      Tracer.clientError("Tried to detach trace span " + span + " but " +
-          "it has already been detached.");
+      Tracer.throwClientError("Can't detach this TraceScope  because " +
+          "it is already detached.");
     }
+    tracer.detachScope(this);
     detached = true;
+    parent = null;
+  }
 
-    Span cur = Tracer.getInstance().currentSpan();
-    if (cur != span) {
-      Tracer.clientError("Tried to detach trace span " + span + " but " +
-          "it is not the current span for the " +
-          Thread.currentThread().getName() + " thread.  You have " +
-          "probably forgotten to close or detach " + cur);
-    } else {
-      Tracer.getInstance().setCurrentSpan(savedSpan);
+  /**
+   * Attach this TraceScope to the current thread.
+   */
+  public void reattach() {
+    if (!detached) {
+      Tracer.throwClientError("Can't reattach this TraceScope  because " +
+          "it is not detached.");
     }
-    return span;
+    tracer.reattachScope(this);
+    detached = false;
   }
 
   /**
-   * Return true when {@link #detach()} has been called. Helpful when debugging
-   * multiple threads working on a single span.
+   * Close this TraceScope, ending the trace span it is managing.
    */
-  public boolean isDetached() {
-    return detached;
+  @Override
+  public void close() {
+    tracer.closeScope(this);
+
+  }
+
+  public void addKVAnnotation(String key, String value) {
+    span.addKVAnnotation(key, value);
+  }
+
+  public void addTimelineAnnotation(String msg) {
+    span.addTimelineAnnotation(msg);
   }
 
   @Override
-  public void close() {
-    if (detached) {
-      return;
-    }
-    detached = true;
-    Span cur = Tracer.getInstance().currentSpan();
-    if (cur != span) {
-      Tracer.clientError("Tried to close trace span " + span + " but " +
-          "it is not the current span for the " +
-          Thread.currentThread().getName() + " thread.  You have " +
-          "probably forgotten to close or detach " + cur);
-    } else {
-      span.stop();
-      Tracer.getInstance().setCurrentSpan(savedSpan);
-    }
+  public String toString() {
+    return "TraceScope(tracerId=" + tracer.getTracerId() +
+        ", span=" + span.toJson() +
+        ", detached=" + detached + ")";
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/Tracer.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/Tracer.java b/htrace-core/src/main/java/org/apache/htrace/core/Tracer.java
index b2ef6e6..6054a27 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/Tracer.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/Tracer.java
@@ -16,114 +16,526 @@
  */
 package org.apache.htrace.core;
 
+import java.io.Closeable;
+import java.lang.System;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ThreadLocalRandom;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import java.util.List;
-import java.util.Random;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ThreadLocalRandom;
-
 /**
  * A Tracer provides the implementation for collecting and distributing Spans
  * within a process.
  */
-public class Tracer {
+public class Tracer implements Closeable {
   private static final Log LOG = LogFactory.getLog(Tracer.class);
 
-  static long nonZeroRandom64() {
-    long id;
-    Random random = ThreadLocalRandom.current();
-    do {
-      id = random.nextLong();
-    } while (id == 0);
-    return id;
-  }
+  /**
+   * The thread-specific context for this Tracer.
+   *
+   * This tracks the current number of trace scopes in a particular thread
+   * created by this tracer.  We use this to apply our samplers only for the
+   * "top-level" spans.
+   *
+   * Note that we can't put the TraceScope objects themselves in this context,
+   * since we need to be able to use TraceScopes created by other Tracers, and
+   * this context is per-Tracer.
+   */
+  private static class ThreadContext {
+    private long depth;
 
-  private final List<SpanReceiver> receivers = new CopyOnWriteArrayList<SpanReceiver>();
-  private static final ThreadLocal<Span> currentSpan = new ThreadLocal<Span>() {
+    ThreadContext() {
+      this.depth = 0;
+    }
+
+    boolean isTopLevel() {
+      return (depth == 0);
+    }
+
+    void pushScope() {
+      depth++;
+    }
+
+    TraceScope pushNewScope(Tracer tracer, Span span, TraceScope parentScope) {
+      TraceScope scope = new TraceScope(tracer, span, parentScope);
+      threadLocalScope.set(scope);
+      depth++;
+      return scope;
+    }
+
+    void popScope() {
+      if (depth <= 0) {
+        throwClientError("There were more trace scopes closed than " +
+            "were opened.");
+      }
+      depth--;
+    }
+  };
+
+  /**
+   * A subclass of ThreadLocal that starts off with a non-null initial value in
+   * each thread.
+   */
+  private static class ThreadLocalContext extends ThreadLocal<ThreadContext> {
     @Override
-    protected Span initialValue() {
-      return null;
+    protected ThreadContext initialValue() {
+      return new ThreadContext();
     }
   };
+
+  /**
+   * The current trace scope.  This is global, so it is shared amongst all
+   * libraries using HTrace.
+   */
+  final static ThreadLocal<TraceScope> threadLocalScope =
+      new ThreadLocal<TraceScope>();
+
+  /**
+   * An empty array of SpanId objects.  Can be used rather than constructing a
+   * new object whenever we need an empty array.
+   */
   private static final SpanId EMPTY_PARENT_ARRAY[] = new SpanId[0];
 
   /**
+   * The tracerId.
+   */
+  private final String tracerId;
+
+  /**
+   * The TracerPool which this Tracer belongs to.
+   *
+   * This gets set to null after the Tracer is closed in order to catch some
+   * use-after-close errors.  Note that we do not synchronize access on this
+   * field, since it only changes when the Tracer is closed, and the Tracer
+   * should not be used after that.
+   */
+  private TracerPool tracerPool;
+
+  /**
+   * The current thread-local context for this particualr Tracer.
+   */
+  private final ThreadLocalContext threadContext;
+
+  /**
+   * The NullScope instance for this Tracer.
+   */
+  private final NullScope nullScope;
+
+  /**
+   * The currently active Samplers.
+   *
+   * Arrays are immutable once set.  You must take the Tracer lock in order to
+   * set this to a new array.  If this is null, the Tracer is closed.
+   */
+  private volatile Sampler[] curSamplers;
+
+  /**
    * Log a client error, and throw an exception.
    *
    * @param str     The message to use in the log and the exception.
    */
-  static void clientError(String str) {
+  static void throwClientError(String str) {
     LOG.error(str);
     throw new RuntimeException(str);
   }
 
   /**
-   * Internal class for defered singleton idiom.
-   * <p/>
-   * https://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom
+   * If the current thread is tracing, this function returns the Tracer that is
+   * being used; otherwise, it returns null.
    */
-  private static class TracerHolder {
-    private static final Tracer INSTANCE = new Tracer();
+  public static Tracer curThreadTracer() {
+    TraceScope traceScope = threadLocalScope.get();
+    if (traceScope == null) {
+      return null;
+    }
+    return traceScope.tracer;
   }
 
-  public static Tracer getInstance() {
-    return TracerHolder.INSTANCE;
+  Tracer(String tracerId, TracerPool tracerPool, Sampler[] curSamplers) {
+    this.tracerId = tracerId;
+    this.tracerPool = tracerPool;
+    this.threadContext = new ThreadLocalContext();
+    this.nullScope = new NullScope(this);
+    this.curSamplers = curSamplers;
   }
 
-  protected Span createNew(String description) {
-    Span parent = currentSpan.get();
-    if (parent == null) {
-      return new MilliSpan.Builder().
-          begin(System.currentTimeMillis()).
-          end(0).
-          description(description).
-          parents(EMPTY_PARENT_ARRAY).
-          spanId(SpanId.fromRandom()).
-          build();
-    } else {
-      return parent.child(description);
+  public String getTracerId() {
+    return tracerId;
+  }
+
+  private TraceScope newScopeImpl(ThreadContext context, String description) {
+    Span span = new MilliSpan.Builder().
+        tracerId(tracerId).
+        begin(System.currentTimeMillis()).
+        description(description).
+        parents(EMPTY_PARENT_ARRAY).
+        spanId(SpanId.fromRandom()).
+        build();
+    return context.pushNewScope(this, span, null);
+  }
+
+  private TraceScope newScopeImpl(ThreadContext context, String description,
+        TraceScope parentScope) {
+    SpanId parentId = parentScope.getSpan().getSpanId();
+    Span span = new MilliSpan.Builder().
+        tracerId(tracerId).
+        begin(System.currentTimeMillis()).
+        description(description).
+        parents(new SpanId[] { parentId }).
+        spanId(parentId.newChildId()).
+        build();
+    return context.pushNewScope(this, span, parentScope);
+  }
+
+  private TraceScope newScopeImpl(ThreadContext context, String description,
+        SpanId parentId) {
+    Span span = new MilliSpan.Builder().
+        tracerId(tracerId).
+        begin(System.currentTimeMillis()).
+        description(description).
+        parents(new SpanId[] { parentId }).
+        spanId(parentId.newChildId()).
+        build();
+    return context.pushNewScope(this, span, null);
+  }
+
+  private TraceScope newScopeImpl(ThreadContext context, String description,
+        TraceScope parentScope, SpanId secondParentId) {
+    SpanId parentId = parentScope.getSpan().getSpanId();
+    Span span = new MilliSpan.Builder().
+        tracerId(tracerId).
+        begin(System.currentTimeMillis()).
+        description(description).
+        parents(new SpanId[] { parentId, secondParentId }).
+        spanId(parentId.newChildId()).
+        build();
+    return context.pushNewScope(this, span, parentScope);
+  }
+
+  /**
+   * Create a new trace scope.
+   *
+   * If there are no scopes above the current scope, we will apply our
+   * configured samplers.  Otherwise, we will create a span only if this thread
+   * is already tracing, or if the passed parentID was valid.
+   *
+   * @param description         The description of the new span to create.
+   * @param parentId            If this is a valid span ID, it will be added to
+   *                              the parents of the new span we create.
+   * @return                    The new trace scope.
+   */
+  public TraceScope newScope(String description, SpanId parentId) {
+    TraceScope parentScope = threadLocalScope.get();
+    ThreadContext context = threadContext.get();
+    if (parentScope != null) {
+      if (parentId.isValid() &&
+          (!parentId.equals(parentScope.getSpan().getSpanId()))) {
+        return newScopeImpl(context, description, parentScope, parentId);
+      } else {
+        return newScopeImpl(context, description, parentScope);
+      }
+    } else if (parentId.isValid()) {
+      return newScopeImpl(context, description, parentId);
+    }
+    if (!context.isTopLevel()) {
+      context.pushScope();
+      return nullScope;
     }
+    if (!sample()) {
+      context.pushScope();
+      return nullScope;
+    }
+    return newScopeImpl(context, description);
   }
 
-  protected boolean isTracing() {
-    return currentSpan.get() != null;
+  /**
+   * Create a new trace scope.
+   *
+   * If there are no scopes above the current scope, we will apply our
+   * configured samplers.  Otherwise, we will create a span only if this thread
+   * is already tracing.
+   */
+  public TraceScope newScope(String description) {
+    TraceScope parentScope = threadLocalScope.get();
+    ThreadContext context = threadContext.get();
+    if (parentScope != null) {
+      return newScopeImpl(context, description, parentScope);
+    }
+    if (!context.isTopLevel()) {
+      context.pushScope();
+      return nullScope;
+    }
+    if (!sample()) {
+      context.pushScope();
+      return nullScope;
+    }
+    return newScopeImpl(context, description);
   }
 
-  protected Span currentSpan() {
-    return currentSpan.get();
+  /**
+   * Return a null trace scope.
+   */
+  public TraceScope newNullScope() {
+    ThreadContext context = threadContext.get();
+    context.pushScope();
+    return nullScope;
   }
 
-  public void deliver(Span span) {
+  /**
+   * Wrap the callable in a TraceCallable, if tracing.
+   *
+   * @return The callable provided, wrapped if tracing, 'callable' if not.
+   */
+  public <V> Callable<V> wrap(Callable<V> callable, String description) {
+    TraceScope parentScope = threadLocalScope.get();
+    if (parentScope == null) {
+      return callable;
+    }
+    return new TraceCallable<V>(this, parentScope, callable, description);
+  }
+
+  /**
+   * Wrap the runnable in a TraceRunnable, if tracing
+   *
+   * @return The runnable provided, wrapped if tracing, 'runnable' if not.
+   */
+  public Runnable wrap(Runnable runnable, String description) {
+    TraceScope parentScope = threadLocalScope.get();
+    if (parentScope == null) {
+      return runnable;
+    }
+    return new TraceRunnable(this, parentScope, runnable, description);
+  }
+
+  public TraceExecutorService newTraceExecutorService(ExecutorService impl,
+                                                      String scopeName) {
+    return new TraceExecutorService(this, scopeName, impl);
+  }
+
+  public TracerPool getTracerPool() {
+    if (tracerPool == null) {
+      throwClientError(toString() + " is closed.");
+    }
+    return tracerPool;
+  }
+
+  /**
+   * Returns an object that will trace all calls to itself.
+   */
+  @SuppressWarnings("unchecked")
+  <T, V> T createProxy(final T instance) {
+    final Tracer tracer = this;
+    InvocationHandler handler = new InvocationHandler() {
+      @Override
+      public Object invoke(Object obj, Method method, Object[] args)
+          throws Throwable {
+        TraceScope scope = tracer.newScope(method.getName());
+        try {
+          return method.invoke(instance, args);
+        } catch (Throwable ex) {
+          ex.printStackTrace();
+          throw ex;
+        } finally {
+          scope.close();
+        }
+      }
+    };
+    return (T) Proxy.newProxyInstance(instance.getClass().getClassLoader(),
+        instance.getClass().getInterfaces(), handler);
+  }
+
+  /**
+   * Return true if we should create a new top-level span.
+   *
+   * We will create the span if any configured sampler returns true.
+   */
+  private boolean sample() {
+    Sampler[] samplers = curSamplers;
+    for (Sampler sampler : samplers) {
+      if (sampler.next()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns an array of all the current Samplers.
+   *
+   * Note that if the current Samplers change, those changes will not be
+   * reflected in this array.  In other words, this array may be stale.
+   */
+  public Sampler[] getSamplers() {
+    return curSamplers;
+  }
+
+  /**
+   * Add a new Sampler.
+   *
+   * @param sampler       The new sampler to add.
+   *                      You cannot add a particular Sampler object more
+   *                        than once.  You may add multiple Sampler objects
+   *                        of the same type, although this is not recommended.
+   *
+   * @return              True if the sampler was added; false if it already had
+   *                        been added earlier.
+   */
+  public synchronized boolean addSampler(Sampler sampler) {
+    if (tracerPool == null) {
+      throwClientError(toString() + " is closed.");
+    }
+    Sampler[] samplers = curSamplers;
+    int j = 0;
+    for (int i = 0; i < samplers.length; i++) {
+      if (samplers[i] == sampler) {
+        return false;
+      }
+    }
+    Sampler[] newSamplers =
+        Arrays.copyOf(samplers, samplers.length + 1);
+    newSamplers[samplers.length] = sampler;
+    curSamplers = newSamplers;
+    return true;
+  }
+
+  /**
+   * Remove a SpanReceiver.
+   *
+   * @param sampler       The sampler to remove.
+   */
+  public synchronized boolean removeSampler(Sampler sampler) {
+    if (tracerPool == null) {
+      throwClientError(toString() + " is closed.");
+    }
+    Sampler[] samplers = curSamplers;
+    int j = 0;
+    for (int i = 0; i < samplers.length; i++) {
+      if (samplers[i] == sampler) {
+        Sampler[] newSamplers = new Sampler[samplers.length - 1];
+        System.arraycopy(samplers, 0, newSamplers, 0, i);
+        System.arraycopy(samplers, i + 1, newSamplers, i,
+            samplers.length - i - 1);
+        curSamplers = newSamplers;
+        return true;
+      }
+    }
+    return false;
+  }
+
+  void detachScope(TraceScope scope) {
+    TraceScope curScope = threadLocalScope.get();
+    if (curScope != scope) {
+      throwClientError("Can't detach TraceScope for " +
+          scope.getSpan().toJson() + " because it is not the current " +
+          "TraceScope in thread " + Thread.currentThread().getName());
+    }
+    ThreadContext context = threadContext.get();
+    context.popScope();
+    threadLocalScope.set(scope.getParent());
+  }
+
+  void reattachScope(TraceScope scope) {
+    TraceScope parent = threadLocalScope.get();
+    Tracer.threadLocalScope.set(scope);
+    ThreadContext context = threadContext.get();
+    context.pushScope();
+    scope.setParent(parent);
+  }
+
+  void closeScope(TraceScope scope) {
+    TraceScope curScope = threadLocalScope.get();
+    if (curScope != scope) {
+      throwClientError("Can't close TraceScope for " +
+          scope.getSpan().toJson() + " because it is not the current " +
+          "TraceScope in thread " + Thread.currentThread().getName());
+    }
+    if (tracerPool == null) {
+      throwClientError(toString() + " is closed.");
+    }
+    SpanReceiver[] receivers = tracerPool.getReceivers();
+    if (receivers == null) {
+      throwClientError(toString() + " is closed.");
+    }
+    ThreadContext context = threadContext.get();
+    context.popScope();
+    threadLocalScope.set(scope.getParent());
+    scope.setParent(null);
+    Span span = scope.getSpan();
+    span.stop();
     for (SpanReceiver receiver : receivers) {
       receiver.receiveSpan(span);
     }
   }
 
-  protected void addReceiver(SpanReceiver receiver) {
-    receivers.add(receiver);
+  void popNullScope() {
+    TraceScope curScope = threadLocalScope.get();
+    if (curScope != null) {
+      throwClientError("Attempted to close an empty scope, but it was not " +
+          "the current thread scope in thread " +
+          Thread.currentThread().getName());
+    }
+    ThreadContext context = threadContext.get();
+    context.popScope();
   }
 
-  protected void removeReceiver(SpanReceiver receiver) {
-    receivers.remove(receiver);
+  public static Span getCurrentSpan() {
+    TraceScope curScope = threadLocalScope.get();
+    if (curScope == null) {
+      return null;
+    } else {
+      return curScope.getSpan();
+    }
+  }
+
+  public static SpanId getCurrentSpanId() {
+    TraceScope curScope = threadLocalScope.get();
+    if (curScope == null) {
+      return SpanId.INVALID;
+    } else {
+      return curScope.getSpan().getSpanId();
+    }
   }
 
-  protected Span setCurrentSpan(Span span) {
-    if (LOG.isTraceEnabled()) {
-      LOG.trace("setting current span " + span);
+  @Override
+  public synchronized void close() {
+    if (tracerPool == null) {
+      return;
     }
-    currentSpan.set(span);
-    return span;
+    curSamplers = new Sampler[0];
+    tracerPool.removeTracer(this);
   }
 
-  public TraceScope continueSpan(Span s) {
-    Span oldCurrent = currentSpan();
-    setCurrentSpan(s);
-    return new TraceScope(s, oldCurrent);
+  /**
+   * Get the hash code of a Tracer object.
+   *
+   * This hash code is based on object identity.
+   * This is used in TracerPool to create a hash table of Tracers.
+   */
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
   }
 
-  protected int numReceivers() {
-    return receivers.size();
+  /**
+   * Compare two tracer objects.
+   *
+   * Tracer objects are always compared by object equality.
+   * This is used in TracerPool to create a hash table of Tracers.
+   */
+  @Override
+  public boolean equals(Object other) {
+    return (this == other);
+  }
+
+  @Override
+  public String toString() {
+    return "Tracer(" + tracerId + ")";
   }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/TracerBuilder.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/TracerBuilder.java b/htrace-core/src/main/java/org/apache/htrace/core/TracerBuilder.java
new file mode 100644
index 0000000..0f12253
--- /dev/null
+++ b/htrace-core/src/main/java/org/apache/htrace/core/TracerBuilder.java
@@ -0,0 +1,144 @@
+/*
+ * 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.apache.htrace.core;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.lang.reflect.Constructor;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Builds a new Tracer object.
+ */
+public class TracerBuilder {
+  public final static String SPAN_RECEIVER_CLASSES_KEY =
+      "span.receiver.classes";
+  public final static String SAMPLER_CLASSES_KEY =
+      "sampler.classes";
+
+  private static final Log LOG = LogFactory.getLog(TracerBuilder.class);
+
+  private String name;
+  private HTraceConfiguration conf = HTraceConfiguration.EMPTY;
+  private ClassLoader classLoader =
+      TracerBuilder.class.getClassLoader();
+  private TracerPool tracerPool = TracerPool.GLOBAL;
+
+  public TracerBuilder() {
+  }
+
+  public TracerBuilder name(String name) {
+    this.name = name;
+    return this;
+  }
+
+  public TracerBuilder conf(HTraceConfiguration conf) {
+    this.conf = conf;
+    return this;
+  }
+
+  public TracerBuilder tracerPool(TracerPool tracerPool) {
+    this.tracerPool = tracerPool;
+    return this;
+  }
+
+  private void loadSamplers(List<Sampler> samplers) {
+    String classNamesStr = conf.get(SAMPLER_CLASSES_KEY, "");
+    List<String> classNames = getClassNamesFromConf(classNamesStr);
+    StringBuilder bld = new StringBuilder();
+    String prefix = "";
+    for (String className : classNames) {
+      try {
+        Sampler sampler = new Sampler.Builder(conf).
+          className(className).
+          classLoader(classLoader).
+          build();
+        samplers.add(sampler);
+        bld.append(prefix).append(className);
+        prefix = ", ";
+      } catch (Throwable e) {
+        LOG.error("Failed to create SpanReceiver of type " + className, e);
+      }
+    }
+    String resultString = bld.toString();
+    if (resultString.isEmpty()) {
+      resultString = "no samplers";
+    }
+    LOG.info(SAMPLER_CLASSES_KEY + " = " + classNamesStr +
+        "; loaded " + resultString);
+  }
+
+  private void loadSpanReceivers() {
+    String classNamesStr = conf.get(SPAN_RECEIVER_CLASSES_KEY, "");
+    List<String> classNames = getClassNamesFromConf(classNamesStr);
+    StringBuilder bld = new StringBuilder();
+    String prefix = "";
+    for (String className : classNames) {
+      try {
+        tracerPool.loadReceiverType(className, conf, classLoader);
+        bld.append(prefix).append(className);
+        prefix = ", ";
+      } catch (Throwable e) {
+        LOG.error("Failed to create SpanReceiver of type " + className, e);
+      }
+    }
+    String resultString = bld.toString();
+    if (resultString.isEmpty()) {
+      resultString = "no span receivers";
+    }
+    LOG.info(SPAN_RECEIVER_CLASSES_KEY + " = " + classNamesStr +
+        "; loaded " + resultString);
+  }
+
+  /**
+   * Get a list of class names from the HTrace configuration.
+   * Entries which are empty will be removed.  Entries which lack a package will
+   * be given the default package.
+   *
+   * @param classNamesStr     A semicolon-separated string containing a list
+   *                            of class names.
+   * @return                  A list of class names.
+   */
+  private List<String> getClassNamesFromConf(String classNamesStr) {
+    String classNames[] = classNamesStr.split(";");
+    LinkedList<String> cleanedClassNames = new LinkedList<String>();
+    for (String className : classNames) {
+      String cleanedClassName = className.trim();
+      if (!cleanedClassName.isEmpty()) {
+        cleanedClassNames.add(cleanedClassName);
+      }
+    }
+    return cleanedClassNames;
+  }
+
+  public Tracer build() {
+    if (name == null) {
+      throw new RuntimeException("You must specify a name for this Tracer.");
+    }
+    LinkedList<SpanReceiver> spanReceivers = new LinkedList<SpanReceiver>();
+    LinkedList<Sampler> samplers = new LinkedList<Sampler>();
+    loadSamplers(samplers);
+    String tracerId = new TracerId(conf, name).get();
+    Tracer tracer = new Tracer(tracerId, tracerPool,
+        samplers.toArray(new Sampler[samplers.size()]));
+    tracerPool.addTracer(tracer);
+    loadSpanReceivers();
+    return tracer;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7997d208/htrace-core/src/main/java/org/apache/htrace/core/TracerId.java
----------------------------------------------------------------------
diff --git a/htrace-core/src/main/java/org/apache/htrace/core/TracerId.java b/htrace-core/src/main/java/org/apache/htrace/core/TracerId.java
index 7cdbd34..da482fe 100644
--- a/htrace-core/src/main/java/org/apache/htrace/core/TracerId.java
+++ b/htrace-core/src/main/java/org/apache/htrace/core/TracerId.java
@@ -16,9 +16,6 @@
  */
 package org.apache.htrace.core;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -30,6 +27,9 @@ import java.util.Enumeration;
 import java.util.Locale;
 import java.util.TreeSet;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 /**
  * The HTrace tracer ID.<p/>
  *
@@ -38,17 +38,19 @@ import java.util.TreeSet;
  * replace with the correct values at runtime.<p/>
  *
  * <ul>
- * <li>${ip}: will be replaced with an ip address.</li>
- * <li>${pname}: will be replaced the process name obtained from java.</li>
+ * <li>%{tname}: the tracer name supplied when creating the Tracer.</li>
+ * <li>%{pname}: the process name obtained from the JVM.</li>
+ * <li>%{ip}: will be replaced with an ip address.</li>
+ * <li>%{pid}: the numerical process ID from the operating system.</li>
  * </ul><p/>
  *
- * For example, the string "${pname}/${ip}" will be replaced with something
+ * For example, the string "%{pname}/%{ip}" will be replaced with something
  * like: DataNode/192.168.0.1, assuming that the process' name is DataNode
  * and its IP address is 192.168.0.1.<p/>
  *
- * Process ID strings can contain backslashes as escapes.
- * For example, "\a" will map to "a".  "\${ip}" will map to the literal
- * string "${ip}", not the IP address.  A backslash itself can be escaped by a
+ *  ID strings can contain backslashes as escapes.
+ * For example, "\a" will map to "a".  "\%{ip}" will map to the literal
+ * string "%{ip}", not the IP address.  A backslash itself can be escaped by a
  * preceding backslash.
  */
 public final class TracerId {
@@ -57,16 +59,20 @@ public final class TracerId {
   /**
    * The configuration key to use for process id
    */
-  public static final String TRACER_ID_KEY = "process.id";
+  public static final String TRACER_ID_KEY = "tracer.id";
 
   /**
-   * The default process ID to use if no other ID is configured.
+   * The default tracer ID to use if no other ID is configured.
    */
-  private static final String DEFAULT_TRACER_ID = "${pname}/${ip}";
+  private static final String DEFAULT_TRACER_ID = "%{tname}/%{ip}";
+
+  private final String tracerName;
 
   private final String tracerId;
 
-  TracerId(String fmt) {
+  public TracerId(HTraceConfiguration conf, String tracerName) {
+    this.tracerName = tracerName;
+    String fmt = conf.get(TRACER_ID_KEY, DEFAULT_TRACER_ID);
     StringBuilder bld = new StringBuilder();
     StringBuilder varBld = null;
     boolean escaping = false;
@@ -81,7 +87,7 @@ public final class TracerId {
       }
       switch (varSeen) {
         case 0:
-          if (c == '$') {
+          if (c == '%') {
             if (!escaping) {
               varSeen = 1;
               continue;
@@ -101,7 +107,7 @@ public final class TracerId {
           }
           escaping = false;
           varSeen = 0;
-          bld.append("$").append(c);
+          bld.append("%").append(c);
           break;
         default:
           if (c == '}') {
@@ -130,12 +136,10 @@ public final class TracerId {
     }
   }
 
-  public TracerId(HTraceConfiguration conf) {
-    this(conf.get(TRACER_ID_KEY, DEFAULT_TRACER_ID));
-  }
-
   private String processShellVar(String var) {
-    if (var.equals("pname")) {
+    if (var.equals("tname")) {
+      return tracerName;
+    } else if (var.equals("pname")) {
       return getProcessName();
     } else if (var.equals("ip")) {
       return getBestIpString();