autotel-devtools 4.0.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -5,9 +5,30 @@ var ws = require('ws');
5
5
  var fs = require('fs');
6
6
  var path = require('path');
7
7
  var url = require('url');
8
+ var protobuf = require('protobufjs');
8
9
  var core = require('@opentelemetry/core');
9
10
 
10
11
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
12
+ function _interopNamespace(e) {
13
+ if (e && e.__esModule) return e;
14
+ var n = Object.create(null);
15
+ if (e) {
16
+ Object.keys(e).forEach(function (k) {
17
+ if (k !== 'default') {
18
+ var d = Object.getOwnPropertyDescriptor(e, k);
19
+ Object.defineProperty(n, k, d.get ? d : {
20
+ enumerable: true,
21
+ get: function () { return e[k]; }
22
+ });
23
+ }
24
+ });
25
+ }
26
+ n.default = e;
27
+ return Object.freeze(n);
28
+ }
29
+
30
+ var protobuf__namespace = /*#__PURE__*/_interopNamespace(protobuf);
31
+
11
32
  // src/index.ts
12
33
 
13
34
  // src/server/error-aggregator.ts
@@ -530,7 +551,7 @@ var SPAN_KIND_MAP = {
530
551
  function normalizeHexId(id) {
531
552
  if (!id) return "";
532
553
  const isBase64Like = /^[A-Za-z0-9+/=]+$/.test(id) && !/^[0-9a-f]+$/i.test(id);
533
- const isLikelyBase64Id = isBase64Like && (id.length === 24 || id.length === 28 || id.length === 44 || id.length === 48);
554
+ const isLikelyBase64Id = isBase64Like && (id.length === 12 || id.length === 24 || id.length === 28 || id.length === 44 || id.length === 48);
534
555
  if (isLikelyBase64Id) {
535
556
  try {
536
557
  const bytes = Buffer.from(id, "base64");
@@ -662,13 +683,255 @@ async function readJsonBody(req) {
662
683
  req.on("error", reject);
663
684
  });
664
685
  }
686
+ async function readRawBody(req) {
687
+ return new Promise((resolve2, reject) => {
688
+ const chunks = [];
689
+ req.on("data", (chunk) => chunks.push(chunk));
690
+ req.on("end", () => resolve2(Buffer.concat(chunks)));
691
+ req.on("error", reject);
692
+ });
693
+ }
694
+ function isProtobufContentType(contentType) {
695
+ if (!contentType) return false;
696
+ const value = contentType.toLowerCase();
697
+ return value.includes("application/x-protobuf") || value.includes("application/protobuf");
698
+ }
665
699
  function sendJson(res, status, data) {
666
700
  const body = JSON.stringify(data);
667
701
  res.writeHead(status, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) });
668
702
  res.end(body);
669
703
  }
