Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New modules + span context maybe? #236

Merged
merged 16 commits into from
Oct 3, 2023
Merged

Conversation

sherriesyt
Copy link
Contributor

Draft PR as usual! New modules added to the build file.

Feedback welcome; I took a guess at what to do here (and a very big guess at possible dependencies, though, I'll know as I go?)

I also figured "add a new module to the build file" and "implement my first thing (span context)?" could go hand-in-hand for my first PR, because with some implementation, I'll better know some of those dependencies (and also because the former, by itself, felt a bit small for one PR. Though let me know if we should get that merged first anyway, and I'll separate the concerns).

Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 module setup looks good!

build.sbt Outdated
Comment on lines 241 to 263
lazy val `sdk-common` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("sdk/common"))
.dependsOn(`core-common`)
.settings(
name := "otel4s-sdk-common",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion,
"org.typelevel" %%% "cats-mtl" % CatsMtlVersion,
"org.scalameta" %%% "munit" % MUnitVersion % Test
),
)

lazy val `sdk-trace` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("sdk/trace"))
.dependsOn(`sdk-common`, `core-trace`)
.settings(
name := "otel4s-sdk-trace",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect" % CatsEffectVersion,
),
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we can add munitDependencies to both modules. You are planning to write tests for tracing right 😜

Suggested change
lazy val `sdk-common` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("sdk/common"))
.dependsOn(`core-common`)
.settings(
name := "otel4s-sdk-common",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion,
"org.typelevel" %%% "cats-mtl" % CatsMtlVersion,
"org.scalameta" %%% "munit" % MUnitVersion % Test
),
)
lazy val `sdk-trace` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("sdk/trace"))
.dependsOn(`sdk-common`, `core-trace`)
.settings(
name := "otel4s-sdk-trace",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect" % CatsEffectVersion,
),
)
lazy val `sdk-common` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("sdk/common"))
.dependsOn(`core-common`)
.settings(
name := "otel4s-sdk-common",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion,
"org.typelevel" %%% "cats-mtl" % CatsMtlVersion
),
)
.settings(munitDependencies)
lazy val `sdk-trace` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("sdk/trace"))
.dependsOn(`sdk-common`, `core-trace`)
.settings(
name := "otel4s-sdk-trace",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect" % CatsEffectVersion,
),
)
.settings(munitDependencies)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, just saw this. Will revise to add :)

@sherriesyt
Copy link
Contributor Author

I wanted to clarify -- is an implementation of SpanContext supposed to sit in our new SDK? I thought "yes," at first, but I did find this line in the OTel specs for a span context, so I wanted to clarify:

The API MUST implement methods to create a SpanContext. These methods SHOULD be the only way to create a SpanContext. This functionality MUST be fully implemented in the API, and SHOULD NOT be overridable.

Is the implementation instead going to sit in our existing SpanContext.scala file? I see that right now we have the creation of an "invalid" SpanContext there, but I'm wondering where else we're meant to implement the SpanContext trait (in the SDK, in an existing file, elsewhere?) now.

* limitations under the License.
*/

package org.typelevel.otel4s // ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be the appropriate package name?

Suggested change
package org.typelevel.otel4s // ?
package org.typelevel.otel4s.sdk.trace


import org.typelevel.otel4s.trace.SpanContext

final class SpanContextImpl extends SpanContext { // does a impl belong in the sdk?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can make this a case class with all the fields below as its members. And actually we can probably keep the class private, since users will always work with the SpanContext interface.

@armanbilge armanbilge mentioned this pull request Jun 5, 2023
9 tasks
@armanbilge
Copy link
Member

Ohhhh, very good catch 🤔

I wanted to clarify -- is an implementation of SpanContext supposed to sit in our new SDK? I thought "yes," at first, but I did find this line in the OTel specs for a span context, so I wanted to clarify:

The API MUST implement methods to create a SpanContext. These methods SHOULD be the only way to create a SpanContext. This functionality MUST be fully implemented in the API, and SHOULD NOT be overridable.

That does sound like it should be implemented in the API, and not in the SDK. That seems fine for the Scala SDK, but will it complicate the Java wrapper?

@sherriesyt
Copy link
Contributor Author

Wondering if some work here rightfully belongs in #243 instead. Will make new PR and shift some changes over there, if that's appropriate

@armanbilge
Copy link
Member

Will make new PR and shift some changes over there, if that's appropriate

👍 yes definitely, I think the SpanContextImpl work should go in the API modules and target the main branch. Thanks!

build.sbt Outdated
)
.settings(munitDependencies)

