autotel-devtools 5.0.0 → 5.1.0

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/README.md CHANGED
@@ -16,9 +16,9 @@ Standalone OTLP receiver with web UI for local development. Think TanStack Devto
16
16
  │ npx autotel-devtools │
17
17
  │ ┌───────────────────────────────────────┐ │
18
18
  │ │ HTTP Server (port 4318) │ │
19
- │ │ ├── POST /v1/traces ← OTLP JSON │ │
20
- │ │ ├── POST /v1/logs ← OTLP JSON │ │
21
- │ │ ├── POST /v1/metrics ← OTLP JSON │ │
19
+ │ │ ├── POST /v1/traces ← OTLP JSON/PB │ │
20
+ │ │ ├── POST /v1/logs ← OTLP JSON/PB │ │
21
+ │ │ ├── POST /v1/metrics ← OTLP JSON/PB │ │
22
22
  │ │ ├── GET / → Full page UI │ │
23
23
  │ │ ├── GET /widget.js → Widget bundle │ │
24
24
  │ │ ├── GET /healthz → Health check │ │
@@ -41,6 +41,16 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
41
41
  node app.js
42
42
  ```
43
43
 
44
+ The endpoints accept **both OTLP/JSON and OTLP/protobuf** (`application/x-protobuf`),
45
+ selected automatically from the request `Content-Type`. That means SDKs that default
46
+ to protobuf over OTLP HTTP — including the Python, Java, and Go OpenTelemetry SDKs —
47
+ work without any extra configuration:
48
+
49
+ ```bash
50
+ # Python / Java / Go SDKs default to http/protobuf — just point them at the receiver
51
+ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 python app.py
52
+ ```
53
+
44
54
  Open http://localhost:4318 to see traces, logs, and metrics.
45
55
 
46
56
  ### Embedded Widget
@@ -100,7 +110,7 @@ const myFunction = trace((ctx) => async () => {
100
110
  ### Server (Node.js)
101
111
 
102
112
  - **DevtoolsServer** - WebSocket server + in-memory data store
103
- - **HTTP Routes** - OTLP receivers for traces/logs/metrics
113
+ - **HTTP Routes** - OTLP receivers for traces/logs/metrics (JSON + protobuf)
104
114
  - **Exporters** - OpenTelemetry span/log exporters
105
115
 
106
116
  ### Widget (Preact)
package/dist/cli.cjs CHANGED
@@ -6,8 +6,13 @@ var fs = require('fs');
6
6
  var path = require('path');
7
7
  var url = require('url');
8
8
  var ws = require('ws');
9
+ var protobuf = require('protobufjs');
9
10
 
10
11
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ var protobuf__default = /*#__PURE__*/_interopDefault(protobuf);
15
+
11
16
  // src/server/error-aggregator.ts
12
17
  var ErrorAggregator = class {
13
18
  errorGroups = /* @__PURE__ */ new Map();
@@ -528,7 +533,7 @@ var SPAN_KIND_MAP = {
528
533
  function normalizeHexId(id) {
529
534
  if (!id) return "";
530
535
  const isBase64Like = /^[A-Za-z0-9+/=]+$/.test(id) && !/^[0-9a-f]+$/i.test(id);
531
- const isLikelyBase64Id = isBase64Like && (id.length === 24 || id.length === 28 || id.length === 44 || id.length === 48);
536
+ const isLikelyBase64Id = isBase64Like && (id.length === 12 || id.length === 24 || id.length === 28 || id.length === 44 || id.length === 48);
532
537
  if (isLikelyBase64Id) {
533
538
  try {
534
539
  const bytes = Buffer.from(id, "base64");
@@ -660,13 +665,255 @@ async function readJsonBody(req) {
660
665
  req.on("error", reject);
661
666
  });
662
667
  }
668
+ async function readRawBody(req) {
669
+ return new Promise((resolve3, reject) => {
670
+ const chunks = [];
671
+ req.on("data", (chunk) => chunks.push(chunk));
672
+ req.on("end", () => resolve3(Buffer.concat(chunks)));
673
+ req.on("error", reject);
674
+ });
675
+ }
676
+ function isProtobufContentType(contentType) {
677
+ if (!contentType) return false;
678
+ const value = contentType.toLowerCase();
679
+ return value.includes("application/x-protobuf") || value.includes("application/protobuf");
680
+ }
663
681
  function sendJson(res, status, data) {
664
682
  const body = JSON.stringify(data);
665
683
  res.writeHead(status, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) });
666
684
  res.end(body);
667
685
  }
686
+ var COMMON_PROTO = `
687
+ syntax = "proto3";
688
+ package opentelemetry.proto.common.v1;
689
+
690
+ message AnyValue {
691
+ oneof value {
692
+ string string_value = 1;
693
+ bool bool_value = 2;
694
+ int64 int_value = 3;
695
+ double double_value = 4;
696
+ ArrayValue array_value = 5;
697
+ KeyValueList kvlist_value = 6;
698
+ bytes bytes_value = 7;
699
+ }
700
+ }
701
+ message ArrayValue { repeated AnyValue values = 1; }
702
+ message KeyValueList { repeated KeyValue values = 1; }
703
+ message KeyValue {
704
+ string key = 1;
705
+ AnyValue value = 2;
706
+ }
707
+ message InstrumentationScope {
708
+ string name = 1;
709
+ string version = 2;
710
+ repeated KeyValue attributes = 3;
711
+ uint32 dropped_attributes_count = 4;
712
+ }
713
+ `;
714
+ var RESOURCE_PROTO = `
715
+ syntax = "proto3";
716
+ package opentelemetry.proto.resource.v1;
717
+
718
+ message Resource {
719
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 1;
720
+ uint32 dropped_attributes_count = 2;
721
+ }
722
+ `;
723
+ var TRACE_PROTO = `
724
+ syntax = "proto3";
725
+ package opentelemetry.proto.trace.v1;
726
+
727
+ message ResourceSpans {
728
+ opentelemetry.proto.resource.v1.Resource resource = 1;
729
+ repeated ScopeSpans scope_spans = 2;
730
+ string schema_url = 3;
731
+ }
732
+ message ScopeSpans {
733
+ opentelemetry.proto.common.v1.InstrumentationScope scope = 1;
734
+ repeated Span spans = 2;
735
+ string schema_url = 3;
736
+ }
737
+ message Span {
738
+ bytes trace_id = 1;
739
+ bytes span_id = 2;
740
+ string trace_state = 3;
741
+ bytes parent_span_id = 4;
742
+ fixed32 flags = 16;
743
+ string name = 5;
744
+ SpanKind kind = 6;
745
+ fixed64 start_time_unix_nano = 7;
746
+ fixed64 end_time_unix_nano = 8;
747
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 9;
748
+ uint32 dropped_attributes_count = 10;
749
+ repeated Event events = 11;
750
+ uint32 dropped_events_count = 12;
751
+ repeated Link links = 13;
752
+ uint32 dropped_links_count = 14;
753
+ Status status = 15;
754
+
755
+ enum SpanKind {
756
+ SPAN_KIND_UNSPECIFIED = 0;
757
+ SPAN_KIND_INTERNAL = 1;
758
+ SPAN_KIND_SERVER = 2;
759
+ SPAN_KIND_CLIENT = 3;
760
+ SPAN_KIND_PRODUCER = 4;
761
+ SPAN_KIND_CONSUMER = 5;
762
+ }
763
+ message Event {
764
+ fixed64 time_unix_nano = 1;
765
+ string name = 2;
766
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 3;
767
+ uint32 dropped_attributes_count = 4;
768
+ }
769
+ message Link {
770
+ bytes trace_id = 1;
771
+ bytes span_id = 2;
772
+ string trace_state = 3;
773
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 4;
774
+ uint32 dropped_attributes_count = 5;
775
+ fixed32 flags = 6;
776
+ }
777
+ }
778
+ message Status {
779
+ reserved 1;
780
+ string message = 2;
781
+ StatusCode code = 3;
782
+
783
+ enum StatusCode {
784
+ STATUS_CODE_UNSET = 0;
785
+ STATUS_CODE_OK = 1;
786
+ STATUS_CODE_ERROR = 2;
787
+ }
788
+ }
789
+ message ExportTraceServiceRequest {
790
+ repeated ResourceSpans resource_spans = 1;
791
+ }
792
+ `;
793
+ var LOGS_PROTO = `
794
+ syntax = "proto3";
795
+ package opentelemetry.proto.logs.v1;
796
+
797
+ enum SeverityNumber {
798
+ SEVERITY_NUMBER_UNSPECIFIED = 0;
799
+ SEVERITY_NUMBER_TRACE = 1;
800
+ SEVERITY_NUMBER_TRACE2 = 2;
801
+ SEVERITY_NUMBER_TRACE3 = 3;
802
+ SEVERITY_NUMBER_TRACE4 = 4;
803
+ SEVERITY_NUMBER_DEBUG = 5;
804
+ SEVERITY_NUMBER_DEBUG2 = 6;
805
+ SEVERITY_NUMBER_DEBUG3 = 7;
806
+ SEVERITY_NUMBER_DEBUG4 = 8;
807
+ SEVERITY_NUMBER_INFO = 9;
808
+ SEVERITY_NUMBER_INFO2 = 10;
809
+ SEVERITY_NUMBER_INFO3 = 11;
810
+ SEVERITY_NUMBER_INFO4 = 12;
811
+ SEVERITY_NUMBER_WARN = 13;
812
+ SEVERITY_NUMBER_WARN2 = 14;
813
+ SEVERITY_NUMBER_WARN3 = 15;
814
+ SEVERITY_NUMBER_WARN4 = 16;
815
+ SEVERITY_NUMBER_ERROR = 17;
816
+ SEVERITY_NUMBER_ERROR2 = 18;
817
+ SEVERITY_NUMBER_ERROR3 = 19;
818
+ SEVERITY_NUMBER_ERROR4 = 20;
819
+ SEVERITY_NUMBER_FATAL = 21;
820
+ SEVERITY_NUMBER_FATAL2 = 22;
821
+ SEVERITY_NUMBER_FATAL3 = 23;
822
+ SEVERITY_NUMBER_FATAL4 = 24;
823
+ }
824
+ message ResourceLogs {
825
+ opentelemetry.proto.resource.v1.Resource resource = 1;
826
+ repeated ScopeLogs scope_logs = 2;
827
+ string schema_url = 3;
828
+ }
829
+ message ScopeLogs {
830
+ opentelemetry.proto.common.v1.InstrumentationScope scope = 1;
831
+ repeated LogRecord log_records = 2;
832
+ string schema_url = 3;
833
+ }
834
+ message LogRecord {
835
+ reserved 4;
836
+ fixed64 time_unix_nano = 1;
837
+ fixed64 observed_time_unix_nano = 11;
838
+ SeverityNumber severity_number = 2;
839
+ string severity_text = 3;
840
+ opentelemetry.proto.common.v1.AnyValue body = 5;
841
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 6;
842
+ uint32 dropped_attributes_count = 7;
843
+ fixed32 flags = 8;
844
+ bytes trace_id = 9;
845
+ bytes span_id = 10;
846
+ }
847
+ message ExportLogsServiceRequest {
848
+ repeated ResourceLogs resource_logs = 1;
849
+ }
850
+ `;
851
+ var METRICS_PROTO = `
852
+ syntax = "proto3";
853
+ package opentelemetry.proto.metrics.v1;
854
+
855
+ message ResourceMetrics {
856
+ opentelemetry.proto.resource.v1.Resource resource = 1;
857
+ repeated ScopeMetrics scope_metrics = 2;
858
+ string schema_url = 3;
859
+ }
860
+ message ScopeMetrics {
861
+ opentelemetry.proto.common.v1.InstrumentationScope scope = 1;
862
+ repeated Metric metrics = 2;
863
+ string schema_url = 3;
864
+ }
865
+ message Metric {
866
+ string name = 1;
867
+ string description = 2;
868
+ string unit = 3;
869
+ }
870
+ message ExportMetricsServiceRequest {
871
+ repeated ResourceMetrics resource_metrics = 1;
872
+ }
873
+ `;
874
+ var TO_OBJECT_OPTIONS = {
875
+ longs: String,
876
+ bytes: String,
877
+ defaults: false
878
+ };
879
+ var cachedRoot = null;
880
+ function getRoot() {
881
+ if (cachedRoot) return cachedRoot;
882
+ const root = new protobuf__default.default.Root();
883
+ for (const source of [COMMON_PROTO, RESOURCE_PROTO, TRACE_PROTO, LOGS_PROTO, METRICS_PROTO]) {
884
+ protobuf__default.default.parse(source, root, { keepCase: false });
885
+ }
886
+ root.resolveAll();
887
+ cachedRoot = root;
888
+ return root;
889
+ }
890
+ function decodeRequest(typeName, body) {
891
+ const messageType = getRoot().lookupType(typeName);
892
+ const message = messageType.decode(body);
893
+ return messageType.toObject(message, TO_OBJECT_OPTIONS);
894
+ }
895
+ function decodeOtlpTraceRequest(body) {
896
+ return decodeRequest("opentelemetry.proto.trace.v1.ExportTraceServiceRequest", body);
897
+ }
898
+ function decodeOtlpLogsRequest(body) {
899
+ return decodeRequest("opentelemetry.proto.logs.v1.ExportLogsServiceRequest", body);
900
+ }
901
+ function decodeOtlpMetricsRequest(body) {
902
+ return decodeRequest("opentelemetry.proto.metrics.v1.ExportMetricsServiceRequest", body);
903
+ }
668
904
 
669
905
  // src/server/http.ts
906
+ var PROTOBUF_DECODERS = {
907
+ traces: decodeOtlpTraceRequest,
908
+ logs: decodeOtlpLogsRequest,
909
+ metrics: decodeOtlpMetricsRequest
910
+ };
911
+ async function readOtlpPayload(req, signal) {
912
+ if (isProtobufContentType(req.headers["content-type"])) {
913
+ return PROTOBUF_DECODERS[signal](await readRawBody(req));
914
+ }
915
+ return readJsonBody(req);
916
+ }
670
917
  function findPackageRoot() {
671
918
  let dir = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
672
919
  for (let i = 0; i < 5; i++) {
@@ -735,33 +982,33 @@ function attachDevtoolsRoutes(httpServer, devtools) {
735
982
  }
736
983
  if (req.method === "POST" && url === "/v1/traces") {
737
984
  try {
738
- const payload = await readJsonBody(req);
985
+ const payload = await readOtlpPayload(req, "traces");
739
986
  const traces = parseOtlpTraces(payload);
740
987
  devtools.addTraces(traces);
741
988
  sendJson(res, 200, { acceptedTraces: traces.length });
742
989
  } catch (e) {
743
- sendJson(res, 400, { error: "Invalid OTLP JSON", message: e instanceof Error ? e.message : String(e) });
990
+ sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
744
991
  }
745
992
  return;
746
993
  }
747
994
  if (req.method === "POST" && url === "/v1/logs") {
748
995
  try {
749
- const payload = await readJsonBody(req);
996
+ const payload = await readOtlpPayload(req, "logs");
750
997
  const logs = parseOtlpLogs(payload);
751
998
  devtools.addLogs(logs);
752
999
  sendJson(res, 200, { acceptedLogs: logs.length });
753
1000
  } catch (e) {
754
- sendJson(res, 400, { error: "Invalid OTLP JSON", message: e instanceof Error ? e.message : String(e) });
1001
+ sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
755
1002
  }
756
1003
  return;
757
1004
  }
758
1005
  if (req.method === "POST" && url === "/v1/metrics") {
759
1006
  try {
760
- const payload = await readJsonBody(req);
1007
+ const payload = await readOtlpPayload(req, "metrics");
761
1008
  const count = countOtlpMetrics(payload);
762
1009
  sendJson(res, 200, { acceptedMetrics: count });
763
1010
  } catch (e) {
764
- sendJson(res, 400, { error: "Invalid OTLP JSON", message: e instanceof Error ? e.message : String(e) });
1011
+ sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
765
1012
  }
766
1013
  return;
767
1014
  }