704
+ var COMMON_PROTO = `
705
+ syntax = "proto3";
706
+ package opentelemetry.proto.common.v1;
707
+
708
+ message AnyValue {
709
+ oneof value {
710
+ string string_value = 1;
711
+ bool bool_value = 2;
712
+ int64 int_value = 3;
713
+ double double_value = 4;
714
+ ArrayValue array_value = 5;
715
+ KeyValueList kvlist_value = 6;
716
+ bytes bytes_value = 7;
717
+ }
718
+ }
719
+ message ArrayValue { repeated AnyValue values = 1; }
720
+ message KeyValueList { repeated KeyValue values = 1; }
721
+ message KeyValue {
722
+ string key = 1;
723
+ AnyValue value = 2;
724
+ }
725
+ message InstrumentationScope {
726
+ string name = 1;
727
+ string version = 2;
728
+ repeated KeyValue attributes = 3;
729
+ uint32 dropped_attributes_count = 4;
730
+ }
731
+ `;
732
+ var RESOURCE_PROTO = `
733
+ syntax = "proto3";
734
+ package opentelemetry.proto.resource.v1;
735
+
736
+ message Resource {
737
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 1;
738
+ uint32 dropped_attributes_count = 2;
739
+ }
740
+ `;
741
+ var TRACE_PROTO = `
742
+ syntax = "proto3";
743
+ package opentelemetry.proto.trace.v1;
744
+
745
+ message ResourceSpans {
746
+ opentelemetry.proto.resource.v1.Resource resource = 1;
747
+ repeated ScopeSpans scope_spans = 2;
748
+ string schema_url = 3;
749
+ }
750
+ message ScopeSpans {
751
+ opentelemetry.proto.common.v1.InstrumentationScope scope = 1;
752
+ repeated Span spans = 2;
753
+ string schema_url = 3;
754
+ }
755
+ message Span {
756
+ bytes trace_id = 1;
757
+ bytes span_id = 2;
758
+ string trace_state = 3;
759
+ bytes parent_span_id = 4;
760
+ fixed32 flags = 16;
761
+ string name = 5;
762
+ SpanKind kind = 6;
763
+ fixed64 start_time_unix_nano = 7;
764
+ fixed64 end_time_unix_nano = 8;
765
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 9;
766
+ uint32 dropped_attributes_count = 10;
767
+ repeated Event events = 11;
768
+ uint32 dropped_events_count = 12;
769
+ repeated Link links = 13;
770
+ uint32 dropped_links_count = 14;
771
+ Status status = 15;
772
+
773
+ enum SpanKind {
774
+ SPAN_KIND_UNSPECIFIED = 0;
775
+ SPAN_KIND_INTERNAL = 1;
776
+ SPAN_KIND_SERVER = 2;
777
+ SPAN_KIND_CLIENT = 3;
778
+ SPAN_KIND_PRODUCER = 4;
779
+ SPAN_KIND_CONSUMER = 5;
780
+ }
781
+ message Event {
782
+ fixed64 time_unix_nano = 1;
783
+ string name = 2;
784
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 3;
785
+ uint32 dropped_attributes_count = 4;
786
+ }
787
+ message Link {
788
+ bytes trace_id = 1;
789
+ bytes span_id = 2;
790
+ string trace_state = 3;
791
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 4;
792
+ uint32 dropped_attributes_count = 5;
793
+ fixed32 flags = 6;
794
+ }
795
+ }
796
+ message Status {
797
+ reserved 1;
798
+ string message = 2;
799
+ StatusCode code = 3;
800
+
801
+ enum StatusCode {
802
+ STATUS_CODE_UNSET = 0;
803
+ STATUS_CODE_OK = 1;
804
+ STATUS_CODE_ERROR = 2;
805
+ }
806
+ }
807
+ message ExportTraceServiceRequest {
808
+ repeated ResourceSpans resource_spans = 1;
809
+ }
810
+ `;
811
+ var LOGS_PROTO = `
812
+ syntax = "proto3";
813
+ package opentelemetry.proto.logs.v1;
814
+
815
+ enum SeverityNumber {
816
+ SEVERITY_NUMBER_UNSPECIFIED = 0;
817
+ SEVERITY_NUMBER_TRACE = 1;
818
+ SEVERITY_NUMBER_TRACE2 = 2;
819
+ SEVERITY_NUMBER_TRACE3 = 3;
820
+ SEVERITY_NUMBER_TRACE4 = 4;
821
+ SEVERITY_NUMBER_DEBUG = 5;
822
+ SEVERITY_NUMBER_DEBUG2 = 6;
823
+ SEVERITY_NUMBER_DEBUG3 = 7;
824
+ SEVERITY_NUMBER_DEBUG4 = 8;
825
+ SEVERITY_NUMBER_INFO = 9;
826
+ SEVERITY_NUMBER_INFO2 = 10;
827
+ SEVERITY_NUMBER_INFO3 = 11;
828
+ SEVERITY_NUMBER_INFO4 = 12;
829
+ SEVERITY_NUMBER_WARN = 13;
830
+ SEVERITY_NUMBER_WARN2 = 14;
831
+ SEVERITY_NUMBER_WARN3 = 15;
832
+ SEVERITY_NUMBER_WARN4 = 16;
833
+ SEVERITY_NUMBER_ERROR = 17;
834
+ SEVERITY_NUMBER_ERROR2 = 18;
835
+ SEVERITY_NUMBER_ERROR3 = 19;
836
+ SEVERITY_NUMBER_ERROR4 = 20;
837
+ SEVERITY_NUMBER_FATAL = 21;
838
+ SEVERITY_NUMBER_FATAL2 = 22;
839
+ SEVERITY_NUMBER_FATAL3 = 23;
840
+ SEVERITY_NUMBER_FATAL4 = 24;
841
+ }
842
+ message ResourceLogs {
843
+ opentelemetry.proto.resource.v1.Resource resource = 1;
844
+ repeated ScopeLogs scope_logs = 2;
845
+ string schema_url = 3;
846
+ }
847
+ message ScopeLogs {
848
+ opentelemetry.proto.common.v1.InstrumentationScope scope = 1;
849
+ repeated LogRecord log_records = 2;
850
+ string schema_url = 3;
851
+ }
852
+ message LogRecord {
853
+ reserved 4;
854
+ fixed64 time_unix_nano = 1;
855
+ fixed64 observed_time_unix_nano = 11;
856
+ SeverityNumber severity_number = 2;
857
+ string severity_text = 3;
858
+ opentelemetry.proto.common.v1.AnyValue body = 5;
859
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 6;
860
+ uint32 dropped_attributes_count = 7;
861
+ fixed32 flags = 8;
862
+ bytes trace_id = 9;
863
+ bytes span_id = 10;
864
+ }
865
+ message ExportLogsServiceRequest {
866
+ repeated ResourceLogs resource_logs = 1;
867
+ }
868
+ `;
869
+ var METRICS_PROTO = `
870
+ syntax = "proto3";
871
+ package opentelemetry.proto.metrics.v1;
872
+
873
+ message ResourceMetrics {
874
+ opentelemetry.proto.resource.v1.Resource resource = 1;
875
+ repeated ScopeMetrics scope_metrics = 2;
876
+ string schema_url = 3;
877
+ }
878
+ message ScopeMetrics {
879
+ opentelemetry.proto.common.v1.InstrumentationScope scope = 1;
880
+ repeated Metric metrics = 2;
881
+ string schema_url = 3;
882
+ }
883
+ message Metric {
884
+ string name = 1;
885
+ string description = 2;
886
+ string unit = 3;
887
+ }
888
+ message ExportMetricsServiceRequest {
889
+ repeated ResourceMetrics resource_metrics = 1;
890
+ }
891
+ `;
892
+ var TO_OBJECT_OPTIONS = {
893
+ longs: String,
894
+ bytes: String,
895
+ defaults: false
896
+ };
897
+ var cachedRoot = null;
898
+ function getRoot() {
899
+ if (cachedRoot) return cachedRoot;
900
+ const root = new protobuf__namespace.Root();
901
+ for (const source of [COMMON_PROTO, RESOURCE_PROTO, TRACE_PROTO, LOGS_PROTO, METRICS_PROTO]) {
902
+ protobuf__namespace.parse(source, root, { keepCase: false });
903
+ }
904
+ root.resolveAll();
905
+ cachedRoot = root;
906
+ return root;
907
+ }
908
+ function decodeRequest(typeName, body) {
909
+ const messageType = getRoot().lookupType(typeName);
910
+ const message = messageType.decode(body);
911
+ return messageType.toObject(message, TO_OBJECT_OPTIONS);
912
+ }
913
+ function decodeOtlpTraceRequest(body) {
914
+ return decodeRequest("opentelemetry.proto.trace.v1.ExportTraceServiceRequest", body);
915
+ }
916
+ function decodeOtlpLogsRequest(body) {
917
+ return decodeRequest("opentelemetry.proto.logs.v1.ExportLogsServiceRequest", body);
918
+ }
919
+ function decodeOtlpMetricsRequest(body) {
920
+ return decodeRequest("opentelemetry.proto.metrics.v1.ExportMetricsServiceRequest", body);
921
+ }
670
922
 