lazy val `sdk-trace` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I forgot, you also need to add these to the "root aggregate" somewhere higher up in the build. I think that's why CI appears to be passing, because it's not actually compiling these sources yet 😅

samplingDecision: SamplingDecision,
isValid: Boolean,
isRemote: Boolean,
storeInContext: Vault
Copy link
Contributor

@iRevive iRevive Jun 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SpanContext defines storeInContext as follows:

def storeInContext(context: Vault): Vault =
  context.insert(SpanContext.key, this)

Should we make it final and remove it from the SpanContextImpl? I guess it should work unless there will be a custom logic.

Comment on lines 105 to 108
def samplingDecision: SamplingDecision =
SamplingDecision.fromBoolean(
(flags & SpanContext.SampledMask) == SpanContext.SampledMask
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iRevive I'm a little confused if this is the right API to expose here.

I see SamplingDecision is described here, which is actually an enum with three states.
https://opentelemetry.io/docs/specs/otel/trace/sdk/#shouldsample

But this seems to be different from the Sampled flag, which is only a Boolean and is described here.
https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling

I see that the Java API exposes a Boolean on SpanContext, even though they also have a SamplingDecision enum.

lazy val samplingDecision: SamplingDecision =
SamplingDecision.fromBoolean(jSpanContext.isSampled)

@sherriesyt sherriesyt changed the base branch from sdk to main July 1, 2023 03:20
Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nearly there! Just needs a couple tests, some bike shed, some polish, and a fight with CI 😅

build.sbt Outdated
"org.typelevel" %%% "cats-mtl" % CatsMtlVersion,
),
)
.settings(munitDependencies)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add .enablePlugins(NoPublishPlugin) for sdk-common and sdk-trace until we are ready to publish them.

Comment on lines 101 to 108
def isValidSpanId(id: ByteVector): Boolean =
(id.length == SpanContext.SpanId.HexLength) && (id != SpanContext.invalid.spanId)

/** Checks whether a trace id has correct length and is not the
* invalid id.
*/
def isValidTraceId(id: ByteVector): Boolean =
(id.length == SpanContext.TraceId.HexLength) && (id != SpanContext.invalid.traceId)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are some good methods to have some tests for.


sealed trait TraceFlags {
def toByte: Byte
def isSampled: Boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think isSampled is another good one to test. e.g. check that it's value is expected for fromByte(0) and fromByte(1) and maybe a couple other random values ...

private[trace] object WrappedSpanContext {
private[java] object SpanConversions {

def wrap(jSpanContext: JSpanContext): SpanContext = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe fromJSpanContext, toJSpanContext?

Comment on lines 53 to 62
lazy val traceId: ByteVector =
ByteVector(jSpanContext.getTraceIdBytes)

lazy val spanId: ByteVector =
ByteVector(jSpanContext.getSpanIdBytes)

val traceFlags = TraceFlags.fromByte(jSpanContext.getTraceFlags().asByte())

def isRemote: Boolean =
jSpanContext.isRemote
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably these can all be vals now.

Comment on lines 69 to 71
context match {
case ctx: WrappedSpanContext =>
ctx.jSpanContext

case other if other.isRemote =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we no longer need a match, we can directly call methods on context.

@armanbilge armanbilge added this to the 0.3 milestone Jul 15, 2023
@armanbilge armanbilge mentioned this pull request Oct 1, 2023
9 tasks
# Conflicts:
#	.github/workflows/ci.yml
#	java/trace/src/main/scala/org/typelevel/otel4s/java/ContextConversions.scala
@iRevive iRevive marked this pull request as ready for review October 3, 2023 14:36
@iRevive iRevive merged commit cc7e98a into typelevel:main Oct 3, 2023
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants