@m1212e/rumble 0.23.15 → 0.23.18

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/out/index.cjs CHANGED
@@ -30,12 +30,13 @@ let _opentelemetry_api = require("@opentelemetry/api");
30
30
  let es_toolkit = require("es-toolkit");
31
31
  let graphql_yoga = require("graphql-yoga");
32
32
  let drizzle_orm = require("drizzle-orm");
33
+ let _opentelemetry_semantic_conventions_incubating = require("@opentelemetry/semantic-conventions/incubating");
34
+ let _pothos_tracing_opentelemetry = require("@pothos/tracing-opentelemetry");
33
35
  let drizzle_orm_casing = require("drizzle-orm/casing");
34
36
  let drizzle_orm_pg_core = require("drizzle-orm/pg-core");
35
37
  let drizzle_orm_mysql_core = require("drizzle-orm/mysql-core");
36
38
  let pluralize = require("pluralize");
37
39
  pluralize = __toESM(pluralize, 1);
38
- let _pothos_tracing_opentelemetry = require("@pothos/tracing-opentelemetry");
39
40
  let drizzle_orm_sqlite_core = require("drizzle-orm/sqlite-core");
40
41
  let _pothos_core = require("@pothos/core");
41
42
  _pothos_core = __toESM(_pothos_core, 1);
@@ -210,7 +211,39 @@ function mapNullFieldsToUndefined(obj) {
210
211
  }
211
212
  //#endregion
212
213
  //#region package.json
213
- var version = "0.23.15";
214
+ var version = "0.23.18";
215
+ //#endregion
216
+ //#region lib/helpers/errorLogging.ts
217
+ function hasMessage(error) {
218
+ return typeof error === "object" && error !== null && "message" in error;
219
+ }
220
+ function toLoggedException(error) {
221
+ if (hasMessage(error)) {
222
+ const gqlError = error;
223
+ return {
224
+ "exception.type": gqlError.constructor?.name ?? gqlError.name ?? "Error",
225
+ "exception.message": String(gqlError.message),
226
+ ...gqlError.stack ? { "exception.stacktrace": gqlError.stack } : {},
227
+ ...gqlError.path ? { path: gqlError.path } : {},
228
+ ...gqlError.extensions && Object.keys(gqlError.extensions).length > 0 ? { extensions: gqlError.extensions } : {}
229
+ };
230
+ }
231
+ return {
232
+ "exception.type": "UnknownError",
233
+ "exception.message": String(error)
234
+ };
235
+ }
236
+ /**
237
+ * Every erroneous rumble log carries the same `errors` array shape,
238
+ * whether it originates from a thrown exception (single-element array)
239
+ * or a GraphQL `ExecutionResult.errors` list (multi-element array).
240
+ */
241
+ function errorsLogField(errors) {
242
+ return { errors: errors.map(toLoggedException) };
243
+ }
244
+ function errorLogField(error) {
245
+ return errorsLogField([error]);
246
+ }
214
247
  //#endregion
215
248
  //#region lib/helpers/mergeFilters.ts
216
249
  const EmptyFilter$1 = Symbol.for("drizzle:EmptyFilter");
@@ -470,6 +503,164 @@ function tableHelper({ db, table }) {
470
503
  };
471
504
  }
472
505
  //#endregion