671
923
  // src/server/http.ts
924
+ var PROTOBUF_DECODERS = {
925
+ traces: decodeOtlpTraceRequest,
926
+ logs: decodeOtlpLogsRequest,
927
+ metrics: decodeOtlpMetricsRequest
928
+ };
929
+ async function readOtlpPayload(req, signal) {
930
+ if (isProtobufContentType(req.headers["content-type"])) {
931
+ return PROTOBUF_DECODERS[signal](await readRawBody(req));
932
+ }
933
+ return readJsonBody(req);
934
+ }
672
935
  function findPackageRoot() {
673
936
  let dir = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
674
937
  for (let i = 0; i < 5; i++) {
@@ -702,7 +965,7 @@ function getWidgetJs() {
702
965
  function attachDevtoolsRoutes(httpServer, devtools) {
703
966
  httpServer.on("request", async (req, res) => {
704
967
  res.setHeader("Access-Control-Allow-Origin", "*");
705
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
968
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
706
969
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
707
970
  if (req.method === "OPTIONS") {
708
971
  res.writeHead(204);
@@ -725,41 +988,99 @@ function attachDevtoolsRoutes(httpServer, devtools) {
725
988
  sendJson(res, 200, { ok: true, clients: devtools.clientCount });
726
989
  return;
727
990
  }
991
+ if (req.method === "GET" && url === "/v1/traces") {
992
+ const data = devtools.getCurrentData();
993
+ sendJson(res, 200, { traces: data.traces, count: data.traces.length });
994
+ return;
995
+ }
996
+ if (req.method === "DELETE" && url === "/v1/traces") {
997
+ devtools.clearData();
998
+ sendJson(res, 200, { cleared: true });
999
+ return;
1000
+ }
728
1001
  if (req.method === "POST" && url === "/v1/traces") {
729
1002
  try {
730
- const payload = await readJsonBody(req);
1003
+ const payload = await readOtlpPayload(req, "traces");
731
1004
  const traces = parseOtlpTraces(payload);
732
1005
  devtools.addTraces(traces);
733
1006
  sendJson(res, 200, { acceptedTraces: traces.length });
734
1007
  } catch (e) {
735
- sendJson(res, 400, { error: "Invalid OTLP JSON", message: e instanceof Error ? e.message : String(e) });
1008
+ sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
736
1009
  }
737
1010
  return;
738
1011
  }
739
1012
  if (req.method === "POST" && url === "/v1/logs") {
740
1013
  try {
741
- const payload = await readJsonBody(req);
1014
+ const payload = await readOtlpPayload(req, "logs");
742
1015
  const logs = parseOtlpLogs(payload);
743
1016
  devtools.addLogs(logs);
744
1017
  sendJson(res, 200, { acceptedLogs: logs.length });
745
1018
  } catch (e) {
746
- sendJson(res, 400, { error: "Invalid OTLP JSON", message: e instanceof Error ? e.message : String(e) });
1019
+ sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
747
1020
  }
748
1021
  return;
749
1022
  }
750
1023
  if (req.method === "POST" && url === "/v1/metrics") {
751
1024
  try {
752
- const payload = await readJsonBody(req);
1025
+ const payload = await readOtlpPayload(req, "metrics");
753
1026
  const count = countOtlpMetrics(payload);
754
1027
  sendJson(res, 200, { acceptedMetrics: count });
755
1028
  } catch (e) {
756
- sendJson(res, 400, { error: "Invalid OTLP JSON", message: e instanceof Error ? e.message : String(e) });
1029
+ sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
757
1030
  }
758
1031
  return;
759
1032
  }
760
1033
  sendJson(res, 404, { error: "Not found" });
761
1034
  });
762
1035
  }
1036
+ var LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
1037
+ function formatAddress(host, port) {
1038
+ return host.includes(":") ? `[${host}]:${port}` : `${host}:${port}`;
1039
+ }
1040
+ function listenLoopbackDualStack(args) {
1041
+ const { primary, port, host, attachSecondary } = args;
1042
+ let sibling;
1043
+ const ready = new Promise(
1044
+ (resolve2) => {
1045
+ const addresses = [];
1046
+ const warnings = [];
1047
+ const primaryHost = host === "localhost" ? "127.0.0.1" : host;
1048
+ primary.listen(port, primaryHost, () => {
1049
+ const addr = primary.address();
1050
+ const resolvedPort = addr && typeof addr === "object" ? addr.port : port;
1051
+ addresses.push(formatAddress(primaryHost, resolvedPort));
1052
+ if (!LOOPBACK.has(host)) {
1053
+ resolve2({ addresses, warnings });
1054
+ return;
1055
+ }
1056
+ const siblingHost = primaryHost === "::1" ? "127.0.0.1" : "::1";
1057
+ const s = http.createServer();
1058
+ attachSecondary(s);
1059
+ const onError = (e) => {
1060
+ s.close();
1061
+ warnings.push(
1062
+ `could not also bind ${formatAddress(siblingHost, resolvedPort)} (${e.message}); clients using the ${siblingHost === "::1" ? "IPv6" : "IPv4"} form of "localhost" may not connect.`
1063
+ );
1064
+ resolve2({ addresses, warnings });
1065
+ };
1066
+ s.once("error", onError);
1067
+ s.listen(resolvedPort, siblingHost, () => {
1068
+ s.off("error", onError);
1069
+ sibling = s;
1070
+ addresses.push(formatAddress(siblingHost, resolvedPort));
1071
+ resolve2({ addresses, warnings });
1072
+ });
1073
+ });
1074
+ }
1075
+ );
1076
+ return {
1077
+ ready,
1078
+ closeSibling: () => new Promise((res) => {
1079
+ if (!sibling) return res();
1080
+ sibling.close(() => res());
1081
+ })
1082
+ };
1083
+ }
763
1084
 
764
1085
  // src/server/exporter.ts
765
1086
  var DevtoolsSpanExporter = class {
@@ -1222,7 +1543,17 @@ function createDevtools(options = {}) {
1222
1543
  maxMetricCount: options.maxMetricCount
1223
1544
  });
1224
1545
  attachDevtoolsRoutes(httpServer, wsServer);
1225
- httpServer.listen(port, host);
1546
+ const listeners = listenLoopbackDualStack({
1547
+ primary: httpServer,
1548
+ port,
1549
+ host,
1550
+ attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer)
1551
+ });
1552
+ if (options.verbose) {
1553
+ listeners.ready.then(({ warnings }) => {
1554
+ for (const w of warnings) console.warn(`[autotel-devtools] ${w}`);
1555
+ });
1556
+ }
1226
1557
  const exporter = new DevtoolsSpanExporter(wsServer);
1227
1558
  return {
1228
1559
  server: wsServer,
@@ -1231,6 +1562,7 @@ function createDevtools(options = {}) {
1231
1562
  port,
1232
1563
  close: async () => {
1233
1564
  await wsServer.close();
1565
+ await listeners.closeSibling();
1234
1566
  }
1235
1567
  };
1236
1568
  }