From 303f9927a3a89a191619580d7d62dce012f41a20 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Fri, 22 Mar 2024 12:19:47 +0700 Subject: [PATCH 01/17] Added tracing context in Scala SDK and using sttp in the Scala sample --- samples/scala-protobuf-tracing/.gitignore | 33 ++++++ samples/scala-protobuf-tracing/README.md | 104 ++++++++++++++++++ samples/scala-protobuf-tracing/build.sbt | 37 +++++++ .../scala-protobuf-tracing/docker-compose.yml | 33 ++++++ .../project/build.properties | 1 + .../com/example/controller_action.proto | 24 ++++ .../protobuf/com/example/kalix_policy.proto | 14 +++ .../src/main/resources/application.conf | 1 + .../src/main/resources/logback-dev-mode.xml | 19 ++++ .../src/main/resources/logback.xml | 37 +++++++ .../scala/com/example/ControllerAction.scala | 75 +++++++++++++ .../src/main/scala/com/example/Main.scala | 31 ++++++ .../src/test/resources/logback-test.xml | 18 +++ .../com/example/ControllerActionSpec.scala | 43 ++++++++ .../testkit/impl/TestKitActionContext.scala | 3 +- .../main/scala/kalix/scalasdk/Metadata.scala | 9 +- .../scala/kalix/scalasdk/TraceContext.scala | 51 +++++++++ .../kalix/scalasdk/action/ActionContext.scala | 23 +--- .../action/ActionCreationContext.scala | 9 ++ .../kalix/scalasdk/impl/MetadataImpl.scala | 32 +++++- 20 files changed, 571 insertions(+), 26 deletions(-) create mode 100644 samples/scala-protobuf-tracing/.gitignore create mode 100644 samples/scala-protobuf-tracing/README.md create mode 100644 samples/scala-protobuf-tracing/build.sbt create mode 100644 samples/scala-protobuf-tracing/docker-compose.yml create mode 100644 samples/scala-protobuf-tracing/project/build.properties create mode 100644 samples/scala-protobuf-tracing/src/main/protobuf/com/example/controller_action.proto create mode 100644 samples/scala-protobuf-tracing/src/main/protobuf/com/example/kalix_policy.proto create mode 100644 samples/scala-protobuf-tracing/src/main/resources/application.conf create mode 100644 samples/scala-protobuf-tracing/src/main/resources/logback-dev-mode.xml create mode 100644 samples/scala-protobuf-tracing/src/main/resources/logback.xml create mode 100644 samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala create mode 100644 samples/scala-protobuf-tracing/src/main/scala/com/example/Main.scala create mode 100644 samples/scala-protobuf-tracing/src/test/resources/logback-test.xml create mode 100644 samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala create mode 100644 sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/TraceContext.scala diff --git a/samples/scala-protobuf-tracing/.gitignore b/samples/scala-protobuf-tracing/.gitignore new file mode 100644 index 0000000000..ba2b76452a --- /dev/null +++ b/samples/scala-protobuf-tracing/.gitignore @@ -0,0 +1,33 @@ +boot/ +lib_managed/ +src_managed/ +.bsp + +# intellij +/src/intellij*/*.iml +/src/intellij*/*.ipr +/src/intellij*/*.iws +**/.cache +/.idea +/.settings + +# vscode +/.vscode + +# sbt's target directories +/target/ +/project/target +/project/plugins/project/ +/project/project +/project/**/target/ +/test/macro-annot/target/ +/test/files/target/ +/test/target/ +/build-sbt/ +local.sbt +jitwatch.out + +# metals +.metals +.bloop +project/**/metals.sbt \ No newline at end of file diff --git a/samples/scala-protobuf-tracing/README.md b/samples/scala-protobuf-tracing/README.md new file mode 100644 index 0000000000..7febc376af --- /dev/null +++ b/samples/scala-protobuf-tracing/README.md @@ -0,0 +1,104 @@ +# scala-protobuf-tracing + +## Designing + +While designing your service it is useful to read [designing services](https://docs.kalix.io/services/development-process.html). + +## Developing + +This project has a bare-bones skeleton service ready to go, but in order to adapt and +extend it it may be useful to read up on [developing services](https://docs.kalix.io/developing/index.html) +and in particular the [JVM section](https://docs.kalix.io/java-services/index.html). + +## Building + +You can use [sbt](https://www.scala-sbt.org/) to build your project, +which will also take care of generating code based on the `.proto` definitions: + +``` +sbt compile +``` + +## Running Locally + +In order to run your application locally, you must run the Kalix proxy. The included `docker-compose.yml` file contains the configuration required to run the proxy for a locally running application. +It also contains the configuration to start a local Google Pub/Sub emulator that the Kalix proxy will connect to. +To start the proxy, run the following command from this directory: + +``` +docker-compose up +``` + +> On Linux this requires Docker 20.10 or later (https://github.com/moby/moby/pull/40007), +> or for a `USER_FUNCTION_HOST` environment variable to be set manually. + +To start the application locally, start it from your IDE or use: + +``` +sbt run +``` + +With both the proxy and your application running, any defined endpoints should be available at `http://localhost:9000`. In addition to the defined gRPC interface, each method has a corresponding HTTP endpoint. Unless configured otherwise (see [Transcoding HTTP](https://docs.kalix.io/java-protobuf/writing-grpc-descriptors-protobuf.html#_transcoding_http)), this endpoint accepts POST requests at the path `/[package].[entity name]/[method]`. For example, using `curl`: + +```shell +> curl -XPOST -H "Content-Type: application/json" localhost:9000/com.example.CounterService/GetCurrentCounter -d '{"counterId": "foo"}' +The command handler for `GetCurrentCounter` is not implemented, yet +``` + +For example, using [`grpcurl`](https://github.com/fullstorydev/grpcurl): + +```shell +> grpcurl -plaintext -d '{"counterId": "foo"}' localhost:9000 com.example.CounterService/GetCurrentCounter +ERROR: + Code: Unknown + Message: The command handler for `GetCurrentCounter` is not implemented, yet +``` + +> Note: The failure is to be expected if you have not yet provided an implementation of `GetCurrentCounter` in +> your entity. + +## Deploying + +To deploy your service, install the `kalix` CLI as documented in +[Setting up a local development environment](https://docs.kalix.io/setting-up/) +and configure a Docker Registry to upload your Docker image to. + +You will need to set your `docker.username` as a system property: + +``` +sbt -Ddocker.username=mary docker:publish +``` + +Refer to [Configuring registries](https://docs.kalix.io/projects/container-registries.html) +for more information on how to make your Docker image available to Kalix. + +You can now deploy your service through the [kalix](https://docs.kalix.io/kalix/using-cli.html) CLI: + +``` +$ kalix auth login +``` + +If this is your first time using Kalix, this will let you +register an account, create your first project and set it as the default. + +Now: + +``` +$ kalix services deploy \ + my-service \ + my-container-uri/container-name:tag-name +``` + +Once the service has been successfully started (this may take a while), +you can create an ad-hoc proxy to call it from your local machine: + +``` +$ kalix services proxy my-service +Listening on 127.0.0.1:8080 +``` + +Or expose it to the Internet: + +``` +kalix service expose my-service +``` diff --git a/samples/scala-protobuf-tracing/build.sbt b/samples/scala-protobuf-tracing/build.sbt new file mode 100644 index 0000000000..a8dffde252 --- /dev/null +++ b/samples/scala-protobuf-tracing/build.sbt @@ -0,0 +1,37 @@ +organization := "com.example" + +scalaVersion := "2.13.10" + + +enablePlugins(KalixPlugin, JavaAppPackaging, DockerPlugin) +dockerBaseImage := "docker.io/library/adoptopenjdk:11-jre-hotspot" +dockerUsername := sys.props.get("docker.username") +dockerRepository := sys.props.get("docker.registry") +dockerUpdateLatest := true +dockerBuildCommand := { + val arch = sys.props("os.arch") + if (arch != "amd64" && !arch.contains("x86")) { + // use buildx with platform to build supported amd64 images on other CPU architectures + // this may require that you have first run 'docker buildx create' to set docker buildx up + dockerExecCommand.value ++ Seq("buildx", "build", "--platform=linux/amd64", "--load") ++ dockerBuildOptions.value :+ "." + } else dockerBuildCommand.value +} +ThisBuild / dynverSeparator := "-" +run / fork := true +run / envVars += ("HOST", "0.0.0.0") +run / javaOptions ++= Seq("-Dlogback.configurationFile=logback-dev-mode.xml") + +Compile / scalacOptions ++= Seq( + "-release:11", + "-deprecation", + "-feature", + "-unchecked", + "-Xlog-reflective-calls", + "-Xlint") +Compile / javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation", "-parameters" // for Jackson +) + +libraryDependencies ++= Seq( + "com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1" % Compile, + "org.scalatest" %% "scalatest" % "3.2.7" % Test +) diff --git a/samples/scala-protobuf-tracing/docker-compose.yml b/samples/scala-protobuf-tracing/docker-compose.yml new file mode 100644 index 0000000000..322791cef2 --- /dev/null +++ b/samples/scala-protobuf-tracing/docker-compose.yml @@ -0,0 +1,33 @@ +# If you're looking to use eventing with Google PubSub, to get an emulator running: +# - add property "-Dkalix.proxy.eventing.support=google-pubsub-emulator" to the JAVA_TOOL_OPTIONS environment map under the kalix-runtime service +# - uncomment the env var PUBSUB_EMULATOR_HOST and the section below for gcloud-pubsub-emulator service +version: "3" +services: + kalix-runtime: + image: gcr.io/kalix-public/kalix-runtime:1.1.33 + ports: + - "9000:9000" + extra_hosts: + - "host.docker.internal:host-gateway" + environment: + JAVA_TOOL_OPTIONS: > + # jvm -D properties can be added under this environment map (note: remove this comment when adding properties) + + USER_FUNCTION_HOST: ${USER_FUNCTION_HOST:-host.docker.internal} + USER_FUNCTION_PORT: ${USER_FUNCTION_PORT:-8080} + # Comment to enable ACL check in dev-mode (see https://docs.kalix.io/services/using-acls.html#_local_development_with_acls) + ACL_ENABLED: "false" + # Uncomment to enable advanced view features locally (note: disabled in deployed services by default) + #VIEW_FEATURES_ALL: "true" + # Uncomment to disable the JWT dev secret + #JWT_DEV_SECRET: "false" + # Uncomment to set the JWT dev secret issuer + #JWT_DEV_SECRET_ISSUER: "my-issuer" + # Uncomment if using pubsub emulator + #PUBSUB_EMULATOR_HOST: gcloud-pubsub-emulator + #gcloud-pubsub-emulator: + # image: gcr.io/google.com/cloudsdktool/cloud-sdk:341.0.0 + # command: gcloud beta emulators pubsub start --project=test --host-port=0.0.0.0:8085 + # ports: + # - 8085:8085 + # diff --git a/samples/scala-protobuf-tracing/project/build.properties b/samples/scala-protobuf-tracing/project/build.properties new file mode 100644 index 0000000000..04267b14af --- /dev/null +++ b/samples/scala-protobuf-tracing/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.9.9 diff --git a/samples/scala-protobuf-tracing/src/main/protobuf/com/example/controller_action.proto b/samples/scala-protobuf-tracing/src/main/protobuf/com/example/controller_action.proto new file mode 100644 index 0000000000..4c535eceda --- /dev/null +++ b/samples/scala-protobuf-tracing/src/main/protobuf/com/example/controller_action.proto @@ -0,0 +1,24 @@ +// This is the public API offered by your entity. +syntax = "proto3"; + +import "kalix/annotations.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/empty.proto"; + +package com.example; + +option java_outer_classname = "ControllerActionApi"; + +message MessageResponse { + string message = 1; +} + +service Controller { + option (kalix.codegen) = { + action: {} + }; + + rpc CallSyncEndpoint(google.protobuf.Empty) returns (MessageResponse){} + + rpc CallAsyncEndpoint(google.protobuf.Empty) returns (MessageResponse){} +} \ No newline at end of file diff --git a/samples/scala-protobuf-tracing/src/main/protobuf/com/example/kalix_policy.proto b/samples/scala-protobuf-tracing/src/main/protobuf/com/example/kalix_policy.proto new file mode 100644 index 0000000000..7d3ae35065 --- /dev/null +++ b/samples/scala-protobuf-tracing/src/main/protobuf/com/example/kalix_policy.proto @@ -0,0 +1,14 @@ +// This is the default Access Control List (ACL) for all components of this Kalix Service +syntax = "proto3"; + +package com.example; + +import "kalix/annotations.proto"; + +// Allow all other Kalix services deployed in the same project to access the components of this +// Kalix service, but disallow access from the internet. This can be overridden explicitly +// per component or method using annotations. +// Documentation at https://docs.kalix.io/java-protobuf/access-control.html +option (kalix.file).acl = { + allow: { service: "*" } +}; \ No newline at end of file diff --git a/samples/scala-protobuf-tracing/src/main/resources/application.conf b/samples/scala-protobuf-tracing/src/main/resources/application.conf new file mode 100644 index 0000000000..927bcdbf20 --- /dev/null +++ b/samples/scala-protobuf-tracing/src/main/resources/application.conf @@ -0,0 +1 @@ +kalix.telemetry.tracing.collector-endpoint="http://localhost:4317" \ No newline at end of file diff --git a/samples/scala-protobuf-tracing/src/main/resources/logback-dev-mode.xml b/samples/scala-protobuf-tracing/src/main/resources/logback-dev-mode.xml new file mode 100644 index 0000000000..0ce19a10f4 --- /dev/null +++ b/samples/scala-protobuf-tracing/src/main/resources/logback-dev-mode.xml @@ -0,0 +1,19 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/samples/scala-protobuf-tracing/src/main/resources/logback.xml b/samples/scala-protobuf-tracing/src/main/resources/logback.xml new file mode 100644 index 0000000000..114aab6bac --- /dev/null +++ b/samples/scala-protobuf-tracing/src/main/resources/logback.xml @@ -0,0 +1,37 @@ + + + + %date{ISO8601} %-5level %logger - %msg%n + + + + + + + yyyy-MM-dd'T'HH:mm:ss.SSSX + Etc/UTC + true + + + false + + + + + + + 8192 + true + + + + + + + + + + + + + diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala new file mode 100644 index 0000000000..3624e7f167 --- /dev/null +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala @@ -0,0 +1,75 @@ +package com.example + +import akka.http.scaladsl.Http +import akka.http.scaladsl.model.{HttpRequest, HttpResponse} +import com.google.protobuf.empty.Empty +import io.opentelemetry.api.trace.Span +import io.opentelemetry.context.Scope +import kalix.scalasdk.action.Action +import kalix.scalasdk.action.ActionCreationContext +import sttp.client4.quick._ +import sttp.client4.Response + +import java.io.IOException +import scala.concurrent.{ExecutionContext, Future} +import scala.util.Using.Releasable +import scala.util.{Failure, Success, Try, Using} + +// This class was initially generated based on the .proto definition by Kalix tooling. +// +// As long as this file exists it will not be overwritten: you can maintain it yourself, +// or delete it so it is regenerated as needed. + + + +class ControllerAction(creationContext: ActionCreationContext) extends AbstractControllerAction { + + val url = "https://jsonplaceholder.typicode.com/posts/1" + + override def callSyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { + val tracerOpt = actionContext.getOpenTelemetryTracer + var responseBody = "" + tracerOpt match { + case Some(tracer) => + var span:Span = null; + val trySpan: Try[Span] = Using.Manager { use => + use(span = tracer + .spanBuilder("b") + .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext) + .startSpan())(ReleaseSpan) + use(span.makeCurrent()) + val response = quickRequest.get(uri"$url").send() + responseBody = response.body + span.setAttribute("result", response.body) + } + trySpan match { + case Success(span) => {} + case Failure(exception) => responseBody = exception.getMessage + } + + case None => { + val response = quickRequest.get(uri"$url").send() + responseBody = response.body + } + + } + effects.reply(MessageResponse(responseBody)) + + //effects.asyncEffect(responseFuture) + } + override def callAsyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { + val response: Response[String] = quickRequest.get(uri"$url").send() + response.body + } + + def callEndpoint(url: String): String = { + val response: Response[String] = quickRequest.get(uri"$url").send() + response.body + } + + object ReleaseSpan extends Releasable[Span] { + /** Releases the specified resource. */ + override def release(resource: Span): Unit = resource.end() + } +} + diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/Main.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/Main.scala new file mode 100644 index 0000000000..27275dc3ac --- /dev/null +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/Main.scala @@ -0,0 +1,31 @@ +package com.example + +import akka.actor.ActorSystem +import kalix.scalasdk.Kalix +import org.slf4j.LoggerFactory + +// This class was initially generated based on the .proto definition by Kalix tooling. +// +// As long as this file exists it will not be overwritten: you can maintain it yourself, +// or delete it so it is regenerated as needed. + +object Main { + + private val log = LoggerFactory.getLogger("com.example.Main") + + implicit val actorSystem = ActorSystem("external-calls") + + def createKalix(): Kalix = { + // The KalixFactory automatically registers any generated Actions, Views or Entities, + // and is kept up-to-date with any changes in your protobuf definitions. + // If you prefer, you may remove this and manually register these components in a + // `Kalix()` instance. + KalixFactory.withComponents( + new ControllerAction(_)) + } + + def main(args: Array[String]): Unit = { + log.info("starting the Kalix service") + createKalix().start() + } +} diff --git a/samples/scala-protobuf-tracing/src/test/resources/logback-test.xml b/samples/scala-protobuf-tracing/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..0e3fe5ae60 --- /dev/null +++ b/samples/scala-protobuf-tracing/src/test/resources/logback-test.xml @@ -0,0 +1,18 @@ + + + + %date{ISO8601} %-5level %logger - %msg%n + + + + + + + + + + + + + + diff --git a/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala b/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala new file mode 100644 index 0000000000..f0fe4fd7d0 --- /dev/null +++ b/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala @@ -0,0 +1,43 @@ +package com.example + +import com.google.protobuf.empty.Empty +import kalix.scalasdk.action.Action +import kalix.scalasdk.testkit.ActionResult +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +// This class was initially generated based on the .proto definition by Kalix tooling. +// +// As long as this file exists it will not be overwritten: you can maintain it yourself, +// or delete it so it is regenerated as needed. + +class ControllerActionSpec + extends AnyWordSpec + with Matchers { + + "ControllerAction" must { + + "have example test that can be removed" in { + val service = ControllerActionTestKit(new ControllerAction(_)) + pending + // use the testkit to execute a command + // and verify final updated state: + // val result = service.someOperation(SomeRequest) + // verify the reply + // result.reply shouldBe expectedReply + } + + "handle command CallSyncEndpoint" in { + val service = ControllerActionTestKit(new ControllerAction(_)) + pending + // val result = service.callSyncEndpoint(Empty(...)) + } + + "handle command CallAsyncEndpoint" in { + val service = ControllerActionTestKit(new ControllerAction(_)) + pending + // val result = service.callAsyncEndpoint(Empty(...)) + } + + } +} diff --git a/sdk/scala-sdk-protobuf-testkit/src/main/scala/kalix/scalasdk/testkit/impl/TestKitActionContext.scala b/sdk/scala-sdk-protobuf-testkit/src/main/scala/kalix/scalasdk/testkit/impl/TestKitActionContext.scala index d7d8784631..f1290cb764 100644 --- a/sdk/scala-sdk-protobuf-testkit/src/main/scala/kalix/scalasdk/testkit/impl/TestKitActionContext.scala +++ b/sdk/scala-sdk-protobuf-testkit/src/main/scala/kalix/scalasdk/testkit/impl/TestKitActionContext.scala @@ -16,6 +16,7 @@ package kalix.scalasdk.testkit.impl +import io.opentelemetry.api.trace.Tracer import kalix.scalasdk.Metadata import kalix.scalasdk.action.ActionContext import kalix.scalasdk.action.ActionCreationContext @@ -33,5 +34,5 @@ final class TestKitActionContext( override def eventSubject = metadata.get("ce-subject") override def getGrpcClient[T](clientClass: Class[T], service: String): T = getComponentGrpcClient(clientClass) - + override def getOpenTelemetryTracer: Option[Tracer] = None } diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/Metadata.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/Metadata.scala index 40715d7744..cb96ea2788 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/Metadata.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/Metadata.scala @@ -16,8 +16,8 @@ package kalix.scalasdk +import kalix.javasdk.impl.{MetadataImpl => JMetadataImpl} import kalix.scalasdk.impl.MetadataImpl -import kalix.javasdk.impl.{ MetadataImpl => JMetadataImpl } import java.net.URI import java.nio.ByteBuffer @@ -286,6 +286,13 @@ trait Metadata extends Iterable[MetadataEntry] { * a copy of this metadata with the HTTP response code set. */ def withStatusCode(httpStatusCode: StatusCode.Redirect): Metadata + + /** + * Get the trace context associated with this request metadata. + * @return + * The trace context. + */ + def traceContext: TraceContext } object Metadata { diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/TraceContext.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/TraceContext.scala new file mode 100644 index 0000000000..8fb432cf8b --- /dev/null +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/TraceContext.scala @@ -0,0 +1,51 @@ +/* + * Copyright 2024 Lightbend Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kalix.scalasdk + +import io.opentelemetry.context.{ Context => OtelContext } + +trait TraceContext { + + /** + * Allows retrieving the trace context as an OpenTelemetry context for easier construction of child spans. If the + * trace context is not available, a new empty context will be returned. + * + * @return + * the trace context as an OpenTelemetry context. + */ + def asOpenTelemetryContext: OtelContext + + /** + * Allows retrieving the trace parent for easier injection in external calls (e.g. HTTP request headers). + * + * @return + * the trace parent using W3C Trace Context format. + * @see + * W3C Trace Context section 3 + */ + def traceParent: Option[String] + + /** + * Allows retrieving the trace state for easier injection in external calls (e.g. HTTP request headers). + * + * @return + * the trace state using W3C Trace Context format. + * @see + * W3C Trace Context section 3 + */ + def traceState: Option[String] +} diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionContext.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionContext.scala index f76a1b551d..8cbd8378e0 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionContext.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionContext.scala @@ -16,10 +16,9 @@ package kalix.scalasdk.action -import kalix.scalasdk.Metadata -import kalix.scalasdk.MetadataContext +import kalix.scalasdk.{Metadata, MetadataContext} -trait ActionContext extends MetadataContext { +trait ActionContext extends MetadataContext with ActionCreationContext { /** * Get the metadata associated with this call. @@ -29,7 +28,7 @@ trait ActionContext extends MetadataContext { * was a gRPC call, it will contain the HTTP headers for that gRPC call. * * @return - * The call level metadata. + * The call level metadata. */ def metadata: Metadata @@ -37,18 +36,4 @@ trait ActionContext extends MetadataContext { * The origin subject of the {{{CloudEvent}}}. For example, the entity id when the event was emitted from an entity. */ def eventSubject: Option[String] - - /** - * Get an Akka gRPC client for the given service name. The same client instance is shared across components in the - * application. The lifecycle of the client is managed by the SDK and it should not be stopped by user code. - * - * @tparam T - * The "service" interface generated for the service by Akka gRPC - * @param clientClass - * The class of a gRPC service generated by Akka gRPC - * @param service - * The name of the service to connect to, either a name of another Kalix service or an external service where - * connection details are configured under `akka.grpc.client.[service-name]` in `application.conf`. - */ - def getGrpcClient[T](clientClass: Class[T], service: String): T -} +} \ No newline at end of file diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala index b967abe644..b20f83f7da 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala @@ -16,6 +16,7 @@ package kalix.scalasdk.action +import io.opentelemetry.api.trace.Tracer import kalix.scalasdk.Context trait ActionCreationContext extends Context { @@ -33,4 +34,12 @@ trait ActionCreationContext extends Context { * connection details are configured under `akka.grpc.client.[service-name]` in `application.conf`. */ def getGrpcClient[T](clientClass: Class[T], service: String): T + + /** + * Get an OpenTelemetry tracer for the current action. This will allow for building and automatic + * exporting of spans. + * + * @return A tracer for the current action, if tracing is configured. + */ + def getOpenTelemetryTracer: Option[Tracer] } diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/MetadataImpl.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/MetadataImpl.scala index 1e0a5413c0..2f041dec6d 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/MetadataImpl.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/MetadataImpl.scala @@ -15,14 +15,17 @@ */ package kalix.scalasdk.impl -import java.nio.ByteBuffer -import scala.collection.immutable.Seq -import kalix.scalasdk.{ CloudEvent, JwtClaims, Metadata, MetadataEntry, Principal, Principals } +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator +import io.opentelemetry.context.propagation.TextMapGetter +import io.opentelemetry.context.{ Context => OtelContext } +import kalix.scalasdk.TraceContext +import kalix.javasdk.impl.telemetry.TraceInstrumentation import kalix.protocol.component.{ MetadataEntry => ProtocolMetadataEntry } -import kalix.scalasdk.StatusCode +import kalix.scalasdk._ -import scala.jdk.OptionConverters._ +import java.nio.ByteBuffer import scala.jdk.CollectionConverters._ +import scala.jdk.OptionConverters._ private[kalix] object MetadataImpl { def apply(impl: kalix.javasdk.impl.MetadataImpl): MetadataImpl = new MetadataImpl(impl) @@ -100,4 +103,23 @@ private[kalix] class MetadataImpl(val impl: kalix.javasdk.impl.MetadataImpl) ext override def withStatusCode(code: StatusCode.Redirect): Metadata = set("_kalix-http-code", code.value.toString) + + override lazy val traceContext: TraceContext = new TraceContext { + override def asOpenTelemetryContext = W3CTraceContextPropagator + .getInstance() + .extract(OtelContext.current(), asMetadata, otelGetter) + + override def traceParent: Option[String] = get(TraceInstrumentation.TRACE_PARENT_KEY) + + override def traceState: Option[String] = get(TraceInstrumentation.TRACE_STATE_KEY) + } + + lazy val otelGetter = new TextMapGetter[Metadata]() { + override def get(carrier: Metadata, key: String): String = { + carrier.get(key).getOrElse("") + } + + override def keys(carrier: Metadata): java.lang.Iterable[String] = + carrier.getAllKeys.asJava + } } From 2abc5a7c2bb263f649bbfe921956b0f8da1d27d7 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Fri, 22 Mar 2024 12:31:22 +0700 Subject: [PATCH 02/17] simpler sync example --- .../scala/com/example/ControllerAction.scala | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala index 3624e7f167..1ef834557b 100644 --- a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala @@ -3,7 +3,7 @@ package com.example import akka.http.scaladsl.Http import akka.http.scaladsl.model.{HttpRequest, HttpResponse} import com.google.protobuf.empty.Empty -import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.{Span, StatusCode} import io.opentelemetry.context.Scope import kalix.scalasdk.action.Action import kalix.scalasdk.action.ActionCreationContext @@ -31,45 +31,38 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC var responseBody = "" tracerOpt match { case Some(tracer) => - var span:Span = null; - val trySpan: Try[Span] = Using.Manager { use => - use(span = tracer - .spanBuilder("b") - .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext) - .startSpan())(ReleaseSpan) - use(span.makeCurrent()) + val span = tracer + .spanBuilder("b") + .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext) + .startSpan() + val scope: Scope = span.makeCurrent() + try { val response = quickRequest.get(uri"$url").send() + if(response.code.isSuccess) { + span.setAttribute("result", response.body) + } else { + span.setStatus(StatusCode.ERROR,response.statusText) + } responseBody = response.body - span.setAttribute("result", response.body) + } finally { + span.end() + scope.close() } - trySpan match { - case Success(span) => {} - case Failure(exception) => responseBody = exception.getMessage - } - - case None => { + case None => val response = quickRequest.get(uri"$url").send() responseBody = response.body - } - } effects.reply(MessageResponse(responseBody)) - - //effects.asyncEffect(responseFuture) } + override def callAsyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { val response: Response[String] = quickRequest.get(uri"$url").send() response.body } - def callEndpoint(url: String): String = { + def callEndpoint(url: String): String = { val response: Response[String] = quickRequest.get(uri"$url").send() response.body } - - object ReleaseSpan extends Releasable[Span] { - /** Releases the specified resource. */ - override def release(resource: Span): Unit = resource.end() - } } From f4231b69920981a85fa711d301364eb39c5af437 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Fri, 22 Mar 2024 13:34:49 +0700 Subject: [PATCH 03/17] added async calling endpoint --- .../scala/com/example/ControllerAction.scala | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala index 1ef834557b..b717206528 100644 --- a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala @@ -1,19 +1,13 @@ package com.example -import akka.http.scaladsl.Http -import akka.http.scaladsl.model.{HttpRequest, HttpResponse} import com.google.protobuf.empty.Empty -import io.opentelemetry.api.trace.{Span, StatusCode} +import io.opentelemetry.api.trace.StatusCode import io.opentelemetry.context.Scope -import kalix.scalasdk.action.Action -import kalix.scalasdk.action.ActionCreationContext +import kalix.scalasdk.action.{Action, ActionCreationContext} import sttp.client4.quick._ -import sttp.client4.Response +import sttp.client4.{Response, _} -import java.io.IOException -import scala.concurrent.{ExecutionContext, Future} -import scala.util.Using.Releasable -import scala.util.{Failure, Success, Try, Using} +import scala.concurrent.Future // This class was initially generated based on the .proto definition by Kalix tooling. // @@ -55,9 +49,28 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC effects.reply(MessageResponse(responseBody)) } + + import sttp.client4.akkahttp._ + import sttp.client4.json4s._ + + import scala.concurrent.ExecutionContext.global + + case class HttpBinResponse(origin:String, headers: Map[String, String]) + implicit val serialization = org.json4s.native.Serialization + implicit val formats = org.json4s.DefaultFormats + val backend: StreamBackend[Future, Any] = AkkaHttpBackend() override def callAsyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { - val response: Response[String] = quickRequest.get(uri"$url").send() - response.body + val request = basicRequest.get(uri"$url").response(asJson[HttpBinResponse]) + val response: Future[Response[Either[ResponseException[String, Exception], HttpBinResponse]]] = + request.send(backend) + + val responseMessage: Future[MessageResponse] = response.map { response => + response.body match { + case Left(resEx) => MessageResponse(resEx.toString) + case Right(value) => MessageResponse(value.origin) + } + } + effects.asyncReply(responseMessage) } def callEndpoint(url: String): String = { From ba881f025ee790425584c3ed8e6308f355683640 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Fri, 22 Mar 2024 16:27:39 +0700 Subject: [PATCH 04/17] using core sttp and some cleaning --- samples/scala-protobuf-tracing/.scalafmt.conf | 47 +++++++++++++++++++ samples/scala-protobuf-tracing/build.sbt | 3 +- .../scala-protobuf-tracing/docker-compose.yml | 31 ++++-------- .../scala/com/example/ControllerAction.scala | 47 +++++-------------- .../src/main/scala/com/example/Main.scala | 9 ++-- .../com/example/ControllerActionSpec.scala | 8 ++-- 6 files changed, 79 insertions(+), 66 deletions(-) create mode 100644 samples/scala-protobuf-tracing/.scalafmt.conf diff --git a/samples/scala-protobuf-tracing/.scalafmt.conf b/samples/scala-protobuf-tracing/.scalafmt.conf new file mode 100644 index 0000000000..d7ea3a6d5a --- /dev/null +++ b/samples/scala-protobuf-tracing/.scalafmt.conf @@ -0,0 +1,47 @@ +version = 3.0.3 + +style = defaultWithAlign + +docstrings.style = Asterisk +indentOperator.preset = spray +maxColumn = 120 +rewrite.rules = [RedundantParens, SortImports, AvoidInfix] +unindentTopLevelOperators = true +align.tokens = [{code = "=>", owner = "Case"}] +align.openParenDefnSite = false +align.openParenCallSite = false +optIn.configStyleArguments = false +danglingParentheses.preset = false +spaces.inImportCurlyBraces = true +newlines.afterCurlyLambda = preserve +rewrite.neverInfix.excludeFilters = [ + and + min + max + until + to + by + eq + ne + "should.*" + "contain.*" + "must.*" + in + ignore + be + taggedAs + thrownBy + synchronized + have + when + size + only + noneOf + oneElementOf + noElementsOf + atLeastOneElementOf + atMostOneElementOf + allElementsOf + inOrderElementsOf + theSameElementsAs +] diff --git a/samples/scala-protobuf-tracing/build.sbt b/samples/scala-protobuf-tracing/build.sbt index a8dffde252..1ba0ae52ed 100644 --- a/samples/scala-protobuf-tracing/build.sbt +++ b/samples/scala-protobuf-tracing/build.sbt @@ -32,6 +32,7 @@ Compile / javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation", "-param ) libraryDependencies ++= Seq( - "com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1" % Compile, + "com.softwaremill.sttp.client4" %% "core" % "4.0.0-M11" % Compile, + "org.json4s" %% "json4s-native" % "4.1.0-M5"% Compile, "org.scalatest" %% "scalatest" % "3.2.7" % Test ) diff --git a/samples/scala-protobuf-tracing/docker-compose.yml b/samples/scala-protobuf-tracing/docker-compose.yml index 322791cef2..7e746ff956 100644 --- a/samples/scala-protobuf-tracing/docker-compose.yml +++ b/samples/scala-protobuf-tracing/docker-compose.yml @@ -1,33 +1,22 @@ -# If you're looking to use eventing with Google PubSub, to get an emulator running: -# - add property "-Dkalix.proxy.eventing.support=google-pubsub-emulator" to the JAVA_TOOL_OPTIONS environment map under the kalix-runtime service -# - uncomment the env var PUBSUB_EMULATOR_HOST and the section below for gcloud-pubsub-emulator service version: "3" services: kalix-runtime: image: gcr.io/kalix-public/kalix-runtime:1.1.33 + container_name: tracing ports: - "9000:9000" extra_hosts: - "host.docker.internal:host-gateway" environment: JAVA_TOOL_OPTIONS: > - # jvm -D properties can be added under this environment map (note: remove this comment when adding properties) - - USER_FUNCTION_HOST: ${USER_FUNCTION_HOST:-host.docker.internal} - USER_FUNCTION_PORT: ${USER_FUNCTION_PORT:-8080} + -Dkalix.proxy.telemetry.tracing.enabled=true + -Dkalix.proxy.telemetry.tracing.collector-endpoint=http://jaeger:4317 + USER_SERVICE_HOST: ${USER_SERVICE_HOST:-host.docker.internal} + USER_SERVICE_PORT: ${USER_SERVICE_PORT:-8080} # Comment to enable ACL check in dev-mode (see https://docs.kalix.io/services/using-acls.html#_local_development_with_acls) ACL_ENABLED: "false" - # Uncomment to enable advanced view features locally (note: disabled in deployed services by default) - #VIEW_FEATURES_ALL: "true" - # Uncomment to disable the JWT dev secret - #JWT_DEV_SECRET: "false" - # Uncomment to set the JWT dev secret issuer - #JWT_DEV_SECRET_ISSUER: "my-issuer" - # Uncomment if using pubsub emulator - #PUBSUB_EMULATOR_HOST: gcloud-pubsub-emulator - #gcloud-pubsub-emulator: - # image: gcr.io/google.com/cloudsdktool/cloud-sdk:341.0.0 - # command: gcloud beta emulators pubsub start --project=test --host-port=0.0.0.0:8085 - # ports: - # - 8085:8085 - # + jaeger: + image: jaegertracing/all-in-one:1.54 + ports: + - 4317:4317 + - 16686:16686 \ No newline at end of file diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala index b717206528..fdc10c3f98 100644 --- a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala @@ -3,18 +3,12 @@ package com.example import com.google.protobuf.empty.Empty import io.opentelemetry.api.trace.StatusCode import io.opentelemetry.context.Scope -import kalix.scalasdk.action.{Action, ActionCreationContext} -import sttp.client4.quick._ -import sttp.client4.{Response, _} - -import scala.concurrent.Future - -// This class was initially generated based on the .proto definition by Kalix tooling. -// -// As long as this file exists it will not be overwritten: you can maintain it yourself, -// or delete it so it is regenerated as needed. +import kalix.scalasdk.action.{ Action, ActionCreationContext } +import sttp.client4.quick.RichRequest +import sttp.client4.{ Response, _ } +import scala.concurrent.Future class ControllerAction(creationContext: ActionCreationContext) extends AbstractControllerAction { @@ -32,10 +26,10 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC val scope: Scope = span.makeCurrent() try { val response = quickRequest.get(uri"$url").send() - if(response.code.isSuccess) { + if (response.code.isSuccess) { span.setAttribute("result", response.body) } else { - span.setStatus(StatusCode.ERROR,response.statusText) + span.setStatus(StatusCode.ERROR, response.statusText) } responseBody = response.body } finally { @@ -49,33 +43,18 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC effects.reply(MessageResponse(responseBody)) } - - import sttp.client4.akkahttp._ - import sttp.client4.json4s._ - - import scala.concurrent.ExecutionContext.global - - case class HttpBinResponse(origin:String, headers: Map[String, String]) - implicit val serialization = org.json4s.native.Serialization - implicit val formats = org.json4s.DefaultFormats - val backend: StreamBackend[Future, Any] = AkkaHttpBackend() override def callAsyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { - val request = basicRequest.get(uri"$url").response(asJson[HttpBinResponse]) - val response: Future[Response[Either[ResponseException[String, Exception], HttpBinResponse]]] = - request.send(backend) - + val request = basicRequest.get(uri"$url") + val response: Future[Response[Either[String, String]]] = + request.send(Main.backend) val responseMessage: Future[MessageResponse] = response.map { response => response.body match { - case Left(resEx) => MessageResponse(resEx.toString) - case Right(value) => MessageResponse(value.origin) + case Left(resEx) => + MessageResponse(resEx) + case Right(value) => + MessageResponse(value) } } effects.asyncReply(responseMessage) } - - def callEndpoint(url: String): String = { - val response: Response[String] = quickRequest.get(uri"$url").send() - response.body - } } - diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/Main.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/Main.scala index 27275dc3ac..47b03cd6d5 100644 --- a/samples/scala-protobuf-tracing/src/main/scala/com/example/Main.scala +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/Main.scala @@ -1,8 +1,8 @@ package com.example -import akka.actor.ActorSystem import kalix.scalasdk.Kalix import org.slf4j.LoggerFactory +import sttp.client4.httpclient.HttpClientFutureBackend // This class was initially generated based on the .proto definition by Kalix tooling. // @@ -13,19 +13,18 @@ object Main { private val log = LoggerFactory.getLogger("com.example.Main") - implicit val actorSystem = ActorSystem("external-calls") - + val backend = HttpClientFutureBackend() def createKalix(): Kalix = { // The KalixFactory automatically registers any generated Actions, Views or Entities, // and is kept up-to-date with any changes in your protobuf definitions. // If you prefer, you may remove this and manually register these components in a // `Kalix()` instance. - KalixFactory.withComponents( - new ControllerAction(_)) + KalixFactory.withComponents(new ControllerAction(_)) } def main(args: Array[String]): Unit = { log.info("starting the Kalix service") + Runtime.getRuntime.addShutdownHook { new Thread(() => backend.close()) } createKalix().start() } } diff --git a/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala b/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala index f0fe4fd7d0..674c90ee2d 100644 --- a/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala +++ b/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala @@ -11,9 +11,7 @@ import org.scalatest.wordspec.AnyWordSpec // As long as this file exists it will not be overwritten: you can maintain it yourself, // or delete it so it is regenerated as needed. -class ControllerActionSpec - extends AnyWordSpec - with Matchers { +class ControllerActionSpec extends AnyWordSpec with Matchers { "ControllerAction" must { @@ -29,13 +27,13 @@ class ControllerActionSpec "handle command CallSyncEndpoint" in { val service = ControllerActionTestKit(new ControllerAction(_)) - pending + pending // val result = service.callSyncEndpoint(Empty(...)) } "handle command CallAsyncEndpoint" in { val service = ControllerActionTestKit(new ControllerAction(_)) - pending + pending // val result = service.callAsyncEndpoint(Empty(...)) } From 6214e7af11b277bf54073c9fec0b305ce39a9f42 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Fri, 22 Mar 2024 17:02:38 +0700 Subject: [PATCH 05/17] refactoring --- .../scala/com/example/ControllerAction.scala | 86 +++++++++++++------ 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala index fdc10c3f98..ae88817d29 100644 --- a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala @@ -1,14 +1,14 @@ package com.example import com.google.protobuf.empty.Empty -import io.opentelemetry.api.trace.StatusCode +import io.opentelemetry.api.trace.{StatusCode, Tracer} import io.opentelemetry.context.Scope -import kalix.scalasdk.action.{ Action, ActionCreationContext } - +import kalix.scalasdk.action.{Action, ActionCreationContext} import sttp.client4.quick.RichRequest -import sttp.client4.{ Response, _ } +import sttp.client4.{Response, _} import scala.concurrent.Future +import scala.util.{Failure, Success} class ControllerAction(creationContext: ActionCreationContext) extends AbstractControllerAction { @@ -16,38 +16,72 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC override def callSyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { val tracerOpt = actionContext.getOpenTelemetryTracer - var responseBody = "" tracerOpt match { case Some(tracer) => - val span = tracer - .spanBuilder("b") - .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext) - .startSpan() - val scope: Scope = span.makeCurrent() - try { - val response = quickRequest.get(uri"$url").send() - if (response.code.isSuccess) { - span.setAttribute("result", response.body) - } else { - span.setStatus(StatusCode.ERROR, response.statusText) - } - responseBody = response.body - } finally { - span.end() - scope.close() - } + val response = callSync(tracer) + effects.reply(response) case None => val response = quickRequest.get(uri"$url").send() - responseBody = response.body + effects.reply(MessageResponse(response.body)) + } + } + + private def callSync(tracer: Tracer): MessageResponse = { + val span = tracer + .spanBuilder("loreipsumendpoint") + .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext) + .startSpan() + val scope: Scope = span.makeCurrent() + try { + val response = quickRequest.get(uri"$url").send() + if (response.code.isSuccess) { + span.setAttribute("result", response.body) + } else { + span.setStatus(StatusCode.ERROR, response.statusText) + } + MessageResponse(response.body) + } finally { + span.end() + scope.close() } - effects.reply(MessageResponse(responseBody)) } override def callAsyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { + val tracerOpt = actionContext.getOpenTelemetryTracer + tracerOpt match { + case Some(tracer) => + val responseBody = callAsync(tracer) + effects.asyncReply(responseBody) + case None => + val responseBody = callAsync() + effects.asyncReply(responseBody) + } + } + + private def callAsync(tracer: Tracer): Future[MessageResponse] = { + val span = tracer + .spanBuilder("loreipsumendpoint") + .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext) + .startSpan() + val scope: Scope = span.makeCurrent() + try { + val responseBody: Future[MessageResponse] = callAsync() + responseBody.onComplete { + case Success(response) => span.setAttribute("result", response.message) + case Failure(exception) => span.setStatus(StatusCode.ERROR, exception.getMessage) + } + responseBody + } finally { + span.end() + scope.close() + } + } + + private def callAsync(): Future[MessageResponse] = { val request = basicRequest.get(uri"$url") val response: Future[Response[Either[String, String]]] = request.send(Main.backend) - val responseMessage: Future[MessageResponse] = response.map { response => + response.map { response => response.body match { case Left(resEx) => MessageResponse(resEx) @@ -55,6 +89,6 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC MessageResponse(value) } } - effects.asyncReply(responseMessage) } + } From 0ac6deb199bdd53549bc3d5214c9d89e3a19ba9e Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Fri, 22 Mar 2024 17:13:07 +0700 Subject: [PATCH 06/17] added readme --- samples/scala-protobuf-tracing/README.md | 112 +++++++---------------- 1 file changed, 34 insertions(+), 78 deletions(-) diff --git a/samples/scala-protobuf-tracing/README.md b/samples/scala-protobuf-tracing/README.md index 7febc376af..c3eda2487e 100644 --- a/samples/scala-protobuf-tracing/README.md +++ b/samples/scala-protobuf-tracing/README.md @@ -1,104 +1,60 @@ -# scala-protobuf-tracing +## This example show how to create Spans by the users -## Designing -While designing your service it is useful to read [designing services](https://docs.kalix.io/services/development-process.html). - -## Developing - -This project has a bare-bones skeleton service ready to go, but in order to adapt and -extend it it may be useful to read up on [developing services](https://docs.kalix.io/developing/index.html) -and in particular the [JVM section](https://docs.kalix.io/java-services/index.html). - -## Building - -You can use [sbt](https://www.scala-sbt.org/) to build your project, -which will also take care of generating code based on the `.proto` definitions: - -``` -sbt compile -``` ## Running Locally -In order to run your application locally, you must run the Kalix proxy. The included `docker-compose.yml` file contains the configuration required to run the proxy for a locally running application. -It also contains the configuration to start a local Google Pub/Sub emulator that the Kalix proxy will connect to. -To start the proxy, run the following command from this directory: - -``` -docker-compose up -``` -> On Linux this requires Docker 20.10 or later (https://github.com/moby/moby/pull/40007), -> or for a `USER_FUNCTION_HOST` environment variable to be set manually. +When running a Kalix service locally, we need to have its companion Kalix Runtime running alongside it. -To start the application locally, start it from your IDE or use: +To start your service locally, run: -``` -sbt run +```shell +sbt runAll ``` -With both the proxy and your application running, any defined endpoints should be available at `http://localhost:9000`. In addition to the defined gRPC interface, each method has a corresponding HTTP endpoint. Unless configured otherwise (see [Transcoding HTTP](https://docs.kalix.io/java-protobuf/writing-grpc-descriptors-protobuf.html#_transcoding_http)), this endpoint accepts POST requests at the path `/[package].[entity name]/[method]`. For example, using `curl`: +It's worth noting that `application.conf` is passing `kalix.telemetry.tracing.collector-endpoint="http://localhost:4317"` +to the application so the SDK knows where to export the traces. This is NOT needed when deploying in Kalix, only when run in local, that is, `mvn kalix:runAll`. -```shell -> curl -XPOST -H "Content-Type: application/json" localhost:9000/com.example.CounterService/GetCurrentCounter -d '{"counterId": "foo"}' -The command handler for `GetCurrentCounter` is not implemented, yet -``` +This command will start your Kalix service and a companion Kalix Runtime as configured in [docker-compose.yml](./docker-compose.yml) file. +This will also start a Jaeger service to which the services above will push the traces. You can find Jaeger at `http://localhost:16686` + +With both the Kalix Runtime and your service running, any defined endpoints should be available at `http://localhost:9000`. In addition to the defined gRPC interface, each method has a corresponding HTTP endpoint. Unless configured otherwise (see [Transcoding HTTP](https://docs.kalix.io/java-protobuf/writing-grpc-descriptors-protobuf.html#_transcoding_http)), this endpoint accepts POST requests at the path `/[package].[entity name]/[method]`. For example, using [`grpcurl`](https://github.com/fullstorydev/grpcurl): ```shell -> grpcurl -plaintext -d '{"counterId": "foo"}' localhost:9000 com.example.CounterService/GetCurrentCounter -ERROR: - Code: Unknown - Message: The command handler for `GetCurrentCounter` is not implemented, yet +grpcurl -plaintext localhost:9000 com.example.Controller/CallAsyncEndpoint ``` - -> Note: The failure is to be expected if you have not yet provided an implementation of `GetCurrentCounter` in -> your entity. - -## Deploying - -To deploy your service, install the `kalix` CLI as documented in -[Setting up a local development environment](https://docs.kalix.io/setting-up/) -and configure a Docker Registry to upload your Docker image to. - -You will need to set your `docker.username` as a system property: - +produces ``` -sbt -Ddocker.username=mary docker:publish +{ + "message": "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\",\n \"body\": \"quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto\"\n}" +} ``` +or -Refer to [Configuring registries](https://docs.kalix.io/projects/container-registries.html) -for more information on how to make your Docker image available to Kalix. - -You can now deploy your service through the [kalix](https://docs.kalix.io/kalix/using-cli.html) CLI: - +```shell +grpcurl -plaintext localhost:9000 com.example.Controller/CallAsyncEndpoint ``` -$ kalix auth login +produces ``` - -If this is your first time using Kalix, this will let you -register an account, create your first project and set it as the default. - -Now: - -``` -$ kalix services deploy \ - my-service \ - my-container-uri/container-name:tag-name +{ + "message": "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\",\n \"body\": \"quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto\"\n}" +} ``` -Once the service has been successfully started (this may take a while), -you can create an ad-hoc proxy to call it from your local machine: +## Deploying -``` -$ kalix services proxy my-service -Listening on 127.0.0.1:8080 -``` +To deploy your service, install the `kalix` CLI as documented in +[Install Kalix](https://docs.kalix.io/kalix/install-kalix.html) +and configure a Docker Registry to upload your docker image to. -Or expose it to the Internet: +You will need to update the `dockerImage` property in the `pom.xml` and refer to +[Configuring registries](https://docs.kalix.io/projects/container-registries.html) +for more information on how to make your docker image available to Kalix. -``` -kalix service expose my-service -``` +Finally, you use the `kalix` CLI to create a project as described in [Create a new Project](https://docs.kalix.io/projects/create-project.html). Once you have a project you can deploy your service into the project either +by using `mvn deploy kalix:deploy` which will package, publish your docker image, and deploy your service to Kalix, +or by first packaging and publishing the docker image through `mvn deploy` and +then [deploying the image through the `kalix` CLI](https://docs.kalix.io/services/deploy-service.html#_deploy). \ No newline at end of file From 9853a186b3e8b03ed7b296a8372493a81f933fc8 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Fri, 22 Mar 2024 17:18:39 +0700 Subject: [PATCH 07/17] removing sync --- .../com/example/controller_action.proto | 2 -- .../scala/com/example/ControllerAction.scala | 32 ------------------- .../com/example/ControllerActionSpec.scala | 9 ------ 3 files changed, 43 deletions(-) diff --git a/samples/scala-protobuf-tracing/src/main/protobuf/com/example/controller_action.proto b/samples/scala-protobuf-tracing/src/main/protobuf/com/example/controller_action.proto index 4c535eceda..d6cd4fb4ba 100644 --- a/samples/scala-protobuf-tracing/src/main/protobuf/com/example/controller_action.proto +++ b/samples/scala-protobuf-tracing/src/main/protobuf/com/example/controller_action.proto @@ -18,7 +18,5 @@ service Controller { action: {} }; - rpc CallSyncEndpoint(google.protobuf.Empty) returns (MessageResponse){} - rpc CallAsyncEndpoint(google.protobuf.Empty) returns (MessageResponse){} } \ No newline at end of file diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala index ae88817d29..075ab79c21 100644 --- a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala @@ -14,38 +14,6 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC val url = "https://jsonplaceholder.typicode.com/posts/1" - override def callSyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { - val tracerOpt = actionContext.getOpenTelemetryTracer - tracerOpt match { - case Some(tracer) => - val response = callSync(tracer) - effects.reply(response) - case None => - val response = quickRequest.get(uri"$url").send() - effects.reply(MessageResponse(response.body)) - } - } - - private def callSync(tracer: Tracer): MessageResponse = { - val span = tracer - .spanBuilder("loreipsumendpoint") - .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext) - .startSpan() - val scope: Scope = span.makeCurrent() - try { - val response = quickRequest.get(uri"$url").send() - if (response.code.isSuccess) { - span.setAttribute("result", response.body) - } else { - span.setStatus(StatusCode.ERROR, response.statusText) - } - MessageResponse(response.body) - } finally { - span.end() - scope.close() - } - } - override def callAsyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { val tracerOpt = actionContext.getOpenTelemetryTracer tracerOpt match { diff --git a/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala b/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala index 674c90ee2d..5000919603 100644 --- a/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala +++ b/samples/scala-protobuf-tracing/src/test/scala/com/example/ControllerActionSpec.scala @@ -1,8 +1,5 @@ package com.example -import com.google.protobuf.empty.Empty -import kalix.scalasdk.action.Action -import kalix.scalasdk.testkit.ActionResult import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec @@ -25,12 +22,6 @@ class ControllerActionSpec extends AnyWordSpec with Matchers { // result.reply shouldBe expectedReply } - "handle command CallSyncEndpoint" in { - val service = ControllerActionTestKit(new ControllerAction(_)) - pending - // val result = service.callSyncEndpoint(Empty(...)) - } - "handle command CallAsyncEndpoint" in { val service = ControllerActionTestKit(new ControllerAction(_)) pending From fc4b828dbf7c437efc6f897469d103bdbbac6408 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Fri, 22 Mar 2024 17:32:55 +0700 Subject: [PATCH 08/17] implementing interface for ScalaActionContextAdapter --- .../scala/kalix/scalasdk/impl/action/ActionAdapters.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala index 0799b94b60..0f18f4f918 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala @@ -30,6 +30,7 @@ import kalix.scalasdk.action.MessageEnvelope import kalix.scalasdk.impl.InternalContext import kalix.scalasdk.impl.MetadataConverters import com.google.protobuf.Descriptors +import io.opentelemetry.api.trace.Tracer import kalix.javasdk.impl.telemetry.Telemetry import kalix.protocol.component.MetadataEntry import kalix.scalasdk.impl.MetadataImpl @@ -139,6 +140,8 @@ private[scalasdk] final case class ScalaActionCreationContextAdapter( javaSdkCreationContext.getGrpcClient(clientClass, service) override def materializer(): Materializer = javaSdkCreationContext.materializer() + + override def getOpenTelemetryTracer: Option[Tracer] = javaSdkCreationContext.getOpenTelemetryTracer.toScala } private[scalasdk] final case class ScalaActionContextAdapter(javaSdkContext: javasdk.action.ActionContext) @@ -151,9 +154,6 @@ private[scalasdk] final case class ScalaActionContextAdapter(javaSdkContext: jav override def eventSubject: Option[String] = javaSdkContext.eventSubject().toScala - override def getGrpcClient[T](clientClass: Class[T], service: String): T = - javaSdkContext.getGrpcClient(clientClass, service) - def getComponentGrpcClient[T](serviceClass: Class[T]): T = javaSdkContext match { case ctx: javasdk.impl.AbstractContext => ctx.getComponentGrpcClient(serviceClass) } @@ -170,4 +170,5 @@ private[scalasdk] final case class ScalaActionContextAdapter(javaSdkContext: jav } } + override def getOpenTelemetryTracer: Option[Tracer] = javaSdkContext.getOpenTelemetryTracer.toScala } From 2dc6ce4bd3c666ee9e9828779cf600f8f12fd7f0 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Tue, 2 Apr 2024 12:50:12 +0700 Subject: [PATCH 09/17] Cleaned and created docs --- .../modules/java-protobuf/pages/actions.adoc | 28 +++++++- samples/scala-protobuf-tracing/.env | 11 ++++ samples/scala-protobuf-tracing/README.md | 22 +------ .../scala-protobuf-tracing/docker-compose.yml | 14 ++-- .../project/plugins.sbt | 4 ++ .../scala/com/example/ControllerAction.scala | 64 +++++++++---------- .../main/scala/com/example/domain/Post.scala | 4 ++ .../main/scala/kalix/scalasdk/Metadata.scala | 2 +- .../kalix/scalasdk/action/ActionContext.scala | 6 +- .../action/ActionCreationContext.scala | 6 +- .../scalasdk/impl/action/ActionAdapters.scala | 3 + 11 files changed, 96 insertions(+), 68 deletions(-) create mode 100644 samples/scala-protobuf-tracing/.env create mode 100644 samples/scala-protobuf-tracing/project/plugins.sbt create mode 100644 samples/scala-protobuf-tracing/src/main/scala/com/example/domain/Post.scala diff --git a/docs/src/modules/java-protobuf/pages/actions.adoc b/docs/src/modules/java-protobuf/pages/actions.adoc index 2706ee9efd..981dd8043e 100644 --- a/docs/src/modules/java-protobuf/pages/actions.adoc +++ b/docs/src/modules/java-protobuf/pages/actions.adoc @@ -449,7 +449,6 @@ include::example$java-protobuf-valueentity-counter/src/main/java/com/example/act Please note that, the result of a side effect is ignored by the current command meaning that even if the call to the `Counter` entity fails, the `Action` reply will succeed. - == Adding tracing spans To add spans in your actions you can add the tracer available in the link:{attachmentsdir}/api/kalix/javasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"] and with it create the span. @@ -468,9 +467,21 @@ To get the tracer from the link:{attachmentsdir}/api/kalix/javasdk/action/Action ---- include::example$java-protobuf-tracing/src/main/java/com/example/ControllerAction.java[tag=get-tracer] ---- - Note that if link:https://docs.kalix.io/operations/observability-exports.html#_activating_tracing_beta[tracing] is not enabled in your service this `Optional` will be empty. Otherwise you can map over `tracerOpt` to retreive the tracer. +Scala:: ++ +To add spans in your actions you can add the tracer available in the link:{attachmentsdir}/scala-api/kalix/scalasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"] and with it create the span. ++ +To get the tracer from the link:{attachmentsdir}/scala-api/kalix/scalasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"]: ++ +[source,java,indent=0] +.src/main/scala/com/example/ControllerAction.java +---- +include::example$scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala[tag=get-tracer] +---- +Note that if link:https://docs.kalix.io/operations/observability-exports.html#_activating_tracing_beta[tracing] is not enabled in your service this `Option` will be empty. Otherwise you can map over `tracerOpt` to use tracer. + To create a link:https://opentelemetry.io/docs/languages/java/instrumentation/#create-spans[span] and end it over an asynchronous call, you can do the following: [.tabset] @@ -487,6 +498,19 @@ include::example$java-protobuf-tracing/src/main/java/com/example/ControllerActio <4> Sets the status of the span as error. <5> Closes the span. +Scala:: ++ +[source,java,indent=0] +.src/main/scala/com/example/ControllerAction.java +---- +include::example$scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala[tag=create-close-span] +---- +<1> Sets the action's TraceContext the parent of this span. Linking the action's trace to this span. +<2> Creates and starts the span. +<3> Adds some attribute. +<4> Sets the status of the span as error. +<5> Closes the span. + NOTE: You can find how tracing is enabled and more info link:https://docs.kalix.io/operations/observability-exports.html#_activating_tracing_beta[here]. == Unit testing the side effects diff --git a/samples/scala-protobuf-tracing/.env b/samples/scala-protobuf-tracing/.env new file mode 100644 index 0000000000..dcdc6f9863 --- /dev/null +++ b/samples/scala-protobuf-tracing/.env @@ -0,0 +1,11 @@ +# this is the port where the kalix runtime container will be exposed +# when running multiple services on your local machine, make sure that this port is unique +ADVERTISED_HTTP_PORT=9000 + +# this is the port where the user services (your application) will open +# when running multiple services on your local machine, make sure that this port is unique +USER_SERVICE_PORT=8080 + +# this variable defines the host where the kalix runtime (running in docker) +# will reach the user service in local development +USER_SERVICE_HOST=host.docker.internal diff --git a/samples/scala-protobuf-tracing/README.md b/samples/scala-protobuf-tracing/README.md index c3eda2487e..d71ec96533 100644 --- a/samples/scala-protobuf-tracing/README.md +++ b/samples/scala-protobuf-tracing/README.md @@ -1,10 +1,7 @@ ## This example show how to create Spans by the users - - ## Running Locally - When running a Kalix service locally, we need to have its companion Kalix Runtime running alongside it. To start your service locally, run: @@ -13,12 +10,8 @@ To start your service locally, run: sbt runAll ``` -It's worth noting that `application.conf` is passing `kalix.telemetry.tracing.collector-endpoint="http://localhost:4317"` -to the application so the SDK knows where to export the traces. This is NOT needed when deploying in Kalix, only when run in local, that is, `mvn kalix:runAll`. - This command will start your Kalix service and a companion Kalix Runtime as configured in [docker-compose.yml](./docker-compose.yml) file. -This will also start a Jaeger service to which the services above will push the traces. You can find Jaeger at `http://localhost:16686` - +This will also start a Jaeger service to which the Kalix service will push the traces. You can find Jaeger at `http://localhost:16686` With both the Kalix Runtime and your service running, any defined endpoints should be available at `http://localhost:9000`. In addition to the defined gRPC interface, each method has a corresponding HTTP endpoint. Unless configured otherwise (see [Transcoding HTTP](https://docs.kalix.io/java-protobuf/writing-grpc-descriptors-protobuf.html#_transcoding_http)), this endpoint accepts POST requests at the path `/[package].[entity name]/[method]`. For example, using [`grpcurl`](https://github.com/fullstorydev/grpcurl): @@ -29,18 +22,7 @@ grpcurl -plaintext localhost:9000 com.example.Controller/CallAsyncEndpoint produces ``` { - "message": "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\",\n \"body\": \"quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto\"\n}" -} -``` -or - -```shell -grpcurl -plaintext localhost:9000 com.example.Controller/CallAsyncEndpoint -``` -produces -``` -{ - "message": "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\",\n \"body\": \"quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto\"\n}" + "message": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit" } ``` diff --git a/samples/scala-protobuf-tracing/docker-compose.yml b/samples/scala-protobuf-tracing/docker-compose.yml index 7e746ff956..0cf0d940d8 100644 --- a/samples/scala-protobuf-tracing/docker-compose.yml +++ b/samples/scala-protobuf-tracing/docker-compose.yml @@ -1,20 +1,22 @@ +# If you're looking to use eventing with Google PubSub, to get an emulator running: +# - add property "-Dkalix.proxy.eventing.support=google-pubsub-emulator" to the JAVA_TOOL_OPTIONS environment map under the kalix-runtime service +# - uncomment the env var PUBSUB_EMULATOR_HOST and the section below for gcloud-pubsub-emulator service version: "3" services: kalix-runtime: - image: gcr.io/kalix-public/kalix-runtime:1.1.33 + image: gcr.io/kalix-public/kalix-runtime:1.1.34 container_name: tracing ports: - - "9000:9000" + - "${ADVERTISED_HTTP_PORT}:9000" extra_hosts: - "host.docker.internal:host-gateway" environment: JAVA_TOOL_OPTIONS: > -Dkalix.proxy.telemetry.tracing.enabled=true -Dkalix.proxy.telemetry.tracing.collector-endpoint=http://jaeger:4317 - USER_SERVICE_HOST: ${USER_SERVICE_HOST:-host.docker.internal} - USER_SERVICE_PORT: ${USER_SERVICE_PORT:-8080} - # Comment to enable ACL check in dev-mode (see https://docs.kalix.io/services/using-acls.html#_local_development_with_acls) - ACL_ENABLED: "false" + ADVERTISED_HTTP_PORT: ${ADVERTISED_HTTP_PORT} + USER_SERVICE_HOST: ${USER_SERVICE_HOST} + USER_SERVICE_PORT: ${USER_SERVICE_PORT} jaeger: image: jaegertracing/all-in-one:1.54 ports: diff --git a/samples/scala-protobuf-tracing/project/plugins.sbt b/samples/scala-protobuf-tracing/project/plugins.sbt new file mode 100644 index 0000000000..8e753b7224 --- /dev/null +++ b/samples/scala-protobuf-tracing/project/plugins.sbt @@ -0,0 +1,4 @@ +addSbtPlugin("io.kalix" % "sbt-kalix" % "1.4.1") +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.8.1") +addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala index 075ab79c21..015531ace2 100644 --- a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala @@ -1,62 +1,60 @@ package com.example +import com.example.domain.Post import com.google.protobuf.empty.Empty -import io.opentelemetry.api.trace.{StatusCode, Tracer} -import io.opentelemetry.context.Scope +import io.opentelemetry.api.trace.StatusCode import kalix.scalasdk.action.{Action, ActionCreationContext} -import sttp.client4.quick.RichRequest -import sttp.client4.{Response, _} +import org.json4s.native.JsonMethods +import org.json4s.{DefaultFormats, Formats} +import sttp.client4._ import scala.concurrent.Future import scala.util.{Failure, Success} class ControllerAction(creationContext: ActionCreationContext) extends AbstractControllerAction { - val url = "https://jsonplaceholder.typicode.com/posts/1" + implicit val formats: Formats = DefaultFormats + val url = "https://jsonplaceholder.typicode.com/posts" + + // tag::create-close-span[] override def callAsyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { + // tag::get-tracer[] val tracerOpt = actionContext.getOpenTelemetryTracer - tracerOpt match { - case Some(tracer) => - val responseBody = callAsync(tracer) - effects.asyncReply(responseBody) - case None => - val responseBody = callAsync() - effects.asyncReply(responseBody) - } - } + // end::get-tracer[] + val span = tracerOpt.map(_.spanBuilder(s"$url/{}") + .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext)// <1> + .startSpan()// <2> + .setAttribute("post", "1"))// <3> - private def callAsync(tracer: Tracer): Future[MessageResponse] = { - val span = tracer - .spanBuilder("loreipsumendpoint") - .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext) - .startSpan() - val scope: Scope = span.makeCurrent() - try { val responseBody: Future[MessageResponse] = callAsync() responseBody.onComplete { - case Success(response) => span.setAttribute("result", response.message) - case Failure(exception) => span.setStatus(StatusCode.ERROR, exception.getMessage) + case Failure(exception) => + span.map(_ + .setStatus(StatusCode.ERROR, exception.getMessage)// <4> + .end())// <5> + case Success(response) => + span.map(_ + .setAttribute("result", response.message)// <3> + .end())// <5> } - responseBody - } finally { - span.end() - scope.close() - } + effects.asyncReply(responseBody) } + // end::create-close-span[] + private def callAsync(): Future[MessageResponse] = { - val request = basicRequest.get(uri"$url") - val response: Future[Response[Either[String, String]]] = - request.send(Main.backend) + val request = basicRequest.get(uri"${url}/1") + val response: Future[Response[Either[String, String]]] = request.send(Main.backend) response.map { response => response.body match { case Left(resEx) => MessageResponse(resEx) case Right(value) => - MessageResponse(value) + val post = JsonMethods.parse(value).extract[Post] + MessageResponse(post.title) } } } - } + diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/domain/Post.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/domain/Post.scala new file mode 100644 index 0000000000..b94bc4667e --- /dev/null +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/domain/Post.scala @@ -0,0 +1,4 @@ +package com.example.domain + +case class Post(userId: String, id: String, title: String, body: String) + diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/Metadata.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/Metadata.scala index cb96ea2788..9818e22456 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/Metadata.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/Metadata.scala @@ -16,7 +16,7 @@ package kalix.scalasdk -import kalix.javasdk.impl.{MetadataImpl => JMetadataImpl} +import kalix.javasdk.impl.{ MetadataImpl => JMetadataImpl } import kalix.scalasdk.impl.MetadataImpl import java.net.URI diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionContext.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionContext.scala index 8cbd8378e0..f51ad4ba1f 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionContext.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionContext.scala @@ -16,7 +16,7 @@ package kalix.scalasdk.action -import kalix.scalasdk.{Metadata, MetadataContext} +import kalix.scalasdk.{ Metadata, MetadataContext } trait ActionContext extends MetadataContext with ActionCreationContext { @@ -28,7 +28,7 @@ trait ActionContext extends MetadataContext with ActionCreationContext { * was a gRPC call, it will contain the HTTP headers for that gRPC call. * * @return - * The call level metadata. + * The call level metadata. */ def metadata: Metadata @@ -36,4 +36,4 @@ trait ActionContext extends MetadataContext with ActionCreationContext { * The origin subject of the {{{CloudEvent}}}. For example, the entity id when the event was emitted from an entity. */ def eventSubject: Option[String] -} \ No newline at end of file +} diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala index b20f83f7da..83a9e02033 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala @@ -36,10 +36,10 @@ trait ActionCreationContext extends Context { def getGrpcClient[T](clientClass: Class[T], service: String): T /** - * Get an OpenTelemetry tracer for the current action. This will allow for building and automatic - * exporting of spans. + * Get an OpenTelemetry tracer for the current action. This will allow for building and automatic exporting of spans. * - * @return A tracer for the current action, if tracing is configured. + * @return + * A tracer for the current action, if tracing is configured. */ def getOpenTelemetryTracer: Option[Tracer] } diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala index 0f18f4f918..c3f8eb9dfb 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala @@ -157,6 +157,9 @@ private[scalasdk] final case class ScalaActionContextAdapter(javaSdkContext: jav def getComponentGrpcClient[T](serviceClass: Class[T]): T = javaSdkContext match { case ctx: javasdk.impl.AbstractContext => ctx.getComponentGrpcClient(serviceClass) } + override def getGrpcClient[T](clientClass: Class[T], service: String): T = { + getComponentGrpcClient(clientClass) + } override def materializer(): Materializer = javaSdkContext.materializer() From add9e0ab496127f76ff4cfcf1900a32c5ff841ab Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Thu, 4 Apr 2024 18:27:10 +0700 Subject: [PATCH 10/17] Clean up scala-protobuf-traicing sample and adding NoOps tracer for a better DX --- samples/java-protobuf-tracing/pom.xml | 1 - .../protobuf/com/example/kalix_policy.proto | 2 +- .../src/main/resources/application.conf | 1 - .../scala/com/example/ControllerAction.scala | 15 ++++----- .../testkit/impl/TestKitActionContext.scala | 3 ++ .../javasdk/action/ActionCreationContext.java | 3 ++ .../javasdk/impl/action/ActionsImpl.scala | 31 +++++++------------ .../javasdk/impl/telemetry/Telemetry.scala | 24 +++++--------- .../spring/impl/KalixSpringApplication.scala | 4 +-- .../testkit/impl/TestKitActionContext.scala | 4 ++- .../action/ActionCreationContext.scala | 2 +- .../scalasdk/impl/action/ActionAdapters.scala | 7 +++-- 12 files changed, 45 insertions(+), 52 deletions(-) diff --git a/samples/java-protobuf-tracing/pom.xml b/samples/java-protobuf-tracing/pom.xml index c3d6978164..5a49dab534 100644 --- a/samples/java-protobuf-tracing/pom.xml +++ b/samples/java-protobuf-tracing/pom.xml @@ -219,7 +219,6 @@ maven-surefire-plugin 2.22.2 - -Dkalix.telemetry.tracing.collector-endpoint="http://localhost:4317" **/*IntegrationTest diff --git a/samples/scala-protobuf-tracing/src/main/protobuf/com/example/kalix_policy.proto b/samples/scala-protobuf-tracing/src/main/protobuf/com/example/kalix_policy.proto index 7d3ae35065..2ab8bc6882 100644 --- a/samples/scala-protobuf-tracing/src/main/protobuf/com/example/kalix_policy.proto +++ b/samples/scala-protobuf-tracing/src/main/protobuf/com/example/kalix_policy.proto @@ -10,5 +10,5 @@ import "kalix/annotations.proto"; // per component or method using annotations. // Documentation at https://docs.kalix.io/java-protobuf/access-control.html option (kalix.file).acl = { - allow: { service: "*" } + allow: { principal: INTERNET } }; \ No newline at end of file diff --git a/samples/scala-protobuf-tracing/src/main/resources/application.conf b/samples/scala-protobuf-tracing/src/main/resources/application.conf index 927bcdbf20..e69de29bb2 100644 --- a/samples/scala-protobuf-tracing/src/main/resources/application.conf +++ b/samples/scala-protobuf-tracing/src/main/resources/application.conf @@ -1 +0,0 @@ -kalix.telemetry.tracing.collector-endpoint="http://localhost:4317" \ No newline at end of file diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala index 015531ace2..c2e3b5e8ae 100644 --- a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala @@ -20,23 +20,24 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC // tag::create-close-span[] override def callAsyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { // tag::get-tracer[] - val tracerOpt = actionContext.getOpenTelemetryTracer + val tracer = actionContext.getTracer + println(tracer) // end::get-tracer[] - val span = tracerOpt.map(_.spanBuilder(s"$url/{}") + val span = tracer.spanBuilder(s"$url/{}") .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext)// <1> .startSpan()// <2> - .setAttribute("post", "1"))// <3> + .setAttribute("post", "1")// <3> val responseBody: Future[MessageResponse] = callAsync() responseBody.onComplete { case Failure(exception) => - span.map(_ + span .setStatus(StatusCode.ERROR, exception.getMessage)// <4> - .end())// <5> + .end()// <5> case Success(response) => - span.map(_ + span .setAttribute("result", response.message)// <3> - .end())// <5> + .end()// <5> } effects.asyncReply(responseBody) } diff --git a/sdk/java-sdk-protobuf-testkit/src/main/scala/kalix/javasdk/testkit/impl/TestKitActionContext.scala b/sdk/java-sdk-protobuf-testkit/src/main/scala/kalix/javasdk/testkit/impl/TestKitActionContext.scala index 84c0d96653..0f4a93948b 100644 --- a/sdk/java-sdk-protobuf-testkit/src/main/scala/kalix/javasdk/testkit/impl/TestKitActionContext.scala +++ b/sdk/java-sdk-protobuf-testkit/src/main/scala/kalix/javasdk/testkit/impl/TestKitActionContext.scala @@ -16,6 +16,7 @@ package kalix.javasdk.testkit.impl; +import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.api.trace.Tracer import kalix.javasdk.Metadata import kalix.javasdk.action.{ ActionContext, ActionCreationContext } @@ -48,4 +49,6 @@ final class TestKitActionContext(metadata: Metadata, mockRegistry: MockRegistry override def getGrpcClient[T](clientClass: Class[T], service: String): T = getComponentGrpcClient(clientClass) override def getOpenTelemetryTracer: Optional[Tracer] = Optional.empty() + + override def getTracer: Tracer = OpenTelemetry.noop().getTracer("noop") } diff --git a/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java b/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java index 644b5b5854..3b180a5a28 100644 --- a/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java +++ b/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java @@ -46,5 +46,8 @@ public interface ActionCreationContext extends Context { * * @return A tracer for the current action, if tracing is configured. */ + @Deprecated(since = "1.4.2") Optional getOpenTelemetryTracer(); + + Tracer getTracer(); } diff --git a/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/action/ActionsImpl.scala b/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/action/ActionsImpl.scala index 1ef2a637ba..8a112091dc 100644 --- a/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/action/ActionsImpl.scala +++ b/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/action/ActionsImpl.scala @@ -18,35 +18,25 @@ package kalix.javasdk.impl.action import akka.NotUsed import akka.actor.ActorSystem -import akka.stream.scaladsl.Sink -import akka.stream.scaladsl.Source +import akka.stream.scaladsl.{ Sink, Source } import com.google.protobuf.Descriptors import com.google.protobuf.any.Any import io.grpc.Status -import io.opentelemetry.api.trace.Span -import io.opentelemetry.api.trace.SpanContext -import io.opentelemetry.api.trace.Tracer +import io.opentelemetry.api.trace.{ Span, SpanContext, Tracer } import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator import kalix.javasdk._ import kalix.javasdk.action._ -import kalix.javasdk.impl.ActionFactory import kalix.javasdk.impl.ErrorHandling.BadRequestException import kalix.javasdk.impl._ import kalix.javasdk.impl.effect.EffectSupport.asProtocol +import kalix.javasdk.impl.telemetry.TraceInstrumentation.{ TRACE_PARENT_KEY, TRACE_STATE_KEY } import kalix.javasdk.impl.telemetry.{ ActionCategory, Instrumentation, Telemetry, TraceInstrumentation } -import kalix.javasdk.impl.telemetry.TraceInstrumentation.TRACE_PARENT_KEY -import kalix.javasdk.impl.telemetry.TraceInstrumentation.TRACE_STATE_KEY -import kalix.protocol.action.ActionCommand -import kalix.protocol.action.ActionResponse -import kalix.protocol.action.Actions +import kalix.protocol.action.{ ActionCommand, ActionResponse, Actions } import kalix.protocol.component -import kalix.protocol.component.Failure -import kalix.protocol.component.MetadataEntry -import org.slf4j.Logger -import org.slf4j.LoggerFactory +import kalix.protocol.component.{ Failure, MetadataEntry } +import org.slf4j.{ Logger, LoggerFactory } import java.util.Optional -import scala.collection.mutable import scala.compat.java8.OptionConverters.RichOptionForJava8 import scala.concurrent.Future import scala.jdk.CollectionConverters.SeqHasAsJava @@ -366,7 +356,7 @@ private[javasdk] final class ActionsImpl( serviceName: String): ActionContext = { val metadata = MetadataImpl.of(in.metadata.map(_.entries.toVector).getOrElse(Nil)) val updatedMetadata = spanContext.map(metadataWithTracing(metadata, _)).getOrElse(metadata) - new ActionContextImpl(updatedMetadata, messageCodec, system, serviceName, telemetries) + new ActionContextImpl(updatedMetadata, messageCodec, system, serviceName, telemetries(serviceName)) } private def metadataWithTracing(metadata: MetadataImpl, spanContext: SpanContext): Metadata = { @@ -396,7 +386,7 @@ class ActionContextImpl( val messageCodec: MessageCodec, val system: ActorSystem, serviceName: String, - telemetries: Map[String, Instrumentation]) + instrumentation: Instrumentation) extends AbstractContext(system) with ActionContext { @@ -422,6 +412,9 @@ class ActionContextImpl( } override def getOpenTelemetryTracer: Optional[Tracer] = - telemetries.get(serviceName).map(_.getTracer()).asJava + Option(instrumentation.getTracer).asJava + + override def getTracer: Tracer = + instrumentation.getTracer } diff --git a/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/telemetry/Telemetry.scala b/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/telemetry/Telemetry.scala index 71171e6295..ddce02fac6 100644 --- a/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/telemetry/Telemetry.scala +++ b/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/telemetry/Telemetry.scala @@ -16,16 +16,11 @@ package kalix.javasdk.impl.telemetry -import akka.actor.ActorSystem -import akka.actor.ExtendedActorSystem -import akka.actor.Extension -import akka.actor.ExtensionId +import akka.actor.{ ActorSystem, ExtendedActorSystem, Extension, ExtensionId } import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.api.common.Attributes -import io.opentelemetry.api.trace.Span -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.api.trace.Tracer import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator +import io.opentelemetry.api.trace.{ Span, SpanKind, Tracer } import io.opentelemetry.context.propagation.{ ContextPropagators, TextMapGetter, TextMapSetter } import io.opentelemetry.context.{ Context => OtelContext } import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter @@ -35,15 +30,12 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor import io.opentelemetry.semconv.resource.attributes.ResourceAttributes import kalix.javasdk.Metadata -import kalix.javasdk.impl.MetadataImpl -import kalix.javasdk.impl.ProxyInfoHolder -import kalix.javasdk.impl.Service +import kalix.javasdk.impl.{ MetadataImpl, ProxyInfoHolder, Service } import kalix.protocol.action.ActionCommand import kalix.protocol.component.MetadataEntry import kalix.protocol.component.MetadataEntry.Value.StringValue import kalix.protocol.entity.Command -import org.slf4j.Logger -import org.slf4j.LoggerFactory +import org.slf4j.{ Logger, LoggerFactory } import scala.collection.mutable import scala.concurrent.ExecutionContext @@ -114,7 +106,7 @@ trait Instrumentation { def buildSpan(service: Service, command: ActionCommand): Option[Span] - def getTracer(): Tracer + def getTracer: Tracer } @@ -219,7 +211,7 @@ private final class TraceInstrumentation( val context = openTelemetry.getPropagators.getTextMapPropagator .extract(OtelContext.current(), metadata, otelGetter) - val span = getTracer() + val span = getTracer .spanBuilder(command.name) .setParent(context) .setSpanKind(SpanKind.SERVER) @@ -235,7 +227,7 @@ private final class TraceInstrumentation( } // TODO: should this be specific per sdk? - override def getTracer(): Tracer = openTelemetry.getTracer("java-sdk") + override def getTracer: Tracer = openTelemetry.getTracer("java-sdk") } private object NoOpInstrumentation extends Instrumentation { @@ -244,5 +236,5 @@ private object NoOpInstrumentation extends Instrumentation { override def buildSpan(service: Service, command: ActionCommand): Option[Span] = None - override def getTracer(): Tracer = null + override def getTracer: Tracer = OpenTelemetry.noop().getTracer("noop") } diff --git a/sdk/java-sdk-spring/src/main/scala/kalix/spring/impl/KalixSpringApplication.scala b/sdk/java-sdk-spring/src/main/scala/kalix/spring/impl/KalixSpringApplication.scala index 1c6948c0f6..5d78143ea8 100644 --- a/sdk/java-sdk-spring/src/main/scala/kalix/spring/impl/KalixSpringApplication.scala +++ b/sdk/java-sdk-spring/src/main/scala/kalix/spring/impl/KalixSpringApplication.scala @@ -379,9 +379,7 @@ case class KalixSpringApplication(applicationContext: ApplicationContext, config case p if p == classOf[KalixClient] => kalixClient(context) case p if p == classOf[ComponentClient] => componentClient(context) case p if p == classOf[WebClientProvider] => webClientProvider(context) - case p if p == classOf[Tracer] => - context.getOpenTelemetryTracer.orElseGet(() => - throw new IllegalStateException("Tracer not available. Make sure to have enabled tracing.")) + case p if p == classOf[Tracer] => context.getTracer }) private def workflowProvider[S, W <: Workflow[S]](clz: Class[W]): WorkflowProvider[S, W] = { diff --git a/sdk/scala-sdk-protobuf-testkit/src/main/scala/kalix/scalasdk/testkit/impl/TestKitActionContext.scala b/sdk/scala-sdk-protobuf-testkit/src/main/scala/kalix/scalasdk/testkit/impl/TestKitActionContext.scala index f1290cb764..5267916d5c 100644 --- a/sdk/scala-sdk-protobuf-testkit/src/main/scala/kalix/scalasdk/testkit/impl/TestKitActionContext.scala +++ b/sdk/scala-sdk-protobuf-testkit/src/main/scala/kalix/scalasdk/testkit/impl/TestKitActionContext.scala @@ -16,6 +16,7 @@ package kalix.scalasdk.testkit.impl +import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.api.trace.Tracer import kalix.scalasdk.Metadata import kalix.scalasdk.action.ActionContext @@ -34,5 +35,6 @@ final class TestKitActionContext( override def eventSubject = metadata.get("ce-subject") override def getGrpcClient[T](clientClass: Class[T], service: String): T = getComponentGrpcClient(clientClass) - override def getOpenTelemetryTracer: Option[Tracer] = None + def getOpenTelemetryTracer: Option[Tracer] = None + override def getTracer: Tracer = OpenTelemetry.noop().getTracer("noop") } diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala index 83a9e02033..48207b9a1a 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/action/ActionCreationContext.scala @@ -41,5 +41,5 @@ trait ActionCreationContext extends Context { * @return * A tracer for the current action, if tracing is configured. */ - def getOpenTelemetryTracer: Option[Tracer] + def getTracer: Tracer } diff --git a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala index c3f8eb9dfb..3a47f04d34 100644 --- a/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala +++ b/sdk/scala-sdk-protobuf/src/main/scala/kalix/scalasdk/impl/action/ActionAdapters.scala @@ -141,7 +141,9 @@ private[scalasdk] final case class ScalaActionCreationContextAdapter( override def materializer(): Materializer = javaSdkCreationContext.materializer() - override def getOpenTelemetryTracer: Option[Tracer] = javaSdkCreationContext.getOpenTelemetryTracer.toScala + def getOpenTelemetryTracer: Option[Tracer] = javaSdkCreationContext.getOpenTelemetryTracer.toScala + + override def getTracer: Tracer = javaSdkCreationContext.getTracer } private[scalasdk] final case class ScalaActionContextAdapter(javaSdkContext: javasdk.action.ActionContext) @@ -173,5 +175,6 @@ private[scalasdk] final case class ScalaActionContextAdapter(javaSdkContext: jav } } - override def getOpenTelemetryTracer: Option[Tracer] = javaSdkContext.getOpenTelemetryTracer.toScala + override def getTracer: Tracer = javaSdkContext.getTracer + } From 5385b74eeb0bfbc71a2aef4d63d0e4f4421931f4 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Fri, 5 Apr 2024 11:24:29 +0700 Subject: [PATCH 11/17] docs and new implementation for the java-protobuf-tracing example --- .../modules/java-protobuf/pages/actions.adoc | 44 +++++++++++++------ samples/java-protobuf-tracing/README.md | 1 - samples/java-protobuf-tracing/pom.xml | 1 - .../java/com/example/ControllerAction.java | 24 +++------- .../main/proto/com/example/kalix_policy.proto | 2 +- .../scala/com/example/ControllerAction.scala | 1 - 6 files changed, 38 insertions(+), 35 deletions(-) diff --git a/docs/src/modules/java-protobuf/pages/actions.adoc b/docs/src/modules/java-protobuf/pages/actions.adoc index 981dd8043e..7daf3fdd5c 100644 --- a/docs/src/modules/java-protobuf/pages/actions.adoc +++ b/docs/src/modules/java-protobuf/pages/actions.adoc @@ -451,38 +451,57 @@ the `Counter` entity fails, the `Action` reply will succeed. == Adding tracing spans -To add spans in your actions you can add the tracer available in the link:{attachmentsdir}/api/kalix/javasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"] and with it create the span. - -To get the tracer from the link:{attachmentsdir}/api/kalix/javasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"]: +To create link:https://opentelemetry.io/docs/specs/otel/trace/api/#span[`spans`{tab-icon}, window="new"] in your actions you need the link:https://opentelemetry.io/docs/specs/otel/trace/api/#tracer[`tracer`{tab-icon}, window="new"] available through the `actionContext()` method. [.tabset] Java:: + -To add spans in your actions you can add the tracer available in the link:{attachmentsdir}/api/kalix/javasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"] and with it create the span. -+ -To get the tracer from the link:{attachmentsdir}/api/kalix/javasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"]: +`actionContext()` gives you the link:{attachmentsdir}/api/kalix/javasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"]. That is, some methods that apply to the context of the action's request. + [source,java,indent=0] .src/main/java/com/example/ControllerAction.java ---- include::example$java-protobuf-tracing/src/main/java/com/example/ControllerAction.java[tag=get-tracer] ---- -Note that if link:https://docs.kalix.io/operations/observability-exports.html#_activating_tracing_beta[tracing] is not enabled in your service this `Optional` will be empty. Otherwise you can map over `tracerOpt` to retreive the tracer. Scala:: + -To add spans in your actions you can add the tracer available in the link:{attachmentsdir}/scala-api/kalix/scalasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"] and with it create the span. -+ -To get the tracer from the link:{attachmentsdir}/scala-api/kalix/scalasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"]: +`actionContext()` gives you the link:{attachmentsdir}/scala-api/kalix/scalasdk/action/ActionContext.html[`ActionContext`{tab-icon}, window="new"]. That is, some methods that apply to the context of the action's request. + [source,java,indent=0] .src/main/scala/com/example/ControllerAction.java ---- include::example$scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala[tag=get-tracer] ---- -Note that if link:https://docs.kalix.io/operations/observability-exports.html#_activating_tracing_beta[tracing] is not enabled in your service this `Option` will be empty. Otherwise you can map over `tracerOpt` to use tracer. -To create a link:https://opentelemetry.io/docs/languages/java/instrumentation/#create-spans[span] and end it over an asynchronous call, you can do the following: +IMPORTANT: If your tracing is enabled, you will get a tracer that actually creates span and exports data as expected. +But if tracing is not enabled, you will get a no operational tracer instead, which will not create traces. + +Trace generation is disabled by default. To enable it in a service deployed to Kalix, see link:https://docs.kalix.io/operations/observability-exports.html#_activating_tracing_beta[`here`]. +To enable it in a service running on your local machine you need to add the following `JAVA_TOOL_OPTIONS` to your `docker-compose.yml` in the base of your project. +[source,yaml] +.docker-compose.yml +--- +services: + kalix-runtime: + image: ... + ... + environment: + JAVA_TOOL_OPTIONS: > + -Dkalix.proxy.telemetry.tracing.enabled=true + -Dkalix.proxy.telemetry.tracing.collector-endpoint=http://jaeger:4317 + ... + jaeger: + image: jaegertracing/all-in-one:1.54 + ports: + - 4317:4317 + - 16686:16686 +--- + +Here the traces are pushed to a `jaeger` docker image, to the port `4317`. And you can check them out at `http://localhost:16686`. + + +To link:https://opentelemetry.io/docs/languages/java/instrumentation/#create-spans[create a span] and end it over an asynchronous call, you can do the following: [.tabset] Java:: @@ -510,7 +529,6 @@ include::example$scala-protobuf-tracing/src/main/scala/com/example/ControllerAct <3> Adds some attribute. <4> Sets the status of the span as error. <5> Closes the span. - NOTE: You can find how tracing is enabled and more info link:https://docs.kalix.io/operations/observability-exports.html#_activating_tracing_beta[here]. == Unit testing the side effects diff --git a/samples/java-protobuf-tracing/README.md b/samples/java-protobuf-tracing/README.md index 717092c739..e86a320c1a 100644 --- a/samples/java-protobuf-tracing/README.md +++ b/samples/java-protobuf-tracing/README.md @@ -16,7 +16,6 @@ mvn kalix:runAll This command will start your Kalix service and a companion Kalix Runtime as configured in [docker-compose.yml](./docker-compose.yml) file. This will also start a Jaeger service to which the services above will push the traces. You can find Jaeger at `http://localhost:16686` - With both the Kalix Runtime and your service running, any defined endpoints should be available at `http://localhost:9000`. In addition to the defined gRPC interface, each method has a corresponding HTTP endpoint. Unless configured otherwise (see [Transcoding HTTP](https://docs.kalix.io/java-protobuf/writing-grpc-descriptors-protobuf.html#_transcoding_http)), this endpoint accepts POST requests at the path `/[package].[entity name]/[method]`. For example, using [`grpcurl`](https://github.com/fullstorydev/grpcurl): diff --git a/samples/java-protobuf-tracing/pom.xml b/samples/java-protobuf-tracing/pom.xml index 5a49dab534..9cd049bb0b 100644 --- a/samples/java-protobuf-tracing/pom.xml +++ b/samples/java-protobuf-tracing/pom.xml @@ -18,7 +18,6 @@ ${project.version}-${build.timestamp} yyyyMMddHHmmss com.example.Main - -Dkalix.telemetry.tracing.collector-endpoint=http://localhost:4317 17 UTF-8 diff --git a/samples/java-protobuf-tracing/src/main/java/com/example/ControllerAction.java b/samples/java-protobuf-tracing/src/main/java/com/example/ControllerAction.java index 0e3e84f7e1..fada8f7f59 100644 --- a/samples/java-protobuf-tracing/src/main/java/com/example/ControllerAction.java +++ b/samples/java-protobuf-tracing/src/main/java/com/example/ControllerAction.java @@ -1,24 +1,16 @@ package com.example; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Charsets; import com.google.protobuf.Empty; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Scope; import kalix.javasdk.action.ActionCreationContext; -import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.nio.charset.Charset; -import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; public class ControllerAction extends AbstractControllerAction { @@ -30,29 +22,25 @@ public ControllerAction(ActionCreationContext creationContext) {} @Override public Effect callAsyncEndpoint(Empty empty) { // tag::get-tracer[] - Optional tracerOpt = actionContext().getOpenTelemetryTracer(); + Tracer tracer = actionContext().getTracer(); // end::get-tracer[] - Optional span = tracerOpt.map(tracer -> { - return tracer + Span span = tracer .spanBuilder(url+"/{}") .setParent(actionContext().metadata().traceContext().asOpenTelemetryContext())// <1> .startSpan() // <2> .setAttribute("post", "1");// <3> - }); CompletableFuture asyncComputation = callAsyncService() .whenComplete((response, ex) -> { if (ex != null) { - span.ifPresent(presentSpan -> - presentSpan + span .setStatus(StatusCode.ERROR, ex.getMessage())// <4> - .end());// <5> + .end();// <5> } else { - span.ifPresent(presentSpan -> - presentSpan + span .setAttribute("result", response.body().title)// <3> - .end());// <5> + .end();// <5> } }) .thenApply(response -> diff --git a/samples/java-protobuf-tracing/src/main/proto/com/example/kalix_policy.proto b/samples/java-protobuf-tracing/src/main/proto/com/example/kalix_policy.proto index 7d3ae35065..2ab8bc6882 100644 --- a/samples/java-protobuf-tracing/src/main/proto/com/example/kalix_policy.proto +++ b/samples/java-protobuf-tracing/src/main/proto/com/example/kalix_policy.proto @@ -10,5 +10,5 @@ import "kalix/annotations.proto"; // per component or method using annotations. // Documentation at https://docs.kalix.io/java-protobuf/access-control.html option (kalix.file).acl = { - allow: { service: "*" } + allow: { principal: INTERNET } }; \ No newline at end of file diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala index c2e3b5e8ae..0f07753ae4 100644 --- a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala @@ -21,7 +21,6 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC override def callAsyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { // tag::get-tracer[] val tracer = actionContext.getTracer - println(tracer) // end::get-tracer[] val span = tracer.spanBuilder(s"$url/{}") .setParent(actionContext.metadata.traceContext.asOpenTelemetryContext)// <1> From 7cef2f97d6aa535714e410ae0a8636406cf5b5c2 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Fri, 5 Apr 2024 11:36:49 +0700 Subject: [PATCH 12/17] avoid validating local urls and validate back --- docs/config/validate-links.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/config/validate-links.json b/docs/config/validate-links.json index 962f6f9f54..ebe6803d8e 100644 --- a/docs/config/validate-links.json +++ b/docs/config/validate-links.json @@ -2,6 +2,7 @@ "ignorePatterns": [ { "pattern": "^https://mvnrepository\\.com" }, { "pattern": "^http://127.0.0.1:8080" }, - { "pattern": "^https://site\\.mockito\\.org/" } + { "pattern": "^http://jaeger:4317"}, + { "pattern": "^http://localhost:16686"} ] } From 67eb19f801a6db9da197db3ff17cecb8c1b9fea4 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Mon, 8 Apr 2024 12:37:51 +0700 Subject: [PATCH 13/17] Update docs/src/modules/java-protobuf/pages/actions.adoc Co-authored-by: Renato Cavalcanti --- docs/src/modules/java-protobuf/pages/actions.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/modules/java-protobuf/pages/actions.adoc b/docs/src/modules/java-protobuf/pages/actions.adoc index 7daf3fdd5c..210709bc3b 100644 --- a/docs/src/modules/java-protobuf/pages/actions.adoc +++ b/docs/src/modules/java-protobuf/pages/actions.adoc @@ -474,7 +474,7 @@ Scala:: include::example$scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala[tag=get-tracer] ---- -IMPORTANT: If your tracing is enabled, you will get a tracer that actually creates span and exports data as expected. +IMPORTANT: If tracing is enabled, you will get a tracer that actually creates span and exports data as expected. But if tracing is not enabled, you will get a no operational tracer instead, which will not create traces. Trace generation is disabled by default. To enable it in a service deployed to Kalix, see link:https://docs.kalix.io/operations/observability-exports.html#_activating_tracing_beta[`here`]. From 2d62791deea99a0d1c595d518aeb8f8a5743a73a Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Mon, 8 Apr 2024 12:38:55 +0700 Subject: [PATCH 14/17] Update samples/scala-protobuf-tracing/project/plugins.sbt Co-authored-by: Renato Cavalcanti --- samples/scala-protobuf-tracing/project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/scala-protobuf-tracing/project/plugins.sbt b/samples/scala-protobuf-tracing/project/plugins.sbt index 8e753b7224..2d5430a0a6 100644 --- a/samples/scala-protobuf-tracing/project/plugins.sbt +++ b/samples/scala-protobuf-tracing/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("io.kalix" % "sbt-kalix" % "1.4.1") +addSbtPlugin("io.kalix" % "sbt-kalix" % System.getProperty("kalix-sdk.version", "1.4.1")) addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.8.1") addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") From 5856ad06bf19afabf2cf2bf2dd0cc773c473ef39 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Mon, 8 Apr 2024 12:42:42 +0700 Subject: [PATCH 15/17] Update sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java Co-authored-by: Renato Cavalcanti --- .../java/kalix/javasdk/action/ActionCreationContext.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java b/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java index 3b180a5a28..ff7a1f5b12 100644 --- a/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java +++ b/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java @@ -49,5 +49,11 @@ public interface ActionCreationContext extends Context { @Deprecated(since = "1.4.2") Optional getOpenTelemetryTracer(); +/** + * Get an OpenTelemetry tracer for the current action. This will allow for building and automatic + * exporting of spans. + * + * @return A tracer for the current action, if tracing is configured. Otherwise, a noops tracer. + */ Tracer getTracer(); } From 0ac9bc8318e460b616c3e037c59c014202cf82f3 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Mon, 8 Apr 2024 15:10:10 +0700 Subject: [PATCH 16/17] addressing comments --- docs/src/modules/java-protobuf/pages/actions.adoc | 1 + .../main/java/kalix/javasdk/action/ActionCreationContext.java | 3 ++- .../main/scala/kalix/javasdk/impl/telemetry/Telemetry.scala | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/src/modules/java-protobuf/pages/actions.adoc b/docs/src/modules/java-protobuf/pages/actions.adoc index 210709bc3b..c867424fd2 100644 --- a/docs/src/modules/java-protobuf/pages/actions.adoc +++ b/docs/src/modules/java-protobuf/pages/actions.adoc @@ -479,6 +479,7 @@ But if tracing is not enabled, you will get a no operational tracer instead, whi Trace generation is disabled by default. To enable it in a service deployed to Kalix, see link:https://docs.kalix.io/operations/observability-exports.html#_activating_tracing_beta[`here`]. To enable it in a service running on your local machine you need to add the following `JAVA_TOOL_OPTIONS` to your `docker-compose.yml` in the base of your project. +It's also necessary to have where to collect the traces. For example, an extra container, like the `jaeger` below. [source,yaml] .docker-compose.yml --- diff --git a/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java b/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java index ff7a1f5b12..fae5845887 100644 --- a/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java +++ b/sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/action/ActionCreationContext.java @@ -44,12 +44,13 @@ public interface ActionCreationContext extends Context { * Get an OpenTelemetry tracer for the current action. This will allow for building and automatic * exporting of spans. * + * @deprecated use getTracer() instead. * @return A tracer for the current action, if tracing is configured. */ @Deprecated(since = "1.4.2") Optional getOpenTelemetryTracer(); -/** + /** * Get an OpenTelemetry tracer for the current action. This will allow for building and automatic * exporting of spans. * diff --git a/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/telemetry/Telemetry.scala b/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/telemetry/Telemetry.scala index ddce02fac6..19d9ec5a0c 100644 --- a/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/telemetry/Telemetry.scala +++ b/sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/telemetry/Telemetry.scala @@ -227,7 +227,7 @@ private final class TraceInstrumentation( } // TODO: should this be specific per sdk? - override def getTracer: Tracer = openTelemetry.getTracer("java-sdk") + override def getTracer: Tracer = openTelemetry.getTracer("kalix") } private object NoOpInstrumentation extends Instrumentation { From 2e73e56248e6b7ca19bc100e1bda68c3eedca589 Mon Sep 17 00:00:00 2001 From: Francisco Lopez-Sancho Date: Mon, 8 Apr 2024 15:47:59 +0700 Subject: [PATCH 17/17] simplifying request/response types --- .../main/scala/com/example/ControllerAction.scala | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala index 0f07753ae4..efb01a6543 100644 --- a/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala +++ b/samples/scala-protobuf-tracing/src/main/scala/com/example/ControllerAction.scala @@ -15,7 +15,7 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC implicit val formats: Formats = DefaultFormats - val url = "https://jsonplaceholder.typicode.com/posts" + val url = "https://jsonpilaceholder.typicode.com/posts" // tag::create-close-span[] override def callAsyncEndpoint(empty: Empty): Action.Effect[MessageResponse] = { @@ -44,16 +44,11 @@ class ControllerAction(creationContext: ActionCreationContext) extends AbstractC private def callAsync(): Future[MessageResponse] = { - val request = basicRequest.get(uri"${url}/1") - val response: Future[Response[Either[String, String]]] = request.send(Main.backend) + val request = quickRequest.get(uri"${url}/1") + val response = request.send(Main.backend) response.map { response => - response.body match { - case Left(resEx) => - MessageResponse(resEx) - case Right(value) => - val post = JsonMethods.parse(value).extract[Post] - MessageResponse(post.title) - } + val post = JsonMethods.parse(response.body).extract[Post] + MessageResponse(post.title) } } }