Repository: honeycombio/beeline-nodejs
Pull request #228 (2020-07-22 05:23:03)

add aws marshal function and test
Realized we need an aws _marshal_ function as well! I mostly was aiming to translate this recent addition to the [go beeline](https://github.com/honeycombio/beeline-go/blob/main/propagation/amazon.go#L17), just translated to javascript!

katiebayes: Re-requesting because the changes to aws unmarshal are non-negligible. Lemme know what you think of this, see comments for a couple things I'm not sure on.
katiebayes: I'm actually switching back to the object, because I think returning only `parentSpanId` and `traceId` rather than adding `customContext: {}` when it's undefined is more consistent with how we handle other potentially undefined parsed fields 

Patch:
diff --git a/lib/api/index.js b/lib/api/index.js
index 29a16c1..dbafb0b 100644
--- a/lib/api/index.js
+++ b/lib/api/index.js
@@ -67,6 +67,7 @@ module.exports = {
   aws: {
     TRACE_HTTP_HEADER: aws.TRACE_HTTP_HEADER,
     unmarshalTraceContext: aws.unmarshalTraceContext,
+    marshalTraceContext: aws.marshalTraceContext,
   },
 
   w3c: {
diff --git a/lib/propagation/aws.js b/lib/propagation/aws.js
index 6722b0c..9fadbae 100644
--- a/lib/propagation/aws.js
+++ b/lib/propagation/aws.js
@@ -1,12 +1,33 @@
-/* global exports */
+/* global exports, require */
+const util = require("./util");
+
 const TRACE_HTTP_HEADER = "X-Amzn-Trace-Id";
 exports.TRACE_HTTP_HEADER = TRACE_HTTP_HEADER;
 
+// marshalTraceContext takes a trace context object
+// and returns a serialized honeycomb trace header
+exports.marshalTraceContext = marshalTraceContextv1;
+
+function marshalTraceContextv1(context) {
+  let traceId = context.id;
+  let spanId = util.currentSpanId(context);
+  let contextToSend = context.traceContext || {};
+
+  if (context.traceContext) {
+    const elements = Object.keys(context.traceContext).map(key => {
+      return `${key}=${context.traceContext[key]}`;
+    });
+    contextToSend = ";" + elements.join(";");
+  }
+
+  return `Root=1-${traceId};Parent=${spanId}${contextToSend}`;
+}
+
 // unmarshalTraceContext takes a string trace header and returns a context
 exports.unmarshalTraceContext = unmarshalTraceContext;
 
 function unmarshalTraceContext(header) {
-  let traceId, parentSpanId;
+  let traceId, parentSpanId, customContext;
 
   const split = header.split(";");
   for (const s of split) {
@@ -15,6 +36,10 @@ function unmarshalTraceContext(header) {
       traceId = value;
     } else if (name === "Parent") {
       parentSpanId = value;
+    } else {
+      customContext = Object.assign({}, customContext, {
+        [name]: value,
+      });
     }
   }
 
@@ -30,5 +55,6 @@ function unmarshalTraceContext(header) {
   return {
     traceId,
     parentSpanId,
+    customContext,
   };
 }


Test patch:
diff --git a/lib/instrumentation/trace-util.test.js b/lib/instrumentation/trace-util.test.js
index 3867984..eac842f 100644
--- a/lib/instrumentation/trace-util.test.js
+++ b/lib/instrumentation/trace-util.test.js
@@ -43,9 +43,9 @@ describe("getTraceContext", () => {
       {
         name: "self / root / no parent",
         headerVal:
-          "Self=1-5983f5c9-36d365bc453d28036a63032b;Root=1-5983f5c9-56dcf0bc6d4d214d2dbbe8c6",
+          "Parent=1-5983f5c9-36d365bc453d28036a63032b;Root=1-5983f5c9-56dcf0bc6d4d214d2dbbe8c6",
         expectedContext: {
-          parentSpanId: "1-5983f5c9-56dcf0bc6d4d214d2dbbe8c6",
+          parentSpanId: "1-5983f5c9-36d365bc453d28036a63032b",
           traceId: "1-5983f5c9-56dcf0bc6d4d214d2dbbe8c6",
           source: "X-Amzn-Trace-Id http header",
         },
diff --git a/lib/propagation/propagation.test.js b/lib/propagation/propagation.test.js
index f17fe3a..14bd01f 100644
--- a/lib/propagation/propagation.test.js
+++ b/lib/propagation/propagation.test.js
@@ -4,7 +4,7 @@ const propagation = require("."),
   Span = require("../api/span"),
   cases = require("jest-in-case");
 
-const { honeycomb, w3c } = propagation;
+const { honeycomb, w3c, aws } = propagation;
 // this context structure is the same as what the libhoney event api implementation generate.
 let testContext = {
   id: "abcdef123456",
@@ -25,6 +25,56 @@ describe("marshaling", () => {
   });
 });
 
+describe("marshaling aws", () => {
+  test("version string prefix", () => {
+    expect(aws.marshalTraceContext(testContext)).toEqual(
+      "Root=1-abcdef123456;Parent=0102030405;userID=1;errorMsg=failed to sign on;toRetry=true"
+    );
+  });
+});
+
+cases(
+  "unmarshaling aws",
+  opts => expect(aws.unmarshalTraceContext(opts.contextStr)).toEqual(opts.value),
+  [
+    {
+      name: "aws header with trace context",
+      contextStr:
+        "Root=1-abcdef123456;Parent=0102030405;userID=1;errorMsg=failed to sign on;toRetry=true",
+      value: {
+        traceId: "1-abcdef123456",
+        parentSpanId: "0102030405",
+        customContext: {
+          userID: "1",
+          errorMsg: "failed to sign on",
+          toRetry: "true",
+        },
+      },
+    },
+    {
+      name: "aws header with no trace context",
+      contextStr: "Root=1-abcdef123456;Parent=0102030405",
+      value: {
+        traceId: "1-abcdef123456",
+        parentSpanId: "0102030405",
+      },
+    },
+    {
+      name: "aws header missing root",
+      contextStr: "Root=;Parent=0102030405",
+      value: undefined,
+    },
+    {
+      name: "aws header with no parent span id, no trace context",
+      contextStr: "Root=1-abcdef123456",
+      value: {
+        traceId: "1-abcdef123456",
+        parentSpanId: "1-abcdef123456",
+      },
+    },
+  ]
+);
+
 cases(
   "unmarshaling",
   opts => expect(propagation.unmarshalTraceContext(opts.contextStr)).toEqual(opts.value),