Skip to content

X-Ray#427

Merged
tpolecat merged 10 commits into
typelevel:series/0.1from
christiankjaer:xray
Nov 17, 2021
Merged

X-Ray#427
tpolecat merged 10 commits into
typelevel:series/0.1from
christiankjaer:xray

Conversation

@christiankjaer

@christiankjaer christiankjaer commented Sep 27, 2021

Copy link
Copy Markdown

Will serialize segments as described here and send them to the running X-Ray daemon as described here

Will not send child spans before the parent finishes.

@bpholt

bpholt commented Oct 29, 2021

Copy link
Copy Markdown
Member

This is pretty cool! I really want to use Natchez with AWS Lambda and X-Ray, and this seems more promising than trying to integrate with the X-Ray SDK, which (as you probably know) uses ThreadLocals on the JVM, and so is hard to make work correctly with cats-effect code.

Instead of using the JVM's DatagramSocket and InetAddress classes in the implementation, have you given any thought to using fs2's DatagramSocket and ip4s's Host or IpAddress? Those are both pure implementations (whereas I think there are some side effects not wrapped in Sync[F].delay here) and available for both JVM and Scala.js.

@christiankjaer

Copy link
Copy Markdown
Author

Hi @bpholt
That is definitely an option. I was just happy to get something working. I will try to update the implementation to make it a little more cats-y this weekend. Then maybe you will take a look at it, and we can get it merged so people can try it out?

@bpholt

bpholt commented Nov 9, 2021

Copy link
Copy Markdown
Member

@tpolecat When you get a chance, would you mind approving the GitHub Actions workflow for this PR? It'd be nice to get that feedback.

@armanbilge armanbilge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just some nits :)

Comment thread modules/xray/js/src/main/scala/natchez/xray/PlatformEnvironment.scala Outdated
) extends EntryPoint[F] {

def sendSegment(foo: JsonObject): F[Unit] = {
val payload = (XRayEntryPoint.header + foo.asJson.noSpaces).getBytes()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just a nit but I'm a big fan of scodec-bits methods for this e.g. encodeAscii or encodeUtf8:
https://github.com/scodec/scodec-bits/blob/main/core/shared/src/main/scala/scodec/bits/ByteVector.scala#L2105

Also, if you encode the header and json separately bytevector concatenation is faster than string concatenation :)

Comment thread modules/xray/shared/src/main/scala/natchez/xray/XRayEnvironment.scala Outdated
Comment thread modules/xray/shared/src/main/scala/natchez/xray/XRaySpan.scala
Comment on lines +70 to +84
"exceptions" -> Json.arr(
Json.obj(
"id" -> id.asJson,
"message" -> ex.getMessage.asJson,
"type" -> ex.getClass.getName.asJson,
"stack" -> ex
.getStackTrace
.map(x =>
Json.obj(
"line" -> x.getLineNumber.asJson,
"path" -> x.getFileName.asJson,
"label" -> x.getMethodName.asJson
)
)
.asJson

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe worth factoring this out into an Encoder[Exception] or something?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Well. The current design has the child segments as JsonObjects. Doing this in a more typed way requires a bigger refactor.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I mean, it doesn't need to be anything fancy. It's like declaring it its own method? Just wrap it up in an Encoder :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Isn't encoders implicitly encoding Json? Then I would need to convert it to a JsonObject again which might return None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Comment thread modules/xray/shared/src/main/scala/natchez/xray/XRaySpan.scala Outdated
Comment thread modules/xray/shared/src/main/scala/natchez/xray/XRaySpan.scala Outdated
Comment thread build.sbt Outdated
@tpolecat

Copy link
Copy Markdown
Member

Since it looks like you're still working on this I'm going to change it to a draft. Click the thing when it's ready and it looks like we can get it merged quickly. Thanks!

Christian Kjær Larsen added 2 commits November 12, 2021 09:58
@christiankjaer

Copy link
Copy Markdown
Author

@armanbilge @bpholt Could one of you guys take a quick look again before I set this as ready?

@armanbilge armanbilge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Personally I'd like to see #427 (comment) and #427 (comment) addressed. But I'm not a maintainer so no stake here :)

"Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1"
val notSampled = "Root=1-5759e988-bd862e3fe1be46a994272793;Sampled=0"

test("header parsing") {

@armanbilge armanbilge Nov 14, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe overkill, but since you have both header parsing/encoding we could have a scalacheck prop testing the roundtrip.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I thought about it. Came to the conclusion that it would be overkill.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FWIW I also think it's a good idea, but IMO it can come in a followup PR if someone wants to do it. Similarly there are places where it might be more clear to use JSON literals instead of constructing JSON objects from key/value pairs, but I think it'd be fine to do that later too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes I had the same thought about JSON literals :)

@armanbilge armanbilge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looking good to me, thanks for addressing all my feedback! Apologies since we dragged this a bit out from your original scope, but much appreciated 😁

@christiankjaer

christiankjaer commented Nov 14, 2021

Copy link
Copy Markdown
Author

@armanbilge Right. To give some context, the original implementation was just a simple adaption of the logging implementation, so the design choices are just stolen.

@christiankjaer
christiankjaer marked this pull request as ready for review November 14, 2021 20:43
@christiankjaer

Copy link
Copy Markdown
Author

@tpolecat
I think the arguments are all over now. You can merge it if you think it's alright.
Thanks!


override def kernelFromEnvironment: F[Kernel] =
OptionT(traceId)
.map(XRaySpan.XRayHeader(_, None, sampled = true).toKernel)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think I got this wrong in my pull request. It looks like traceId returns something like Root=1-619446d4-2de198a61485ba3d50bf889d;Parent=7c143d692eca32b3;Sampled=1 if _X_AMZN_TRACE_ID is present in the environment, meaning the value should be parsed instead of just dropped into a new XRayHeader directly.

Instead, I think this works:

OptionT(traceId)
  .subflatMap(XRaySpan.parseHeader)
  .map(_.toKernel)
  .getOrElse(Kernel(Map.empty))

.get(Header)
.flatMap(parseHeader)
.map(x => fromHeader(name, x, entry))
.get

@bpholt bpholt Nov 17, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this .get is unsafe because it may throw an exception outside the scope of the F[_] effect. One option would be to do something like this:

OptionT.fromOption[F](kernel.toHeaders.get(Header))
  .subflatMap(parseHeader)
  .semiflatMap(fromHeader(name, _, entry))
  .getOrElseF(new NoSuchElementException().raiseError)

Comment on lines +35 to +36
def continue(name: String, kernel: Kernel): Resource[F, Span[F]] =
make(XRaySpan.fromKernel(name, kernel, this))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I've been thinking about this, just a random (bad) idea/question:
Besides the kernel provided by the user, should this also attempt to source a kernel from the environment as a fallback? Isn't that also some form of "continuing"?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That's an interesting idea. Basically, look for the headers needed in the kernel provided by the user, and if they're missing, then check the environment? I think that could work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, I think this is worth pursuing, so that clients don't have to specialize to this backend by manually sourcing the kernel from the environment.

@tpolecat
tpolecat merged commit 48bf225 into typelevel:series/0.1 Nov 17, 2021
@armanbilge armanbilge mentioned this pull request Dec 13, 2021
6 tasks
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.

4 participants