X-Ray#427
Conversation
|
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 Instead of using the JVM's |
|
Hi @bpholt |
|
@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. |
| ) extends EntryPoint[F] { | ||
|
|
||
| def sendSegment(foo: JsonObject): F[Unit] = { | ||
| val payload = (XRayEntryPoint.header + foo.asJson.noSpaces).getBytes() |
There was a problem hiding this comment.
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 :)
| "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 |
There was a problem hiding this comment.
Maybe worth factoring this out into an Encoder[Exception] or something?
There was a problem hiding this comment.
Well. The current design has the child segments as JsonObjects. Doing this in a more typed way requires a bigger refactor.
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
Isn't encoders implicitly encoding Json? Then I would need to convert it to a JsonObject again which might return None
There was a problem hiding this comment.
|
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! |
…stem environment (which is useful in Lambda functions)
|
@armanbilge @bpholt Could one of you guys take a quick look again before I set this as ready? |
There was a problem hiding this comment.
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") { |
There was a problem hiding this comment.
Maybe overkill, but since you have both header parsing/encoding we could have a scalacheck prop testing the roundtrip.
There was a problem hiding this comment.
I thought about it. Came to the conclusion that it would be overkill.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Yes I had the same thought about JSON literals :)
armanbilge
left a comment
There was a problem hiding this comment.
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 😁
|
@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. |
|
@tpolecat |
|
|
||
| override def kernelFromEnvironment: F[Kernel] = | ||
| OptionT(traceId) | ||
| .map(XRaySpan.XRayHeader(_, None, sampled = true).toKernel) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)| def continue(name: String, kernel: Kernel): Resource[F, Span[F]] = | ||
| make(XRaySpan.fromKernel(name, kernel, this)) |
There was a problem hiding this comment.
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"?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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.