diff --git a/lib/api/index.js b/lib/api/index.js index c09a921b..db26cbe1 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -6,7 +6,8 @@ const process = require("process"), pkg = require("../../package.json"), debug = require("debug")(`${pkg.name}:event`), LibhoneyImpl = require("./libhoney"), - MockImpl = require("./mock"); + MockImpl = require("./mock"), + utils = require("../util"); const { honeycomb, aws, w3c, util } = propagation; @@ -38,6 +39,9 @@ module.exports = { } debug(`using impl: ${impl == LibhoneyImpl ? "libhoney-event" : "mock"}`); apiImpl = new impl(opts); + + // tell honeycomb propagator whether we should propagate dataset + honeycomb.setPropagateDataset(utils.isClassic(opts.writeKey)); }, traceActive() { diff --git a/lib/api/index.test.js b/lib/api/index.test.js index 1c46d0ff..ffd00437 100644 --- a/lib/api/index.test.js +++ b/lib/api/index.test.js @@ -10,17 +10,103 @@ jest.mock("../deterministic_sampler"); beforeEach(() => { api._resetForTesting(); - api.configure({ impl: "libhoney-event", transmission: "mock", writeKey: "abc123" }); + api.configure({ + impl: "libhoney-event", + transmission: "mock", + writeKey: "e38be416d0d68f9ed1e96432ac1a3380", + }); }); -test("libhoney default config", () => { +test("libhoney default config - classic", () => { const honey = api._apiForTesting().honey; expect(honey.transmission.constructorArg.apiHost).toBe("https://api.honeycomb.io"); expect(honey.transmission.constructorArg.dataset).toBe("nodejs"); - expect(honey.transmission.constructorArg.writeKey).toBe("abc123"); + expect(honey.transmission.constructorArg.writeKey).toBe("e38be416d0d68f9ed1e96432ac1a3380"); + expect(honey.transmission.constructorArg.userAgentAddition).toBe( + `honeycomb-beeline/${pkg.version}` + ); + expect(honey._builder._fields["service_name"]).toBe("unknown_service:nodejs"); + expect(honey._builder._fields["service.name"]).toBe("unknown_service:nodejs"); +}); + +test("libhoney default config - non-classic", () => { + api._resetForTesting(); + api.configure({ + impl: "libhoney-event", + transmission: "mock", + writeKey: "d68f9ed1e96432ac1a3380", + }); + + const honey = api._apiForTesting().honey; + expect(honey.transmission.constructorArg.apiHost).toBe("https://api.honeycomb.io"); + expect(honey.transmission.constructorArg.dataset).toBe("unknown_service"); + expect(honey.transmission.constructorArg.writeKey).toBe("d68f9ed1e96432ac1a3380"); + expect(honey.transmission.constructorArg.userAgentAddition).toBe( + `honeycomb-beeline/${pkg.version}` + ); + expect(honey._builder._fields["service_name"]).toBe("unknown_service:nodejs"); + expect(honey._builder._fields["service.name"]).toBe("unknown_service:nodejs"); +}); + +test("libhoney config - non-classic - empty service name", () => { + api._resetForTesting(); + api.configure({ + impl: "libhoney-event", + transmission: "mock", + writeKey: "d68f9ed1e96432ac1a3380", + serviceName: "", + }); + + const honey = api._apiForTesting().honey; + expect(honey.transmission.constructorArg.apiHost).toBe("https://api.honeycomb.io"); + expect(honey.transmission.constructorArg.dataset).toBe("unknown_service"); + expect(honey.transmission.constructorArg.writeKey).toBe("d68f9ed1e96432ac1a3380"); + expect(honey.transmission.constructorArg.userAgentAddition).toBe( + `honeycomb-beeline/${pkg.version}` + ); + expect(honey._builder._fields["service_name"]).toBe("unknown_service:nodejs"); + expect(honey._builder._fields["service.name"]).toBe("unknown_service:nodejs"); +}); + +test("libhoney config - non-classic - whitespace service name", () => { + api._resetForTesting(); + api.configure({ + impl: "libhoney-event", + transmission: "mock", + writeKey: "d68f9ed1e96432ac1a3380", + serviceName: " " + }); + + const honey = api._apiForTesting().honey; + expect(honey.transmission.constructorArg.apiHost).toBe("https://api.honeycomb.io"); + expect(honey.transmission.constructorArg.dataset).toBe("unknown_service"); + expect(honey.transmission.constructorArg.writeKey).toBe("d68f9ed1e96432ac1a3380"); + expect(honey.transmission.constructorArg.userAgentAddition).toBe( + `honeycomb-beeline/${pkg.version}` + ); + console.log(honey._builder._fields["service_name"]); + expect(honey._builder._fields["service_name"]).toBe(" "); + expect(honey._builder._fields["service.name"]).toBe(" "); +}); + +test("libhoney config - non-classic - custom service name", () => { + api._resetForTesting(); + api.configure({ + impl: "libhoney-event", + transmission: "mock", + writeKey: "d68f9ed1e96432ac1a3380", + serviceName: " my-service ", + }); + + const honey = api._apiForTesting().honey; + expect(honey.transmission.constructorArg.apiHost).toBe("https://api.honeycomb.io"); + expect(honey.transmission.constructorArg.dataset).toBe("my-service"); + expect(honey.transmission.constructorArg.writeKey).toBe("d68f9ed1e96432ac1a3380"); expect(honey.transmission.constructorArg.userAgentAddition).toBe( `honeycomb-beeline/${pkg.version}` ); + expect(honey._builder._fields["service_name"]).toBe(" my-service "); + expect(honey._builder._fields["service.name"]).toBe(" my-service "); }); test("startTrace starts tracking and creates an initial event, finishTrace sends it", () => { diff --git a/lib/api/libhoney.js b/lib/api/libhoney.js index 01e19122..78620c94 100644 --- a/lib/api/libhoney.js +++ b/lib/api/libhoney.js @@ -10,7 +10,8 @@ const libhoney = require("libhoney"), schema = require("../schema"), Span = require("./span"), pkg = require("../../package.json"), - debug = require("debug")(`${pkg.name}:event`); + debug = require("debug")(`${pkg.name}:event`), + util = require("../util"); const defaultName = "nodejs"; @@ -81,7 +82,38 @@ module.exports = class LibhoneyEventAPI { debug(`using proxy ${proxy}`); } - this.defaultDataset = opts.dataset || process.env["HONEYCOMB_DATASET"] || defaultName; + if (!opts.serviceName) { + opts.serviceName = ["unknown_service", process.name ? process.name.trim() : "nodejs"].join(":"); + console.warn(`empty serviceName configuration option - setting service name to '${opts.serviceName}'`); + } + + if (!opts.writeKey) { + console.warn("empty writeKey configuration option"); + } + + if (util.isClassic(opts.writeKey)) { + let dataset = opts.dataset || process.env["HONEYCOMB_DATASET"]; + if (!dataset || dataset.trim() === "") { + dataset = defaultName; + console.warn(`empty dataset configuration option - setting to '${dataset}'`); + } else { + dataset = dataset.trim(); + } + + this.defaultDataset = dataset; + } else { + if (opts.serviceName !== opts.serviceName.trim()) { + console.warn(`service name contains whitespace '${opts.serviceName}'`); + } + // if servicename is empty (whitespace) or starts with "unknown_service", use "unknown_service" + // or use trimmed service name + this.defaultDataset = opts.serviceName.startsWith("unknown_service") || opts.serviceName.trim() === "" + ? "unknown_service" + : opts.serviceName.trim(); + if (opts.dataset) { + console.warn(`dataset should be empty - sending data to '${this.defaultDataset}'`); + } + } const libhoneyOpts = getFilteredOptions(opts); @@ -105,6 +137,7 @@ module.exports = class LibhoneyEventAPI { this.honey.add({ [schema.HOSTNAME]: os.hostname(), [schema.TRACE_SERVICE_NAME]: opts.serviceName, + [schema.TRACE_SERVICE_DOT_NAME]: opts.serviceName, }); } diff --git a/lib/propagation/honeycomb.js b/lib/propagation/honeycomb.js index 23975ee5..29490388 100644 --- a/lib/propagation/honeycomb.js +++ b/lib/propagation/honeycomb.js @@ -9,6 +9,13 @@ exports.TRACE_HTTP_HEADER = TRACE_HTTP_HEADER; const VERSION = "1"; exports.VERSION = VERSION; +let propagateDataset = true; + +exports.setPropagateDataset = setPropagateDataset; +function setPropagateDataset(enabled) { + propagateDataset = enabled; +} + // assumes a header of the form: // VERSION;PAYLOAD @@ -38,7 +45,7 @@ function marshalTraceContextv1(context) { let dataset = context.dataset; let datasetClause = ""; - if (dataset) { + if (propagateDataset && dataset) { datasetClause = `dataset=${encodeURIComponent(dataset)},`; } @@ -84,7 +91,9 @@ function unmarshalTraceContextv1(payload) { parentSpanId = v; break; case "dataset": - dataset = decodeURIComponent(v); + if (propagateDataset) { + dataset = decodeURIComponent(v); + } break; case "context": contextb64 = v; diff --git a/lib/propagation/honeycomb.test.js b/lib/propagation/honeycomb.test.js index 45b6f80e..cd0f16c1 100644 --- a/lib/propagation/honeycomb.test.js +++ b/lib/propagation/honeycomb.test.js @@ -80,7 +80,7 @@ cases( ); describe("roundtrip", () => { - test("works", () => { + test("classic", () => { let contextStr = honeycomb.marshalTraceContext(testContext); expect(honeycomb.unmarshalTraceContext(contextStr)).toEqual({ traceId: "abcdef123456", @@ -93,4 +93,18 @@ describe("roundtrip", () => { }, }); }); + + test("non-classic", () => { + honeycomb.setPropagateDataset(false); + let contextStr = honeycomb.marshalTraceContext(testContext); + expect(honeycomb.unmarshalTraceContext(contextStr)).toEqual({ + traceId: "abcdef123456", + parentSpanId: "0102030405", + customContext: { + userID: 1, + errorMsg: "failed to sign on", + toRetry: true, + }, + }); + }); }); diff --git a/lib/schema.js b/lib/schema.js index 4849c152..a634ec09 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -14,6 +14,7 @@ exports.TRACE_ID_SOURCE = "trace.trace_id_source"; exports.TRACE_PARENT_ID = "trace.parent_id"; exports.TRACE_SPAN_ID = "trace.span_id"; exports.TRACE_SERVICE_NAME = "service_name"; +exports.TRACE_SERVICE_DOT_NAME = "service.name"; exports.TRACE_SPAN_NAME = "name"; // custom context fields will have this prefix diff --git a/lib/util.js b/lib/util.js index 603d723e..fbe47aca 100644 --- a/lib/util.js +++ b/lib/util.js @@ -17,3 +17,9 @@ function captureStackTrace(skipFrames = 0, limitFrames = 10) { // the +1 here to get rid of the `Error\n` line at the top of the stacktrace. return frames.slice(1 + skipFrames).join("\n"); } + +exports.isClassic = isClassic; + +function isClassic(writeKey) { + return !writeKey || writeKey.length == 32; +} \ No newline at end of file