From c4d10b1d17d948be68258c510fe4f146bcb6fd44 Mon Sep 17 00:00:00 2001 From: dale1020 Date: Mon, 9 Feb 2026 11:51:44 +0500 Subject: [PATCH 1/7] create git_clone_logging branch --- git-clone.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/git-clone.sh b/git-clone.sh index c47638c..61c7212 100644 --- a/git-clone.sh +++ b/git-clone.sh @@ -54,6 +54,7 @@ clone_repository() { ls -la tests fi + # Clear Git token from environment for security unset ATP_TESTS_GIT_TOKEN echo "🔐 Git token cleared from environment" From a740032e7a1336360bee645b1f8843de553f23c0 Mon Sep 17 00:00:00 2001 From: artem kostiuchik <101344500+callmesi@users.noreply.github.com> Date: Mon, 9 Feb 2026 11:59:47 +0500 Subject: [PATCH 2/7] feat: added git clone logging in case if token/url incorrect (#2) Co-authored-by: dale1020 --- git-clone.sh | 176 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 170 insertions(+), 6 deletions(-) diff --git a/git-clone.sh b/git-clone.sh index 61c7212..7daef1d 100644 --- a/git-clone.sh +++ b/git-clone.sh @@ -7,22 +7,186 @@ clone_repository() { else echo "📥 Cloning tests repository..." + # ============================================ + # Pre-flight validation + # ============================================ + if [ -z "${ATP_TESTS_GIT_TOKEN:-}" ]; then + echo "❌ ERROR: ATP_TESTS_GIT_TOKEN is not set (required to download repository archive)" + exit 1 + fi + if [ -z "${ATP_TESTS_GIT_REPO_URL:-}" ]; then + echo "❌ ERROR: ATP_TESTS_GIT_REPO_URL is not set" + exit 1 + fi + if [ -z "${ATP_TESTS_GIT_REPO_BRANCH:-}" ]; then + echo "❌ ERROR: ATP_TESTS_GIT_REPO_BRANCH is not set" + exit 1 + fi + # Basic URL sanity check (no whitespace, http/https) + if [[ "$ATP_TESTS_GIT_REPO_URL" =~ [[:space:]] ]] || [[ ! "$ATP_TESTS_GIT_REPO_URL" =~ ^https?:// ]]; then + echo "❌ ERROR: ATP_TESTS_GIT_REPO_URL is invalid URL: $ATP_TESTS_GIT_REPO_URL" + exit 1 + fi + # Strip .git from URL and extract repo name REPO_PATH=$(echo "$ATP_TESTS_GIT_REPO_URL" | sed 's|\.git$||') GIT_BRANCH_CLEANED=$(echo "$ATP_TESTS_GIT_REPO_BRANCH" | sed 's|/|-|') REPO_NAME=$(basename "$REPO_PATH") ARCHIVE_URL="${REPO_PATH}/-/archive/${ATP_TESTS_GIT_REPO_BRANCH}/${REPO_NAME}-${GIT_BRANCH_CLEANED}.zip" + if [ -z "${TMP_DIR:-}" ]; then + echo "❌ ERROR: TMP_DIR is not set" + exit 1 + fi echo "📥 Downloading archive from: $ARCHIVE_URL" - curl -sSL --fail -H "PRIVATE-TOKEN: ${ATP_TESTS_GIT_TOKEN}" "$ARCHIVE_URL" -o "$TMP_DIR/repo.zip" + mkdir -p "$TMP_DIR" 2>/dev/null || true + ZIP_PATH="$TMP_DIR/repo.zip" + CURL_ERR_PATH="$TMP_DIR/curl_download.err" + + # ============================================ + # Download archive with enhanced error handling + # - connect timeout: 30s + # - max time: 120s + # ============================================ + HTTP_CODE="$( + curl -sS -L \ + --connect-timeout 30 \ + --max-time 120 \ + --fail \ + -H "PRIVATE-TOKEN: ${ATP_TESTS_GIT_TOKEN}" \ + "$ARCHIVE_URL" \ + -o "$ZIP_PATH" \ + -w "%{http_code}" \ + 2>"$CURL_ERR_PATH" + )" + CURL_EXIT_CODE=$? + + # ============================================ + # Error detection and messaging + # ============================================ + if [ "$CURL_EXIT_CODE" -ne 0 ]; then + CURL_ERR_MSG="" + if [ -f "$CURL_ERR_PATH" ]; then + CURL_ERR_MSG=$(tr '\n' ' ' < "$CURL_ERR_PATH" | sed 's/[[:space:]]\+/ /g' | sed 's/[[:space:]]*$//') + fi + + case "$CURL_EXIT_CODE" in + 6) + echo "❌ ERROR: Couldn't resolve host while downloading repository archive." + ;; + 7) + echo "❌ ERROR: Failed to connect to host while downloading repository archive." + ;; + 22) + # HTTP error (4xx/5xx). We'll handle using HTTP_CODE below. + ;; + 28) + echo "❌ ERROR: Download timed out (connect-timeout=30s, max-time=120s)." + ;; + 35) + echo "❌ ERROR: SSL/TLS connection error while downloading repository archive." + ;; + 52) + echo "❌ ERROR: Empty reply from server while downloading repository archive." + ;; + 56) + echo "❌ ERROR: Network receive error while downloading repository archive." + ;; + *) + echo "❌ ERROR: Network error while downloading repository archive (curl exit code: $CURL_EXIT_CODE)." + ;; + esac + + if [ -n "$CURL_ERR_MSG" ]; then + echo " curl: $CURL_ERR_MSG" + fi + + # For non-HTTP curl failures, stop here (HTTP_CODE may be empty/undefined). + if [ "$CURL_EXIT_CODE" -ne 22 ]; then + exit 1 + fi + fi + + # HTTP code interpretation (also covers curl --fail exit 22) + if [ "$HTTP_CODE" != "200" ]; then + case "$HTTP_CODE" in + 200) + # ok + ;; + 000) + echo "❌ ERROR: No HTTP response received from server (HTTP 000)." + echo " Check network connectivity and URL: $ARCHIVE_URL" + exit 1 + ;; + 401|403) + echo "❌ ERROR: Authentication failed (HTTP $HTTP_CODE)." + echo " Check that ATP_TESTS_GIT_TOKEN is valid and has access to the repository." + exit 1 + ;; + 404) + echo "❌ ERROR: Repository or branch not found (HTTP 404)." + echo " Check URL and branch:" + echo " - URL: $ATP_TESTS_GIT_REPO_URL" + echo " - Branch: $ATP_TESTS_GIT_REPO_BRANCH" + echo " - Archive URL: $ARCHIVE_URL" + exit 1 + ;; + 429) + echo "❌ ERROR: Rate limited by the server (HTTP 429)." + echo " Try again later or reduce request frequency." + exit 1 + ;; + 5??) + echo "❌ ERROR: Server error while downloading archive (HTTP $HTTP_CODE)." + echo " The Git server may be temporarily unavailable." + exit 1 + ;; + *) + echo "❌ ERROR: Failed to download repository archive (HTTP $HTTP_CODE, curl exit code: $CURL_EXIT_CODE)." + echo " Archive URL: $ARCHIVE_URL" + exit 1 + ;; + esac + fi + + # ============================================ + # Downloaded archive validation + # ============================================ + if [ ! -f "$ZIP_PATH" ] || [ ! -s "$ZIP_PATH" ]; then + echo "❌ ERROR: Downloaded file is missing or empty: $ZIP_PATH" + exit 1 + fi + + if command -v file >/dev/null 2>&1; then + FILE_TYPE="$(file "$ZIP_PATH" 2>/dev/null || true)" + if ! printf '%s' "$FILE_TYPE" | grep -qi "zip archive"; then + # Git servers sometimes return an HTML login page when the token is invalid/expired. + if printf '%s' "$FILE_TYPE" | grep -qi "html"; then + echo "❌ ERROR: Downloaded an HTML login page instead of a repository." + echo " Check that ATP_TESTS_GIT_TOKEN is valid and has access to the repository." + echo " Check that the repository URL is correct." + else + echo "❌ ERROR: Downloaded repository is not recognized as a zip archive." + fi + echo " File type: $FILE_TYPE" + exit 1 + fi + else + echo "⚠️ 'file' command not available; skipping zip magic-byte validation." + fi - if [[ $? -ne 0 ]]; then - echo "❌ Failed to download repository archive" + if command -v unzip >/dev/null 2>&1; then + if ! unzip -t "$ZIP_PATH" > /dev/null 2>&1; then + echo "❌ ERROR: Archive integrity test failed (unzip -t). Please retry the operation." + exit 1 + fi + else + echo "❌ ERROR: 'unzip' command is not available; cannot validate/extract archive." exit 1 fi echo "📦 Unzipping..." - unzip -q "$TMP_DIR/repo.zip" -d "$TMP_DIR" + unzip -q "$ZIP_PATH" -d "$TMP_DIR" mv "$TMP_DIR"/${REPO_NAME}-${GIT_BRANCH_CLEANED}/* "$TMP_DIR" echo "✅ Repository extracted to: $TMP_DIR" @@ -39,11 +203,11 @@ clone_repository() { echo "✅ Validation successful. Found 'collections/' directory in the repo." else echo "❌ ERROR: Neither 'app/' nor 'tests/' nor 'collections/' directory nor 'postman_collection' file found in the cloned repo!" - exit 1 + exit 1 fi # Move into the work directory - cd $TMP_DIR + cd "$TMP_DIR" # List contents to verify if [ -d "$TMP_DIR/app" ]; then From d20a77b9cd388711b05a9cdbf66a9ac8f3103d82 Mon Sep 17 00:00:00 2001 From: artem kostiuchik <101344500+callmesi@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:04:08 +0500 Subject: [PATCH 3/7] feat:telemetry otel and trace-id generation logic (#3) --- otel-init.js | 86 ++++++++++++++++++++++++++++++++++++++ trace-id-generator.sh | 97 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 otel-init.js create mode 100644 trace-id-generator.sh diff --git a/otel-init.js b/otel-init.js new file mode 100644 index 0000000..f12c96f --- /dev/null +++ b/otel-init.js @@ -0,0 +1,86 @@ +/** + * OpenTelemetry SDK bootstrap. + * + * Intended usage: + * export NODE_OPTIONS="--require /scripts/otel-init.js" + * + * This file must be resilient: failures should not prevent tests from running. + */ + +/* eslint-disable no-console */ + +function isTruthy(val) { + if (val == null) return false; + return String(val).toLowerCase() === 'true' || String(val) === '1' || String(val).toLowerCase() === 'yes'; +} + +// Allow disabling even if NODE_OPTIONS forces a require. +const otelDisabled = String(process.env.OTEL_ENABLED ?? '').toLowerCase() === 'false'; + +if (otelDisabled) { + module.exports = {}; +} else { + try { + const { NodeSDK } = require('@opentelemetry/sdk-node'); + const { B3Propagator } = require('@opentelemetry/propagator-b3'); + const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); + const { Resource } = require('@opentelemetry/resources'); + const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); + + // Prefer an explicit runner-specific variable, but also respect standard OTEL env vars if present. + const collectorUrl = + process.env.OTEL_COLLECTOR_ENDPOINT || + process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || + (process.env.OTEL_EXPORTER_OTLP_ENDPOINT + ? String(process.env.OTEL_EXPORTER_OTLP_ENDPOINT).replace(/\/+$/, '') + '/v1/traces' + : undefined) || + 'http://otel-collector:4318/v1/traces'; + + const resource = new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'atp3-playwright-runner', + [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.ENVIRONMENT_NAME, + 'b3.trace_id': process.env.X_B3_TRACE_ID, + 'b3.span_id': process.env.X_B3_SPAN_ID, + 'b3.sampled': process.env.X_B3_SAMPLED, + }); + + const sdk = new NodeSDK({ + resource, + traceExporter: new OTLPTraceExporter({ url: collectorUrl }), + textMapPropagator: new B3Propagator(), + }); + + // Start SDK but do not crash the process if it fails. + sdk + .start() + .then(() => { + if (isTruthy(process.env.OTEL_LOG_STARTUP ?? 'true')) { + console.log(`✅ OpenTelemetry started (exporter: ${collectorUrl})`); + } + }) + .catch((err) => { + console.error('⚠️ OpenTelemetry failed to start; continuing without tracing.', err); + }); + + const shutdown = () => { + sdk + .shutdown() + .catch((err) => console.error('⚠️ OpenTelemetry shutdown error', err)) + .finally(() => process.exit(0)); + }; + + // Best-effort flush on termination. + process.once('SIGTERM', shutdown); + process.once('SIGINT', shutdown); + process.once('beforeExit', () => { + // Don't force exit; just attempt to flush. + sdk.shutdown().catch(() => undefined); + }); + + module.exports = {}; + } catch (err) { + console.error('⚠️ OpenTelemetry bootstrap unavailable; continuing without tracing.', err); + module.exports = {}; + } +} + diff --git a/trace-id-generator.sh b/trace-id-generator.sh new file mode 100644 index 0000000..cec287f --- /dev/null +++ b/trace-id-generator.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# +# Generates B3 tracing identifiers for job-level correlation. +# Format (16 chars): project_id{4}run_id{4}step_id{5}random{3} +# +# Exports: +# X_B3_TRACE_ID +# X_B3_SPAN_ID +# X_B3_SAMPLED +# + +_b3__upper() { + # Uppercase stdin + tr '[:lower:]' '[:upper:]' +} + +_b3__hash4() { + # Produce 4 hex-ish chars from input string (stdin). + local out="" + if command -v md5sum >/dev/null 2>&1; then + out="$(md5sum | cut -c1-4)" + elif command -v sha256sum >/dev/null 2>&1; then + out="$(sha256sum | cut -c1-4)" + else + # Last-resort fallback + out="$(date +%s%N | cut -c1-4)" + fi + echo -n "$out" | _b3__upper +} + +_b3__rand_alnum_upper() { + local n="${1:-3}" + if [ -r /dev/urandom ]; then + LC_ALL=C tr -dc 'A-Z0-9' < /dev/urandom | head -c "$n" + else + # Fallback: not cryptographically strong + echo -n "${RANDOM}${RANDOM}${RANDOM}" | _b3__upper | tr -dc 'A-Z0-9' | head -c "$n" + fi +} + +_b3__rand_hex_lower() { + local n="${1:-16}" + if [ -r /dev/urandom ]; then + LC_ALL=C tr -dc 'a-f0-9' < /dev/urandom | head -c "$n" + else + # Fallback: not cryptographically strong + echo -n "${RANDOM}${RANDOM}${RANDOM}" | tr -dc '0-9' | head -c "$n" + fi +} + +_b3__next_step_id() { + # 5 digits sequential, persisted per container run. + local step_file="${B3_STEP_ID_FILE:-/tmp/b3-step-id}" + local current="0" + if [ -f "$step_file" ]; then + current="$(cat "$step_file" 2>/dev/null || true)" + fi + current="${current//[^0-9]/}" + if [ -z "$current" ]; then current="0"; fi + local next=$((10#$current + 1)) + local formatted + formatted="$(printf "%05d" "$next")" + printf "%s" "$formatted" > "$step_file" 2>/dev/null || true + echo -n "$formatted" +} + +_b3__generate() { + local project="${PROJECT_TRACE_ID:-UNKW}" + project="$(echo -n "$project" | _b3__upper | cut -c1-4)" + if [ "${#project}" -lt 4 ]; then + project="$(printf "%-4s" "$project" | tr ' ' 'W')" # pad to 4 chars + fi + + local timestamp="${CURRENT_DATE:-}""${CURRENT_TIME:-}" + if [ -z "$timestamp" ]; then + timestamp="$(date +%F)$(date +%H-%M-%S)" + fi + local run_id + run_id="$(echo -n "$timestamp" | _b3__hash4)" + + local step_id + step_id="$(_b3__next_step_id)" + + local rand3 + rand3="$(_b3__rand_alnum_upper 3)" + + local trace_id="${project}${run_id}${step_id}${rand3}" + local span_id + span_id="$(_b3__rand_hex_lower 16)" + + export X_B3_TRACE_ID="$trace_id" + export X_B3_SPAN_ID="$span_id" + export X_B3_SAMPLED="${X_B3_SAMPLED:-1}" +} + +_b3__generate + From 91e0df64c40bffd62778cc93601596213dd8fee6 Mon Sep 17 00:00:00 2001 From: artem kostiuchik <101344500+callmesi@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:49:07 +0500 Subject: [PATCH 4/7] Feat/telemetry (#4) * feat:telemetry otel and trace-id generation logic * feat:telemetry update otel initialisation --- otel-init.js | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/otel-init.js b/otel-init.js index f12c96f..b71b50d 100644 --- a/otel-init.js +++ b/otel-init.js @@ -50,32 +50,35 @@ if (otelDisabled) { textMapPropagator: new B3Propagator(), }); - // Start SDK but do not crash the process if it fails. - sdk - .start() - .then(() => { - if (isTruthy(process.env.OTEL_LOG_STARTUP ?? 'true')) { - console.log(`✅ OpenTelemetry started (exporter: ${collectorUrl})`); - } - }) - .catch((err) => { - console.error('⚠️ OpenTelemetry failed to start; continuing without tracing.', err); - }); + // Start SDK synchronously (NodeSDK.start() is sync in v0.54+). + let started = false; + try { + sdk.start(); + started = true; + } catch (startErr) { + console.error('⚠️ OpenTelemetry failed to start; continuing without tracing.', startErr); + } - const shutdown = () => { - sdk - .shutdown() - .catch((err) => console.error('⚠️ OpenTelemetry shutdown error', err)) - .finally(() => process.exit(0)); - }; + if (started) { + if (isTruthy(process.env.OTEL_LOG_STARTUP ?? 'true')) { + console.log(`✅ OpenTelemetry started (exporter: ${collectorUrl})`); + } - // Best-effort flush on termination. - process.once('SIGTERM', shutdown); - process.once('SIGINT', shutdown); - process.once('beforeExit', () => { - // Don't force exit; just attempt to flush. - sdk.shutdown().catch(() => undefined); - }); + const shutdown = () => { + sdk + .shutdown() + .catch((err) => console.error('⚠️ OpenTelemetry shutdown error', err)) + .finally(() => process.exit(0)); + }; + + // Best-effort flush on termination. + process.once('SIGTERM', shutdown); + process.once('SIGINT', shutdown); + process.once('beforeExit', () => { + // Don't force exit; just attempt to flush. + sdk.shutdown().catch(() => undefined); + }); + } module.exports = {}; } catch (err) { From 28ba3901dee787eac320d9fb74a1a12f1cc53108 Mon Sep 17 00:00:00 2001 From: artem kostiuchik <101344500+callmesi@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:36:06 +0500 Subject: [PATCH 5/7] Feat/telemetry (#5) * feat:telemetry otel and trace-id generation logic * feat:telemetry update otel initialisation * feat:telemetry add htp instrument to auto inject headers on each request --- otel-init.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/otel-init.js b/otel-init.js index b71b50d..3a4b42a 100644 --- a/otel-init.js +++ b/otel-init.js @@ -26,6 +26,8 @@ if (otelDisabled) { const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); const { Resource } = require('@opentelemetry/resources'); const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); + const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); + const { UndiciInstrumentation } = require('@opentelemetry/instrumentation-undici'); // Prefer an explicit runner-specific variable, but also respect standard OTEL env vars if present. const collectorUrl = @@ -48,6 +50,10 @@ if (otelDisabled) { resource, traceExporter: new OTLPTraceExporter({ url: collectorUrl }), textMapPropagator: new B3Propagator(), + instrumentations: [ + new HttpInstrumentation(), // instruments Node.js http/https (used by axios) + new UndiciInstrumentation(), // instruments undici / global fetch + ], }); // Start SDK synchronously (NodeSDK.start() is sync in v0.54+). From cc367692668ab474d38afc38fa2034f1a1767db8 Mon Sep 17 00:00:00 2001 From: artem kostiuchik <101344500+callmesi@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:40:00 +0500 Subject: [PATCH 6/7] Feat/telemetry (#6) * feat:telemetry otel and trace-id generation logic * feat:telemetry update otel initialisation * feat:telemetry add htp instrument to auto inject headers on each request * feat:telemetry update logic --- otel-init.js | 230 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 167 insertions(+), 63 deletions(-) diff --git a/otel-init.js b/otel-init.js index 3a4b42a..43790a2 100644 --- a/otel-init.js +++ b/otel-init.js @@ -1,17 +1,31 @@ /** - * OpenTelemetry SDK bootstrap. + * B3 header injection + OpenTelemetry SDK bootstrap. * * Intended usage: * export NODE_OPTIONS="--require /scripts/otel-init.js" * - * This file must be resilient: failures should not prevent tests from running. + * Two independent concerns are handled here: + * + * 1. B3 Header Injection (Phase 1) + * Patches http/https/fetch to add X-B3-TraceId, X-B3-SpanId, and + * X-B3-Sampled headers to ALL outgoing HTTP requests. Works without + * a collector, without valid-hex trace IDs, and regardless of which + * HTTP client the tests use (axios, node-fetch, undici, Playwright + * APIRequestContext, etc.). + * + * 2. OTel SDK (Phase 2) + * Sets up span tracking and OTLP export. Useful when a collector is + * deployed. Spans include the B3 correlation IDs as resource attributes. + * + * This file must be resilient: failures must not prevent tests from running. */ /* eslint-disable no-console */ function isTruthy(val) { if (val == null) return false; - return String(val).toLowerCase() === 'true' || String(val) === '1' || String(val).toLowerCase() === 'yes'; + const s = String(val).toLowerCase(); + return s === 'true' || s === '1' || s === 'yes'; } // Allow disabling even if NODE_OPTIONS forces a require. @@ -21,75 +35,165 @@ if (otelDisabled) { module.exports = {}; } else { try { - const { NodeSDK } = require('@opentelemetry/sdk-node'); - const { B3Propagator } = require('@opentelemetry/propagator-b3'); - const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); - const { Resource } = require('@opentelemetry/resources'); - const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); - const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); - const { UndiciInstrumentation } = require('@opentelemetry/instrumentation-undici'); - - // Prefer an explicit runner-specific variable, but also respect standard OTEL env vars if present. - const collectorUrl = - process.env.OTEL_COLLECTOR_ENDPOINT || - process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || - (process.env.OTEL_EXPORTER_OTLP_ENDPOINT - ? String(process.env.OTEL_EXPORTER_OTLP_ENDPOINT).replace(/\/+$/, '') + '/v1/traces' - : undefined) || - 'http://otel-collector:4318/v1/traces'; - - const resource = new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'atp3-playwright-runner', - [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.ENVIRONMENT_NAME, - 'b3.trace_id': process.env.X_B3_TRACE_ID, - 'b3.span_id': process.env.X_B3_SPAN_ID, - 'b3.sampled': process.env.X_B3_SAMPLED, - }); - - const sdk = new NodeSDK({ - resource, - traceExporter: new OTLPTraceExporter({ url: collectorUrl }), - textMapPropagator: new B3Propagator(), - instrumentations: [ - new HttpInstrumentation(), // instruments Node.js http/https (used by axios) - new UndiciInstrumentation(), // instruments undici / global fetch - ], - }); - - // Start SDK synchronously (NodeSDK.start() is sync in v0.54+). - let started = false; - try { - sdk.start(); - started = true; - } catch (startErr) { - console.error('⚠️ OpenTelemetry failed to start; continuing without tracing.', startErr); - } + // ═════════════════════════════════════════════════════════════════ + // Freeze B3 values at startup. Environment variables may be + // cleared later by clear_sensitive_vars, so we snapshot them now. + // ═════════════════════════════════════════════════════════════════ + const b3TraceId = process.env.X_B3_TRACE_ID || ''; + const b3SpanId = process.env.X_B3_SPAN_ID || ''; + const b3Sampled = process.env.X_B3_SAMPLED || '1'; + const hasB3 = !!b3TraceId; + + // ═════════════════════════════════════════════════════════════════ + // Phase 1 – B3 header injection + // + // We monkey-patch http.request / https.request / globalThis.fetch + // BEFORE the OTel SDK starts. This way our wrappers sit between + // OTel's instrumentation and the real Node.js transport, ensuring + // the correct env-var B3 values always end up on the wire. + // ═════════════════════════════════════════════════════════════════ + if (hasB3) { + const http = require('http'); + const https = require('https'); - if (started) { - if (isTruthy(process.env.OTEL_LOG_STARTUP ?? 'true')) { - console.log(`✅ OpenTelemetry started (exporter: ${collectorUrl})`); + /** Merge B3 headers into a headers object (returns a new object). */ + function withB3(headers) { + return Object.assign({}, headers, { + 'X-B3-TraceId': b3TraceId, + 'X-B3-SpanId': b3SpanId, + 'X-B3-Sampled': b3Sampled, + }); + } + + /** + * Wrap http.request / http.get (and their https counterparts). + * + * The Node.js request() signature is overloaded: + * request(url[, options][, callback]) + * request(options[, callback]) + */ + function wrapRequestFn(original) { + return function (input, options, callback) { + if (typeof input === 'string' || input instanceof URL) { + if (typeof options === 'function') { + // request(url, callback) + return original.call(this, input, { headers: withB3({}) }, options); + } + // request(url, options[, callback]) + const opts = options || {}; + opts.headers = withB3(opts.headers); + return original.call(this, input, opts, callback); + } + // request(options[, callback]) + const opts = input || {}; + opts.headers = withB3(opts.headers); + return original.call(this, opts, options); + }; + } + + http.request = wrapRequestFn(http.request); + http.get = wrapRequestFn(http.get); + https.request = wrapRequestFn(https.request); + https.get = wrapRequestFn(https.get); + + // Patch globalThis.fetch (Node.js 18+ uses undici internally). + if (typeof globalThis.fetch === 'function') { + const origFetch = globalThis.fetch; + globalThis.fetch = function (input, init) { + init = Object.assign({}, init); + const h = new Headers(init.headers || {}); + h.set('X-B3-TraceId', b3TraceId); + h.set('X-B3-SpanId', b3SpanId); + h.set('X-B3-Sampled', b3Sampled); + init.headers = h; + return origFetch.call(this, input, init); + }; + } } - const shutdown = () => { - sdk - .shutdown() - .catch((err) => console.error('⚠️ OpenTelemetry shutdown error', err)) - .finally(() => process.exit(0)); + // ═════════════════════════════════════════════════════════════════ + // Phase 2 – OpenTelemetry SDK (span tracking / export) + // ═════════════════════════════════════════════════════════════════ + const { NodeSDK } = require('@opentelemetry/sdk-node'); + const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); + const { Resource } = require('@opentelemetry/resources'); + const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); + const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); + const { UndiciInstrumentation } = require('@opentelemetry/instrumentation-undici'); + + const collectorUrl = + process.env.OTEL_COLLECTOR_ENDPOINT || + process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || + (process.env.OTEL_EXPORTER_OTLP_ENDPOINT + ? String(process.env.OTEL_EXPORTER_OTLP_ENDPOINT).replace(/\/+$/, '') + '/v1/traces' + : undefined) || + 'http://otel-collector:4318/v1/traces'; + + const resource = new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: + process.env.OTEL_SERVICE_NAME || 'atp3-playwright-runner', + [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: + process.env.ENVIRONMENT_NAME, + 'b3.trace_id': b3TraceId, + 'b3.span_id': b3SpanId, + 'b3.sampled': b3Sampled, + }); + + // No-op propagator – B3 headers are injected manually in Phase 1. + // This prevents OTel from adding 'traceparent' or 'b3' headers with + // random trace IDs that would conflict with our env-var values. + const noopPropagator = { + inject() {}, + extract(context) { return context; }, + fields() { return []; }, }; - // Best-effort flush on termination. - process.once('SIGTERM', shutdown); - process.once('SIGINT', shutdown); - process.once('beforeExit', () => { - // Don't force exit; just attempt to flush. - sdk.shutdown().catch(() => undefined); + const sdk = new NodeSDK({ + resource, + traceExporter: new OTLPTraceExporter({ url: collectorUrl }), + textMapPropagator: noopPropagator, + instrumentations: [ + new HttpInstrumentation(), + new UndiciInstrumentation(), + ], }); - } - module.exports = {}; + // Start SDK synchronously (NodeSDK.start() is sync in v0.54+). + let started = false; + try { + sdk.start(); + started = true; + } catch (startErr) { + console.error('⚠️ OpenTelemetry failed to start; continuing without tracing.', startErr); + } + + if (started) { + if (isTruthy(process.env.OTEL_LOG_STARTUP ?? 'true')) { + console.log(`✅ OpenTelemetry started (exporter: ${collectorUrl})`); + if (hasB3) { + console.log(`✅ B3 headers will be injected (traceId: ${b3TraceId})`); + } + } + + const shutdown = () => { + sdk + .shutdown() + .catch((err) => console.error('⚠️ OpenTelemetry shutdown error', err)) + .finally(() => process.exit(0)); + }; + + // Best-effort flush on termination. + process.once('SIGTERM', shutdown); + process.once('SIGINT', shutdown); + process.once('beforeExit', () => { + // Don't force exit; just attempt to flush. + sdk.shutdown().catch(() => undefined); + }); + } + + module.exports = {}; } catch (err) { console.error('⚠️ OpenTelemetry bootstrap unavailable; continuing without tracing.', err); module.exports = {}; } } - From a6ec71f0675908f9ce03686045691cc650b5bd02 Mon Sep 17 00:00:00 2001 From: arko0220 Date: Thu, 12 Mar 2026 12:25:55 +0500 Subject: [PATCH 7/7] feat:git clone logging remove not used files --- otel-init.js | 199 ------------------------------------------ trace-id-generator.sh | 97 -------------------- 2 files changed, 296 deletions(-) delete mode 100644 otel-init.js delete mode 100644 trace-id-generator.sh diff --git a/otel-init.js b/otel-init.js deleted file mode 100644 index 43790a2..0000000 --- a/otel-init.js +++ /dev/null @@ -1,199 +0,0 @@ -/** - * B3 header injection + OpenTelemetry SDK bootstrap. - * - * Intended usage: - * export NODE_OPTIONS="--require /scripts/otel-init.js" - * - * Two independent concerns are handled here: - * - * 1. B3 Header Injection (Phase 1) - * Patches http/https/fetch to add X-B3-TraceId, X-B3-SpanId, and - * X-B3-Sampled headers to ALL outgoing HTTP requests. Works without - * a collector, without valid-hex trace IDs, and regardless of which - * HTTP client the tests use (axios, node-fetch, undici, Playwright - * APIRequestContext, etc.). - * - * 2. OTel SDK (Phase 2) - * Sets up span tracking and OTLP export. Useful when a collector is - * deployed. Spans include the B3 correlation IDs as resource attributes. - * - * This file must be resilient: failures must not prevent tests from running. - */ - -/* eslint-disable no-console */ - -function isTruthy(val) { - if (val == null) return false; - const s = String(val).toLowerCase(); - return s === 'true' || s === '1' || s === 'yes'; -} - -// Allow disabling even if NODE_OPTIONS forces a require. -const otelDisabled = String(process.env.OTEL_ENABLED ?? '').toLowerCase() === 'false'; - -if (otelDisabled) { - module.exports = {}; -} else { - try { - // ═════════════════════════════════════════════════════════════════ - // Freeze B3 values at startup. Environment variables may be - // cleared later by clear_sensitive_vars, so we snapshot them now. - // ═════════════════════════════════════════════════════════════════ - const b3TraceId = process.env.X_B3_TRACE_ID || ''; - const b3SpanId = process.env.X_B3_SPAN_ID || ''; - const b3Sampled = process.env.X_B3_SAMPLED || '1'; - const hasB3 = !!b3TraceId; - - // ═════════════════════════════════════════════════════════════════ - // Phase 1 – B3 header injection - // - // We monkey-patch http.request / https.request / globalThis.fetch - // BEFORE the OTel SDK starts. This way our wrappers sit between - // OTel's instrumentation and the real Node.js transport, ensuring - // the correct env-var B3 values always end up on the wire. - // ═════════════════════════════════════════════════════════════════ - if (hasB3) { - const http = require('http'); - const https = require('https'); - - /** Merge B3 headers into a headers object (returns a new object). */ - function withB3(headers) { - return Object.assign({}, headers, { - 'X-B3-TraceId': b3TraceId, - 'X-B3-SpanId': b3SpanId, - 'X-B3-Sampled': b3Sampled, - }); - } - - /** - * Wrap http.request / http.get (and their https counterparts). - * - * The Node.js request() signature is overloaded: - * request(url[, options][, callback]) - * request(options[, callback]) - */ - function wrapRequestFn(original) { - return function (input, options, callback) { - if (typeof input === 'string' || input instanceof URL) { - if (typeof options === 'function') { - // request(url, callback) - return original.call(this, input, { headers: withB3({}) }, options); - } - // request(url, options[, callback]) - const opts = options || {}; - opts.headers = withB3(opts.headers); - return original.call(this, input, opts, callback); - } - // request(options[, callback]) - const opts = input || {}; - opts.headers = withB3(opts.headers); - return original.call(this, opts, options); - }; - } - - http.request = wrapRequestFn(http.request); - http.get = wrapRequestFn(http.get); - https.request = wrapRequestFn(https.request); - https.get = wrapRequestFn(https.get); - - // Patch globalThis.fetch (Node.js 18+ uses undici internally). - if (typeof globalThis.fetch === 'function') { - const origFetch = globalThis.fetch; - globalThis.fetch = function (input, init) { - init = Object.assign({}, init); - const h = new Headers(init.headers || {}); - h.set('X-B3-TraceId', b3TraceId); - h.set('X-B3-SpanId', b3SpanId); - h.set('X-B3-Sampled', b3Sampled); - init.headers = h; - return origFetch.call(this, input, init); - }; - } - } - - // ═════════════════════════════════════════════════════════════════ - // Phase 2 – OpenTelemetry SDK (span tracking / export) - // ═════════════════════════════════════════════════════════════════ - const { NodeSDK } = require('@opentelemetry/sdk-node'); - const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); - const { Resource } = require('@opentelemetry/resources'); - const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); - const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); - const { UndiciInstrumentation } = require('@opentelemetry/instrumentation-undici'); - - const collectorUrl = - process.env.OTEL_COLLECTOR_ENDPOINT || - process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || - (process.env.OTEL_EXPORTER_OTLP_ENDPOINT - ? String(process.env.OTEL_EXPORTER_OTLP_ENDPOINT).replace(/\/+$/, '') + '/v1/traces' - : undefined) || - 'http://otel-collector:4318/v1/traces'; - - const resource = new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: - process.env.OTEL_SERVICE_NAME || 'atp3-playwright-runner', - [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: - process.env.ENVIRONMENT_NAME, - 'b3.trace_id': b3TraceId, - 'b3.span_id': b3SpanId, - 'b3.sampled': b3Sampled, - }); - - // No-op propagator – B3 headers are injected manually in Phase 1. - // This prevents OTel from adding 'traceparent' or 'b3' headers with - // random trace IDs that would conflict with our env-var values. - const noopPropagator = { - inject() {}, - extract(context) { return context; }, - fields() { return []; }, - }; - - const sdk = new NodeSDK({ - resource, - traceExporter: new OTLPTraceExporter({ url: collectorUrl }), - textMapPropagator: noopPropagator, - instrumentations: [ - new HttpInstrumentation(), - new UndiciInstrumentation(), - ], - }); - - // Start SDK synchronously (NodeSDK.start() is sync in v0.54+). - let started = false; - try { - sdk.start(); - started = true; - } catch (startErr) { - console.error('⚠️ OpenTelemetry failed to start; continuing without tracing.', startErr); - } - - if (started) { - if (isTruthy(process.env.OTEL_LOG_STARTUP ?? 'true')) { - console.log(`✅ OpenTelemetry started (exporter: ${collectorUrl})`); - if (hasB3) { - console.log(`✅ B3 headers will be injected (traceId: ${b3TraceId})`); - } - } - - const shutdown = () => { - sdk - .shutdown() - .catch((err) => console.error('⚠️ OpenTelemetry shutdown error', err)) - .finally(() => process.exit(0)); - }; - - // Best-effort flush on termination. - process.once('SIGTERM', shutdown); - process.once('SIGINT', shutdown); - process.once('beforeExit', () => { - // Don't force exit; just attempt to flush. - sdk.shutdown().catch(() => undefined); - }); - } - - module.exports = {}; - } catch (err) { - console.error('⚠️ OpenTelemetry bootstrap unavailable; continuing without tracing.', err); - module.exports = {}; - } -} diff --git a/trace-id-generator.sh b/trace-id-generator.sh deleted file mode 100644 index cec287f..0000000 --- a/trace-id-generator.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -# -# Generates B3 tracing identifiers for job-level correlation. -# Format (16 chars): project_id{4}run_id{4}step_id{5}random{3} -# -# Exports: -# X_B3_TRACE_ID -# X_B3_SPAN_ID -# X_B3_SAMPLED -# - -_b3__upper() { - # Uppercase stdin - tr '[:lower:]' '[:upper:]' -} - -_b3__hash4() { - # Produce 4 hex-ish chars from input string (stdin). - local out="" - if command -v md5sum >/dev/null 2>&1; then - out="$(md5sum | cut -c1-4)" - elif command -v sha256sum >/dev/null 2>&1; then - out="$(sha256sum | cut -c1-4)" - else - # Last-resort fallback - out="$(date +%s%N | cut -c1-4)" - fi - echo -n "$out" | _b3__upper -} - -_b3__rand_alnum_upper() { - local n="${1:-3}" - if [ -r /dev/urandom ]; then - LC_ALL=C tr -dc 'A-Z0-9' < /dev/urandom | head -c "$n" - else - # Fallback: not cryptographically strong - echo -n "${RANDOM}${RANDOM}${RANDOM}" | _b3__upper | tr -dc 'A-Z0-9' | head -c "$n" - fi -} - -_b3__rand_hex_lower() { - local n="${1:-16}" - if [ -r /dev/urandom ]; then - LC_ALL=C tr -dc 'a-f0-9' < /dev/urandom | head -c "$n" - else - # Fallback: not cryptographically strong - echo -n "${RANDOM}${RANDOM}${RANDOM}" | tr -dc '0-9' | head -c "$n" - fi -} - -_b3__next_step_id() { - # 5 digits sequential, persisted per container run. - local step_file="${B3_STEP_ID_FILE:-/tmp/b3-step-id}" - local current="0" - if [ -f "$step_file" ]; then - current="$(cat "$step_file" 2>/dev/null || true)" - fi - current="${current//[^0-9]/}" - if [ -z "$current" ]; then current="0"; fi - local next=$((10#$current + 1)) - local formatted - formatted="$(printf "%05d" "$next")" - printf "%s" "$formatted" > "$step_file" 2>/dev/null || true - echo -n "$formatted" -} - -_b3__generate() { - local project="${PROJECT_TRACE_ID:-UNKW}" - project="$(echo -n "$project" | _b3__upper | cut -c1-4)" - if [ "${#project}" -lt 4 ]; then - project="$(printf "%-4s" "$project" | tr ' ' 'W')" # pad to 4 chars - fi - - local timestamp="${CURRENT_DATE:-}""${CURRENT_TIME:-}" - if [ -z "$timestamp" ]; then - timestamp="$(date +%F)$(date +%H-%M-%S)" - fi - local run_id - run_id="$(echo -n "$timestamp" | _b3__hash4)" - - local step_id - step_id="$(_b3__next_step_id)" - - local rand3 - rand3="$(_b3__rand_alnum_upper 3)" - - local trace_id="${project}${run_id}${step_id}${rand3}" - local span_id - span_id="$(_b3__rand_hex_lower 16)" - - export X_B3_TRACE_ID="$trace_id" - export X_B3_SPAN_ID="$span_id" - export X_B3_SAMPLED="${X_B3_SAMPLED:-1}" -} - -_b3__generate -