506
+ //#region lib/helpers/telemetry.ts
507
+ const SPAN_EXECUTE = _pothos_tracing_opentelemetry.SpanNames.EXECUTE;
508
+ /** @pothos/tracing-opentelemetry's SpanNames enum has no subscribe member. */
509
+ const SPAN_SUBSCRIBE = "graphql.subscribe";
510
+ const SPAN_SUBSCRIBE_EVENT = "graphql.subscribe.event";
511
+ const SPAN_ABILITIES_PREPARE = "rumble.abilities.prepare";
512
+ const SPAN_FILTERS_APPLY = "rumble.filters.apply";
513
+ const SPAN_FILTERS_RESOLVE = "rumble.filters.resolve";
514
+ const ATTR_OPERATION_NAME = _opentelemetry_semantic_conventions_incubating.ATTR_GRAPHQL_OPERATION_NAME;
515
+ const ATTR_OPERATION_TYPE = _opentelemetry_semantic_conventions_incubating.ATTR_GRAPHQL_OPERATION_TYPE;
516
+ const ATTR_DOCUMENT = _opentelemetry_semantic_conventions_incubating.ATTR_GRAPHQL_DOCUMENT;
517
+ const ATTR_FIELD_NAME = _pothos_tracing_opentelemetry.AttributeNames.FIELD_NAME;
518
+ /** Prefix for per-variable span attributes, e.g. `graphql.variables.userId`. */
519
+ const ATTR_VARIABLES_PREFIX = _pothos_tracing_opentelemetry.AttributeNames.VARIABLES;
520
+ /** Log field holding all variables as one object (logs keep nesting, spans flatten). */
521
+ const FIELD_VARIABLES = "graphql.variables";
522
+ const ATTR_PARENT_TYPE = "graphql.parent.type";
523
+ const ATTR_TRANSPORT = "rumble.transport";
524
+ const ATTR_TABLE = "rumble.table";
525
+ const ATTR_ACTION = "rumble.action";
526
+ const ATTR_ABILITIES_STATUS = "rumble.abilities.status";
527
+ const ATTR_ABILITIES_STATIC = "rumble.abilities.static";
528
+ const ATTR_ABILITIES_DYNAMIC = "rumble.abilities.dynamic";
529
+ const ATTR_ABILITIES_TOTAL = "rumble.abilities.total";
530
+ const ATTR_FILTERS_TOTAL = "rumble.filters.total";
531
+ const ATTR_FILTERS_ALLOWED = "rumble.filters.allowed";
532
+ const ATTR_SUBSCRIPTION_EVENT_INDEX = "rumble.subscription.event_index";
533
+ const FIELD_DURATION_MS = "duration_ms";
534
+ const FIELD_EVENT_COUNT = "event_count";
535
+ function telemetryLogger(config) {
536
+ return config.logger?.enabled ? config.logger.logger : void 0;
537
+ }
538
+ function telemetryTracer(config) {
539
+ return config.otel?.enabled ? config.otel.tracer : void 0;
540
+ }
541
+ /**
542
+ * Whether anything at all should be instrumented. When this is false, rumble
543
+ * hands the untouched execute/subscribe functions back to the transports so a
544
+ * user who wants neither traces nor logs pays no overhead.
545
+ */
546
+ function telemetryEnabled(config) {
547
+ return Boolean(config.otel?.enabled || config.logger?.enabled);
548
+ }
549
+ /**
550
+ * `trace_id`/`span_id`/`trace_flags` for the given span, or for the currently
551
+ * active one when no span is passed. Field names and the two-hex-digit flags
552
+ * format follow the OpenTelemetry log data model, which is what log backends
553
+ * (Loki, Tempo, Elastic) expect when linking a log line to its trace.
554
+ *
555
+ * Reading the *active* span means every rumble log site gets correlation for
556
+ * free, without threading a child logger through the whole library.
557
+ */
558
+ function traceCorrelationFields(config, span) {
559
+ if (!config.otel?.enabled || config.logger?.injectTraceId === false) return {};
560
+ const target = span ?? _opentelemetry_api.trace.getActiveSpan();
561
+ if (!target) return {};
562
+ const spanContext = target.spanContext();
563
+ if (!(0, _opentelemetry_api.isSpanContextValid)(spanContext)) return {};
564
+ return {
565
+ trace_id: spanContext.traceId,
566
+ span_id: spanContext.spanId,
567
+ trace_flags: spanContext.traceFlags.toString(16).padStart(2, "0")
568
+ };
569
+ }
570
+ function toException(error) {
571
+ if (error instanceof Error) return error;
572
+ if (typeof error === "object" && error !== null && "message" in error) return error;
573
+ return String(error);
574
+ }
575
+ function exceptionMessage(error) {
576
+ if (typeof error === "object" && error !== null && "message" in error) return String(error.message);
577
+ return error === void 0 ? void 0 : String(error);
578
+ }
579
+ /**
580
+ * Records every error as an exception event and flips the span to ERROR, per
581
+ * OpenTelemetry's exception conventions. Used for rumble's own spans as well as
582
+ * the GraphQL operation spans so a failure looks identical wherever it happens.
583
+ */
584
+ function recordSpanErrors(span, errors) {
585
+ if (!span || errors.length === 0) return;
586
+ for (const error of errors) span.recordException(toException(error));
587
+ span.setStatus({
588
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
589
+ message: exceptionMessage(errors[0])
590
+ });
591
+ }
592
+ function recordSpanError(span, error) {
593
+ recordSpanErrors(span, [error]);
594
+ }
595
+ function safeJsonStringify(value) {
596
+ try {
597
+ return JSON.stringify(value, (_key, inner) => typeof inner === "bigint" ? inner.toString() : inner) ?? "null";
598
+ } catch {
599
+ return String(value);
600
+ }
601
+ }
602
+ function isHomogeneousPrimitiveArray(value) {
603
+ let seen;
604
+ for (const entry of value) {
605
+ if (entry === null || entry === void 0) continue;
606
+ const type = typeof entry;
607
+ if (type !== "string" && type !== "number" && type !== "boolean") return false;
608
+ if (seen && seen !== type) return false;
609
+ seen = type;
610
+ }
611
+ return true;
612
+ }
613
+ /**
614
+ * Coerces an arbitrary value into something the OpenTelemetry attribute type
615
+ * allows: primitives pass through, homogeneous primitive arrays stay arrays
616
+ * (mixed ones are invalid per spec) and everything else becomes JSON.
617
+ *
618
+ * Values are intentionally not truncated here — length limits belong to the SDK
619
+ * (`OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT`) so they stay configurable per deployment.
620
+ */
621
+ function attributeValue(value) {
622
+ switch (typeof value) {
623
+ case "string": return value;
624
+ case "number": return Number.isFinite(value) ? value : String(value);
625
+ case "boolean": return value;
626
+ case "bigint": return value.toString();
627
+ case "undefined": return "null";
628
+ default: break;
629
+ }
630
+ if (value === null) return "null";
631
+ if (Array.isArray(value) && isHomogeneousPrimitiveArray(value)) return value;
632
+ return safeJsonStringify(value);
633
+ }
634
+ function resolveVariables(variables, include) {
635
+ if (include === false || !variables) return void 0;
636
+ const resolved = typeof include === "function" ? include({ ...variables }) : variables;
637
+ if (!resolved || Object.keys(resolved).length === 0) return void 0;
638
+ return resolved;
639
+ }
640
+ /** Variables flattened into `graphql.variables.<name>` span attributes. */
641
+ function variableAttributes(variables, include) {
642
+ const resolved = resolveVariables(variables, include);
643
+ if (!resolved) return {};
644
+ const attributes = {};
645
+ for (const [key, value] of Object.entries(resolved)) attributes[`${ATTR_VARIABLES_PREFIX}${key}`] = attributeValue(value);
646
+ return attributes;
647
+ }
648
+ /** Variables as a single nested `graphql.variables` log field. */
649
+ function variableLogField(variables, include) {
650
+ const resolved = resolveVariables(variables, include);
651
+ return resolved ? { [FIELD_VARIABLES]: resolved } : {};
652
+ }
653
+ /**
654
+ * Monotonic clock reading. `Date.now()` can jump backwards when the wall clock is
655
+ * adjusted (NTP), which would produce negative or inflated durations.
656
+ */
657
+ function startTimer() {
658
+ return performance.now();
659
+ }
660
+ function durationMs(start) {
661
+ return Math.round((performance.now() - start) * 100) / 100;
662
+ }
663
+ //#endregion
473
664
  //#region lib/abilityBuilder.ts
