aboutsummaryrefslogtreecommitdiff
path: root/kamon-core/src/main/scala
diff options
context:
space:
mode:
Diffstat (limited to 'kamon-core/src/main/scala')
-rw-r--r--kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala81
-rw-r--r--kamon-core/src/main/scala/kamon/context/Context.scala126
-rw-r--r--kamon-core/src/main/scala/kamon/context/HttpPropagation.scala65
-rw-r--r--kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala24
-rw-r--r--kamon-core/src/main/scala/kamon/metric/Accumulator.scala6
-rw-r--r--kamon-core/src/main/scala/kamon/metric/Metric.scala24
-rw-r--r--kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala4
-rw-r--r--kamon-core/src/main/scala/kamon/metric/Timer.scala6
-rw-r--r--kamon-core/src/main/scala/kamon/package.scala2
-rw-r--r--kamon-core/src/main/scala/kamon/tag/Lookups.scala32
-rw-r--r--kamon-core/src/main/scala/kamon/tag/Tag.scala50
-rw-r--r--kamon-core/src/main/scala/kamon/tag/TagSet.scala (renamed from kamon-core/src/main/scala/kamon/tag/Tags.scala)160
12 files changed, 384 insertions, 196 deletions
diff --git a/kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala b/kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala
index 75e65c44..296003d5 100644
--- a/kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala
+++ b/kamon-core/src/main/scala/kamon/context/BinaryPropagation.scala
@@ -20,7 +20,9 @@ import java.io.{ByteArrayInputStream, ByteArrayOutputStream, InputStream, Output
import com.typesafe.config.Config
import kamon.context.BinaryPropagation.{ByteStreamReader, ByteStreamWriter}
-import kamon.context.generated.binary.context.{Context => ColferContext, Entry => ColferEntry}
+import kamon.context.generated.binary.context.{Context => ColferContext, Entry => ColferEntry, Tags => ColferTags}
+import kamon.context.generated.binary.context.{StringTag => ColferStringTag, LongTag => ColferLongTag, BooleanTag => ColferBooleanTag}
+import kamon.tag.{Tag, TagSet}
import org.slf4j.LoggerFactory
import scala.reflect.ClassTag
@@ -152,7 +154,7 @@ object BinaryPropagation {
* configured entry readers and writers.
*/
class Default(settings: Settings) extends Propagation[ByteStreamReader, ByteStreamWriter] {
- private val _log = LoggerFactory.getLogger(classOf[BinaryPropagation.Default])
+ private val _logger = LoggerFactory.getLogger(classOf[BinaryPropagation.Default])
private val _streamPool = new ThreadLocal[Default.ReusableByteStreamWriter] {
override def initialValue(): Default.ReusableByteStreamWriter = new Default.ReusableByteStreamWriter(128)
}
@@ -170,39 +172,29 @@ object BinaryPropagation {
}
contextData.failed.foreach {
- case NonFatal(t) => _log.warn("Failed to read Context from ByteStreamReader", t)
+ case NonFatal(t) => _logger.warn("Failed to read Context from ByteStreamReader", t)
}
contextData.map { colferContext =>
// Context tags
- var tagSectionsCount = colferContext.tags.length
- if (tagSectionsCount > 0 && tagSectionsCount % 2 != 0) {
- _log.warn("Malformed Context tags found, tags consistency might be compromised")
- tagSectionsCount -= 1
+ val tagsBuilder = Map.newBuilder[String, Any]
+ if(colferContext.tags != null) {
+ colferContext.tags.strings.foreach(t => tagsBuilder += (t.key -> t.value))
+ colferContext.tags.longs.foreach(t => tagsBuilder += (t.key -> t.value))
+ colferContext.tags.booleans.foreach(t => tagsBuilder += (t.key -> t.value))
}
-
- val tags = if (tagSectionsCount > 0) {
- val tagsBuilder = Map.newBuilder[String, String]
- var tagIndex = 0
- while (tagIndex < tagSectionsCount) {
- tagsBuilder += (colferContext.tags(tagIndex) -> colferContext.tags(tagIndex + 1))
- tagIndex += 2
- }
- tagsBuilder.result()
-
- } else Map.empty[String, String]
-
+ val tags = TagSet.from(tagsBuilder.result())
// Only reads the entries for which there is a registered reader
colferContext.entries.foldLeft(Context.of(tags)) {
case (context, entryData) =>
- settings.incomingEntries.get(entryData.name).map { entryReader =>
+ settings.incomingEntries.get(entryData.key).map { entryReader =>
var contextWithEntry = context
try {
- contextWithEntry = entryReader.read(ByteStreamReader.of(entryData.content), context)
+ contextWithEntry = entryReader.read(ByteStreamReader.of(entryData.value), context)
} catch {
- case NonFatal(t) => _log.warn("Failed to read entry [{}]", entryData.name.asInstanceOf[Any], t)
+ case NonFatal(t) => _logger.warn("Failed to read entry [{}]", entryData.key.asInstanceOf[Any], t)
}
contextWithEntry
@@ -218,17 +210,36 @@ object BinaryPropagation {
val output = _streamPool.get()
val contextOutgoingBuffer = _contextBufferPool.get()
- if (context.tags.nonEmpty) {
- val tags = Array.ofDim[String](context.tags.size * 2)
- var tagIndex = 0
- context.tags.foreach {
- case (key, value) =>
- tags.update(tagIndex, key)
- tags.update(tagIndex + 1, value)
- tagIndex += 2
+ if(context.tags.nonEmpty()) {
+ val tagsData = new ColferTags()
+ val strings = Array.newBuilder[ColferStringTag]
+ val longs = Array.newBuilder[ColferLongTag]
+ val booleans = Array.newBuilder[ColferBooleanTag]
+
+ context.tags.iterator().foreach {
+ case t: Tag.String =>
+ val st = new ColferStringTag()
+ st.setKey(t.key)
+ st.setValue(t.value)
+ strings += st
+
+ case t: Tag.Long =>
+ val lt = new ColferLongTag()
+ lt.setKey(t.key)
+ lt.setValue(t.value)
+ longs += lt
+
+ case t: Tag.Boolean =>
+ val bt = new ColferBooleanTag()
+ bt.setKey(t.key)
+ bt.setValue(t.value)
+ booleans += bt
}
- contextData.tags = tags
+ tagsData.setStrings(strings.result())
+ tagsData.setLongs(longs.result())
+ tagsData.setBooleans(booleans.result())
+ contextData.setTags(tagsData)
}
if (context.entries.nonEmpty) {
@@ -239,10 +250,10 @@ object BinaryPropagation {
output.reset()
entryWriter.write(context, output)
- colferEntry.name = entryName
- colferEntry.content = output.toByteArray()
+ colferEntry.key = entryName
+ colferEntry.value = output.toByteArray()
} catch {
- case NonFatal(t) => _log.warn("Failed to write entry [{}]", entryName.asInstanceOf[Any], t)
+ case NonFatal(t) => _logger.warn("Failed to write entry [{}]", entryName.asInstanceOf[Any], t)
}
colferEntry
@@ -255,7 +266,7 @@ object BinaryPropagation {
val contextSize = contextData.marshal(contextOutgoingBuffer, 0)
writer.write(contextOutgoingBuffer, 0, contextSize)
} catch {
- case NonFatal(t) => _log.warn("Failed to write Context to ByteStreamWriter", t)
+ case NonFatal(t) => _logger.warn("Failed to write Context to ByteStreamWriter", t)
}
}
}
diff --git a/kamon-core/src/main/scala/kamon/context/Context.scala b/kamon-core/src/main/scala/kamon/context/Context.scala
index 2a7a382e..054a7897 100644
--- a/kamon-core/src/main/scala/kamon/context/Context.scala
+++ b/kamon-core/src/main/scala/kamon/context/Context.scala
@@ -16,32 +16,92 @@
package kamon
package context
-import java.util.{Map => JavaMap}
-import scala.collection.JavaConverters._
-
-class Context private (val entries: Map[String, Any], val tags: Map[String, String]) {
+import kamon.tag.TagSet
+
+
+/**
+ * An immutable set of information that is tied to the processing of single operation in a service. A Context instance
+ * can contain tags and entries.
+ *
+ * Context tags are built on top of the TagSet abstraction that ships with Kamon and since Kamon knows exactly what
+ * types of values can be included in a TagSet it can automatically serialize and deserialize them when going over
+ * HTTP and/or Binary transports.
+ *
+ * Context entries can contain any arbitrary type specified by the user, but require additional configuration and
+ * implementation of entry readers and writers if you need them to go over HTTP and/or Binary transports.
+ *
+ * Context instances are meant to be constructed by using the builder functions on the Context companion object.
+ *
+ */
+class Context private (val entries: Map[String, Any], val tags: TagSet) {
+ /**
+ * Gets an entry from this Context. If the entry is present it's current value is returned, otherwise the empty value
+ * from the provided key will be returned.
+ */
def get[T](key: Context.Key[T]): T =
entries.getOrElse(key.name, key.emptyValue).asInstanceOf[T]
- def getTag(tagKey: String): Option[String] =
- tags.get(tagKey)
+ /**
+ * Executes a lookup on the context tags. The actual return type depends on the provided lookup instance. Take a look
+ * at the built-in lookups available on the Lookups companion object.
+ */
+ def getTag[T](lookup: TagSet.Lookup[T]): T =
+ tags.get(lookup)
+
+
+ /**
+ * Creates a new Context instance that includes the provided key and value. If the provided key was already
+ * associated with another value then the previous value will be discarded and overwritten with the provided one.
+ */
def withKey[T](key: Context.Key[T], value: T): Context =
new Context(entries.updated(key.name, value), tags)
- def withTag(tagKey: String, tagValue: String): Context =
- new Context(entries, tags.updated(tagKey, tagValue))
- def withTags(tags: Map[String, String]): Context =
- new Context(entries, this.tags ++ tags)
+ /**
+ * Creates a new Context instance that includes the provided tag key and value. If the provided tag key was already
+ * associated with another value then the previous tag value will be discarded and overwritten with the provided one.
+ */
+ def withTag(key: String, value: String): Context =
+ new Context(entries, tags.withTag(key, value))
+
+
+ /**
+ * Creates a new Context instance that includes the provided tag key and value. If the provided tag key was already
+ * associated with another value then the previous tag value will be discarded and overwritten with the provided one.
+ */
+ def withTag(key: String, value: Long): Context =
+ new Context(entries, tags.withTag(key, value))
+
+
+ /**
+ * Creates a new Context instance that includes the provided tag key and value. If the provided tag key was already
+ * associated with another value then the previous tag value will be discarded and overwritten with the provided one.
+ */
+ def withTag(key: String, value: Boolean): Context =
+ new Context(entries, tags.withTag(key, value))
+
+
+ /**
+ * Creates a new Context instance that includes the provided tags. If any of the tags in this instance are associated
+ * to a key present on the provided tags then the previous values will be discarded and overwritten with the provided
+ * ones.
+ */
+ def withTags(tags: TagSet): Context =
+ new Context(entries, this.tags.and(tags))
- def withTags(tags: JavaMap[String, String]): Context =
- new Context(entries, this.tags ++ tags.asScala.toMap)
+ /**
+ * Returns whether this Context does not have any tags and does not have any entries.
+ */
def isEmpty(): Boolean =
entries.isEmpty && tags.isEmpty
+
+ /**
+ * Returns whether this Context has any information, either as tags or entries.
+ */
def nonEmpty(): Boolean =
!isEmpty()
@@ -49,32 +109,48 @@ class Context private (val entries: Map[String, Any], val tags: Map[String, Stri
object Context {
- val Empty = new Context(Map.empty, Map.empty)
+ val Empty = new Context(Map.empty, TagSet.Empty)
- def of(tags: JavaMap[String, String]): Context =
- new Context(Map.empty, tags.asScala.toMap)
-
- def of(tags: Map[String, String]): Context =
+ /**
+ * Creates a new Context instance with the provided tags and no entries.
+ */
+ def of(tags: TagSet): Context =
new Context(Map.empty, tags)
+
+ /**
+ * Creates a new Context instance with the provided key and no tags.
+ */
def of[T](key: Context.Key[T], value: T): Context =
- new Context(Map(key.name -> value), Map.empty)
+ new Context(Map(key.name -> value), TagSet.Empty)
- def of[T](key: Context.Key[T], value: T, tags: JavaMap[String, String]): Context =
- new Context(Map(key.name -> value), tags.asScala.toMap)
- def of[T](key: Context.Key[T], value: T, tags: Map[String, String]): Context =
+ /**
+ * Creates a new Context instance with a single entry and the provided tags.
+ */
+ def of[T](key: Context.Key[T], value: T, tags: TagSet): Context =
new Context(Map(key.name -> value), tags)
+
+ /**
+ * Creates a new Context instance with two entries and no tags.
+ */
def of[T, U](keyOne: Context.Key[T], valueOne: T, keyTwo: Context.Key[U], valueTwo: U): Context =
- new Context(Map(keyOne.name -> valueOne, keyTwo.name -> valueTwo), Map.empty)
+ new Context(Map(keyOne.name -> valueOne, keyTwo.name -> valueTwo), TagSet.Empty)
- def of[T, U](keyOne: Context.Key[T], valueOne: T, keyTwo: Context.Key[U], valueTwo: U, tags: JavaMap[String, String]): Context =
- new Context(Map(keyOne.name -> valueOne, keyTwo.name -> valueTwo), tags.asScala.toMap)
- def of[T, U](keyOne: Context.Key[T], valueOne: T, keyTwo: Context.Key[U], valueTwo: U, tags: Map[String, String]): Context =
+ /**
+ * Creates a new Context instance with two entries and the provided tags.
+ */
+ def of[T, U](keyOne: Context.Key[T], valueOne: T, keyTwo: Context.Key[U], valueTwo: U, tags: TagSet): Context =
new Context(Map(keyOne.name -> valueOne, keyTwo.name -> valueTwo), tags)
+
+ /**
+ * Creates a new Context.Key instance that can be used to insert and retrieve values from the context entries.
+ * Context keys must have a unique name since they will be looked up in transports by their name and the context
+ * entries are internally stored using their key name as index.
+ */
def key[T](name: String, emptyValue: T): Context.Key[T] =
new Context.Key(name, emptyValue)
diff --git a/kamon-core/src/main/scala/kamon/context/HttpPropagation.scala b/kamon-core/src/main/scala/kamon/context/HttpPropagation.scala
index 6a15e2a6..fbee75cc 100644
--- a/kamon-core/src/main/scala/kamon/context/HttpPropagation.scala
+++ b/kamon-core/src/main/scala/kamon/context/HttpPropagation.scala
@@ -17,6 +17,7 @@ package kamon
package context
import com.typesafe.config.Config
+import kamon.tag.{Tag, TagSet}
import org.slf4j.LoggerFactory
import scala.reflect.ClassTag
@@ -91,7 +92,7 @@ object HttpPropagation {
* 3. Read all context entries using the incoming entries configuration.
*/
override def read(reader: HeaderReader): Context = {
- val tags = Map.newBuilder[String, String]
+ val tags = Map.newBuilder[String, Any]
// Tags encoded together in the context tags header.
try {
@@ -99,7 +100,7 @@ object HttpPropagation {
contextTagsHeader.split(";").foreach(tagData => {
val tagPair = tagData.split("=")
if (tagPair.length == 2) {
- tags += (tagPair(0) -> tagPair(1))
+ tags += (tagPair(0) -> parseTagValue(tagPair(1)))
}
})
}
@@ -118,7 +119,7 @@ object HttpPropagation {
}
// Incoming Entries
- settings.incomingEntries.foldLeft(Context.of(tags.result())) {
+ settings.incomingEntries.foldLeft(Context.of(TagSet.from(tags.result()))) {
case (context, (entryName, entryDecoder)) =>
var result = context
try {
@@ -145,10 +146,12 @@ object HttpPropagation {
}
// Write tags with specific mappings or append them to the context tags header.
- context.tags.foreach {
- case (tagKey, tagValue) => settings.tagsMappings.get(tagKey) match {
- case Some(mappedHeader) => writer.write(mappedHeader, tagValue)
- case None => appendTag(tagKey, tagValue)
+ context.tags.iterator().foreach { tag =>
+ val tagKey = tag.key
+
+ settings.tagsMappings.get(tagKey) match {
+ case Some(mappedHeader) => writer.write(mappedHeader, tagValueWithPrefix(tag))
+ case None => appendTag(tagKey, Tag.unwrapValue(tag).toString)
}
}
@@ -167,6 +170,54 @@ object HttpPropagation {
}
}
}
+
+
+ private val _longTypePrefix = "l:"
+ private val _booleanTypePrefix = "b:"
+ private val _booleanTrue = "true"
+ private val _booleanFalse = "false"
+
+ /**
+ * Tries to infer and parse a value into one of the supported tag types: String, Long or Boolean by looking for the
+ * type indicator prefix on the tag value. If the inference fails it will default to treat the value as a String.
+ */
+ private def parseTagValue(value: String): Any = {
+ if (value.isEmpty || value.length < 2) // Empty and short values definitely do not have type indicators.
+ value
+ else {
+ if(value.startsWith(_longTypePrefix)) {
+ // Try to parse the content as a Long value.
+ val remaining = value.substring(2)
+ try {
+ java.lang.Long.parseLong(remaining)
+ } catch {
+ case _: Throwable => remaining
+ }
+
+ } else if(value.startsWith(_booleanTypePrefix)) {
+
+ // Try to parse the content as a Boolean value.
+ val remaining = value.substring(2)
+ if(remaining.equals(_booleanTrue))
+ true
+ else if(remaining.equals(_booleanFalse))
+ false
+ else
+ remaining
+
+ } else value
+ }
+ }
+
+ /**
+ * Returns the actual value to be written in the HTTP transport, with a type prefix if applicable.
+ */
+ private def tagValueWithPrefix(tag: Tag): String = tag match {
+ case t: Tag.String => t.value
+ case t: Tag.Boolean => _booleanTypePrefix + t.value.toString
+ case t: Tag.Long => _longTypePrefix + t.value.toString
+ }
+
}
case class Settings(
diff --git a/kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala b/kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala
index 659da8aa..68975711 100644
--- a/kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala
+++ b/kamon-core/src/main/scala/kamon/instrumentation/HttpServer.scala
@@ -6,7 +6,9 @@ import java.time.Duration
import com.typesafe.config.Config
import kamon.context.Context
import kamon.instrumentation.HttpServer.Settings.TagMode
-import kamon.metric.MeasurementUnit.{time, information}
+import kamon.metric.MeasurementUnit.{information, time}
+import kamon.tag.TagSet
+import kamon.tag.Lookups.{any, option}
import kamon.trace.{IdentityProvider, Span}
import kamon.util.GlobPathFilter
import org.slf4j.LoggerFactory
@@ -348,7 +350,7 @@ object HttpServer {
span.disableMetrics()
- for { traceIdTag <- settings.traceIDTag; customTraceID <- context.getTag(traceIdTag) } {
+ for {traceIdTag <- settings.traceIDTagLookup; customTraceID <- context.getTag(traceIdTag) } {
val identifier = Kamon.identityProvider.traceIdGenerator().from(customTraceID)
if(identifier != IdentityProvider.NoIdentifier)
span.withTraceID(identifier)
@@ -361,9 +363,12 @@ object HttpServer {
}
addRequestTag("http.url", request.url, settings.urlTagMode)
- addRequestTag("http.method", request.method, settings.urlTagMode)
+ addRequestTag("http.method", request.method, settings.methodTagMode)
settings.contextTags.foreach {
- case (tagName, mode) => context.getTag(tagName).foreach(tagValue => addRequestTag(tagName, tagValue, mode))
+ case (tagName, mode) =>
+ val tagValue = context.getTag(any(tagName))
+ if(tagValue != null)
+ addRequestTag(tagName, tagValue.toString, mode)
}
span.start()
@@ -385,7 +390,7 @@ object HttpServer {
propagationChannel: String,
enableServerMetrics: Boolean,
enableTracing: Boolean,
- traceIDTag: Option[String],
+ traceIDTagLookup: Option[TagSet.Lookup[Option[String]]],
enableSpanMetrics: Boolean,
urlTagMode: TagMode,
methodTagMode: TagMode,
@@ -424,7 +429,10 @@ object HttpServer {
// Tracing settings
val enableTracing = config.getBoolean("tracing.enabled")
- val traceIdTag = Option(config.getString("tracing.preferred-trace-id-tag")).filterNot(_ == "none")
+ val traceIdTagLookup = Option(config.getString("tracing.preferred-trace-id-tag"))
+ .filterNot(_ == "none")
+ .map(option)
+
val enableSpanMetrics = config.getBoolean("tracing.span-metrics")
val urlTagMode = TagMode.from(config.getString("tracing.tags.url"))
val methodTagMode = TagMode.from(config.getString("tracing.tags.method"))
@@ -441,12 +449,12 @@ object HttpServer {
case (pattern, operationName) => (new GlobPathFilter(pattern), operationName)
}
- Settings(
+ Settings (
enablePropagation,
propagationChannel,
enableServerMetrics,
enableTracing,
- traceIdTag,
+ traceIdTagLookup,
enableSpanMetrics,
urlTagMode,
methodTagMode,
diff --git a/kamon-core/src/main/scala/kamon/metric/Accumulator.scala b/kamon-core/src/main/scala/kamon/metric/Accumulator.scala
index bf412980..945feeeb 100644
--- a/kamon-core/src/main/scala/kamon/metric/Accumulator.scala
+++ b/kamon-core/src/main/scala/kamon/metric/Accumulator.scala
@@ -17,7 +17,7 @@ package kamon.metric
import java.time.{Duration, Instant}
-import kamon.{Kamon, Tags}
+import kamon.{Kamon, STags}
import kamon.metric.PeriodSnapshotAccumulator.{MetricDistributionKey, MetricValueKey}
import kamon.util.Clock
@@ -169,6 +169,6 @@ class PeriodSnapshotAccumulator(duration: Duration, margin: Duration) {
}
object PeriodSnapshotAccumulator {
- case class MetricValueKey(name: String, tags: Tags, unit: MeasurementUnit)
- case class MetricDistributionKey(name: String, tags: Tags, unit: MeasurementUnit, dynamicRange: DynamicRange)
+ case class MetricValueKey(name: String, tags: STags, unit: MeasurementUnit)
+ case class MetricDistributionKey(name: String, tags: STags, unit: MeasurementUnit, dynamicRange: DynamicRange)
}
diff --git a/kamon-core/src/main/scala/kamon/metric/Metric.scala b/kamon-core/src/main/scala/kamon/metric/Metric.scala
index f5ce7b45..69ef88bc 100644
--- a/kamon-core/src/main/scala/kamon/metric/Metric.scala
+++ b/kamon-core/src/main/scala/kamon/metric/Metric.scala
@@ -34,12 +34,12 @@ trait Metric[T] {
def unit: MeasurementUnit
def refine(tags: JTags): T
- def refine(tags: Tags): T
+ def refine(tags: STags): T
def refine(tags: (String, String)*): T
def refine(tag: String, value: String): T
def remove(tags: JTags): Boolean
- def remove(tags: Tags): Boolean
+ def remove(tags: STags): Boolean
def remove(tags: (String, String)*): Boolean
def remove(tag: String, value: String): Boolean
}
@@ -52,7 +52,7 @@ trait CounterMetric extends Metric[Counter] with Counter
private[kamon] abstract sealed class BaseMetric[T, S](val instrumentType: InstrumentType) extends Metric[T] {
- private[kamon] val instruments = TrieMap.empty[Tags, T]
+ private[kamon] val instruments = TrieMap.empty[STags, T]
protected lazy val baseInstrument: T = instruments.atomicGetOrElseUpdate(Map.empty, createInstrument(Map.empty))
override def refine(tags: JTags):T =
@@ -72,7 +72,7 @@ private[kamon] abstract sealed class BaseMetric[T, S](val instrumentType: Instru
override def remove(tags: JTags):Boolean =
remove(tags.asScala.toMap)
- override def remove(tags: Tags): Boolean =
+ override def remove(tags: STags): Boolean =
if(tags.nonEmpty) instruments.remove(tags).nonEmpty else false
override def remove(tags: (String, String)*): Boolean =
@@ -88,7 +88,7 @@ private[kamon] abstract sealed class BaseMetric[T, S](val instrumentType: Instru
private[kamon] def incarnations(): Seq[Map[String, String]] =
instruments.keys.toSeq
- protected def createInstrument(tags: Tags): T
+ protected def createInstrument(tags: STags): T
protected def createSnapshot(instrument: T): S
}
@@ -106,7 +106,7 @@ private[kamon] final class HistogramMetricImpl(val name: String, val unit: Measu
override def record(value: Long, times: Long): Unit =
baseInstrument.record(value, times)
- override protected def createInstrument(tags: Tags): Histogram =
+ override protected def createInstrument(tags: STags): Histogram =
factory.get().buildHistogram(customDynamicRange)(name, tags, unit)
override protected def createSnapshot(instrument: Histogram): MetricDistribution =
@@ -118,7 +118,7 @@ private[kamon] final class RangeSamplerMetricImpl(val name: String, val unit: Me
extends BaseMetric[RangeSampler, MetricDistribution](RangeSampler) with RangeSamplerMetric {
private val logger = LoggerFactory.getLogger(classOf[RangeSamplerMetric])
- private val scheduledSamplers = TrieMap.empty[Tags, ScheduledFuture[_]]
+ private val scheduledSamplers = TrieMap.empty[STags, ScheduledFuture[_]]
def dynamicRange: DynamicRange =
baseInstrument.dynamicRange
@@ -141,7 +141,7 @@ private[kamon] final class RangeSamplerMetricImpl(val name: String, val unit: Me
override def sample(): Unit =
baseInstrument.sample()
- override protected def createInstrument(tags: Tags): RangeSampler = {
+ override protected def createInstrument(tags: STags): RangeSampler = {
val rangeSampler = factory.get().buildRangeSampler(customDynamicRange, customSampleInterval)(name, tags, unit)
val sampleInterval = rangeSampler.sampleInterval.toMillis
val scheduledFuture = scheduler.scheduleAtFixedRate(scheduledSampler(rangeSampler), sampleInterval, sampleInterval, TimeUnit.MILLISECONDS)
@@ -153,7 +153,7 @@ private[kamon] final class RangeSamplerMetricImpl(val name: String, val unit: Me
override def remove(tags: JTags): Boolean =
removeAndStopSampler(tags.asScala.toMap)
- override def remove(tags: Tags): Boolean =
+ override def remove(tags: STags): Boolean =
removeAndStopSampler(tags)
override def remove(tags: (String, String)*): Boolean =
@@ -162,7 +162,7 @@ private[kamon] final class RangeSamplerMetricImpl(val name: String, val unit: Me
override def remove(tag: String, value: String): Boolean =
removeAndStopSampler(Map(tag -> value))
- private def removeAndStopSampler(tags: Tags): Boolean = {
+ private def removeAndStopSampler(tags: STags): Boolean = {
val removed = super.remove(tags)
if(removed)
scheduledSamplers.remove(tags).foreach(sf => {
@@ -190,7 +190,7 @@ private[kamon] final class CounterMetricImpl(val name: String, val unit: Measure
override def increment(times: Long): Unit =
baseInstrument.increment(times)
- override protected def createInstrument(tags: Tags): Counter =
+ override protected def createInstrument(tags: STags): Counter =
factory.get().buildCounter(name, tags, unit)
override protected def createSnapshot(instrument: Counter): MetricValue =
@@ -215,7 +215,7 @@ private[kamon] final class GaugeMetricImpl(val name: String, val unit: Measureme
override def set(value: Long): Unit =
baseInstrument.set(value)
- override protected def createInstrument(tags: Tags): Gauge =
+ override protected def createInstrument(tags: STags): Gauge =
factory.get().buildGauge(name, tags, unit)
override protected def createSnapshot(instrument: Gauge): MetricValue =
diff --git a/kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala b/kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala
index 50a5f778..09a0e029 100644
--- a/kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala
+++ b/kamon-core/src/main/scala/kamon/metric/PeriodSnapshot.scala
@@ -39,13 +39,13 @@ case class MetricsSnapshot(
* Snapshot for instruments that internally track a single value. Meant to be used for counters and gauges.
*
*/
-case class MetricValue(name: String, tags: Tags, unit: MeasurementUnit, value: Long)
+case class MetricValue(name: String, tags: STags, unit: MeasurementUnit, value: Long)
/**
* Snapshot for instruments that internally the distribution of values in a defined dynamic range. Meant to be used
* with histograms and min max counters.
*/
-case class MetricDistribution(name: String, tags: Tags, unit: MeasurementUnit, dynamicRange: DynamicRange, distribution: Distribution)
+case class MetricDistribution(name: String, tags: STags, unit: MeasurementUnit, dynamicRange: DynamicRange, distribution: Distribution)
trait Distribution {
diff --git a/kamon-core/src/main/scala/kamon/metric/Timer.scala b/kamon-core/src/main/scala/kamon/metric/Timer.scala
index 74d203a9..749ac876 100644
--- a/kamon-core/src/main/scala/kamon/metric/Timer.scala
+++ b/kamon-core/src/main/scala/kamon/metric/Timer.scala
@@ -15,7 +15,7 @@
package kamon.metric
-import kamon.{JTags, Tags}
+import kamon.{JTags, STags}
trait Timer extends Histogram {
def start(): StartedTimer
@@ -82,7 +82,7 @@ private[kamon] final class TimerMetricImpl(val underlyingHistogram: HistogramMet
override def refine(tags: JTags): Timer =
refine(tags.asScala.toMap)
- override def refine(tags: Tags): Timer =
+ override def refine(tags: STags): Timer =
new TimerImpl(underlyingHistogram.refine(tags))
override def refine(tags: (String, String)*): Timer =
@@ -94,7 +94,7 @@ private[kamon] final class TimerMetricImpl(val underlyingHistogram: HistogramMet
override def remove(tags: JTags): Boolean =
remove(tags.asScala.toMap)
- override def remove(tags: Tags): Boolean =
+ override def remove(tags: STags): Boolean =
underlyingHistogram.remove(tags)
override def remove(tags: (String, String)*): Boolean =
diff --git a/kamon-core/src/main/scala/kamon/package.scala b/kamon-core/src/main/scala/kamon/package.scala
index d694206c..3da676cd 100644
--- a/kamon-core/src/main/scala/kamon/package.scala
+++ b/kamon-core/src/main/scala/kamon/package.scala
@@ -23,7 +23,7 @@ import scala.collection.concurrent.TrieMap
package object kamon {
- type Tags = Map[String, String]
+ type STags = Map[String, String]
type JTags = java.util.Map[String, String]
diff --git a/kamon-core/src/main/scala/kamon/tag/Lookups.scala b/kamon-core/src/main/scala/kamon/tag/Lookups.scala
index 44ffb6f4..beb1996a 100644
--- a/kamon-core/src/main/scala/kamon/tag/Lookups.scala
+++ b/kamon-core/src/main/scala/kamon/tag/Lookups.scala
@@ -3,18 +3,27 @@ package kamon.tag
import java.util.Optional
import java.lang.{Boolean => JBoolean, Long => JLong, String => JString}
-import kamon.tag.Tags.Lookup
+import kamon.tag.TagSet.Lookup
import scala.reflect.ClassTag
object Lookups {
/**
+ * Finds a value associated to the provided key and returns it. If the key is not present then a null is returned.
+ */
+ def any(key: JString) = new Lookup[Any] {
+ override def execute(storage: Map[JString, Any]): Any =
+ findAndTransform(key, storage, _any, null)
+ }
+
+
+ /**
* Finds a String value associated to the provided key and returns it. If the key is not present or the value
* associated with they is not a String then a null is returned.
*/
def plain(key: JString) = new Lookup[JString] {
- override def run(storage: Map[JString, Any]): JString =
+ override def execute(storage: Map[JString, Any]): JString =
findAndTransform(key, storage, _plainString, null)
}
@@ -24,7 +33,7 @@ object Lookups {
* not present or the value associated with they is not a String then a None is returned.
*/
def option(key: JString) = new Lookup[Option[JString]] {
- override def run(storage: Map[JString, Any]): Option[JString] =
+ override def execute(storage: Map[JString, Any]): Option[JString] =
findAndTransform(key, storage, _stringOption, None)
}
@@ -34,7 +43,7 @@ object Lookups {
* is not present or the value associated with they is not a String then Optional.empty() is returned.
*/
def optional(key: JString) = new Lookup[Optional[String]] {
- override def run(storage: Map[String, Any]): Optional[String] =
+ override def execute(storage: Map[String, Any]): Optional[String] =
findAndTransform(key, storage, _stringOptional, Optional.empty())
}
@@ -47,7 +56,7 @@ object Lookups {
* This lookup type is guaranteed to return a non-null String representation of value.
*/
def coerce(key: String) = new Lookup[String] {
- override def run(storage: Map[String, Any]): String = {
+ override def execute(storage: Map[String, Any]): String = {
val value = storage(key)
if(value == null)
"unknown"
@@ -62,7 +71,7 @@ object Lookups {
* associated with they is not a Boolean then a null is returned.
*/
def plainBoolean(key: String) = new Lookup[JBoolean] {
- override def run(storage: Map[String, Any]): JBoolean =
+ override def execute(storage: Map[String, Any]): JBoolean =
findAndTransform(key, storage, _plainBoolean, null)
}
@@ -72,7 +81,7 @@ object Lookups {
* is not present or the value associated with they is not a Boolean then a None is returned.
*/
def booleanOption(key: String) = new Lookup[Option[JBoolean]] {
- override def run(storage: Map[String, Any]): Option[JBoolean] =
+ override def execute(storage: Map[String, Any]): Option[JBoolean] =
findAndTransform(key, storage, _booleanOption, None)
}
@@ -82,7 +91,7 @@ object Lookups {
* is not present or the value associated with they is not a Boolean then Optional.empty() is returned.
*/
def booleanOptional(key: String) = new Lookup[Optional[JBoolean]] {
- override def run(storage: Map[String, Any]): Optional[JBoolean] =
+ override def execute(storage: Map[String, Any]): Optional[JBoolean] =
findAndTransform(key, storage, _booleanOptional, Optional.empty())
}
@@ -92,7 +101,7 @@ object Lookups {
* associated with they is not a Long then a null is returned.
*/
def plainLong(key: String) = new Lookup[JLong] {
- override def run(storage: Map[String, Any]): JLong =
+ override def execute(storage: Map[String, Any]): JLong =
findAndTransform(key, storage, _plainLong, null)
}
@@ -102,7 +111,7 @@ object Lookups {
* not present or the value associated with they is not a Long then a None is returned.
*/
def longOption(key: String) = new Lookup[Option[JLong]] {
- override def run(storage: Map[String, Any]): Option[JLong] =
+ override def execute(storage: Map[String, Any]): Option[JLong] =
findAndTransform(key, storage, _longOption, None)
}
@@ -112,7 +121,7 @@ object Lookups {
* is not present or the value associated with they is not a Long then Optional.empty() is returned.
*/
def longOptional(key: String) = new Lookup[Optional[JLong]] {
- override def run(storage: Map[String, Any]): Optional[JLong] =
+ override def execute(storage: Map[String, Any]): Optional[JLong] =
findAndTransform(key, storage, _longOptional, Optional.empty())
}
@@ -134,6 +143,7 @@ object Lookups {
transform(value.asInstanceOf[R])
}
+ private val _any = (a: Any) => a
private val _plainString = (a: JString) => a
private val _stringOption = (a: JString) => Option(a)
private val _stringOptional = (a: JString) => Optional.of(a)
diff --git a/kamon-core/src/main/scala/kamon/tag/Tag.scala b/kamon-core/src/main/scala/kamon/tag/Tag.scala
new file mode 100644
index 00000000..69a5d7e7
--- /dev/null
+++ b/kamon-core/src/main/scala/kamon/tag/Tag.scala
@@ -0,0 +1,50 @@
+package kamon.tag
+
+import java.lang.{Boolean => JBoolean, Long => JLong, String => JString}
+
+/**
+ * Marker trait for allowed Tag implementations. Users are not meant to create implementations of this trait outside
+ * of Kamon. Furthermore, users of TagSet might never need to interact with these classes but rather perform lookups
+ * using the lookup DSL.
+ */
+sealed trait Tag {
+ def key: JString
+}
+
+object Tag {
+
+ /**
+ * Represents a String key pointing to a String value.
+ */
+ trait String extends Tag {
+ def value: JString
+ }
+
+
+ /**
+ * Represents a String key pointing to a Boolean value.
+ */
+ trait Boolean extends Tag {
+ def value: JBoolean
+ }
+
+
+ /**
+ * Represents a String key pointing to a Long value.
+ */
+ trait Long extends Tag {
+ def value: JLong
+ }
+
+
+ /**
+ * Returns the value held inside of a Tag instance. This utility function is specially useful when iterating over
+ * tags but not caring about the concrete tag type.
+ */
+ def unwrapValue(tag: Tag): Any = tag match {
+ case t: Tag.String => t.value
+ case t: Tag.Boolean => t.value
+ case t: Tag.Long => t.value
+ }
+}
+
diff --git a/kamon-core/src/main/scala/kamon/tag/Tags.scala b/kamon-core/src/main/scala/kamon/tag/TagSet.scala
index b7813da6..1090ca5a 100644
--- a/kamon-core/src/main/scala/kamon/tag/Tags.scala
+++ b/kamon-core/src/main/scala/kamon/tag/TagSet.scala
@@ -1,47 +1,12 @@
package kamon.tag
-import kamon.tag.Tags.Lookup
+import kamon.tag.TagSet.Lookup
import scala.collection.JavaConverters.asScalaIteratorConverter
import java.lang.{Boolean => JBoolean, Long => JLong, String => JString}
import org.slf4j.LoggerFactory
-
-/**
- * Marker trait for allowed Tag implementations. Users are not meant to create implementations of this trait outside
- * of Kamon.
- */
-sealed trait Tag
-
-object Tag {
-
- /**
- * Represents a String key pointing to a String value.
- */
- trait String extends Tag {
- def key: JString
- def value: JString
- }
-
- /**
- * Represents a String key pointing to a Boolean value.
- */
- trait Boolean extends Tag {
- def key: JString
- def value: JBoolean
- }
-
- /**
- * Represents a String key pointing to a Long value.
- */
- trait Long extends Tag {
- def key: JString
- def value: JLong
- }
-}
-
-
/**
* A immutable collection of key/value pairs with specialized support for storing String keys pointing to String, Long
* and/or Boolean values.
@@ -51,8 +16,8 @@ object Tag {
* lookup pairs without prescribing a mechanism for handling missing values. I.e. users of this class can decide
* whether to receive a null, java.util.Optional, scala.Option or any other value when looking up a pair.
*
- * Tags can only be created from the builder functions on the Tags companion object. There are two different options
- * to read the contained pairs from a Tags instance:
+ * TagSet instances can only be created from the builder functions on the TagSet companion object. There are two
+ * different options to read the contained pairs from a Tags instance:
*
* 1. Using the lookup DSL. You can use the Lookup DSL when you know exactly that you are trying to get out of the
* tags instance. The lookup DSL is biased towards String keys since they are by far the most common case. For
@@ -69,80 +34,96 @@ object Tag {
* cumbersome operation is rarely necessary on user-facing code.
*
*/
-class Tags private(private val _tags: Map[String, Any]) {
- import Tags.withPair
+class TagSet private(private val _tags: Map[String, Any]) {
+ import TagSet.withPair
/**
- * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
* with another value then the previous value will be discarded and overwritten with the provided one.
*/
- def withTag(key: String, value: JString): Tags =
+ def withTag(key: String, value: JString): TagSet =
withPair(this, key, value)
/**
- * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
* with another value then the previous value will be discarded and overwritten with the provided one.
*/
- def withTag(key: String, value: JBoolean): Tags =
+ def withTag(key: String, value: JBoolean): TagSet =
withPair(this, key, value)
/**
- * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
* with another value then the previous value will be discarded and overwritten with the provided one.
*/
- def withTag(key: String, value: JLong): Tags =
+ def withTag(key: String, value: JLong): TagSet =
withPair(this, key, value)
/**
- * Creates a new Tags instance that includes all the tags from the provided Tags instance. If any of the tags in this
+ * Creates a new TagSet instance that includes all the tags from the provided Tags instance. If any of the tags in this
* instance are associated to a key present on the provided instance then the previous value will be discarded and
* overwritten with the provided one.
*/
- def withTags(other: Tags): Tags =
- new Tags(_tags ++ other._tags)
+ def withTags(other: TagSet): TagSet =
+ new TagSet(_tags ++ other._tags)
/**
- * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
* with another value then the previous value will be discarded and overwritten with the provided one.
*/
- def and(key: String, value: JString): Tags =
+ def and(key: String, value: JString): TagSet =
withPair(this, key, value)
/**
- * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
* with another value then the previous value will be discarded and overwritten with the provided one.
*/
- def and(key: String, value: JBoolean): Tags =
+ def and(key: String, value: JBoolean): TagSet =
withPair(this, key, value)
/**
- * Creates a new Tags instance that includes the provided key/value pair. If the provided key was already associated
+ * Creates a new TagSet instance that includes the provided key/value pair. If the provided key was already associated
* with another value then the previous value will be discarded and overwritten with the provided one.
*/
- def and(key: String, value: JLong): Tags =
+ def and(key: String, value: JLong): TagSet =
withPair(this, key, value)
/**
- * Creates a new Tags instance that includes all the tags from the provided Tags instance. If any of the tags in this
+ * Creates a new TagSet instance that includes all the tags from the provided Tags instance. If any of the tags in this
* instance are associated to a key present on the provided instance then the previous value will be discarded and
* overwritten with the provided one.
*/
- def and(other: Tags): Tags =
- new Tags(_tags ++ other._tags)
+ def and(other: TagSet): TagSet =
+ new TagSet(_tags ++ other._tags)
+
/**
- * Executes a tag lookup on the instance. The return type of this function will depend on the provided Lookup
- * instance. Take a look at the built-in lookups on the Tags.Lookup companion object for more information.
+ * Returns whether this TagSet instance does not contain any tags.
+ */
+ def isEmpty(): Boolean =
+ _tags.isEmpty
+
+
+ /**
+ * Returns whether this TagSet instance contains any tags.
+ */
+ def nonEmpty(): Boolean =
+ _tags.nonEmpty
+
+
+ /**
+ * Executes a tag lookup. The return type of this function will depend on the provided Lookup. Take a look at the
+ * built-in lookups on the [[Lookups]] companion object for more information.
*/
def get[T](lookup: Lookup[T]): T =
- lookup.run(_tags)
+ lookup.execute(_tags)
+
/**
* Returns a immutable sequence of tags created from the contained tags internal representation. Calling this method
@@ -160,6 +141,7 @@ class Tags private(private val _tags: Map[String, Any]) {
}
}
+
/**
* Returns an iterator of tags. The underlying iterator reuses the Tag instances to avoid unnecessary intermediate
* allocations and thus, it is not safe to share across threads. The most common case for tags iterators is on
@@ -205,7 +187,7 @@ class Tags private(private val _tags: Map[String, Any]) {
override def equals(other: Any): Boolean =
- other != null && other.isInstanceOf[Tags] && other.asInstanceOf[Tags]._tags == this._tags
+ other != null && other.isInstanceOf[TagSet] && other.asInstanceOf[TagSet]._tags == this._tags
override def toString: JString = {
@@ -228,15 +210,15 @@ class Tags private(private val _tags: Map[String, Any]) {
}
private object immutable {
- class String(val key: JString, val value: JString) extends Tag.String
- class Boolean(val key: JString, val value: JBoolean) extends Tag.Boolean
- class Long(val key: JString, val value: JLong) extends Tag.Long
+ case class String(key: JString, value: JString) extends Tag.String
+ case class Boolean(key: JString, value: JBoolean) extends Tag.Boolean
+ case class Long(key: JString, value: JLong) extends Tag.Long
}
private object mutable {
- class String(var key: JString, var value: JString) extends Tag.String with Updateable[JString]
- class Boolean(var key: JString, var value: JBoolean) extends Tag.Boolean with Updateable[JBoolean]
- class Long(var key: JString, var value: JLong) extends Tag.Long with Updateable[JLong]
+ case class String(var key: JString, var value: JString) extends Tag.String with Updateable[JString]
+ case class Boolean(var key: JString, var value: JBoolean) extends Tag.Boolean with Updateable[JBoolean]
+ case class Long(var key: JString, var value: JLong) extends Tag.Long with Updateable[JLong]
trait Updateable[T] {
var key: JString
@@ -251,63 +233,63 @@ class Tags private(private val _tags: Map[String, Any]) {
}
}
-object Tags {
+object TagSet {
/**
* A valid instance of tags that doesn't contain any pairs.
*/
- val Empty = new Tags(Map.empty.withDefaultValue(null))
+ val Empty = new TagSet(Map.empty.withDefaultValue(null))
/**
- * Construct a new Tags instance with a single key/value pair.
+ * Construct a new TagSet instance with a single key/value pair.
*/
- def from(key: String, value: JString): Tags =
+ def from(key: String, value: JString): TagSet =
withPair(Empty, key, value)
/**
- * Construct a new Tags instance with a single key/value pair.
+ * Construct a new TagSet instance with a single key/value pair.
*/
- def from(key: String, value: JBoolean): Tags =
+ def from(key: String, value: JBoolean): TagSet =
withPair(Empty, key, value)
/**
- * Construct a new Tags instance with a single key/value pair.
+ * Construct a new TagSet instance with a single key/value pair.
*/
- def from(key: String, value: JLong): Tags =
+ def from(key: String, value: JLong): TagSet =
withPair(Empty, key, value)
/**
- * Constructs a new Tags instance from a Map. The returned Tags will only contain the entries that have String, Long
- * or Boolean values from the supplied map, any other entry in the map will be ignored.
+ * Constructs a new TagSet instance from a Map. The returned TagSet will only contain the entries that have String,
+ * Long or Boolean values from the supplied map, any other entry in the map will be ignored.
*/
- def from(map: Map[String, Any]): Tags =
- new Tags(map.filter { case (k, v) => isValidPair(k, v) } withDefaultValue(null))
+ def from(map: Map[String, Any]): TagSet =
+ new TagSet(map.filter { case (k, v) => isValidPair(k, v) } withDefaultValue(null))
/**
- * Constructs a new Tags instance from a Map. The returned Tags will only contain the entries that have String, Long
- * or Boolean values from the supplied map, any other entry in the map will be ignored.
+ * Constructs a new TagSet instance from a Map. The returned TagSet will only contain the entries that have String,
+ * Long or Boolean values from the supplied map, any other entry in the map will be ignored.
*/
- def from(map: java.util.Map[String, Any]): Tags = {
+ def from(map: java.util.Map[String, Any]): TagSet = {
val allowedTags = Map.newBuilder[String, Any]
map.entrySet()
.iterator()
.asScala
.foreach(e => if(isValidPair(e.getKey, e.getValue)) allowedTags += (e.getKey -> e.getValue))
- new Tags(allowedTags.result().withDefaultValue(null))
+ new TagSet(allowedTags.result().withDefaultValue(null))
}
- private val _logger = LoggerFactory.getLogger(classOf[Tags])
+ private val _logger = LoggerFactory.getLogger(classOf[TagSet])
- private def withPair(parent: Tags, key: String, value: Any): Tags =
+ private def withPair(parent: TagSet, key: String, value: Any): TagSet =
if(isValidPair(key, value))
- new Tags(parent._tags.updated(key, value))
+ new TagSet(parent._tags.updated(key, value))
else
parent
@@ -333,7 +315,7 @@ object Tags {
/**
- * Describes a strategy to lookup values from a Tags instance. Implementations of this interface will be provided
+ * Describes a strategy to lookup values from a TagSet instance. Implementations of this interface will be provided
* with the actual data structure containing the tags and must perform any necessary runtime type checks to ensure
* that the returned value is in assignable to the expected type T.
*
@@ -341,6 +323,6 @@ object Tags {
* definitions when looking up keys from a Tags instance.
*/
trait Lookup[T] {
- def run(storage: Map[String, Any]): T
+ def execute(storage: Map[String, Any]): T
}
} \ No newline at end of file