From 78dff816d85e2e8a5fa8ab441dab934e91ee2459 Mon Sep 17 00:00:00 2001 From: Thomas Flament Date: Wed, 3 Jun 2026 17:39:47 +0200 Subject: [PATCH 1/3] chore: depend on arsenal OTEL tracing module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin arsenal at the ARSN-586 branch (shared tracing module + W3C trace-context stamping on MongoDB metadata writes). Drop the SDK-core packages now that arsenal carries them as optionalDependencies, and keep the four instrumentation packages (http, ioredis, mongodb, aws-sdk) here — the consumer owns and configures them. Issue: BB-764 --- package.json | 7 +- yarn.lock | 514 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 516 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5d1b941d3..1ab39a1a3 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,11 @@ }, "homepage": "https://github.com/scality/backbeat#readme", "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.73.0", + "@opentelemetry/instrumentation-http": "^0.218.0", + "@opentelemetry/instrumentation-ioredis": "^0.66.0", + "@opentelemetry/instrumentation-mongodb": "^0.71.0", "@aws-sdk/client-iam": "^3.921.0", "@aws-sdk/client-s3": "^3.921.0", "@aws-sdk/client-sts": "^3.921.0", @@ -54,7 +59,7 @@ "@scality/cloudserverclient": "^1.0.8", "@smithy/node-http-handler": "^3.3.3", "JSONStream": "^1.3.5", - "arsenal": "git+https://github.com/scality/arsenal#8.3.9", + "arsenal": "git+https://github.com/scality/arsenal#improvement/ARSN-586/otel-tracing-module", "async": "^2.3.0", "backo": "^1.1.0", "breakbeat": "scality/breakbeat#v1.0.3", diff --git a/yarn.lock b/yarn.lock index aa740f23e..9d4d3544e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2462,6 +2462,24 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@grpc/grpc-js@^1.14.3": + version "1.14.4" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.14.4.tgz#e73ff57d97802f063999545f43ebb2b1eca65d9d" + integrity sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ== + dependencies: + "@grpc/proto-loader" "^0.8.0" + "@js-sdsl/ordered-map" "^4.4.2" + +"@grpc/proto-loader@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.8.0.tgz#b6c324dd909c458a0e4aa9bfd3d69cf78a4b9bd8" + integrity sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.5.3" + yargs "^17.7.2" + "@hapi/address@2.x.x": version "2.1.4" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" @@ -2677,6 +2695,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@js-sdsl/ordered-map@^4.4.2": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" + integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== + "@js-sdsl/ordered-set@^4.4.2": version "4.4.2" resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-set/-/ordered-set-4.4.2.tgz#ab857eb63cf358b5a0f74fdd458b4601423779b7" @@ -2733,16 +2756,403 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@opentelemetry/api-logs@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz#7b9818e8dfdf1d3dcab88bfe4d6724f2f831f7ec" + integrity sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw== + dependencies: + "@opentelemetry/api" "^1.3.0" + +"@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.9.0": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.1.tgz#c1b0346de336ba55af2d5a7970882037baedec05" + integrity sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q== + "@opentelemetry/api@^1.4.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== +"@opentelemetry/configuration@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/configuration/-/configuration-0.218.0.tgz#a5fdd50ec9cfa0adb3bb41202cb39f79c5f4e7d9" + integrity sha512-W8wIz7H2R1pufR5jfjb3gU2XkMpm2x/7b1RJcsuzvd70Il/rWWE+g5/Od7hQKrxRTSrTrOWlru101PWXz5I1EQ== + dependencies: + "@opentelemetry/core" "2.7.1" + yaml "^2.0.0" + +"@opentelemetry/context-async-hooks@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.1.tgz#1555a6fb269596416d8c626fd020c3f2c38e071f" + integrity sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ== + +"@opentelemetry/core@2.7.1", "@opentelemetry/core@^2.0.0": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.7.1.tgz#162bfab46d6ff4da1bef240ea52e23a926b0fdbc" + integrity sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw== + dependencies: + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/exporter-logs-otlp-grpc@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.218.0.tgz#7e5a7e624074d6449590c851d58efe60096314b3" + integrity sha512-hoxrNH1l/Xy6F9WTJ5IK+6j1r9nQFlPOmrnTlhYHTySdunfXLmUCPv3bQtKYntxag9h3wLYBZQ2HI6FOx+BT2g== + dependencies: + "@grpc/grpc-js" "^1.14.3" + "@opentelemetry/core" "2.7.1" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/otlp-grpc-exporter-base" "0.218.0" + "@opentelemetry/otlp-transformer" "0.218.0" + "@opentelemetry/sdk-logs" "0.218.0" + +"@opentelemetry/exporter-logs-otlp-http@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.218.0.tgz#0996cb45e0ebc6c7465445430a018edcac9871b4" + integrity sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw== + dependencies: + "@opentelemetry/api-logs" "0.218.0" + "@opentelemetry/core" "2.7.1" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/otlp-transformer" "0.218.0" + "@opentelemetry/sdk-logs" "0.218.0" + +"@opentelemetry/exporter-logs-otlp-proto@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.218.0.tgz#e539720b2e145e85a7a4901a1eabb0b2e77990f8" + integrity sha512-1/noQNsp9gXD75HPzgjBrcF1+XTtry7pFAUfxVEJgg7mPv2AawKQuYkhMmJ8qjxz4Ubc3Y8bwvfxevXsKTq4cg== + dependencies: + "@opentelemetry/api-logs" "0.218.0" + "@opentelemetry/core" "2.7.1" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/otlp-transformer" "0.218.0" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-logs" "0.218.0" + "@opentelemetry/sdk-trace-base" "2.7.1" + +"@opentelemetry/exporter-metrics-otlp-grpc@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.218.0.tgz#6d66c2638702489d063b8857741eee9cea1d21d6" + integrity sha512-YapQ9vNMX0NSZF6LK5pWAFfjpJleV2O9uYWfYGeb/5F1Kb9rPGK8tZDMJFa/sOksgdFuflDvYuA0B4qjDB4fjQ== + dependencies: + "@grpc/grpc-js" "^1.14.3" + "@opentelemetry/core" "2.7.1" + "@opentelemetry/exporter-metrics-otlp-http" "0.218.0" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/otlp-grpc-exporter-base" "0.218.0" + "@opentelemetry/otlp-transformer" "0.218.0" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-metrics" "2.7.1" + +"@opentelemetry/exporter-metrics-otlp-http@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.218.0.tgz#c205581585d04c5106d1ca766e23632006e46a2c" + integrity sha512-bV7d2OuMpZu2+gAaxUAhzfZ0h3WVZk8ETQUEE3DNSntbTaMpuITjtm8I0rNyHFdm7Ax57K6ty7SgFXlBmOLIvQ== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/otlp-transformer" "0.218.0" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-metrics" "2.7.1" + +"@opentelemetry/exporter-metrics-otlp-proto@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.218.0.tgz#66642b8bb5e654ca7a5944a4f0b490814fc875fd" + integrity sha512-ubLddKjWULhla9YZRCj/rTBeppjJYE4e9w0icx5mTu3eFhWjQzbV75NYjXuIlEG+NJsBl6d+sTFw5Qu+oej4oQ== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/exporter-metrics-otlp-http" "0.218.0" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/otlp-transformer" "0.218.0" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-metrics" "2.7.1" + +"@opentelemetry/exporter-prometheus@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.218.0.tgz#41bddc97d34cd9d9993382b9c10fed19a8de63f4" + integrity sha512-RT5oEyu1kddZJ1vt7/BUo5wV+P7hpNAESsR3dUd3+8deHuX7gWNoCOZn+SfDT+hJHlIJ5h/AxiCLXIrutswDJg== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-metrics" "2.7.1" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/exporter-trace-otlp-grpc@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.218.0.tgz#5d5532ab94d5514bec8aedc19ce3170b6381eed1" + integrity sha512-3fXxVQEj9TNAFaCi79JeFKfeLd0sDtInaR3gaZDVlzNSPHtz8PZuCV34JKWjD4XXzT20IdMe8IpX6mRVNDA4Tw== + dependencies: + "@grpc/grpc-js" "^1.14.3" + "@opentelemetry/core" "2.7.1" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/otlp-grpc-exporter-base" "0.218.0" + "@opentelemetry/otlp-transformer" "0.218.0" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-trace-base" "2.7.1" + +"@opentelemetry/exporter-trace-otlp-http@0.218.0", "@opentelemetry/exporter-trace-otlp-http@^0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.218.0.tgz#36d6abf6d639b9ea861603c61f434751c0b1a0ea" + integrity sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/otlp-transformer" "0.218.0" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-trace-base" "2.7.1" + +"@opentelemetry/exporter-trace-otlp-proto@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.218.0.tgz#de0b2b3545149dafedd2b948fb891f9bf962940c" + integrity sha512-r1Msf8SNLRmwh9J6XQ5uh82D7CdDWMNHnPB7LAVHjzut0TkSeKc5KcIvr4SvHvfk/xwN5gxC+VLKQ1k0o8PSPw== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/otlp-transformer" "0.218.0" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-trace-base" "2.7.1" + +"@opentelemetry/exporter-zipkin@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.7.1.tgz#3b79d223adc8c097ba3323e4de4ed8abb83c789e" + integrity sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-trace-base" "2.7.1" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/instrumentation-aws-sdk@^0.73.0": + version "0.73.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.73.0.tgz#2cedd670f36ce678d6e009cd6591b17aec1d130b" + integrity sha512-0INPkHbR6o4J3psE+ncwWaE7qtDpb2p+i+qfV82cfwYLCXavYCGosBZ/S4pOErDVJYIyQVIsNAHhaUgaL313SQ== + dependencies: + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.218.0" + "@opentelemetry/semantic-conventions" "^1.34.0" + +"@opentelemetry/instrumentation-http@^0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.218.0.tgz#0b26b702a6288fa11bdea48ff4a3635385a7d587" + integrity sha512-x9djaqdzpT8WAboep1H9nCAQ1E+MMsm08TNfA02TqM3bNNddZeiim+E3KMWVQFaX6JpUy7V0nm/wfN/K2Em+Zw== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/instrumentation" "0.218.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + forwarded-parse "2.1.2" + +"@opentelemetry/instrumentation-ioredis@^0.66.0": + version "0.66.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.66.0.tgz#f0306a5a0ff80eeb71312e2dac19d1beccc918ec" + integrity sha512-UfTAcaBKCzLUZ9opvfOLV4bH46XiNFqUsKykfPCIefDIxJ1iUYtMOucNaiZ+/kjQdPy5i6Ef5tk2IAjxol4X1w== + dependencies: + "@opentelemetry/instrumentation" "^0.218.0" + "@opentelemetry/redis-common" "^0.38.3" + "@opentelemetry/semantic-conventions" "^1.33.0" + +"@opentelemetry/instrumentation-mongodb@^0.71.0": + version "0.71.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.71.0.tgz#d011393ccb0d049fc13a13caa82e3ffc79e1652e" + integrity sha512-6rwfVjAUY69CKkyGqzL+F5X7Nzw0+Ke9pOxk9xUPJpy8vracZxuQYF7rWu02sV1xOgi4u52449SuVhD+zaSiIA== + dependencies: + "@opentelemetry/instrumentation" "^0.218.0" + "@opentelemetry/semantic-conventions" "^1.33.0" + +"@opentelemetry/instrumentation@0.218.0", "@opentelemetry/instrumentation@^0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.218.0.tgz#fcceb4ffb45f99c0d292600769150fc5944dc3e9" + integrity sha512-mIZil8Es+sYDK5m+DQiwAwF57F14TF2YlEqvIjZ/RQWcxDBwRGsKfdK2Tv65OU9meQKCMzSIFS9mxAcnAb6Bkg== + dependencies: + "@opentelemetry/api-logs" "0.218.0" + import-in-the-middle "^3.0.0" + require-in-the-middle "^8.0.0" + +"@opentelemetry/otlp-exporter-base@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz#f33e3217ce568756baa132fabe30f0c9345db086" + integrity sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/otlp-transformer" "0.218.0" + +"@opentelemetry/otlp-grpc-exporter-base@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.218.0.tgz#dc5a1fec716245d84dc4167de9b1c607e07fb6a7" + integrity sha512-H/lCGJ536N98VpYJOaWTQOkv4Dx6TnmStK6Rqfu1W7KkFbPAx04hjdYEMZF/YbnHzPUSIK4kM6OE2GKGBTpV9A== + dependencies: + "@grpc/grpc-js" "^1.14.3" + "@opentelemetry/core" "2.7.1" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/otlp-transformer" "0.218.0" + +"@opentelemetry/otlp-transformer@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz#6784d0ddd13803c63a1b24072606c02fc21b9071" + integrity sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ== + dependencies: + "@opentelemetry/api-logs" "0.218.0" + "@opentelemetry/core" "2.7.1" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-logs" "0.218.0" + "@opentelemetry/sdk-metrics" "2.7.1" + "@opentelemetry/sdk-trace-base" "2.7.1" + +"@opentelemetry/propagator-b3@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-b3/-/propagator-b3-2.7.1.tgz#107fe3e16d0728c489edbad221c402ee197514a4" + integrity sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A== + dependencies: + "@opentelemetry/core" "2.7.1" + +"@opentelemetry/propagator-jaeger@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.7.1.tgz#e8ebf3f6c0e9aa525cf041893425889cf3e69125" + integrity sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q== + dependencies: + "@opentelemetry/core" "2.7.1" + +"@opentelemetry/redis-common@^0.38.3": + version "0.38.3" + resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.38.3.tgz#31a0464a48a991c29408614e3725d94db7c11aee" + integrity sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw== + +"@opentelemetry/resources@2.7.1", "@opentelemetry/resources@^2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.7.1.tgz#3b2a9179f6119bb1f2cddefe41ba9b2855504a5d" + integrity sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/sdk-logs@0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz#78886fe300b82802cee9963208b2af326b928af5" + integrity sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag== + dependencies: + "@opentelemetry/api-logs" "0.218.0" + "@opentelemetry/core" "2.7.1" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/sdk-metrics@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz#b713f69dd67933ecc9c61357f1d452cdc9f4e281" + integrity sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/resources" "2.7.1" + +"@opentelemetry/sdk-node@^0.218.0": + version "0.218.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-node/-/sdk-node-0.218.0.tgz#cf8bdc0c993387189c7c474612e0f801505feb97" + integrity sha512-tPMjHrLV5gsfNdYqoRHjeGbCAZBXXD9c1Qo/2ut7VwnUABDNh76xNxrT0SEhkIIJuCN45bbN1vZnYL1gY0IkOg== + dependencies: + "@opentelemetry/api-logs" "0.218.0" + "@opentelemetry/configuration" "0.218.0" + "@opentelemetry/context-async-hooks" "2.7.1" + "@opentelemetry/core" "2.7.1" + "@opentelemetry/exporter-logs-otlp-grpc" "0.218.0" + "@opentelemetry/exporter-logs-otlp-http" "0.218.0" + "@opentelemetry/exporter-logs-otlp-proto" "0.218.0" + "@opentelemetry/exporter-metrics-otlp-grpc" "0.218.0" + "@opentelemetry/exporter-metrics-otlp-http" "0.218.0" + "@opentelemetry/exporter-metrics-otlp-proto" "0.218.0" + "@opentelemetry/exporter-prometheus" "0.218.0" + "@opentelemetry/exporter-trace-otlp-grpc" "0.218.0" + "@opentelemetry/exporter-trace-otlp-http" "0.218.0" + "@opentelemetry/exporter-trace-otlp-proto" "0.218.0" + "@opentelemetry/exporter-zipkin" "2.7.1" + "@opentelemetry/instrumentation" "0.218.0" + "@opentelemetry/otlp-exporter-base" "0.218.0" + "@opentelemetry/propagator-b3" "2.7.1" + "@opentelemetry/propagator-jaeger" "2.7.1" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/sdk-logs" "0.218.0" + "@opentelemetry/sdk-metrics" "2.7.1" + "@opentelemetry/sdk-trace-base" "2.7.1" + "@opentelemetry/sdk-trace-node" "2.7.1" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/sdk-trace-base@2.7.1", "@opentelemetry/sdk-trace-base@^2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz#9160c3af9ef2219c26563abd136e22fb7d19b34f" + integrity sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw== + dependencies: + "@opentelemetry/core" "2.7.1" + "@opentelemetry/resources" "2.7.1" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/sdk-trace-node@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.7.1.tgz#54dedb8e77fa51a6d02fc2192097739266c82168" + integrity sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg== + dependencies: + "@opentelemetry/context-async-hooks" "2.7.1" + "@opentelemetry/core" "2.7.1" + "@opentelemetry/sdk-trace-base" "2.7.1" + +"@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.33.0", "@opentelemetry/semantic-conventions@^1.34.0": + version "1.41.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz#b04e7151c5913a7a006d4f465479da75efb98a7a" + integrity sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" @@ -2765,6 +3175,14 @@ httpagent "github:scality/httpagent#1.1.0" werelogs "github:scality/werelogs#8.2.2" +"@scality/hdclient@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@scality/hdclient/-/hdclient-1.3.2.tgz#544d08a5b88869a9c30107c05d774e13595966fb" + integrity sha512-voy67AlH1irNmaXno0KP/KpiEBYzzdW8EoGjBXsVztLFjGe+RQnNtAyzCn05secZZy43jBYLOrZ7032gorxvrg== + dependencies: + httpagent "github:scality/httpagent#1.1.0" + werelogs "github:scality/werelogs#8.2.2" + "@senx/warp10@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@senx/warp10/-/warp10-2.0.3.tgz#dcce3890d491c6380f2967abcf126909ed208969" @@ -4489,6 +4907,13 @@ dependencies: undici-types "~7.16.0" +"@types/node@>=13.7.0": + version "25.6.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.6.0.tgz#4e09bad9b469871f2d0f68140198cbd714f4edca" + integrity sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ== + dependencies: + undici-types "~7.19.0" + "@types/node@^18.11.11": version "18.19.130" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.130.tgz#da4c6324793a79defb7a62cba3947ec5add00d59" @@ -4653,6 +5078,11 @@ accesscontrol@^2.2.1: dependencies: notation "^1.3.6" +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -4978,9 +5408,9 @@ arraybuffer.prototype.slice@^1.0.4: optionalDependencies: ioctl "^2.0.2" -"arsenal@git+https://github.com/scality/arsenal#8.3.9": - version "8.3.9" - resolved "git+https://github.com/scality/arsenal#51e5b761f7f0612a722c828fa3d43b438c50ab7c" +"arsenal@git+https://github.com/scality/arsenal#improvement/ARSN-586/otel-tracing-module": + version "8.4.4" + resolved "git+https://github.com/scality/arsenal#15c51a2585b26a0f4afb291ea7539ef093b9e40e" dependencies: "@aws-sdk/client-kms" "^3.975.0" "@aws-sdk/client-s3" "^3.975.0" @@ -4989,7 +5419,8 @@ arraybuffer.prototype.slice@^1.0.4: "@azure/identity" "^4.13.0" "@azure/storage-blob" "^12.31.0" "@js-sdsl/ordered-set" "^4.4.2" - "@scality/hdclient" "^1.3.1" + "@opentelemetry/api" "^1.9.0" + "@scality/hdclient" "^1.3.2" "@smithy/node-http-handler" "^4.3.0" "@smithy/protocol-http" "^5.3.5" JSONStream "^1.3.5" @@ -5020,6 +5451,10 @@ arraybuffer.prototype.slice@^1.0.4: werelogs scality/werelogs#8.2.2 xml2js "^0.6.2" optionalDependencies: + "@opentelemetry/exporter-trace-otlp-http" "^0.218.0" + "@opentelemetry/resources" "^2.7.1" + "@opentelemetry/sdk-node" "^0.218.0" + "@opentelemetry/sdk-trace-base" "^2.7.1" ioctl "^2.0.2" asn1@~0.2.3: @@ -5488,6 +5923,11 @@ chownr@^3.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== +cjs-module-lexer@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz#b3ca5101843389259ade7d88c77bd06ce55849ca" + integrity sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ== + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -6733,6 +7173,11 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +forwarded-parse@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/forwarded-parse/-/forwarded-parse-2.1.2.tgz#08511eddaaa2ddfd56ba11138eee7df117a09325" + integrity sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw== + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -7251,6 +7696,16 @@ import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" +import-in-the-middle@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-3.0.1.tgz#8a0a1230c9b865c0e12698171646ae1e3fff691d" + integrity sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA== + dependencies: + acorn "^8.15.0" + acorn-import-attributes "^1.9.5" + cjs-module-lexer "^2.2.0" + module-details-from-path "^1.0.4" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -8101,6 +8556,11 @@ lodash-compat@^3.10.2: resolved "https://registry.yarnpkg.com/lodash-compat/-/lodash-compat-3.10.2.tgz#c6940128a9d30f8e902cd2cf99fd0cba4ecfc183" integrity sha512-k8SE/OwvWfYZqx3MA/Ry1SHBDWre8Z8tCs0Ba0bF5OqVNvymxgFZ/4VDtbTxzTvcoG11JpTMFsaeZp/yGYvFnA== +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" @@ -8218,6 +8678,11 @@ long-timeout@0.1.1: resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" integrity sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w== +long@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + looper@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/looper/-/looper-2.0.0.tgz#66cd0c774af3d4fedac53794f742db56da8f09ec" @@ -8577,6 +9042,11 @@ mocha@^11.0.1: yargs-parser "^21.1.1" yargs-unparser "^2.0.0" +module-details-from-path@^1.0.3, module-details-from-path@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.4.tgz#b662fdcd93f6c83d3f25289da0ce81c8d9685b94" + integrity sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w== + moment@^2.30.1: version "2.30.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" @@ -9253,6 +9723,24 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" +protobufjs@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a" + integrity sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -9499,6 +9987,14 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-in-the-middle@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz#dbde2587f669398626d56b20c868ab87bf01cce4" + integrity sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ== + dependencies: + debug "^4.3.5" + module-details-from-path "^1.0.3" + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -10515,6 +11011,11 @@ undici-types@~7.16.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== +undici-types@~7.19.0: + version "7.19.2" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.19.2.tgz#1b67fc26d0f157a0cba3a58a5b5c1e2276b8ba2a" + integrity sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -11030,6 +11531,11 @@ yallist@^5.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== +yaml@^2.0.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.0.tgz#78274afd93598a1dfdd6130df6a566defcbf9aa4" + integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA== + yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" From 2bb03a9a6dc8b289dfa49a2af28aecdad583f6e1 Mon Sep 17 00:00:00 2001 From: Thomas Flament Date: Wed, 3 Jun 2026 17:40:02 +0200 Subject: [PATCH 2/3] feat: replace in-tree tracing with arsenal shim lib/tracing/index.js becomes a thin shim over arsenal's shared module: it carries backbeat's config (serviceName, the http/ioredis/mongodb/ aws-sdk instrumentations, outbound-only HTTP via makeHttpInstrumentationConfig + disableIncomingRequestInstrumentation) so the 8 entry points keep calling init() with no args. kafkaTraceContext.js re-exports arsenal's kafka helpers so the existing require sites are unchanged. The trust-boundary filter and SDK bootstrap now live in arsenal. Issue: BB-764 --- lib/tracing/index.js | 42 ++++++++++++++++++++++++++++++++ lib/tracing/kafkaTraceContext.js | 5 ++++ 2 files changed, 47 insertions(+) create mode 100644 lib/tracing/index.js create mode 100644 lib/tracing/kafkaTraceContext.js diff --git a/lib/tracing/index.js b/lib/tracing/index.js new file mode 100644 index 000000000..61a13f023 --- /dev/null +++ b/lib/tracing/index.js @@ -0,0 +1,42 @@ +'use strict'; + +// Thin shim over arsenal's shared tracing module: backbeat's 8 entry points +// call require('../lib/tracing').init() with no args, so the backbeat-specific +// config (serviceName, instrumentations, outbound-only HTTP) lives here once. +const tracing = require('arsenal/build/lib/tracing'); +const { version } = require('../../package.json'); + +function init() { + tracing.init({ + serviceName: 'backbeat', + serviceVersion: version, + // Built lazily inside init() (only when OTEL is enabled). backbeat + // owns its instrumentation packages + options; arsenal owns the + // trust-boundary requestHook (via makeHttpInstrumentationConfig). + instrumentations: () => { + const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); + const { IORedisInstrumentation } = require('@opentelemetry/instrumentation-ioredis'); + const { MongoDBInstrumentation } = require('@opentelemetry/instrumentation-mongodb'); + const { AwsInstrumentation } = require('@opentelemetry/instrumentation-aws-sdk'); + return [ + // Outbound only: backbeat pods serve no application HTTP, so + // disable inbound spans; the spread brings arsenal's trust-boundary + // requestHook (strips traceparent on calls to untrusted hosts). + new HttpInstrumentation({ + ...tracing.makeHttpInstrumentationConfig(), + disableIncomingRequestInstrumentation: true, + }), + new IORedisInstrumentation({ requireParentSpan: true }), + new MongoDBInstrumentation({ enhancedDatabaseReporting: false }), + new AwsInstrumentation(), + ]; + }, + }); +} + +module.exports = { + init, + close: tracing.close, + isEnabled: tracing.isEnabled, + endSpan: tracing.endSpan, +}; diff --git a/lib/tracing/kafkaTraceContext.js b/lib/tracing/kafkaTraceContext.js new file mode 100644 index 000000000..4ac19e199 --- /dev/null +++ b/lib/tracing/kafkaTraceContext.js @@ -0,0 +1,5 @@ +'use strict'; + +// Re-export arsenal's shared kafka trace-context helpers so existing +// `require('.../lib/tracing/kafkaTraceContext')` call sites are unchanged. +module.exports = require('arsenal/build/lib/tracing').kafka; From 8412b736e2be4282426071cd289ccd123a31c755 Mon Sep 17 00:00:00 2001 From: Thomas Flament Date: Wed, 3 Jun 2026 17:40:02 +0200 Subject: [PATCH 3/3] feat: instrument backbeat pods and the Kafka pipeline Wire arsenal's tracing into the replication, lifecycle, GC, notification, and oplog-populator pods: init() at each entry point, per-pod spans, and trace-context propagation across the Kafka pipeline (producers stamp traceparent via the kafka helpers; consumers start linked spans from it). Out-of-process Kafka hops use span links, not parent/child, so traces stay bounded. Issue: BB-764 --- bin/queuePopulator.js | 5 +- extensions/gc/service.js | 4 +- extensions/lifecycle/bucketProcessor/task.js | 4 +- .../lifecycle/conductor/LifecycleConductor.js | 40 +++++++++- extensions/lifecycle/conductor/service.js | 4 +- extensions/lifecycle/objectProcessor/task.js | 4 +- extensions/lifecycle/tasks/LifecycleTask.js | 15 +++- .../NotificationQueuePopulator.js | 6 +- .../KafkaNotificationDestination.js | 12 ++- .../notification/queueProcessor/task.js | 5 +- extensions/replication/ReplicationAPI.js | 5 ++ .../replication/ReplicationQueuePopulator.js | 7 +- extensions/replication/queueProcessor/task.js | 5 +- .../replicationStatusProcessor/task.js | 3 + lib/BackbeatConsumer.js | 29 ++++++- lib/BackbeatProducer.js | 3 +- lib/queuePopulator/QueuePopulatorExtension.js | 5 +- tests/unit/lib/BackbeatConsumer.spec.js | 76 +++++++++++++++++++ tests/unit/lifecycle/LifecycleTask.spec.js | 50 ++++++++++++ .../KafkaNotificationDestination.js | 19 +++++ .../NotificationQueuePopulator.js | 63 +++++++++++++++ .../QueuePopulatorExtension.spec.js | 27 +++++++ tests/unit/replication/ReplicationAPI.spec.js | 39 ++++++++++ tests/utils/withActiveSpan.js | 35 +++++++++ 24 files changed, 449 insertions(+), 16 deletions(-) create mode 100644 tests/unit/lib/BackbeatConsumer.spec.js create mode 100644 tests/unit/queuePopulator/QueuePopulatorExtension.spec.js create mode 100644 tests/utils/withActiveSpan.js diff --git a/bin/queuePopulator.js b/bin/queuePopulator.js index 8f25a361a..177e3dc8c 100644 --- a/bin/queuePopulator.js +++ b/bin/queuePopulator.js @@ -1,3 +1,6 @@ +const tracing = require('../lib/tracing'); +tracing.init(); + const async = require('async'); const schedule = require('node-schedule'); @@ -98,6 +101,6 @@ process.on('SIGTERM', () => { }); process.exit(1); } - process.exit(0); + tracing.close().finally(() => process.exit(0)); }); }); diff --git a/extensions/gc/service.js b/extensions/gc/service.js index 6c132a646..9a9b3e26f 100644 --- a/extensions/gc/service.js +++ b/extensions/gc/service.js @@ -1,4 +1,6 @@ 'use strict'; +const tracing = require('../../lib/tracing'); +tracing.init(); const { errors } = require('arsenal'); const werelogs = require('werelogs'); @@ -101,6 +103,6 @@ initAndStart(); process.on('SIGTERM', () => { logger.info('received SIGTERM, exiting'); garbageCollector.close(() => { - process.exit(0); + tracing.close().finally(() => process.exit(0)); }); }); diff --git a/extensions/lifecycle/bucketProcessor/task.js b/extensions/lifecycle/bucketProcessor/task.js index d40a584d5..0ea3660bd 100644 --- a/extensions/lifecycle/bucketProcessor/task.js +++ b/extensions/lifecycle/bucketProcessor/task.js @@ -1,4 +1,6 @@ 'use strict'; +const tracing = require('../../../lib/tracing'); +tracing.init(); const async = require('async'); const werelogs = require('werelogs'); @@ -133,6 +135,6 @@ async.waterfall([ process.on('SIGTERM', () => { logger.info('received SIGTERM, exiting'); bucketProcessor.close(() => { - process.exit(0); + tracing.close().finally(() => process.exit(0)); }); }); diff --git a/extensions/lifecycle/conductor/LifecycleConductor.js b/extensions/lifecycle/conductor/LifecycleConductor.js index 54a6bd2e0..e4231ce5f 100644 --- a/extensions/lifecycle/conductor/LifecycleConductor.js +++ b/extensions/lifecycle/conductor/LifecycleConductor.js @@ -29,6 +29,11 @@ const { startCircuitBreakerMetricsExport, updateCircuitBreakerConfigForImplicitOutputQueue, } = require('../../../lib/CircuitBreaker'); +const { context: otelContext, trace, SpanKind, ROOT_CONTEXT } = + require('@opentelemetry/api'); +const { traceHeadersFromCurrentContext } = + require('../../../lib/tracing/kafkaTraceContext'); +const { isEnabled, endSpan } = require('../../../lib/tracing'); const DEFAULT_CRON_RULE = '* * * * *'; const DEFAULT_CONCURRENCY = 10; @@ -340,7 +345,8 @@ class LifecycleConductor { } _taskToMessage(task, taskVersion, log) { - return { + const headers = traceHeadersFromCurrentContext(); + const kafkaEntry = { message: JSON.stringify({ action: 'processObjects', contextInfo: { @@ -355,6 +361,10 @@ class LifecycleConductor { details: {}, }), }; + if (headers) { + kafkaEntry.headers = headers; + } + return kafkaEntry; } _getAccountIds(unknownCanonicalIds, log, cb) { @@ -402,6 +412,34 @@ class LifecycleConductor { } processBuckets(cb) { + if (!isEnabled()) { + this._processBucketsInternal((err, res) => { + if (cb) {cb(err, res);} + }); + return; + } + // Root INTERNAL trace per cron firing (no upstream parent); the + // in-process scan work (Mongo bucket listing) nests under it. + const tracer = trace.getTracer('backbeat'); + const span = tracer.startSpan('lifecycle.conductor.scan', { + kind: SpanKind.INTERNAL, + }, ROOT_CONTEXT); + const ctx = trace.setSpan(ROOT_CONTEXT, span); + otelContext.with(ctx, () => { + try { + this._processBucketsInternal((err, res) => { + endSpan(span, err); + if (cb) {cb(err, res);} + }); + } catch (err) { + // sync throw: end span (don't leak), then rethrow + endSpan(span, err); + throw err; + } + }); + } + + _processBucketsInternal(cb) { const log = this.logger.newRequestLogger(); const start = new Date(); let nBucketsQueued = 0; diff --git a/extensions/lifecycle/conductor/service.js b/extensions/lifecycle/conductor/service.js index ec499cdd1..4e286cd0f 100644 --- a/extensions/lifecycle/conductor/service.js +++ b/extensions/lifecycle/conductor/service.js @@ -1,4 +1,6 @@ 'use strict'; +const tracing = require('../../../lib/tracing'); +tracing.init(); const async = require('async'); const werelogs = require('werelogs'); @@ -89,6 +91,6 @@ async.waterfall([ process.on('SIGTERM', () => { logger.info('received SIGTERM, exiting'); lcConductor.stop(() => { - process.exit(0); + tracing.close().finally(() => process.exit(0)); }); }); diff --git a/extensions/lifecycle/objectProcessor/task.js b/extensions/lifecycle/objectProcessor/task.js index 95cd10501..d09c2020a 100644 --- a/extensions/lifecycle/objectProcessor/task.js +++ b/extensions/lifecycle/objectProcessor/task.js @@ -1,4 +1,6 @@ 'use strict'; +const tracing = require('../../../lib/tracing'); +tracing.init(); const async = require('async'); const werelogs = require('werelogs'); @@ -120,6 +122,6 @@ async.waterfall([ process.on('SIGTERM', () => { logger.info('received SIGTERM, exiting'); objectProcessor.close(() => { - process.exit(0); + tracing.close().finally(() => process.exit(0)); }); }); diff --git a/extensions/lifecycle/tasks/LifecycleTask.js b/extensions/lifecycle/tasks/LifecycleTask.js index 591ce63e8..b4175b0c0 100644 --- a/extensions/lifecycle/tasks/LifecycleTask.js +++ b/extensions/lifecycle/tasks/LifecycleTask.js @@ -24,6 +24,7 @@ const ReplicationAPI = require('../../replication/ReplicationAPI'); const { LifecycleMetrics, LIFECYCLE_MARKER_METRICS_LOCATION } = require('../LifecycleMetrics'); const locationsConfig = require('../../../conf/locationConfig.json') || {}; const { rulesSupportTransition } = require('../util/rules'); +const { traceHeadersFromCurrentContext } = require('../../../lib/tracing/kafkaTraceContext'); const { decode } = versioning.VersionID; const errorTransitionInProgress = errors.InternalError. @@ -121,7 +122,12 @@ class LifecycleTask extends BackbeatTask { * @return {undefined} */ _sendBucketEntry(entry, cb) { - const entries = [{ message: JSON.stringify(entry) }]; + const headers = traceHeadersFromCurrentContext(); + const kafkaEntry = { message: JSON.stringify(entry) }; + if (headers) { + kafkaEntry.headers = headers; + } + const entries = [kafkaEntry]; this.producer.sendToTopic(this.bucketTasksTopic, entries, err => { LifecycleMetrics.onKafkaPublish(null, 'BucketTopic', 'bucket', err, 1); return cb(err); @@ -183,7 +189,12 @@ class LifecycleTask extends BackbeatTask { location, Date.now() - entry.getAttribute('transitionTime')); - const entries = [{ message: entry.toKafkaMessage() }]; + const headers = traceHeadersFromCurrentContext(); + const kafkaEntry = { message: entry.toKafkaMessage() }; + if (headers) { + kafkaEntry.headers = headers; + } + const entries = [kafkaEntry]; this.producer.sendToTopic(this.objectTasksTopic, entries, err => { LifecycleMetrics.onKafkaPublish(null, 'ObjectTopic', 'bucket', err, 1); return cb(err); diff --git a/extensions/notification/NotificationQueuePopulator.js b/extensions/notification/NotificationQueuePopulator.js index 30b084d5f..0c495d13b 100644 --- a/extensions/notification/NotificationQueuePopulator.js +++ b/extensions/notification/NotificationQueuePopulator.js @@ -10,6 +10,7 @@ const messageUtil = require('./utils/message'); const notifConstants = require('./constants'); const QueuePopulatorExtension = require('../../lib/queuePopulator/QueuePopulatorExtension'); +const { traceHeadersFromEntry } = require('../../lib/tracing/kafkaTraceContext'); class NotificationQueuePopulator extends QueuePopulatorExtension { /** @@ -290,13 +291,16 @@ class NotificationQueuePopulator extends QueuePopulatorExtension { eventTime: message.dateTime, matchingConfig, }); + const traceHeaders = traceHeadersFromEntry(value); this.publish(topic, // keeping all messages for same object // in the same partition to keep the order. // here we use the object name and not the // "_id" which also includes the versionId `${bucket}/${message.key}`, - JSON.stringify(message)); + JSON.stringify(message), + undefined, + traceHeaders); // keep track of internal topics we have pushed to pushedToTopic[topic] = true; } diff --git a/extensions/notification/destination/KafkaNotificationDestination.js b/extensions/notification/destination/KafkaNotificationDestination.js index b7936f164..d41d3b33c 100644 --- a/extensions/notification/destination/KafkaNotificationDestination.js +++ b/extensions/notification/destination/KafkaNotificationDestination.js @@ -100,7 +100,17 @@ class KafkaNotificationDestination extends NotificationDestination { */ send(messages, done) { const starTime = Date.now(); - this._notificationProducer.send(messages, error => { + // Trust boundary: strip trace headers before producing to the customer Kafka. + // Delete on a shallow copy, never on `m`, so the caller's messages aren't mutated. + const safeMessages = messages.map(m => { + if (m && m.headers) { + const rest = { ...m }; + delete rest.headers; + return rest; + } + return m; + }); + this._notificationProducer.send(safeMessages, error => { if (error) { const { host, topic } = this._destinationConfig; this._log.error('error in message delivery to external Kafka destination', { diff --git a/extensions/notification/queueProcessor/task.js b/extensions/notification/queueProcessor/task.js index a6176ce31..56c8e2bff 100644 --- a/extensions/notification/queueProcessor/task.js +++ b/extensions/notification/queueProcessor/task.js @@ -1,4 +1,7 @@ 'use strict'; +const tracing = require('../../../lib/tracing'); +tracing.init(); + const assert = require('assert'); const { errors } = require('arsenal'); const async = require('async'); @@ -108,7 +111,7 @@ process.on('SIGTERM', () => { }); process.exit(1); } - process.exit(0); + tracing.close().finally(() => process.exit(0)); }); }); diff --git a/extensions/replication/ReplicationAPI.js b/extensions/replication/ReplicationAPI.js index 5be6d3ea1..5bfcc8d2b 100644 --- a/extensions/replication/ReplicationAPI.js +++ b/extensions/replication/ReplicationAPI.js @@ -3,6 +3,7 @@ const locations = require('../../conf/locationConfig.json') || {}; const ActionQueueEntry = require('../../lib/models/ActionQueueEntry'); const ReplicationMetrics = require('./ReplicationMetrics'); +const { traceHeadersFromCurrentContext } = require('../../lib/tracing/kafkaTraceContext'); let { dataMoverTopic } = config.extensions.replication; const { coldStorageArchiveTopicPrefix } = config.extensions.lifecycle; @@ -78,6 +79,10 @@ class ReplicationAPI { key: `${bucket}/${key}`, message: action.toKafkaMessage(), }; + const traceHeaders = traceHeadersFromCurrentContext(); + if (traceHeaders) { + kafkaEntry.headers = traceHeaders; + } let topic = dataMoverTopic; const toLocation = action.getAttribute('toLocation'); const locationConfig = locations[toLocation]; diff --git a/extensions/replication/ReplicationQueuePopulator.js b/extensions/replication/ReplicationQueuePopulator.js index 8e51fc7e8..0927a14c5 100644 --- a/extensions/replication/ReplicationQueuePopulator.js +++ b/extensions/replication/ReplicationQueuePopulator.js @@ -6,6 +6,7 @@ const QueuePopulatorExtension = const ObjectQueueEntry = require('../../lib/models/ObjectQueueEntry'); const locationsConfig = require('../../conf/locationConfig.json') || {}; const safeJsonParse = require('../../lib/util/safeJsonParse'); +const { traceHeadersFromEntry } = require('../../lib/tracing/kafkaTraceContext'); class ReplicationQueuePopulator extends QueuePopulatorExtension { constructor(params) { @@ -112,11 +113,15 @@ class ReplicationQueuePopulator extends QueuePopulatorExtension { const publishedEntry = Object.assign({}, entry); delete publishedEntry.logReader; + const traceHeaders = traceHeadersFromEntry(value); + this.log.trace('publishing object replication entry', { entry: queueEntry.getLogInfo() }); this.publish(this.repConfig.topic, `${queueEntry.getBucket()}/${queueEntry.getObjectKey()}`, - JSON.stringify(publishedEntry)); + JSON.stringify(publishedEntry), + undefined, + traceHeaders); } /** diff --git a/extensions/replication/queueProcessor/task.js b/extensions/replication/queueProcessor/task.js index 23dabde7c..d0af18009 100644 --- a/extensions/replication/queueProcessor/task.js +++ b/extensions/replication/queueProcessor/task.js @@ -1,4 +1,7 @@ 'use strict'; +const tracing = require('../../../lib/tracing'); +tracing.init(); + const async = require('async'); const assert = require('assert'); const werelogs = require('werelogs'); @@ -340,6 +343,6 @@ process.on('SIGTERM', () => { }); process.exit(1); } - process.exit(0); + tracing.close().finally(() => process.exit(0)); }); }); diff --git a/extensions/replication/replicationStatusProcessor/task.js b/extensions/replication/replicationStatusProcessor/task.js index 61948835c..fcf8d4804 100644 --- a/extensions/replication/replicationStatusProcessor/task.js +++ b/extensions/replication/replicationStatusProcessor/task.js @@ -1,4 +1,6 @@ 'use strict'; +const tracing = require('../../../lib/tracing'); +tracing.init(); const werelogs = require('werelogs'); @@ -70,5 +72,6 @@ process.on('SIGTERM', () => { }); process.exit(1); } + tracing.close().finally(() => process.exit(0)); }); }); diff --git a/lib/BackbeatConsumer.js b/lib/BackbeatConsumer.js index af2b372b9..515c19690 100644 --- a/lib/BackbeatConsumer.js +++ b/lib/BackbeatConsumer.js @@ -23,6 +23,10 @@ const { } } = require('./constants'); +const { context: otelContext } = require('@opentelemetry/api'); +const { isEnabled, endSpan } = require('./tracing'); +const { startLinkedSpanFromKafkaEntry } = require('./tracing/kafkaTraceContext'); + const CLIENT_ID = 'BackbeatConsumer'; const { withTopicPrefix } = require('./util/topic'); @@ -530,7 +534,30 @@ class BackbeatConsumer extends EventEmitter { const { topic, partition } = entry; KafkaBacklogMetrics.onTaskStarted(topic, partition, this._groupId); - this._queueProcessor(entry, (err, completionArgs) => done(err, completionArgs, finishProcessingTask)); + if (!isEnabled()) { + this._queueProcessor(entry, (err, completionArgs) => + done(err, completionArgs, finishProcessingTask)); + return; + } + + const { ctx, span } = startLinkedSpanFromKafkaEntry(entry, `${topic}.process`); + span.setAttribute('messaging.system', 'kafka'); + span.setAttribute('messaging.destination.name', topic); + span.setAttribute('messaging.destination.partition.id', `${partition}`); + span.setAttribute('messaging.consumer.group.name', this._groupId); + + otelContext.with(ctx, () => { + try { + this._queueProcessor(entry, (err, completionArgs) => { + endSpan(span, err); + done(err, completionArgs, finishProcessingTask); + }); + } catch (err) { + // sync throw before the callback fired: end span (don't leak), then rethrow + endSpan(span, err); + throw err; + } + }); } /** diff --git a/lib/BackbeatProducer.js b/lib/BackbeatProducer.js index 6fe22b786..493924605 100644 --- a/lib/BackbeatProducer.js +++ b/lib/BackbeatProducer.js @@ -288,7 +288,8 @@ class BackbeatProducer extends EventEmitter { Buffer.from(item.message), // value item.key, // key (for keyed partitioning) Date.now(), // timestamp - sendCtx // opaque + sendCtx, // opaque + item.headers || undefined // Kafka message headers ); }); } catch (err) { diff --git a/lib/queuePopulator/QueuePopulatorExtension.js b/lib/queuePopulator/QueuePopulatorExtension.js index 0ef5a879f..96fe59f45 100644 --- a/lib/queuePopulator/QueuePopulatorExtension.js +++ b/lib/queuePopulator/QueuePopulatorExtension.js @@ -67,7 +67,7 @@ class QueuePopulatorExtension { * @param {Object} [optEntriesToPublish] - optional batch * @return {undefined} */ - publish(topic, key, message, optEntriesToPublish) { + publish(topic, key, message, optEntriesToPublish, headers) { let __batch; if (optEntriesToPublish) { __batch = optEntriesToPublish; @@ -81,6 +81,9 @@ class QueuePopulatorExtension { 'synchronously from the filter() method.'); const kafkaEntry = { key: encodeURIComponent(key), message }; + if (headers) { + kafkaEntry.headers = headers; + } this.log.trace('queueing kafka entry to topic', { key: kafkaEntry.key, topic }); if (__batch[topic] === undefined) { diff --git a/tests/unit/lib/BackbeatConsumer.spec.js b/tests/unit/lib/BackbeatConsumer.spec.js new file mode 100644 index 000000000..16e45704b --- /dev/null +++ b/tests/unit/lib/BackbeatConsumer.spec.js @@ -0,0 +1,76 @@ +'use strict'; + +const assert = require('assert'); +const sinon = require('sinon'); + +const BackbeatConsumer = require('../../../lib/BackbeatConsumer'); +const KafkaBacklogMetrics = require('../../../lib/KafkaBacklogMetrics'); + +// _processTask wraps the queue processor in an OTEL consumer span. With OTEL +// disabled the span is a no-op, but the wrapper logic still runs — exercise it +// in isolation via a mocked `this` (no real consumer / Kafka broker needed). +describe('BackbeatConsumer._processTask', () => { + const savedEnv = process.env.ENABLE_OTEL; + beforeEach(() => { + sinon.stub(KafkaBacklogMetrics, 'onTaskStarted'); + }); + afterEach(() => { + sinon.restore(); + if (savedEnv === undefined) { + delete process.env.ENABLE_OTEL; + } else { + process.env.ENABLE_OTEL = savedEnv; + } + }); + + function makeSelf(queueProcessor) { + return { + _startProcessingTask: () => () => {}, + _groupId: 'test-group', + _queueProcessor: queueProcessor, + }; + } + + const entry = { topic: 'test-topic', partition: 0 }; + + describe('with OTEL enabled', () => { + beforeEach(() => { process.env.ENABLE_OTEL = 'true'; }); + + it('runs the queue processor and forwards the result to done', done => { + const self = makeSelf((e, cb) => cb(null, { ok: true })); + BackbeatConsumer.prototype._processTask.call(self, entry, (err, args) => { + assert.ifError(err); + assert.deepStrictEqual(args, { ok: true }); + done(); + }); + }); + + it('forwards the error when the queue processor fails', done => { + const self = makeSelf((e, cb) => cb(new Error('fail'))); + BackbeatConsumer.prototype._processTask.call(self, entry, err => { + assert(err && err.message === 'fail'); + done(); + }); + }); + + it('ends the span and rethrows on a synchronous throw', () => { + const self = makeSelf(() => { throw new Error('boom'); }); + assert.throws( + () => BackbeatConsumer.prototype._processTask.call(self, entry, () => {}), + /boom/); + }); + }); + + describe('with OTEL disabled', () => { + beforeEach(() => { delete process.env.ENABLE_OTEL; }); + + it('runs the queue processor with no span machinery', done => { + const self = makeSelf((e, cb) => cb(null, { ok: true })); + BackbeatConsumer.prototype._processTask.call(self, entry, (err, args) => { + assert.ifError(err); + assert.deepStrictEqual(args, { ok: true }); + done(); + }); + }); + }); +}); diff --git a/tests/unit/lifecycle/LifecycleTask.spec.js b/tests/unit/lifecycle/LifecycleTask.spec.js index 3c8b93d78..8ae9f536d 100644 --- a/tests/unit/lifecycle/LifecycleTask.spec.js +++ b/tests/unit/lifecycle/LifecycleTask.spec.js @@ -9,6 +9,7 @@ const { ValidLifecycleRules } = require('arsenal').models; const LifecycleTask = require( '../../../extensions/lifecycle/tasks/LifecycleTask'); const fakeLogger = require('../../utils/fakeLogger'); +const { withActiveSpan } = require('../../utils/withActiveSpan'); const { timeOptions } = require('../../functional/lifecycle/configObjects'); const HOUR = 1000 * 60 * 60; @@ -2369,3 +2370,52 @@ describe('lifecycle task helper methods', () => { }); }); }); + +describe('LifecycleTask trace-context propagation', () => { + it('_sendBucketEntry stamps traceparent headers when a span is active', done => { + let captured; + const self = { + bucketTasksTopic: 'bucket-tasks', + producer: { + sendToTopic: (topic, entries, cb) => { + captured = entries[0]; + cb(null); + }, + }, + }; + withActiveSpan(() => { + LifecycleTask.prototype._sendBucketEntry.call(self, { foo: 1 }, err => { + assert.ifError(err); + assert(captured.headers.some(h => h.traceparent)); + done(); + }); + }); + }); + + it('_sendObjectAction stamps traceparent headers when a span is active', done => { + let captured; + const self = { + objectTasksTopic: 'object-tasks', + log: fakeLogger, + circuitBreakers: { tripped: () => false }, + producer: { + sendToTopic: (topic, entries, cb) => { + captured = entries[0]; + cb(null); + }, + }, + }; + const entry = { + getAttribute: key => (key === 'transitionTime' ? Date.now() : undefined), + getActionType: () => 'expiration', + toKafkaMessage: () => JSON.stringify({ foo: 1 }), + }; + withActiveSpan(() => { + LifecycleTask.prototype._sendObjectAction.call(self, entry, err => { + assert.ifError(err); + assert(captured.headers.some(h => h.traceparent)); + done(); + }); + }); + }); +}); diff --git a/tests/unit/notification/KafkaNotificationDestination.js b/tests/unit/notification/KafkaNotificationDestination.js index 90dcd05dc..18d77d543 100644 --- a/tests/unit/notification/KafkaNotificationDestination.js +++ b/tests/unit/notification/KafkaNotificationDestination.js @@ -38,4 +38,23 @@ describe('KafkaNotificationDestination ::', () => { done(); }); }); + + it('strips trace headers before producing to the customer Kafka', done => { + const destConfig = { host: 'localhost', port: 9092, topic: 'test', resource: 'res' }; + const dest = new KafkaNotificationDestination({ destConfig, logger: FakeLogger }); + let captured; + dest._notificationProducer = { send: (msgs, cb) => { captured = msgs; cb(null); } }; + const messages = [ + { key: 'k', message: 'v', headers: [{ traceparent: '00-abc-def-01' }] }, + ]; + dest.send(messages, () => { + assert.strictEqual(captured.length, 1); + assert.strictEqual(captured[0].headers, undefined); + assert.strictEqual(captured[0].key, 'k'); + assert.strictEqual(captured[0].message, 'v'); + // the caller's original message is not mutated (shallow copy, not delete) + assert.deepStrictEqual(messages[0].headers, [{ traceparent: '00-abc-def-01' }]); + done(); + }); + }); }); diff --git a/tests/unit/notification/NotificationQueuePopulator.js b/tests/unit/notification/NotificationQueuePopulator.js index b38a16d67..188dd8583 100644 --- a/tests/unit/notification/NotificationQueuePopulator.js +++ b/tests/unit/notification/NotificationQueuePopulator.js @@ -639,6 +639,69 @@ describe('NotificationQueuePopulator ::', () => { }); }); }); + + describe('trace context propagation ::', () => { + const tp = '00-abcdef1234567890abcdef1234567890-1234567890abcdef-01'; + const ts = 'congo=t61rcWkgMzE'; + + it('should attach traceparent header when oplog value has traceContext', async () => { + sinon.stub(bnConfigManager, 'getConfig').returns(config); + const publishStub = sinon.stub(notificationQueuePopulator, 'publish'); + await notificationQueuePopulator._processObjectEntry( + 'example-bucket', + 'example-key', + { + 'originOp': 's3:ObjectCreated:Put', + 'dataStoreName': 'metastore', + 'content-length': '100', + 'last-modified': '0000', + 'md-model-version': '1', + 'traceContext': { traceparent: tp, tracestate: ts }, + }); + assert(publishStub.calledOnce); + // publish(topic, key, message, optEntriesToPublish, headers) + const headers = publishStub.getCall(0).args.at(4); + assert.deepStrictEqual(headers, [ + { traceparent: tp }, + { tracestate: ts }, + ]); + }); + + it('should attach traceparent-only header when tracestate is missing', async () => { + sinon.stub(bnConfigManager, 'getConfig').returns(config); + const publishStub = sinon.stub(notificationQueuePopulator, 'publish'); + await notificationQueuePopulator._processObjectEntry( + 'example-bucket', + 'example-key', + { + 'originOp': 's3:ObjectCreated:Put', + 'dataStoreName': 'metastore', + 'content-length': '100', + 'last-modified': '0000', + 'md-model-version': '1', + 'traceContext': { traceparent: tp }, + }); + const headers = publishStub.getCall(0).args.at(4); + assert.deepStrictEqual(headers, [{ traceparent: tp }]); + }); + + it('should pass undefined headers when oplog value has no traceContext', async () => { + sinon.stub(bnConfigManager, 'getConfig').returns(config); + const publishStub = sinon.stub(notificationQueuePopulator, 'publish'); + await notificationQueuePopulator._processObjectEntry( + 'example-bucket', + 'example-key', + { + 'originOp': 's3:ObjectCreated:Put', + 'dataStoreName': 'metastore', + 'content-length': '100', + 'last-modified': '0000', + 'md-model-version': '1', + }); + const headers = publishStub.getCall(0).args.at(4); + assert.strictEqual(headers, undefined); + }); + }); }); diff --git a/tests/unit/queuePopulator/QueuePopulatorExtension.spec.js b/tests/unit/queuePopulator/QueuePopulatorExtension.spec.js new file mode 100644 index 000000000..fa56e8a75 --- /dev/null +++ b/tests/unit/queuePopulator/QueuePopulatorExtension.spec.js @@ -0,0 +1,27 @@ +'use strict'; + +const assert = require('assert'); + +const QueuePopulatorExtension = + require('../../../lib/queuePopulator/QueuePopulatorExtension'); +const fakeLogger = require('../../utils/fakeLogger'); + +describe('QueuePopulatorExtension::publish', () => { + let ext; + beforeEach(() => { + ext = new QueuePopulatorExtension({ config: {}, logger: fakeLogger }); + }); + + it('attaches kafka headers when provided', () => { + const batch = {}; + const headers = [{ traceparent: '00-abc-def-01' }]; + ext.publish('topic', 'key', 'message', batch, headers); + assert.deepStrictEqual(batch.topic[0].headers, headers); + }); + + it('omits the headers field when none are provided', () => { + const batch = {}; + ext.publish('topic', 'key', 'message', batch); + assert.strictEqual('headers' in batch.topic[0], false); + }); +}); diff --git a/tests/unit/replication/ReplicationAPI.spec.js b/tests/unit/replication/ReplicationAPI.spec.js index 96b49d611..debb3a670 100644 --- a/tests/unit/replication/ReplicationAPI.spec.js +++ b/tests/unit/replication/ReplicationAPI.spec.js @@ -5,6 +5,7 @@ const assert = require('assert'); const ActionQueueEntry = require('../../../lib/models/ActionQueueEntry'); const fakeLogger = require('../../utils/fakeLogger'); +const { withActiveSpan } = require('../../utils/withActiveSpan'); const bucketName = 'transition-to-dmf'; const owner = '4f5a1a4bd769fd6e4ebca87b96c86a621ebb9c8be0c012f291757410c55a36f7'; const objectKey = 'n2bv20'; @@ -76,6 +77,44 @@ describe('ReplicationAPI', () => { return; }); }); + + it('stamps traceparent headers on the kafka entry when a span is active', done => { + const transitionTime = new Date().toISOString(); + const action = ActionQueueEntry.create('copyLocation'); + action + .setAttribute('target', { + accountId, + owner, + bucket: bucketName, + key: objectKey, + version: versionId, + eTag, + lastModified, + }) + .setAttribute('toLocation', toLocation) + .setAttribute('metrics', { + origin: originLabel, + fromLocation, + contentLength, + transitionTime, + }) + .setResultsTopic(resultsTopic); + let captured; + const capturingProducer = { + sendToTopic: (topic, entries, cb) => { + captured = entries[0]; + process.nextTick(() => cb(null, [{}])); + }, + }; + withActiveSpan(() => { + ReplicationAPI.sendDataMoverAction(capturingProducer, action, fakeLogger, err => { + assert.ifError(err); + assert(Array.isArray(captured.headers)); + assert(captured.headers.some(h => h.traceparent)); + done(); + }); + }); + }); }); describe('getDataMoverTopicPerLocation', () => { diff --git a/tests/utils/withActiveSpan.js b/tests/utils/withActiveSpan.js new file mode 100644 index 000000000..a394c386f --- /dev/null +++ b/tests/utils/withActiveSpan.js @@ -0,0 +1,35 @@ +'use strict'; + +const api = require('@opentelemetry/api'); +const { AsyncLocalStorageContextManager } = require('@opentelemetry/context-async-hooks'); +const { W3CTraceContextPropagator } = require('@opentelemetry/core'); + +let registered = false; + +// The global OTEL API is a no-op until a context manager + propagator are +// registered: without them context.active() never carries a span and +// propagation.inject() emits nothing, so trace-context helpers return undefined. +// Register real ones once so tests can exercise the header-stamping path. +function ensureOtelTestGlobals() { + if (registered) { + return; + } + api.context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable()); + api.propagation.setGlobalPropagator(new W3CTraceContextPropagator()); + registered = true; +} + +// Run `fn` with a valid remote span active, so traceHeadersFromCurrentContext() +// emits a real traceparent header. +function withActiveSpan(fn) { + ensureOtelTestGlobals(); + const spanContext = { + traceId: '0af7651916cd43dd8448eb211c80319c', + spanId: 'b7ad6b7169203331', + traceFlags: 1, + }; + const ctx = api.trace.setSpanContext(api.context.active(), spanContext); + return api.context.with(ctx, fn); +} + +module.exports = { withActiveSpan };