diff options
-rw-r--r-- | kamon-core/src/main/resources/META-INF/aop.xml (renamed from src/main/resources/META-INF/aop.xml) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/resources/application.conf (renamed from src/main/resources/application.conf) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/resources/newrelic.yml (renamed from src/main/resources/newrelic.yml) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/Kamon.scala (renamed from src/main/scala/kamon/Kamon.scala) | 4 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/TraceContext.scala (renamed from src/main/scala/kamon/TraceContext.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/TraceContextSwap.scala (renamed from src/main/scala/kamon/TraceContextSwap.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/TransactionPublisher.scala (renamed from src/main/scala/kamon/TransactionPublisher.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/executor/eventbus.scala (renamed from src/main/scala/kamon/executor/eventbus.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/instrumentation/ActorRefTellInstrumentation.scala (renamed from src/main/scala/kamon/instrumentation/ActorRefTellInstrumentation.scala) | 8 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/instrumentation/AspectJPimps.scala (renamed from src/main/scala/kamon/instrumentation/AspectJPimps.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/instrumentation/ExecutorServiceMetrics.scala (renamed from src/main/scala/kamon/instrumentation/ExecutorServiceMetrics.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/instrumentation/MessageQueueMetrics.scala (renamed from src/main/scala/kamon/instrumentation/MessageQueueMetrics.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/instrumentation/RunnableInstrumentation.scala (renamed from src/main/scala/kamon/instrumentation/RunnableInstrumentation.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/instrumentation/SampleInstrumentation.scala (renamed from src/main/scala/kamon/instrumentation/SampleInstrumentation.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/metric/ExecutorServiceMetricCollector.scala (renamed from src/main/scala/kamon/metric/ExecutorServiceMetricCollector.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/metric/GaugeGenerator.scala (renamed from src/main/scala/kamon/metric/GaugeGenerator.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/metric/MetricFilter.scala (renamed from src/main/scala/kamon/metric/MetricFilter.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/metric/Metrics.scala (renamed from src/main/scala/kamon/metric/Metrics.scala) | 4 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/metric/MetricsUtils.scala (renamed from src/main/scala/kamon/metric/MetricsUtils.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/metric/NewRelicReporter.scala (renamed from src/main/scala/kamon/metric/NewRelicReporter.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/spraytest/ClientTest.scala (renamed from src/main/scala/spraytest/ClientTest.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/spraytest/FutureTesting.scala (renamed from src/main/scala/spraytest/FutureTesting.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/main/scala/test/PingPong.scala (renamed from src/main/scala/test/PingPong.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/test/scala/akka/instrumentation/ActorInstrumentationSpec.scala | 45 | ||||
-rw-r--r-- | kamon-core/src/test/scala/kamon/instrumentation/ActorSystemInstrumentationSpec.scala (renamed from src/test/scala/kamon/instrumentation/ActorSystemInstrumentationSpec.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/test/scala/kamon/instrumentation/DispatcherInstrumentationSpec.scala (renamed from src/test/scala/kamon/instrumentation/DispatcherInstrumentationSpec.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/test/scala/kamon/instrumentation/MessageQueueInstrumentationSpec.scala (renamed from src/test/scala/kamon/instrumentation/MessageQueueInstrumentationSpec.scala) | 0 | ||||
-rw-r--r-- | kamon-core/src/test/scala/kamon/instrumentation/RunnableInstrumentationSpec.scala (renamed from src/test/scala/kamon/instrumentation/RunnableInstrumentationSpec.scala) | 0 | ||||
-rw-r--r-- | kamon-uow/src/main/scala/kamon/logging/UowActorLogging.scala | 14 | ||||
-rw-r--r-- | kamon-uow/src/main/scala/kamon/logging/UowDirectives.scala | 28 | ||||
-rw-r--r-- | project/Build.scala | 12 | ||||
-rw-r--r-- | project/Settings.scala | 4 | ||||
-rw-r--r-- | src/test/scala/akka/instrumentation/ActorInstrumentationSpec.scala | 43 |
33 files changed, 108 insertions, 54 deletions
diff --git a/src/main/resources/META-INF/aop.xml b/kamon-core/src/main/resources/META-INF/aop.xml index e6d61fa1..e6d61fa1 100644 --- a/src/main/resources/META-INF/aop.xml +++ b/kamon-core/src/main/resources/META-INF/aop.xml diff --git a/src/main/resources/application.conf b/kamon-core/src/main/resources/application.conf index 370acae9..370acae9 100644 --- a/src/main/resources/application.conf +++ b/kamon-core/src/main/resources/application.conf diff --git a/src/main/resources/newrelic.yml b/kamon-core/src/main/resources/newrelic.yml index 1b1ad53b..1b1ad53b 100644 --- a/src/main/resources/newrelic.yml +++ b/kamon-core/src/main/resources/newrelic.yml diff --git a/src/main/scala/kamon/Kamon.scala b/kamon-core/src/main/scala/kamon/Kamon.scala index 5a1382a4..c3080909 100644 --- a/src/main/scala/kamon/Kamon.scala +++ b/kamon-core/src/main/scala/kamon/Kamon.scala @@ -117,10 +117,10 @@ class NewrelicReporterActor extends Actor { def receive = { case DispatcherMetrics(actorSystem, dispatcher, activeThreads, poolSize, queueSize) => { - println("PUBLISHED DISPATCHER STATS") + /*println("PUBLISHED DISPATCHER STATS") println(s"Custom/$actorSystem/Dispatcher/$dispatcher/Threads/active =>" + activeThreads.median.toFloat) println(s"Custom/$actorSystem/Dispatcher/$dispatcher/Threads/inactive =>" + (poolSize.median.toFloat-activeThreads.median.toFloat)) - println(s"Custom/$actorSystem/Dispatcher/$dispatcher/Queue =>" + queueSize.median.toFloat) + println(s"Custom/$actorSystem/Dispatcher/$dispatcher/Queue =>" + queueSize.median.toFloat)*/ NewRelic.recordMetric(s"Custom/$actorSystem/Dispatcher/$dispatcher/Threads/active", activeThreads.median.toFloat) diff --git a/src/main/scala/kamon/TraceContext.scala b/kamon-core/src/main/scala/kamon/TraceContext.scala index 6b32550f..6b32550f 100644 --- a/src/main/scala/kamon/TraceContext.scala +++ b/kamon-core/src/main/scala/kamon/TraceContext.scala diff --git a/src/main/scala/kamon/TraceContextSwap.scala b/kamon-core/src/main/scala/kamon/TraceContextSwap.scala index 68ee808b..68ee808b 100644 --- a/src/main/scala/kamon/TraceContextSwap.scala +++ b/kamon-core/src/main/scala/kamon/TraceContextSwap.scala diff --git a/src/main/scala/kamon/TransactionPublisher.scala b/kamon-core/src/main/scala/kamon/TransactionPublisher.scala index 0626b91d..0626b91d 100644 --- a/src/main/scala/kamon/TransactionPublisher.scala +++ b/kamon-core/src/main/scala/kamon/TransactionPublisher.scala diff --git a/src/main/scala/kamon/executor/eventbus.scala b/kamon-core/src/main/scala/kamon/executor/eventbus.scala index 599f2a7a..599f2a7a 100644 --- a/src/main/scala/kamon/executor/eventbus.scala +++ b/kamon-core/src/main/scala/kamon/executor/eventbus.scala diff --git a/src/main/scala/kamon/instrumentation/ActorRefTellInstrumentation.scala b/kamon-core/src/main/scala/kamon/instrumentation/ActorRefTellInstrumentation.scala index 7398a2bd..82915ce9 100644 --- a/src/main/scala/kamon/instrumentation/ActorRefTellInstrumentation.scala +++ b/kamon-core/src/main/scala/kamon/instrumentation/ActorRefTellInstrumentation.scala @@ -18,7 +18,7 @@ case class TraceableMessage(traceContext: Option[TraceContext], message: Any, ti class ActorRefTellInstrumentation { import ProceedingJoinPointPimp._ - @Pointcut("execution(* akka.actor.LocalActorRef+.$bang(..)) && target(actor) && args(message, sender)") + @Pointcut("execution(* akka.actor.ScalaActorRef+.$bang(..)) && target(actor) && args(message, sender)") def sendingMessageToActorRef(actor: ActorRef, message: Any, sender: ActorRef) = {} @Around("sendingMessageToActorRef(actor, message, sender)") @@ -26,7 +26,7 @@ class ActorRefTellInstrumentation { val actorName = MetricDirectory.nameForActor(actor) val t = Metrics.registry.timer(actorName + "LATENCY") - //println(s"About to proceed with: $actor $message $sender") + //println(s"About to proceed with: $actor $message $sender ${Kamon.context}") pjp.proceedWithTarget(actor, TraceableMessage(Kamon.context, message, t.time()), sender) } } @@ -63,7 +63,7 @@ class ActorCellInvokeInstrumentation { @Around("invokingActorBehaviourAtActorCell(envelope)") def around(pjp: ProceedingJoinPoint, envelope: Envelope): Unit = { import ProceedingJoinPointPimp._ - //println("ENVELOPE --------------------->"+envelope) + println("ENVELOPE --------------------->"+envelope) envelope match { case Envelope(TraceableMessage(ctx, msg, timer), sender) => { timer.stop() @@ -75,7 +75,7 @@ class ActorCellInvokeInstrumentation { ctx match { case Some(c) => { Kamon.set(c) - //println("ENVELOPE ORIGINAL:---------------->"+originalEnvelope) + println(s"ENVELOPE ORIGINAL: [$c]---------------->"+originalEnvelope) pjp.proceedWith(originalEnvelope) Kamon.clear } diff --git a/src/main/scala/kamon/instrumentation/AspectJPimps.scala b/kamon-core/src/main/scala/kamon/instrumentation/AspectJPimps.scala index 84c20c52..84c20c52 100644 --- a/src/main/scala/kamon/instrumentation/AspectJPimps.scala +++ b/kamon-core/src/main/scala/kamon/instrumentation/AspectJPimps.scala diff --git a/src/main/scala/kamon/instrumentation/ExecutorServiceMetrics.scala b/kamon-core/src/main/scala/kamon/instrumentation/ExecutorServiceMetrics.scala index b4f8a475..b4f8a475 100644 --- a/src/main/scala/kamon/instrumentation/ExecutorServiceMetrics.scala +++ b/kamon-core/src/main/scala/kamon/instrumentation/ExecutorServiceMetrics.scala diff --git a/src/main/scala/kamon/instrumentation/MessageQueueMetrics.scala b/kamon-core/src/main/scala/kamon/instrumentation/MessageQueueMetrics.scala index c21502ac..c21502ac 100644 --- a/src/main/scala/kamon/instrumentation/MessageQueueMetrics.scala +++ b/kamon-core/src/main/scala/kamon/instrumentation/MessageQueueMetrics.scala diff --git a/src/main/scala/kamon/instrumentation/RunnableInstrumentation.scala b/kamon-core/src/main/scala/kamon/instrumentation/RunnableInstrumentation.scala index e75a638f..e75a638f 100644 --- a/src/main/scala/kamon/instrumentation/RunnableInstrumentation.scala +++ b/kamon-core/src/main/scala/kamon/instrumentation/RunnableInstrumentation.scala diff --git a/src/main/scala/kamon/instrumentation/SampleInstrumentation.scala b/kamon-core/src/main/scala/kamon/instrumentation/SampleInstrumentation.scala index 74261403..74261403 100644 --- a/src/main/scala/kamon/instrumentation/SampleInstrumentation.scala +++ b/kamon-core/src/main/scala/kamon/instrumentation/SampleInstrumentation.scala diff --git a/src/main/scala/kamon/metric/ExecutorServiceMetricCollector.scala b/kamon-core/src/main/scala/kamon/metric/ExecutorServiceMetricCollector.scala index 54a13f39..54a13f39 100644 --- a/src/main/scala/kamon/metric/ExecutorServiceMetricCollector.scala +++ b/kamon-core/src/main/scala/kamon/metric/ExecutorServiceMetricCollector.scala diff --git a/src/main/scala/kamon/metric/GaugeGenerator.scala b/kamon-core/src/main/scala/kamon/metric/GaugeGenerator.scala index 30635432..30635432 100644 --- a/src/main/scala/kamon/metric/GaugeGenerator.scala +++ b/kamon-core/src/main/scala/kamon/metric/GaugeGenerator.scala diff --git a/src/main/scala/kamon/metric/MetricFilter.scala b/kamon-core/src/main/scala/kamon/metric/MetricFilter.scala index fb117968..fb117968 100644 --- a/src/main/scala/kamon/metric/MetricFilter.scala +++ b/kamon-core/src/main/scala/kamon/metric/MetricFilter.scala diff --git a/src/main/scala/kamon/metric/Metrics.scala b/kamon-core/src/main/scala/kamon/metric/Metrics.scala index 3992ab43..cdc0a334 100644 --- a/src/main/scala/kamon/metric/Metrics.scala +++ b/kamon-core/src/main/scala/kamon/metric/Metrics.scala @@ -15,7 +15,9 @@ object Metrics { //val newrelicReporter = NewRelicReporter(registry) //newrelicReporter.start(5, TimeUnit.SECONDS) - def include(name: String, metric: Metric) = registry.register(name, metric) + def include(name: String, metric: Metric) = { + //registry.register(name, metric) + } def exclude(name: String) = { registry.removeMatching(new MetricFilter { diff --git a/src/main/scala/kamon/metric/MetricsUtils.scala b/kamon-core/src/main/scala/kamon/metric/MetricsUtils.scala index 5b4ceaf4..5b4ceaf4 100644 --- a/src/main/scala/kamon/metric/MetricsUtils.scala +++ b/kamon-core/src/main/scala/kamon/metric/MetricsUtils.scala diff --git a/src/main/scala/kamon/metric/NewRelicReporter.scala b/kamon-core/src/main/scala/kamon/metric/NewRelicReporter.scala index 70f3e54a..70f3e54a 100644 --- a/src/main/scala/kamon/metric/NewRelicReporter.scala +++ b/kamon-core/src/main/scala/kamon/metric/NewRelicReporter.scala diff --git a/src/main/scala/spraytest/ClientTest.scala b/kamon-core/src/main/scala/spraytest/ClientTest.scala index 07532d0a..07532d0a 100644 --- a/src/main/scala/spraytest/ClientTest.scala +++ b/kamon-core/src/main/scala/spraytest/ClientTest.scala diff --git a/src/main/scala/spraytest/FutureTesting.scala b/kamon-core/src/main/scala/spraytest/FutureTesting.scala index b864d6d6..b864d6d6 100644 --- a/src/main/scala/spraytest/FutureTesting.scala +++ b/kamon-core/src/main/scala/spraytest/FutureTesting.scala diff --git a/src/main/scala/test/PingPong.scala b/kamon-core/src/main/scala/test/PingPong.scala index f9d6869c..f9d6869c 100644 --- a/src/main/scala/test/PingPong.scala +++ b/kamon-core/src/main/scala/test/PingPong.scala diff --git a/kamon-core/src/test/scala/akka/instrumentation/ActorInstrumentationSpec.scala b/kamon-core/src/test/scala/akka/instrumentation/ActorInstrumentationSpec.scala new file mode 100644 index 00000000..0026d953 --- /dev/null +++ b/kamon-core/src/test/scala/akka/instrumentation/ActorInstrumentationSpec.scala @@ -0,0 +1,45 @@ +package akka.instrumentation + +import org.scalatest.{WordSpecLike, Matchers} +import akka.actor.{Actor, Props, ActorSystem} + +import akka.testkit.{ImplicitSender, TestKit} +import kamon.{TraceContext, Kamon} + + +class ActorInstrumentationSpec extends TestKit(ActorSystem("ActorInstrumentationSpec")) with WordSpecLike with Matchers with ImplicitSender { + + "an instrumented actor ref" when { + "used inside the context of a transaction" should { + "propagate the trace context using bang" in new TraceContextEchoFixture { + echo ! "test" + + expectMsg(Some(testTraceContext)) + } + + "propagate the trace context using tell" in { + + } + + "propagate the trace context using ask" in { + + } + } + } + + trait TraceContextEchoFixture { + val testTraceContext = Kamon.newTraceContext() + val echo = system.actorOf(Props[TraceContextEcho]) + + Kamon.set(testTraceContext) + } + +} + +class TraceContextEcho extends Actor { + def receive = { + case msg ⇒ sender ! Kamon.context() + } +} + + diff --git a/src/test/scala/kamon/instrumentation/ActorSystemInstrumentationSpec.scala b/kamon-core/src/test/scala/kamon/instrumentation/ActorSystemInstrumentationSpec.scala index 1eab6355..1eab6355 100644 --- a/src/test/scala/kamon/instrumentation/ActorSystemInstrumentationSpec.scala +++ b/kamon-core/src/test/scala/kamon/instrumentation/ActorSystemInstrumentationSpec.scala diff --git a/src/test/scala/kamon/instrumentation/DispatcherInstrumentationSpec.scala b/kamon-core/src/test/scala/kamon/instrumentation/DispatcherInstrumentationSpec.scala index 89ef61f3..89ef61f3 100644 --- a/src/test/scala/kamon/instrumentation/DispatcherInstrumentationSpec.scala +++ b/kamon-core/src/test/scala/kamon/instrumentation/DispatcherInstrumentationSpec.scala diff --git a/src/test/scala/kamon/instrumentation/MessageQueueInstrumentationSpec.scala b/kamon-core/src/test/scala/kamon/instrumentation/MessageQueueInstrumentationSpec.scala index cc55ec92..cc55ec92 100644 --- a/src/test/scala/kamon/instrumentation/MessageQueueInstrumentationSpec.scala +++ b/kamon-core/src/test/scala/kamon/instrumentation/MessageQueueInstrumentationSpec.scala diff --git a/src/test/scala/kamon/instrumentation/RunnableInstrumentationSpec.scala b/kamon-core/src/test/scala/kamon/instrumentation/RunnableInstrumentationSpec.scala index de65aaca..de65aaca 100644 --- a/src/test/scala/kamon/instrumentation/RunnableInstrumentationSpec.scala +++ b/kamon-core/src/test/scala/kamon/instrumentation/RunnableInstrumentationSpec.scala diff --git a/kamon-uow/src/main/scala/kamon/logging/UowActorLogging.scala b/kamon-uow/src/main/scala/kamon/logging/UowActorLogging.scala new file mode 100644 index 00000000..e117db1b --- /dev/null +++ b/kamon-uow/src/main/scala/kamon/logging/UowActorLogging.scala @@ -0,0 +1,14 @@ +package kamon.logging + +import akka.actor.Actor +import kamon.Kamon + +trait UowActorLogging { + self: Actor => + + def logWithUOW(text: String) = { + val uow = Kamon.context.map(_.userContext).getOrElse("NA") + println(s"=======>[$uow] - $text") + } + +} diff --git a/kamon-uow/src/main/scala/kamon/logging/UowDirectives.scala b/kamon-uow/src/main/scala/kamon/logging/UowDirectives.scala new file mode 100644 index 00000000..e79602ea --- /dev/null +++ b/kamon-uow/src/main/scala/kamon/logging/UowDirectives.scala @@ -0,0 +1,28 @@ +package kamon.logging + +import java.util.concurrent.atomic.AtomicLong +import spray.routing.Directive0 +import spray.routing.directives.BasicDirectives +import java.net.InetAddress +import scala.util.Try +import kamon.Kamon + +trait UowDirectives extends BasicDirectives { + def uow: Directive0 = mapRequest { request => + val generatedUow = Some(UowDirectives.newUow) + println("Generated UOW: "+generatedUow) + Kamon.set(Kamon.newTraceContext().copy(userContext = generatedUow)) + + + request + } +} + +object UowDirectives { + val uowCounter = new AtomicLong + + val hostnamePrefix = Try(InetAddress.getLocalHost.getHostName.toString).getOrElse("unknown-localhost") + + def newUow = "%s-%s".format(hostnamePrefix, uowCounter.incrementAndGet()) + +} diff --git a/project/Build.scala b/project/Build.scala index 7d89713c..c2822185 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -7,7 +7,11 @@ object Build extends Build { import Settings._ import Dependencies._ - lazy val root = Project("kamon", file(".")) + lazy val root = Project("root", file(".")) + .aggregate(kamonCore, kamonUow) + .settings(basicSettings: _*) + + lazy val kamonCore = Project("kamon-core", file("kamon-core")) .settings(basicSettings: _*) .settings(revolverSettings: _*) .settings(aspectJSettings: _*) @@ -18,5 +22,9 @@ object Build extends Build { compile(akkaActor, akkaAgent, sprayCan, sprayClient, sprayRouting, sprayServlet, aspectJ, metrics, sprayJson) ++ test(scalatest, akkaTestKit, sprayTestkit)) - + lazy val kamonUow = Project("kamon-uow", file("kamon-uow")) + .settings(basicSettings: _*) + .settings(libraryDependencies ++= + compile(akkaActor, akkaSlf4j, sprayRouting)) + .dependsOn(kamonCore) }
\ No newline at end of file diff --git a/project/Settings.scala b/project/Settings.scala index 640a8013..5fadc25d 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -7,8 +7,8 @@ object Settings { lazy val basicSettings = seq( version := VERSION, - organization := "com.despegar", - scalaVersion := "2.10.0", + organization := "kamon", + scalaVersion := "2.10.2", resolvers ++= Dependencies.resolutionRepos, fork in run := true, scalacOptions := Seq( diff --git a/src/test/scala/akka/instrumentation/ActorInstrumentationSpec.scala b/src/test/scala/akka/instrumentation/ActorInstrumentationSpec.scala deleted file mode 100644 index 1a0e509f..00000000 --- a/src/test/scala/akka/instrumentation/ActorInstrumentationSpec.scala +++ /dev/null @@ -1,43 +0,0 @@ -package akka.instrumentation - -import org.scalatest.{Matchers, WordSpec} -import akka.actor.{Actor, Props, ActorSystem} -import kamon.metric.Metrics._ -import scala.collection.JavaConverters._ -import akka.testkit.TestActorRef - - -class ActorInstrumentationSpec extends WordSpec with Matchers { - implicit val system = ActorSystem() - import system._ - - val echoRef = actorOf(Props(new EchoActor), "Echo-Actor") - val meterForEchoActor = "meter-for-akka://default/user/Echo-Actor" - val totalMessages = 1000 - - "an instrumented Actor" should { - "send a message and record a metric on the Metrics Registry with the number of sent messages" in { - - val echoActor = TestActorRef[EchoActor] - - - - (1 to totalMessages).foreach {x:Int => - echoActor ! s"Message ${x}" - } - - //val messages = registry.getMeters.asScala.get(meterForEchoActor).get.getCount - - //messages should equal(totalMessages) - } - } - -} - -class EchoActor extends Actor { - def receive = { - case msg ⇒ sender ! msg - } -} - - |