474
665
  function isDynamicQueryFilter(filter) {
475
666
  return typeof filter === "function" && filter.constructor.name !== "AsyncFunction";
@@ -477,17 +668,22 @@ function isDynamicQueryFilter(filter) {
477
668
  function isStaticQueryFilter(filter) {
478
669
  return typeof filter !== "function";
479
670
  }
480
- const makeNothingRegisteredWarner = (logger) => (0, es_toolkit.debounce)((model, action) => {
671
+ const makeNothingRegisteredWarner = (config, logger) => (0, es_toolkit.debounce)((model, action) => {
481
672
  const msg = `No abilities registered for ${model}/${action} — blocking everything. Register the ability or ignore this warning if intentional.`;
482
673
  if (logger) logger.warn({
483
- "rumble.table": model,
484
- "rumble.action": action
674
+ [ATTR_TABLE]: model,
675
+ [ATTR_ACTION]: action,
676
+ ...traceCorrelationFields(config)
485
677
  }, msg);
486
678
  else console.warn(msg);
487
679
  }, 1e3);
488
680
  const createAbilityBuilder = ({ db, actions, defaultLimit, otel, logger: loggerConfig }) => {
489
681
  const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
490
- const nothingRegisteredWarningLogger = makeNothingRegisteredWarner(log);
682
+ const telemetryConfig = {
683
+ otel,
684
+ logger: loggerConfig
685
+ };
686
+ const nothingRegisteredWarningLogger = makeNothingRegisteredWarner(telemetryConfig, log);
491
687
  const tableRelationNames = Object.keys(db.query).filter((name) => tableHelper({
492
688
  db,
493
689
  table: name
@@ -747,14 +943,14 @@ by: (explicitFilter) => {
747
943
  }
748
944
  return { withContext: (userContext) => {
749
945
  return { filter: (action) => {
750
- const assembleAbilities = (span) => {
946
+ const assembleAbilities = (attributes) => {
751
947
  const filters = queryFilters.get(action);
752
948
  if (filters === "unrestricted") {
753
- span?.setAttribute("abilities.status", "unrestricted");
949
+ attributes[ATTR_ABILITIES_STATUS] = "unrestricted";
754
950
  return transformToResponse();
755
951
  }
756
952
  if (!filters) {
757
- span?.setAttribute("abilities.status", "blocked_everything");
953
+ attributes[ATTR_ABILITIES_STATUS] = "blocked_everything";
758
954
  nothingRegisteredWarningLogger(String(tableName), action);
759
955
  return transformToResponse(blockEverythingFilter);
760
956
  }
@@ -763,58 +959,60 @@ by: (explicitFilter) => {
763
959
  for (let i = 0; i < dynamicQueryFilters[action].length; i++) {
764
960
  const func = dynamicQueryFilters[action][i];
765
961
  const result = func(userContext);
766
- if (result === "allow") return transformToResponse();
962
+ if (result === "allow") {
963
+ attributes[ATTR_ABILITIES_STATUS] = "unrestricted";
964
+ return transformToResponse();
965
+ }
767
966
  if (result === void 0) continue;
768
967
  dynamicResults[filtersReturned++] = result;
769
968
  }
770
969
  dynamicResults.length = filtersReturned;
771
- span?.setAttribute("abilities.dynamic", dynamicResults.length);
772
- span?.setAttribute("abilities.static", simpleQueryFilters[action].length);
970
+ attributes[ATTR_ABILITIES_DYNAMIC] = dynamicResults.length;
971
+ attributes[ATTR_ABILITIES_STATIC] = simpleQueryFilters[action].length;
773
972
  const allQueryFilters = [...simpleQueryFilters[action], ...dynamicResults];
774
- span?.setAttribute("abilities.total", allQueryFilters.length);
973
+ attributes[ATTR_ABILITIES_TOTAL] = allQueryFilters.length;
775
974
  if (allQueryFilters.length === 0) {
776
- span?.setAttribute("abilities.status", "blocked_everything");
975
+ attributes[ATTR_ABILITIES_STATUS] = "blocked_everything";
777
976
  return transformToResponse(blockEverythingFilter);
778
977
  }
779
978
  const mergedFilters = allQueryFilters.length === 1 ? allQueryFilters[0] : allQueryFilters.reduce((a, b) => {
780
979
  return mergeFilters(a, b, "OR");
781
980
  });
782
- span?.setAttribute("abilities.status", "applied");
981
+ attributes[ATTR_ABILITIES_STATUS] = "applied";
783
982
  return transformToResponse(mergedFilters);
784
983
  };
785
- if (otel?.enabled && otel.tracer) return otel.tracer.startActiveSpan(`rumble.abilities.prepare`, (span) => {
786
- span.setAttribute("rumble.action", action);
787
- span.setAttribute("rumble.table", String(tableName));
984
+ const run = (span) => {
985
+ const attributes = {
986
+ [ATTR_TABLE]: String(tableName),
987
+ [ATTR_ACTION]: action
988
+ };
788
989
  try {
789
- const attrs = {
790
- "rumble.table": String(tableName),
791
- "rumble.action": action
792
- };
793
- const proxy = new Proxy(span, { get(target, prop) {
794
- if (prop === "setAttribute") return (k, v) => {
795
- attrs[k] = v;
796
- return target.setAttribute(k, v);
797
- };
798
- return target[prop];
799
- } });
800
- const result = assembleAbilities(proxy);
801
- log?.debug(attrs, "abilities prepared");
990
+ const result = assembleAbilities(attributes);
991
+ log?.debug({
992
+ ...attributes,
993
+ ...traceCorrelationFields(telemetryConfig, span)
994
+ }, "abilities prepared");
802
995
  return result;
996
+ } catch (error) {
997
+ recordSpanError(span, error);
998
+ log?.error({
999
+ ...attributes,
1000
+ ...traceCorrelationFields(telemetryConfig, span),
1001
+ ...errorLogField(error)
1002
+ }, "abilities failed");
1003
+ throw error;
1004
+ } finally {
1005
+ span?.setAttributes(attributes);
1006
+ }
1007
+ };
1008
+ if (otel?.enabled && otel.tracer) return otel.tracer.startActiveSpan(SPAN_ABILITIES_PREPARE, (span) => {
1009
+ try {
1010
+ return run(span);
803
1011
  } finally {
804
1012
  span.end();
805
1013
  }
806
1014
  });
807
- else {
808
- const attrs = {
809
- "rumble.table": String(tableName),
810
- "rumble.action": action
811
- };
812
- const result = assembleAbilities(log ? { setAttribute: (k, v) => {
813
- attrs[k] = v;
814
- } } : void 0);
815
- log?.debug(attrs, "abilities prepared");
816
- return result;
817
- }
1015
+ return run();
818
1016
  } };
819
1017
  } };
820
1018
  };
@@ -1341,207 +1539,188 @@ const sofaOpenAPIWebhookDocs = {
1341
1539
  } }
1342
1540
  };
1343
1541
  //#endregion
1344
- //#region lib/helpers/errorLogging.ts
1345
- function hasMessage(error) {
1346
- return typeof error === "object" && error !== null && "message" in error;
1542
+ //#region lib/helpers/tracing.ts
1543
+ function isAsyncIterable(value) {
1544
+ return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
1347
1545
  }
1348
- function toLoggedException(error) {
1349
- if (hasMessage(error)) {
1350
- const gqlError = error;
1351
- return {
1352
- "exception.type": gqlError.constructor?.name ?? gqlError.name ?? "Error",
1353
- "exception.message": String(gqlError.message),
1354
- ...gqlError.stack ? { "exception.stacktrace": gqlError.stack } : {},
1355
- ...gqlError.path ? { path: gqlError.path } : {},
1356
- ...gqlError.extensions && Object.keys(gqlError.extensions).length > 0 ? { extensions: gqlError.extensions } : {}
1357
- };
1358
- }
1546
+ function buildOperationTelemetry(config, transport, options) {
1547
+ const operationAst = (0, graphql.getOperationAST)(options.document, options.operationName);
1548
+ const operationName = options.operationName ?? operationAst?.name?.value ?? "anonymous";
1549
+ const operationType = operationAst?.operation;
1550
+ const logFields = {
1551
+ [ATTR_OPERATION_NAME]: operationName,
1552
+ ...operationType ? { [ATTR_OPERATION_TYPE]: operationType } : {},
1553
+ [ATTR_TRANSPORT]: transport
1554
+ };
1359
1555
  return {
1360
- "exception.type": "UnknownError",
1361
- "exception.message": String(error)
1556
+ operationName,
1557
+ operationType,
1558
+ logFields,
1559
+ variableLogFields: variableLogField(options.variableValues, config.logger?.includeVariables),
1560
+ attributes: () => ({
1561
+ ...logFields,
1562
+ ...config.otel?.includeDocument === false ? {} : { [ATTR_DOCUMENT]: (0, graphql.print)(options.document) },
1563
+ ...variableAttributes(options.variableValues, config.otel?.includeVariables)
1564
+ })
1362
1565
  };
1363
1566
  }
1364
1567
  /**
1365
- * Every erroneous rumble log carries the same `errors` array shape,
1366
- * whether it originates from a thrown exception (single-element array)
1367
- * or a GraphQL `ExecutionResult.errors` list (multi-element array).
1568
+ * Wraps a subscription's event stream so delivered events are observable no
1569
+ * matter which transport consumes them.
1368
1570
  */
1369
- function errorsLogField(errors) {
1370
- return { errors: errors.map(toLoggedException) };
1371
- }
1372
- function errorLogField(error) {
1373
- return errorsLogField([error]);
1374
- }
1375
- //#endregion
1376
- //#region lib/helpers/tracing.ts
1377
- const SUBSCRIBE_SPAN_NAME = "graphql.subscribe";
1378
- function getOperationType(options) {
1379
- return (0, graphql.getOperationAST)(options.document, options.operationName)?.operation;
1380
- }
1381
- function operationLogFields(operationName, operationType) {
1382
- return {
1383
- "graphql.operation.name": operationName,
1384
- ...operationType ? { "graphql.operation.type": operationType } : {}
1385
- };
1386
- }
1387
- function traceIdLogFields(span) {
1388
- const { traceId, spanId, traceFlags } = span.spanContext();
1389
- return {
1390
- trace_id: traceId,
1391
- span_id: spanId,
1392
- trace_flags: traceFlags.toString(16).padStart(2, "0")
1571
+ async function* wrapSubscriptionIterator(iterator, telemetry) {
1572
+ const { log, tracer, setupSpanContext, logFields, attributes } = telemetry;
1573
+ const recordEventErrors = (eventCount, errors) => {
1574
+ if (!tracer) return;
1575
+ const span = tracer.startSpan(SPAN_SUBSCRIBE_EVENT, {
1576
+ root: true,
1577
+ links: setupSpanContext ? [{ context: setupSpanContext }] : void 0,
1578
+ attributes: {
1579
+ ...attributes,
1580
+ [ATTR_SUBSCRIPTION_EVENT_INDEX]: eventCount
1581
+ }
1582
+ });
1583
+ recordSpanErrors(span, errors);
1584
+ span.end();
1393
1585
  };
1394
- }
1395
- function isAsyncIterable(value) {
1396
- return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
1397
- }
1398
- async function* wrapSubscriptionIterator(iterator, log, operationName, operationType) {
1399
1586
  let eventCount = 0;
1400
1587
  try {
1401
1588
  for await (const event of iterator) {
1402
1589
  eventCount++;
1403
- if (event.errors?.length) log.error({
1404
- ...operationLogFields(operationName, operationType),
1405
- event_count: eventCount,
1406
- ...errorsLogField(event.errors)
1407
- }, "graphql subscription event error");
1590
+ if (event.errors?.length) {
1591
+ log?.error({
1592
+ ...logFields,
1593
+ [FIELD_EVENT_COUNT]: eventCount,
1594
+ ...errorsLogField(event.errors)
1595
+ }, "graphql subscription event error");
1596
+ recordEventErrors(eventCount, event.errors);
1597
+ }
1408
1598
  yield event;
1409
1599
  }
1410
- log.info({
1411
- ...operationLogFields(operationName, operationType),
1412
- event_count: eventCount
1600
+ log?.info({
1601
+ ...logFields,
1602
+ [FIELD_EVENT_COUNT]: eventCount
1413
1603
  }, "graphql subscription completed");
1414
1604
  } catch (error) {
1415
- log.error({
1416
- ...operationLogFields(operationName, operationType),
1417
- event_count: eventCount,
1605
+ log?.error({
1606
+ ...logFields,
1607
+ [FIELD_EVENT_COUNT]: eventCount,
1418
1608
  ...errorLogField(error)
1419
1609
  }, "graphql subscription threw");
1610
+ recordEventErrors(eventCount, [error]);
1420
1611
  throw error;
1421
1612
  }
1422
1613
  }
1423
- function buildTracedExecute(executeFn, rumbleInput) {
1614
+ function buildTracedExecute(executeFn, rumbleInput, transport) {
1615
+ if (!telemetryEnabled(rumbleInput)) return executeFn;
1424
1616
  return async (options) => {
1425
- let log = rumbleInput.logger?.enabled ? rumbleInput.logger.logger : void 0;
1426
- const operationName = options.operationName ?? "anonymous";
1427
- const operationType = getOperationType(options);
1428
- const start = Date.now();
1429
- log?.info(operationLogFields(operationName, operationType), "graphql execute start");
1430
- const run = async () => {
1431
- const result = await executeFn(options);
1432
- if (result && "errors" in result && result.errors?.length) log?.error({
1433
- ...operationLogFields(operationName, operationType),
1434
- duration_ms: Date.now() - start,
1435
- ...errorsLogField(result.errors)
1436
- }, "graphql execute completed with errors");
1437
- else log?.info({
1438
- ...operationLogFields(operationName, operationType),
1439
- duration_ms: Date.now() - start
1440
- }, "graphql execute completed");
1441
- return result;
1442
- };
1443
- if (rumbleInput.otel?.enabled) return rumbleInput.otel.tracer.startActiveSpan(_pothos_tracing_opentelemetry.SpanNames.EXECUTE, { attributes: {
1444
- [_pothos_tracing_opentelemetry.AttributeNames.OPERATION_NAME]: operationName,
1445
- ...operationType ? { [_pothos_tracing_opentelemetry.AttributeNames.OPERATION_TYPE]: operationType } : {},
1446
- "graphql.document": (0, graphql.print)(options.document)
1447
- } }, async (span) => {
1448
- if (log && rumbleInput.logger?.injectTraceId !== false) log = log.child(traceIdLogFields(span));
1617
+ const tracer = telemetryTracer(rumbleInput);
1618
+ let log = telemetryLogger(rumbleInput);
1619
+ const telemetry = buildOperationTelemetry(rumbleInput, transport, options);
1620
+ const start = startTimer();
1621
+ const run = async (span) => {
1622
+ log?.info({
1623
+ ...telemetry.logFields,
1624
+ ...telemetry.variableLogFields
1625
+ }, "graphql execute start");
1449
1626
  try {
1450
- const result = await run();
1627
+ const result = await executeFn(options);
1451
1628
  if (result && "errors" in result && result.errors?.length) {
1452
- for (const error of result.errors) span.recordException(error);
1453
- span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1454
- }
1629
+ log?.error({
1630
+ ...telemetry.logFields,
1631
+ [FIELD_DURATION_MS]: durationMs(start),
1632
+ ...errorsLogField(result.errors)
1633
+ }, "graphql execute completed with errors");
1634
+ recordSpanErrors(span, result.errors);
1635
+ } else log?.info({
1636
+ ...telemetry.logFields,
1637
+ [FIELD_DURATION_MS]: durationMs(start)
1638
+ }, "graphql execute completed");
1455
1639
  return result;
1456
1640
  } catch (error) {
1457
- if (error instanceof Error) span.recordException(error);
1458
1641
  log?.error({
1459
- ...operationLogFields(operationName, operationType),
1460
- duration_ms: Date.now() - start,
1642
+ ...telemetry.logFields,
1643
+ [FIELD_DURATION_MS]: durationMs(start),
1461
1644
  ...errorLogField(error)
1462
1645
  }, "graphql execute threw");
1463
- span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1646
+ recordSpanError(span, error);
1464
1647
  throw error;
1648
+ }
1649
+ };
1650
+ if (!tracer) return run();
1651
+ return tracer.startActiveSpan(SPAN_EXECUTE, { attributes: telemetry.attributes() }, async (span) => {
1652
+ if (log) {
1653
+ const correlation = traceCorrelationFields(rumbleInput, span);
1654
+ if (Object.keys(correlation).length > 0) log = log.child(correlation);
1655
+ }
1656
+ try {
1657
+ return await run(span);
1465
1658
  } finally {
1466
1659
  span.end();
1467
1660
  }
1468
1661
  });
1469
- try {
1470
- return await run();
1471
- } catch (error) {
1472
- log?.error({
1473
- ...operationLogFields(operationName, operationType),
1474
- duration_ms: Date.now() - start,
1475
- ...errorLogField(error)
1476
- }, "graphql execute threw");
1477
- throw error;
1478
- }
1479
1662
  };
1480
1663
  }
1481
- function buildTracedSubscribe(subscribeFn, rumbleInput) {
1664
+ function buildTracedSubscribe(subscribeFn, rumbleInput, transport) {
1665
+ if (!telemetryEnabled(rumbleInput)) return subscribeFn;
1482
1666
  return async (options) => {
1483
- let log = rumbleInput.logger?.enabled ? rumbleInput.logger.logger : void 0;
1484
- const operationName = options.operationName ?? "anonymous";
1485
- const operationType = getOperationType(options);
1486
- const start = Date.now();
1487
- const doSubscribe = async () => {
1488
- log?.info(operationLogFields(operationName, operationType), "graphql subscribe start");
1489
- const result = await subscribeFn(options);
1490
- if (!isAsyncIterable(result)) {
1491
- const execResult = result;
1492
- if (execResult.errors?.length) log?.error({
1493
- ...operationLogFields(operationName, operationType),
1494
- duration_ms: Date.now() - start,
1495
- ...errorsLogField(execResult.errors)
1496
- }, "graphql subscribe completed with errors");
1497
- return execResult;
1498
- }
1667
+ const tracer = telemetryTracer(rumbleInput);
1668
+ let log = telemetryLogger(rumbleInput);
1669
+ const telemetry = buildOperationTelemetry(rumbleInput, transport, options);
1670
+ const start = startTimer();
1671
+ const attributes = tracer ? telemetry.attributes() : {};
1672
+ const doSubscribe = async (span) => {
1499
1673
  log?.info({
1500
- ...operationLogFields(operationName, operationType),
1501
- duration_ms: Date.now() - start
1502
- }, "graphql subscription established");
1503
- if (log) return wrapSubscriptionIterator(result, log, operationName, operationType);
1504
- return result;
1505
- };
1506
- if (rumbleInput.otel?.enabled) return rumbleInput.otel.tracer.startActiveSpan(SUBSCRIBE_SPAN_NAME, { attributes: {
1507
- [_pothos_tracing_opentelemetry.AttributeNames.OPERATION_NAME]: operationName,
1508
- ...operationType ? { [_pothos_tracing_opentelemetry.AttributeNames.OPERATION_TYPE]: operationType } : {},
1509
- "graphql.document": (0, graphql.print)(options.document)
1510
- } }, async (span) => {
1511
- if (log && rumbleInput.logger?.injectTraceId !== false) log = log.child(traceIdLogFields(span));
1674
+ ...telemetry.logFields,
1675
+ ...telemetry.variableLogFields
1676
+ }, "graphql subscribe start");
1512
1677
  try {
1513
- const result = await doSubscribe();
1678
+ const result = await subscribeFn(options);
1514
1679
  if (!isAsyncIterable(result)) {
1515
1680
  const execResult = result;
1516
1681
  if (execResult.errors?.length) {
1517
- for (const error of execResult.errors) span.recordException(error);
1518
- span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1682
+ log?.error({
1683
+ ...telemetry.logFields,
1684
+ [FIELD_DURATION_MS]: durationMs(start),
1685
+ ...errorsLogField(execResult.errors)
1686
+ }, "graphql subscribe completed with errors");
1687
+ recordSpanErrors(span, execResult.errors);
1519
1688
  }
1689
+ return execResult;
1520
1690
  }
1521
- return result;
1691
+ log?.info({
1692
+ ...telemetry.logFields,
1693
+ [FIELD_DURATION_MS]: durationMs(start)
1694
+ }, "graphql subscription established");
1695
+ return wrapSubscriptionIterator(result, {
1696
+ log,
1697
+ tracer,
1698
+ setupSpanContext: span?.spanContext(),
1699
+ logFields: telemetry.logFields,
1700
+ attributes
1701
+ });
1522
1702
  } catch (error) {
1523
- if (error instanceof Error) span.recordException(error);
1524
1703
  log?.error({
1525
- ...operationLogFields(operationName, operationType),
1526
- duration_ms: Date.now() - start,
1704
+ ...telemetry.logFields,
1705
+ [FIELD_DURATION_MS]: durationMs(start),
1527
1706
  ...errorLogField(error)
1528
1707
  }, "graphql subscribe threw");
1529
- span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1708
+ recordSpanError(span, error);
1530
1709
  throw error;
1710
+ }
1711
+ };
1712
+ if (!tracer) return doSubscribe();
1713
+ return tracer.startActiveSpan(SPAN_SUBSCRIBE, { attributes }, async (span) => {
1714
+ if (log) {
1715
+ const correlation = traceCorrelationFields(rumbleInput, span);
1716
+ if (Object.keys(correlation).length > 0) log = log.child(correlation);
1717
+ }
1718
+ try {
1719
+ return await doSubscribe(span);
1531
1720
  } finally {
1532
1721
  span.end();
1533
1722
  }
1534
1723
  });
1535
- try {
1536
- return await doSubscribe();
1537
- } catch (error) {
1538
- log?.error({
1539
- ...operationLogFields(operationName, operationType),
1540
- duration_ms: Date.now() - start,
1541
- ...errorLogField(error)
1542
- }, "graphql subscribe threw");
1543
- throw error;
1544
- }
1545
1724
  };
1546
1725
  }
1547
1726
  //#endregion
@@ -1690,17 +1869,30 @@ const isProbablyAConfigObject = (t) => {
1690
1869
  ].find((e) => e === k))) return true;
1691
1870
  return false;
1692
1871
  };
1693
- const createObjectImplementer = ({ db, search, logger: loggerConfig, schemaBuilder, makePubSubInstance, whereArgImplementer, orderArgImplementer, enumImplementer, abilityBuilder }) => {
1872
+ const createObjectImplementer = ({ db, search, otel, logger: loggerConfig, schemaBuilder, makePubSubInstance, whereArgImplementer, orderArgImplementer, enumImplementer, abilityBuilder }) => {
1873
+ const telemetryConfig = {
1874
+ otel,
1875
+ logger: loggerConfig
1876
+ };
1877
+ /**
1878
+ * Schema construction warnings go through the configured logger when there is
1879
+ * one, so they end up in the same structured stream as everything else, and
1880
+ * fall back to the console otherwise.
1881
+ */
1882
+ const warn = (msg, fields = {}) => {
1883
+ const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
1884
+ if (log) log.warn({
1885
+ ...fields,
1886
+ ...traceCorrelationFields(telemetryConfig)
1887
+ }, msg);
1888
+ else console.warn(msg);
1889
+ };
1694
1890
  return ({ table, refName, readAction = "read", adjust }) => {
1695
1891
  const tableSchema = tableHelper({
1696
1892
  db,
1697
1893
  table
1698
1894
  });
1699
- if (Object.keys(tableSchema.primaryKey).length === 0) {
1700
- const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
1701
- const msg = `Could not find primary key for ${String(table)}. Cannot register subscriptions!`;
1702
- log ? log.warn({ "rumble.table": String(table) }, msg) : console.warn(msg);
1703
- }
1895
+ if (Object.keys(tableSchema.primaryKey).length === 0) warn(`Could not find primary key for ${String(table)}. Cannot register subscriptions!`, { [ATTR_TABLE]: String(table) });
1704
1896
  const primaryKey = Object.values(tableSchema.primaryKey)[0];
1705
1897
  const { registerOnInstance } = makePubSubInstance({ table });
1706
1898
  return schemaBuilder.drizzleObject(table, {
@@ -1709,9 +1901,7 @@ const createObjectImplementer = ({ db, search, logger: loggerConfig, schemaBuild
1709
1901
  if (!primaryKey) return;
1710
1902
  const primaryKeyValue = element[primaryKey.name];
1711
1903
  if (!primaryKeyValue) {
1712
- const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
1713
- const msg = `Could not find primary key value for element on ${String(table)}. Cannot register subscription!`;
1714
- log ? log.warn({ "rumble.table": String(table) }, msg) : console.warn(msg);
1904
+ warn(`Could not find primary key value for element on ${String(table)}. Cannot register subscription!`, { [ATTR_TABLE]: String(table) });
1715
1905
  return;
1716
1906
  }
1717
1907
  registerOnInstance({
@@ -1743,7 +1933,7 @@ const createObjectImplementer = ({ db, search, logger: loggerConfig, schemaBuild
1743
1933
  } })) ?? {};
1744
1934
  const fields = Object.entries(columns).reduce((acc, [key, column]) => {
1745
1935
  if (key.startsWith("__")) {
1746
- console.warn(`Skipping column "${key}" on table "${tableSchema.tsName}": names starting with "__" are reserved by GraphQL introspection. Use a custom adjust() call if you really need to expose this column.`);
1936
+ warn(`Skipping column "${key}" on table "${tableSchema.tsName}": names starting with "__" are reserved by GraphQL introspection. Use a custom adjust() call if you really need to expose this column.`, { [ATTR_TABLE]: tableSchema.tsName });
1747
1937
  return acc;
1748
1938
  }
1749
1939
  if (userAdjustments[key]) {
@@ -1946,8 +2136,12 @@ const createPubSubInstance = ({ subscriptions }) => {
1946
2136
  };
1947
2137
  //#endregion
1948
2138
  //#region lib/query.ts
1949
- const createQueryImplementer = ({ db, schemaBuilder, search, logger: loggerConfig, whereArgImplementer, orderArgImplementer, makePubSubInstance }) => {
2139
+ const createQueryImplementer = ({ db, schemaBuilder, search, otel, logger: loggerConfig, whereArgImplementer, orderArgImplementer, makePubSubInstance }) => {
1950
2140
  const registeredQueryFieldNames = /* @__PURE__ */ new Set();
2141
+ const telemetryConfig = {
2142
+ otel,
2143
+ logger: loggerConfig
2144
+ };
1951
2145
  return ({ table, readAction = "read", listAction = "read", findFirst, findMany }) => {
1952
2146
  const WhereArg = whereArgImplementer({ table });
1953
2147
  const OrderArg = orderArgImplementer({ table });
@@ -2019,7 +2213,7 @@ const createQueryImplementer = ({ db, schemaBuilder, search, logger: loggerConfi
2019
2213
  else {
2020
2214
  const msg = "Database dialect is not postgresql, cannot set cpu_operator_cost.";
2021
2215
  const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
2022
- log ? log.info({}, msg) : console.info(msg);
2216
+ log ? log.info(traceCorrelationFields(telemetryConfig), msg) : console.info(msg);
2023
2217
  }
2024
2218
  return run(tx);
2025
2219
  });
@@ -2311,6 +2505,7 @@ var RuntimeFiltersPlugin = class extends _pothos_core.BasePlugin {
2311
2505
  tracer;
2312
2506
  tracerEnabled;
2313
2507
  logger;
2508
+ telemetryConfig = {};
2314
2509
  filterLoaders = /* @__PURE__ */ new WeakMap();
2315
2510
  getLoader(context, filter) {
2316
2511
  let perContext = this.filterLoaders.get(context);
@@ -2339,6 +2534,10 @@ var RuntimeFiltersPlugin = class extends _pothos_core.BasePlugin {
2339
2534
  this.tracer = this.builder.options.otel?.tracer;
2340
2535
  this.tracerEnabled = this.builder.options.otel?.enabled;
2341
2536
  this.logger = this.builder.options.logger?.enabled ? this.builder.options.logger.logger : void 0;
2537
+ this.telemetryConfig = {
2538
+ otel: this.builder.options.otel,
2539
+ logger: this.builder.options.logger
2540
+ };
2342
2541
  return typeConfig;
2343
2542
  }
2344
2543
  wrapResolve(resolver, fieldConfig) {
@@ -2350,13 +2549,16 @@ var RuntimeFiltersPlugin = class extends _pothos_core.BasePlugin {
2350
2549
  if (!filters || !Array.isArray(filters) || filters.length === 0) return resolver(parent, args, context, info);
2351
2550
  const runFilters = async (span) => {
2352
2551
  const allFilters = Array.isArray(filters) ? filters : [filters];
2353
- span?.setAttribute("filters.total", allFilters.length);
2552
+ span?.setAttribute(ATTR_FILTERS_TOTAL, allFilters.length);
2354
2553
  const loaders = allFilters.map((filter) => this.getLoader(context, filter));
2355
2554
  let resolved;
2356
- if (this.tracer && this.tracerEnabled) resolved = await this.tracer.startActiveSpan(`rumble.filter.resolve`, async (span) => {
2357
- span.setAttribute("graphql.field.name", fieldConfig.name);
2555
+ if (this.tracer && this.tracerEnabled) resolved = await this.tracer.startActiveSpan(SPAN_FILTERS_RESOLVE, async (span) => {
2556
+ span.setAttribute(ATTR_FIELD_NAME, fieldConfig.name);
2358
2557
  try {
2359
2558
  return await resolver(parent, args, context, info);
2559
+ } catch (error) {
2560
+ recordSpanError(span, error);
2561
+ throw error;
2360
2562
  } finally {
2361
2563
  span.end();
2362
2564
  }
@@ -2365,24 +2567,30 @@ var RuntimeFiltersPlugin = class extends _pothos_core.BasePlugin {
2365
2567
  const entities = Array.isArray(resolved) ? resolved : [resolved];
2366
2568
  const perFilterResults = await Promise.all(loaders.map((loader) => Promise.all(entities.map((entity) => loader.load(entity)))));
2367
2569
  const allowed = entities.filter((_, index) => perFilterResults.some((results) => results[index] != null));
2368
- span?.setAttribute("filters.allowed", allowed.length);
2570
+ span?.setAttribute(ATTR_FILTERS_ALLOWED, allowed.length);
2369
2571
  this.logger?.debug({
2370
- "graphql.field.name": fieldConfig.name,
2371
- "filters.total": allFilters.length,
2372
- "filters.allowed": allowed.length
2572
+ [ATTR_FIELD_NAME]: fieldConfig.name,
2573
+ [ATTR_FILTERS_TOTAL]: allFilters.length,
2574
+ [ATTR_FILTERS_ALLOWED]: allowed.length,
2575
+ ...traceCorrelationFields(this.telemetryConfig)
2373
2576
  }, "runtime filters applied");
2374
2577
  if (Array.isArray(resolved)) return allowed;
2375
2578
  return allowed[0] ?? null;
2376
2579
  };
2377
- if (this.tracer && this.tracerEnabled) return this.tracer.startActiveSpan(`rumble.filter.apply`, async (span) => {
2378
- span.setAttribute("graphql.field.name", fieldConfig.name);
2580
+ const logFilterError = (error) => {
2581
+ this.logger?.error({
2582
+ [ATTR_FIELD_NAME]: fieldConfig.name,
2583
+ ...traceCorrelationFields(this.telemetryConfig),
2584
+ ...errorLogField(error)
2585
+ }, "runtime filter threw");
2586
+ };
2587
+ if (this.tracer && this.tracerEnabled) return this.tracer.startActiveSpan(SPAN_FILTERS_APPLY, async (span) => {
2588
+ span.setAttribute(ATTR_FIELD_NAME, fieldConfig.name);
2379
2589
  try {
2380
2590
  return await runFilters(span);
2381
2591
  } catch (error) {
2382
- this.logger?.error({
2383
- "graphql.field.name": fieldConfig.name,
2384
- ...errorLogField(error)
2385
- }, "runtime filter threw");
2592
+ logFilterError(error);
2593
+ recordSpanError(span, error);
2386
2594
  throw error;
2387
2595
  } finally {
2388
2596
  span.end();
@@ -2391,10 +2599,7 @@ var RuntimeFiltersPlugin = class extends _pothos_core.BasePlugin {
2391
2599
  else try {
2392
2600
  return await runFilters();
2393
2601
  } catch (error) {
2394
- this.logger?.error({
2395
- "graphql.field.name": fieldConfig.name,
2396
- ...errorLogField(error)
2397
- }, "runtime filter threw");
2602
+ logFilterError(error);
2398
2603
  throw error;
2399
2604
  }
2400
2605
  };
@@ -2442,18 +2647,22 @@ const createSchemaBuilder = ({ db, pubsub, pothosConfig, otel, logger }) => {
2442
2647
  let r = createSpan ? createSpan(resolver, options) : resolver;
2443
2648
  if (logger?.enabled) {
2444
2649
  const log = logger.logger;
2650
+ const telemetryConfig = {
2651
+ otel,
2652
+ logger
2653
+ };
2445
2654
  r = (0, _pothos_plugin_tracing.wrapResolver)(r, (error, duration) => {
2655
+ const fields = {
2656
+ [ATTR_FIELD_NAME]: config.name,
2657
+ [ATTR_PARENT_TYPE]: config.parentType,
2658
+ [FIELD_DURATION_MS]: duration,
2659
+ ...traceCorrelationFields(telemetryConfig)
2660
+ };
2446
2661
  if (error) log.error({
2447
- "graphql.field.name": config.name,
2448
- "graphql.parent.type": config.parentType,
2449
- duration_ms: duration,
2662
+ ...fields,
2450
2663
  ...errorLogField(error)
2451
2664
  }, "resolver failed");
2452
- else log.debug({
2453
- "graphql.field.name": config.name,
2454
- "graphql.parent.type": config.parentType,
2455
- duration_ms: duration
2456
- }, "resolver completed");
2665
+ else log.debug(fields, "resolver completed");
2457
2666
  });
2458
2667
  }
2459
2668
  return r;
@@ -2475,6 +2684,27 @@ const createSchemaBuilder = ({ db, pubsub, pothosConfig, otel, logger }) => {
2475
2684
  };
2476
2685
  //#endregion
2477
2686
  //#region lib/rumble.ts
2687
+ /**
2688
+ * Mirrors sofa's own default error handler: it derives the HTTP status from
2689
+ * `extensions.http` and returns the errors as JSON. rumble only adds telemetry
2690
+ * around error handling, so a request that fails must still answer with the
2691
+ * status and body a plain sofa setup would have produced.
2692
+ */
2693
+ function sofaErrorResponse(errors) {
2694
+ let status;
2695
+ const headers = { "Content-Type": "application/json; charset=utf-8" };
2696
+ for (const error of errors) {
2697
+ const http = error?.extensions?.http;
2698
+ if (!http) continue;
2699
+ if (http.status && (!status || http.status > status)) status = http.status;
2700
+ if (http.headers) Object.assign(headers, http.headers);
2701
+ delete error.extensions.http;
2702
+ }
2703
+ return Response.json({ errors }, {
2704
+ status: status ?? 500,
2705
+ headers
2706
+ });
2707
+ }
2478
2708
  const rumble = (rumbleInput) => {
2479
2709
  if (!rumbleInput.db._.relations) throw new RumbleError(`
2480
2710
  rumble could not find any relations in the provided drizzle instance.
@@ -2580,6 +2810,33 @@ export const r = rumble({
2580
2810
  types: filteredTypes
2581
2811
  });
2582
2812
  });
2813
+ /**
2814
+ * Adapts the configured rumble logger to the logger interface yoga expects,
2815
+ * so yoga's own diagnostics end up in the same structured stream (with the
2816
+ * same `rumble.transport` and trace correlation fields) as rumble's entries.
2817
+ */
2818
+ const yogaLogging = () => {
2819
+ const log = telemetryLogger(rumbleInput);
2820
+ if (!log) return void 0;
2821
+ const emit = (level) => (...args) => {
2822
+ const messages = args.filter((arg) => typeof arg === "string");
2823
+ const rest = args.filter((arg) => typeof arg !== "string");
2824
+ const errors = rest.filter((entry) => entry instanceof Error);
2825
+ const details = rest.filter((entry) => !(entry instanceof Error));
2826
+ log[level]({
2827
+ [ATTR_TRANSPORT]: "graphql",
2828
+ ...traceCorrelationFields(rumbleInput),
2829
+ ...errors.length > 0 ? errorsLogField(errors) : {},
2830
+ ...details.length > 0 ? { details } : {}
2831
+ }, messages.join(" ") || "yoga diagnostic");
2832
+ };
2833
+ return {
2834
+ debug: emit("debug"),
2835
+ info: emit("info"),
2836
+ warn: emit("warn"),
2837
+ error: emit("error")
2838
+ };
2839
+ };
2583
2840
  const createYoga = (args) => {
2584
2841
  const enableApiDocs = args?.enableApiDocs ?? process?.env?.NODE_ENV === "development";
2585
2842
  return (0, graphql_yoga.createYoga)({
@@ -2587,38 +2844,39 @@ export const r = rumble({
2587
2844
  graphiql: enableApiDocs,
2588
2845
  schema: builtSchema(),
2589
2846
  context,
2847
+ logging: args?.logging ?? yogaLogging() ?? true,
2590
2848
  plugins: [
2591
2849
  ...args?.plugins ?? [],
2592
2850
  ...enableApiDocs ? [] : [(0, _graphql_yoga_plugin_disable_introspection.useDisableIntrospection)(), (0, _escape_tech_graphql_armor.EnvelopArmorPlugin)(args?.armorConfig)],
2593
- rumbleInput.otel?.enabled || rumbleInput.logger?.enabled ? { onExecute: ({ setExecuteFn, executeFn }) => {
2594
- setExecuteFn(buildTracedExecute(executeFn, rumbleInput));
2595
- } } : false
2851
+ telemetryEnabled(rumbleInput) ? {
2852
+ onExecute: ({ setExecuteFn, executeFn }) => {
2853
+ setExecuteFn(buildTracedExecute(executeFn, rumbleInput, "graphql"));
2854
+ },
2855
+ onSubscribe: ({ setSubscribeFn, subscribeFn }) => {
2856
+ setSubscribeFn(buildTracedSubscribe(subscribeFn, rumbleInput, "graphql"));
2857
+ }
2858
+ } : false
2596
2859
  ].filter(Boolean)
2597
2860
  });
2598
2861
  };
2599
2862
  const createSofa = async (args) => {
2600
- const { useSofa: useSofaFn } = await import("sofa-api");
2863
+ const { useSofa: useSofaFn } = await import("@m1212e/sofa-api");
2601
2864
  if (args.openAPI) (0, es_toolkit.merge)(args.openAPI, sofaOpenAPIWebhookDocs);
2602
- if (args.errorHandler) {
2603
- const originalHandler = args.errorHandler;
2604
- args.errorHandler = (errors) => {
2605
- const span = _opentelemetry_api.trace.getActiveSpan();
2606
- for (const error of errors) span?.recordException(error);
2607
- span?.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
2608
- return originalHandler(errors);
2609
- };
2610
- }
2865
+ const userErrorHandler = args.errorHandler;
2611
2866
  return useSofaFn({
2612
2867
  ...args,
2613
2868
  schema: builtSchema(),
2614
2869
  context,
2615
- execute: buildTracedExecute(args.execute ?? graphql.execute, rumbleInput),
2616
- subscribe: buildTracedSubscribe(args.subscribe ?? graphql.subscribe, rumbleInput),
2870
+ execute: buildTracedExecute(args.execute ?? graphql.execute, rumbleInput, "rest"),
2871
+ subscribe: buildTracedSubscribe(args.subscribe ?? graphql.subscribe, rumbleInput, "rest"),
2617
2872
  errorHandler(errors) {
2618
- const span = _opentelemetry_api.trace.getActiveSpan();
2619
- for (const error of errors) span?.recordException(error);
2620
- span?.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
2621
- return new Response(errors[0].message, { status: 500 });
2873
+ recordSpanErrors(_opentelemetry_api.trace.getActiveSpan(), errors);
2874
+ telemetryLogger(rumbleInput)?.error({
2875
+ [ATTR_TRANSPORT]: "rest",
2876
+ ...traceCorrelationFields(rumbleInput),
2877
+ ...errorsLogField(errors)
2878
+ }, "rest request failed");
2879
+ return userErrorHandler?.(errors) ?? sofaErrorResponse(errors);
2622
2880
  }
2623
2881
  });
2624
2882
  };
@@ -2627,8 +2885,8 @@ export const r = rumble({
2627
2885
  ...args,
2628
2886
  schema: builtSchema(),
2629
2887
  context,
2630
- execute: buildTracedExecute(args.execute ?? graphql.execute, rumbleInput),
2631
- subscribe: buildTracedSubscribe(args.subscribe ?? graphql.subscribe, rumbleInput)
2888
+ execute: buildTracedExecute(args.execute ?? graphql.execute, rumbleInput, "ws"),
2889
+ subscribe: buildTracedSubscribe(args.subscribe ?? graphql.subscribe, rumbleInput, "ws")
2632
2890
  }, ...rest);
2633
2891
  };
2634
2892
  return {