@stainlessdev/xray-core 0.3.1 → 0.4.0-branch.bg-rework-exporters.8c5264b

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.js CHANGED
@@ -118,13 +118,10 @@ var defaultRoute = {
118
118
  normalize: true,
119
119
  normalizer: normalizeRoutePattern
120
120
  };
121
- var DEFAULT_ENDPOINT_URL = "http://localhost:4318";
122
121
  var defaultExporterBase = {
123
- endpointUrl: DEFAULT_ENDPOINT_URL,
124
122
  headers: {},
125
- timeoutMs: 5e3,
126
- spanProcessor: "batch",
127
- sampler: { type: "ratio", ratio: 1 }
123
+ timeoutMs: 3e4,
124
+ spanProcessor: "batch"
128
125
  };
129
126
  var XrayConfigError = class extends Error {
130
127
  constructor(code, message) {
@@ -217,29 +214,23 @@ function normalizeExporter(endpointUrl, cfg) {
217
214
  const resolvedEndpoint = normalizeExporterEndpoint(cfg?.endpointUrl ?? endpointUrl);
218
215
  const rawHeaders = cfg?.headers ?? defaultExporterBase.headers ?? {};
219
216
  const parsed = applyEndpointAuth(resolvedEndpoint, rawHeaders);
220
- const enabled = cfg?.enabled ?? true;
221
217
  const exporter = {
222
- ...defaultExporterBase,
223
- ...cfg,
224
- enabled,
225
218
  endpointUrl: parsed.endpointUrl,
226
219
  headers: parsed.headers,
227
220
  timeoutMs: cfg?.timeoutMs ?? defaultExporterBase.timeoutMs,
228
- spanProcessor: cfg?.spanProcessor ?? defaultExporterBase.spanProcessor,
229
- sampler: cfg?.sampler ?? defaultExporterBase.sampler
221
+ spanProcessor: cfg?.spanProcessor ?? defaultExporterBase.spanProcessor
230
222
  };
231
- if (exporter.sampler.type === "ratio") {
232
- const ratio = exporter.sampler.ratio ?? 1;
233
- if (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) {
234
- throw new XrayConfigError("INVALID_EXPORTER", "sampler.ratio must be between 0 and 1");
235
- }
236
- exporter.sampler = { type: "ratio", ratio };
237
- }
238
223
  return exporter;
239
224
  }
240
225
  function normalizeExporterEndpoint(endpointUrl) {
241
226
  const envUrl = typeof process !== "undefined" ? process.env?.["STAINLESS_XRAY_ENDPOINT_URL"] : void 0;
242
- const resolved = endpointUrl ?? envUrl ?? DEFAULT_ENDPOINT_URL;
227
+ const resolved = endpointUrl ?? envUrl;
228
+ if (!resolved || !resolved.trim()) {
229
+ throw new XrayConfigError(
230
+ "INVALID_CONFIG",
231
+ "endpointUrl is required (set endpointUrl or STAINLESS_XRAY_ENDPOINT_URL)"
232
+ );
233
+ }
243
234
  const trimmed = resolved.trim();
244
235
  const withoutTrailingSlash = trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
245
236
  if (withoutTrailingSlash.endsWith("/v1/traces")) {
@@ -667,6 +658,18 @@ function redactJsonPath(value, segments, replacement) {
667
658
  }
668
659
  }
669
660
 
661
+ // src/attributes.ts
662
+ import {
663
+ ATTR_HTTP_REQUEST_BODY_SIZE,
664
+ ATTR_HTTP_REQUEST_METHOD,
665
+ ATTR_HTTP_RESPONSE_BODY_SIZE,
666
+ ATTR_HTTP_RESPONSE_STATUS_CODE,
667
+ ATTR_HTTP_ROUTE,
668
+ ATTR_URL_FULL,
669
+ ATTR_URL_PATH,
670
+ ATTR_USER_ID
671
+ } from "@opentelemetry/semantic-conventions/incubating";
672
+
670
673
  // src/attrkey.ts
671
674
  var AttributeKeyRequestBody = "http.request.body";
672
675
  var AttributeKeyRequestBodyEncoding = "http.request.body.encoding";
@@ -678,14 +681,6 @@ var AttributeKeyResponseBodyTruncated = "http.response.body.truncated";
678
681
  var AttributeKeySpanDrop = "stainlessxray.internal.drop";
679
682
 
680
683
  // src/attributes.ts
681
- var attributeKeyEndUserId = "enduser.id";
682
- var attributeKeyHttpRequestBodySize = "http.request.body.size";
683
- var attributeKeyHttpRequestMethod = "http.request.method";
684
- var attributeKeyHttpResponseBodySize = "http.response.body.size";
685
- var attributeKeyHttpResponseStatusCode = "http.response.status_code";
686
- var attributeKeyHttpRoute = "http.route";
687
- var attributeKeyUrlPath = "url.path";
688
- var attributeKeyUrlFull = "url.full";
689
684
  function setHeaderAttributes(span, headers, prefix) {
690
685
  if (!headers) {
691
686
  return;
@@ -700,12 +695,12 @@ function setHeaderAttributes(span, headers, prefix) {
700
695
  }
701
696
  }
702
697
  function setRequestAttributes(span, method, urlFull) {
703
- span.setAttribute(attributeKeyHttpRequestMethod, method);
698
+ span.setAttribute(ATTR_HTTP_REQUEST_METHOD, method);
704
699
  if (urlFull) {
705
- span.setAttribute(attributeKeyUrlFull, urlFull);
700
+ span.setAttribute(ATTR_URL_FULL, urlFull);
706
701
  const path = extractPath(urlFull);
707
702
  if (path) {
708
- span.setAttribute(attributeKeyUrlPath, path);
703
+ span.setAttribute(ATTR_URL_PATH, path);
709
704
  }
710
705
  }
711
706
  }
@@ -728,7 +723,7 @@ function setRequestBodyAttributes(span, body) {
728
723
  }
729
724
  }
730
725
  function setRequestBodySizeAttribute(span, size) {
731
- span.setAttribute(attributeKeyHttpRequestBodySize, size);
726
+ span.setAttribute(ATTR_HTTP_REQUEST_BODY_SIZE, size);
732
727
  }
733
728
  function setResponseBodyAttributes(span, body) {
734
729
  if (!body.value) {
@@ -741,18 +736,18 @@ function setResponseBodyAttributes(span, body) {
741
736
  }
742
737
  }
743
738
  function setResponseBodySizeAttribute(span, size) {
744
- span.setAttribute(attributeKeyHttpResponseBodySize, size);
739
+ span.setAttribute(ATTR_HTTP_RESPONSE_BODY_SIZE, size);
745
740
  }
746
741
  function setResponseStatusAttribute(span, statusCode) {
747
- span.setAttribute(attributeKeyHttpResponseStatusCode, statusCode);
742
+ span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, statusCode);
748
743
  }
749
744
  function setRouteAttribute(span, route) {
750
745
  if (route) {
751
- span.setAttribute(attributeKeyHttpRoute, route);
746
+ span.setAttribute(ATTR_HTTP_ROUTE, route);
752
747
  }
753
748
  }
754
749
  function setUserIdAttribute(span, userId) {
755
- span.setAttribute(attributeKeyEndUserId, userId);
750
+ span.setAttribute(ATTR_USER_ID, userId);
756
751
  }
757
752
  function setRequestIdAttribute(span, requestId) {
758
753
  span.setAttribute(AttributeKeyRequestID, requestId);
@@ -765,14 +760,11 @@ import {
765
760
  SpanKind,
766
761
  SpanStatusCode
767
762
  } from "@opentelemetry/api";
768
- import { ExportResultCode } from "@opentelemetry/core";
769
763
  import {
770
- AlwaysOffSampler,
771
764
  AlwaysOnSampler,
772
765
  BasicTracerProvider,
773
766
  BatchSpanProcessor,
774
- SimpleSpanProcessor,
775
- TraceIdRatioBasedSampler
767
+ SimpleSpanProcessor
776
768
  } from "@opentelemetry/sdk-trace-base";
777
769
  import { resourceFromAttributes } from "@opentelemetry/resources";
778
770
  import {
@@ -781,12 +773,8 @@ import {
781
773
  ATTR_TELEMETRY_SDK_NAME,
782
774
  ATTR_TELEMETRY_SDK_VERSION
783
775
  } from "@opentelemetry/semantic-conventions";
784
- import { JsonTraceSerializer, ProtobufTraceSerializer } from "@opentelemetry/otlp-transformer";
785
776
  var defaultAttributeCountLimit = 128;
786
- function createTracerProvider(config) {
787
- if (!config.exporter.enabled || !config.exporter.endpointUrl) {
788
- return null;
789
- }
777
+ function createTracerProvider(config, exporter) {
790
778
  if (config.exporter.endpointUrl.startsWith("http://")) {
791
779
  diag.warn("xray: OTLP endpoint uses plaintext HTTP");
792
780
  }
@@ -797,12 +785,6 @@ function createTracerProvider(config) {
797
785
  [ATTR_TELEMETRY_SDK_NAME]: "stainless-xray",
798
786
  [ATTR_TELEMETRY_SDK_VERSION]: sdkVersion()
799
787
  });
800
- const exporter = new FetchSpanExporter({
801
- endpointUrl: config.exporter.endpointUrl,
802
- headers: config.exporter.headers ?? {},
803
- timeoutMillis: config.exporter.timeoutMs
804
- });
805
- const sampler = createSampler(config);
806
788
  const spanProcessor = createSpanProcessor(config.exporter.spanProcessor, exporter);
807
789
  const dropProcessor = new DropFilterSpanProcessor(spanProcessor);
808
790
  const provider = new BasicTracerProvider({
@@ -812,7 +794,7 @@ function createTracerProvider(config) {
812
794
  attributeValueLengthLimit
813
795
  },
814
796
  resource,
815
- sampler,
797
+ sampler: new AlwaysOnSampler(),
816
798
  spanLimits: {
817
799
  attributeCountLimit: defaultAttributeCountLimit,
818
800
  attributePerEventCountLimit: defaultAttributeCountLimit,
@@ -861,69 +843,6 @@ var DropFilterSpanProcessor = class {
861
843
  return this.next.shutdown();
862
844
  }
863
845
  };
864
- var FetchSpanExporter = class {
865
- constructor(options) {
866
- this.endpointUrl = options.endpointUrl;
867
- this.headers = { ...options.headers };
868
- this.timeoutMillis = options.timeoutMillis;
869
- this.isShutdown = false;
870
- const protobufSerializer = ProtobufTraceSerializer && typeof ProtobufTraceSerializer.serializeRequest === "function" ? ProtobufTraceSerializer : null;
871
- this.serializer = protobufSerializer ?? JsonTraceSerializer;
872
- this.contentType = protobufSerializer ? "application/x-protobuf" : "application/json";
873
- }
874
- export(spans, resultCallback) {
875
- if (this.isShutdown) {
876
- resultCallback({ code: ExportResultCode.FAILED });
877
- return;
878
- }
879
- const payload = this.serializer.serializeRequest(spans);
880
- if (!payload) {
881
- resultCallback({
882
- code: ExportResultCode.FAILED,
883
- error: new Error("OTLP export failed: empty payload")
884
- });
885
- return;
886
- }
887
- const headers = {
888
- ...this.headers,
889
- "Content-Type": this.contentType
890
- };
891
- const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
892
- let timeout;
893
- if (controller) {
894
- timeout = setTimeout(() => controller.abort(), this.timeoutMillis);
895
- }
896
- const doExport = async () => {
897
- const response = await fetch(this.endpointUrl, {
898
- method: "POST",
899
- headers,
900
- body: payload,
901
- signal: controller?.signal
902
- });
903
- if (!response.ok) {
904
- throw new Error(`OTLP export failed: ${response.status}`);
905
- }
906
- };
907
- doExport().then(() => {
908
- if (timeout) {
909
- clearTimeout(timeout);
910
- }
911
- resultCallback({ code: ExportResultCode.SUCCESS });
912
- }).catch((err) => {
913
- if (timeout) {
914
- clearTimeout(timeout);
915
- }
916
- diag.error("OTLP export failed", err);
917
- resultCallback({ code: ExportResultCode.FAILED, error: err });
918
- });
919
- }
920
- async forceFlush() {
921
- return;
922
- }
923
- async shutdown() {
924
- this.isShutdown = true;
925
- }
926
- };
927
846
  function createSpanProcessor(mode, exporter) {
928
847
  if (mode === "simple") {
929
848
  return new SimpleSpanProcessor(exporter);
@@ -935,19 +854,9 @@ function createSpanProcessor(mode, exporter) {
935
854
  exportTimeoutMillis: 3e4
936
855
  });
937
856
  }
938
- function createSampler(config) {
939
- const sampler = config.exporter.sampler;
940
- if (sampler.type === "always_off") {
941
- return new AlwaysOffSampler();
942
- }
943
- if (sampler.type === "ratio") {
944
- return new TraceIdRatioBasedSampler(sampler.ratio ?? 1);
945
- }
946
- return new AlwaysOnSampler();
947
- }
948
857
  function sdkVersion() {
949
858
  if (true) {
950
- return "0.3.0";
859
+ return "0.4.0";
951
860
  }
952
861
  return "unknown";
953
862
  }
@@ -976,16 +885,29 @@ function uuidv7() {
976
885
  }
977
886
 
978
887
  // src/emitter.ts
979
- function createEmitter(config) {
888
+ function createEmitter(config, exporter) {
980
889
  const resolved = normalizeConfig(config);
981
- const tracerProvider = createTracerProvider(resolved);
982
- const tracer = tracerProvider ? tracerFromProvider(tracerProvider) : null;
890
+ if (!exporter) {
891
+ throw new XrayConfigError(
892
+ "INVALID_CONFIG",
893
+ "exporter is required (use @stainlessdev/xray-node or @stainlessdev/xray-fetch)"
894
+ );
895
+ }
896
+ logWithLevel(resolved.logger, "info", resolved.logLevel, "xray: emitter configured", {
897
+ serviceName: resolved.serviceName,
898
+ environment: resolved.environment,
899
+ version: resolved.version,
900
+ exporterEndpoint: resolved.exporter.endpointUrl,
901
+ spanProcessor: resolved.exporter.spanProcessor
902
+ });
903
+ const tracerProvider = createTracerProvider(resolved, exporter);
904
+ const tracer = tracerFromProvider(tracerProvider);
983
905
  return {
984
906
  config: resolved,
985
907
  startRequest: (req) => startRequest(resolved, tracer, req),
986
908
  endRequest: (ctx, res, err) => endRequest(resolved, ctx, res, err),
987
- flush: () => tracerProvider ? tracerProvider.forceFlush() : Promise.resolve(),
988
- shutdown: () => tracerProvider ? tracerProvider.shutdown() : Promise.resolve()
909
+ flush: () => tracerProvider.forceFlush(),
910
+ shutdown: () => tracerProvider.shutdown()
989
911
  };
990
912
  }
991
913
  function startRequest(config, tracer, req) {
@@ -996,7 +918,7 @@ function startRequest(config, tracer, req) {
996
918
  if (req.route && config.route.normalize) {
997
919
  req.route = config.route.normalizer ? config.route.normalizer(req.route) : normalizeRoutePattern(req.route);
998
920
  }
999
- const span = tracer ? spanFromTracer(tracer, spanNameFromRequest(req)) : void 0;
921
+ const span = spanFromTracer(tracer, spanNameFromRequest(req));
1000
922
  const context = {
1001
923
  requestId,
1002
924
  traceId: span?.spanContext().traceId,