aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Topolnjak <ivantopo@gmail.com>2017-06-18 12:38:09 +0200
committerIvan Topolnjak <ivantopo@gmail.com>2017-06-18 12:38:09 +0200
commit1d98b9e8a397acf8b6f6f55a3fd5189eb72740ba (patch)
treeb97f219f1714756672310be61b132015a024944a
parent9119413d8dc9b26874e85d8a5f3b135379b9f6da (diff)
downloadKamon-1d98b9e8a397acf8b6f6f55a3fd5189eb72740ba.tar.gz
Kamon-1d98b9e8a397acf8b6f6f55a3fd5189eb72740ba.tar.bz2
Kamon-1d98b9e8a397acf8b6f6f55a3fd5189eb72740ba.zip
add DynamicAccess utility
-rw-r--r--kamon-core/src/main/scala/kamon/ReporterRegistry.scala41
-rw-r--r--kamon-core/src/main/scala/kamon/util/DynamicAccess.scala56
2 files changed, 73 insertions, 24 deletions
diff --git a/kamon-core/src/main/scala/kamon/ReporterRegistry.scala b/kamon-core/src/main/scala/kamon/ReporterRegistry.scala
index 6c8622d4..ae43ca3c 100644
--- a/kamon-core/src/main/scala/kamon/ReporterRegistry.scala
+++ b/kamon-core/src/main/scala/kamon/ReporterRegistry.scala
@@ -22,7 +22,7 @@ import java.util.concurrent._
import com.typesafe.config.Config
import kamon.metric._
import kamon.trace.Span
-import kamon.util.Registration
+import kamon.util.{DynamicAccess, Registration}
import org.slf4j.LoggerFactory
import scala.concurrent.{ExecutionContext, ExecutionContextExecutorService, Future}
@@ -42,19 +42,17 @@ trait ReporterRegistry {
def stopAllReporters(): Future[Unit]
}
-trait MetricReporter {
+sealed trait Reporter {
def start(): Unit
def stop(): Unit
-
def reconfigure(config: Config): Unit
- def reportTickSnapshot(snapshot: TickSnapshot): Unit
}
-trait SpanReporter {
- def start(): Unit
- def stop(): Unit
+trait MetricReporter extends Reporter {
+ def reportTickSnapshot(snapshot: TickSnapshot): Unit
+}
- def reconfigure(config: Config): Unit
+trait SpanReporter extends Reporter {
def reportSpans(spans: Seq[Span.CompletedSpan]): Unit
}
@@ -77,22 +75,17 @@ class ReporterRegistryImpl(metrics: MetricsSnapshotGenerator, initialConfig: Con
logger.info("The kamon.reporters setting is empty, no reporters have been started.")
else {
registryConfiguration.configuredReporters.foreach { reporterFQCN =>
- Try {
- val reporterClass = Class.forName(reporterFQCN)
- val instance = reporterClass.newInstance()
- instance match {
- case mr: MetricReporter =>
- addMetricReporter(mr, "loaded-from-config: " + reporterFQCN)
- logger.info("Loaded metric reporter [{}]", reporterFQCN)
-
- case sr: SpanReporter =>
- addSpanReporter(sr, "loaded-from-config: " + reporterFQCN)
- logger.info("Loaded span reporter [{}]", reporterFQCN)
-
- case anyOther =>
- logger.error("Cannot add [{}] as a reporter, it doesn't implement the MetricReporter or SpanReporter interfaces", anyOther)
- }
- }.failed.foreach {
+ val dynamicAccess = new DynamicAccess(getClass.getClassLoader)
+ dynamicAccess.createInstanceFor[Reporter](reporterFQCN, Nil).map({
+ case mr: MetricReporter =>
+ addMetricReporter(mr, "loaded-from-config: " + reporterFQCN)
+ logger.info("Loaded metric reporter [{}]", reporterFQCN)
+
+ case sr: SpanReporter =>
+ addSpanReporter(sr, "loaded-from-config: " + reporterFQCN)
+ logger.info("Loaded span reporter [{}]", reporterFQCN)
+
+ }).failed.foreach {
t => logger.error(s"Failed to load configured reporter [$reporterFQCN]", t)
}
}
diff --git a/kamon-core/src/main/scala/kamon/util/DynamicAccess.scala b/kamon-core/src/main/scala/kamon/util/DynamicAccess.scala
new file mode 100644
index 00000000..87f7024a
--- /dev/null
+++ b/kamon-core/src/main/scala/kamon/util/DynamicAccess.scala
@@ -0,0 +1,56 @@
+package kamon.util
+
+import scala.collection.immutable
+import java.lang.reflect.InvocationTargetException
+import scala.reflect.ClassTag
+import scala.util.Try
+
+/**
+ * Utility class for creating instances from a FQCN, see [1] for the original source.
+ *
+ * It uses reflection to turn fully-qualified class names into `Class[_]` objects and creates instances from there
+ * using `getDeclaredConstructor()` and invoking that.
+ *
+ * [1] https://github.com/akka/akka/blob/master/akka-actor/src/main/scala/akka/actor/ReflectiveDynamicAccess.scala
+ */
+class DynamicAccess(val classLoader: ClassLoader) {
+
+ def getClassFor[T: ClassTag](fqcn: String): Try[Class[_ <: T]] =
+ Try[Class[_ <: T]]({
+ val c = Class.forName(fqcn, false, classLoader).asInstanceOf[Class[_ <: T]]
+ val t = implicitly[ClassTag[T]].runtimeClass
+ if (t.isAssignableFrom(c)) c else throw new ClassCastException(t + " is not assignable from " + c)
+ })
+
+ def createInstanceFor[T: ClassTag](clazz: Class[_], args: immutable.Seq[(Class[_], AnyRef)]): Try[T] =
+ Try {
+ val types = args.map(_._1).toArray
+ val values = args.map(_._2).toArray
+ val constructor = clazz.getDeclaredConstructor(types: _*)
+ constructor.setAccessible(true)
+ val obj = constructor.newInstance(values: _*)
+ val t = implicitly[ClassTag[T]].runtimeClass
+ if (t.isInstance(obj)) obj.asInstanceOf[T] else throw new ClassCastException(clazz.getName + " is not a subtype of " + t)
+ } recover { case i: InvocationTargetException if i.getTargetException ne null ⇒ throw i.getTargetException }
+
+ def createInstanceFor[T: ClassTag](fqcn: String, args: immutable.Seq[(Class[_], AnyRef)]): Try[T] =
+ getClassFor(fqcn) flatMap { c ⇒ createInstanceFor(c, args) }
+
+ def getObjectFor[T: ClassTag](fqcn: String): Try[T] = {
+ val classTry =
+ if (fqcn.endsWith("$")) getClassFor(fqcn)
+ else getClassFor(fqcn + "$") recoverWith { case _ ⇒ getClassFor(fqcn) }
+ classTry flatMap { c ⇒
+ Try {
+ val module = c.getDeclaredField("MODULE$")
+ module.setAccessible(true)
+ val t = implicitly[ClassTag[T]].runtimeClass
+ module.get(null) match {
+ case null ⇒ throw new NullPointerException
+ case x if !t.isInstance(x) ⇒ throw new ClassCastException(fqcn + " is not a subtype of " + t)
+ case x: T ⇒ x
+ }
+ } recover { case i: InvocationTargetException if i.getTargetException ne null ⇒ throw i.getTargetException }
+ }
+ }
+} \ No newline at end of file