Skip to content

Production Build: 2.1.0-beta.3 - #321

Open
github-actions[bot] wants to merge 16 commits into
mainfrom
release/images-2.1.0-beta.3
Open

github-actions[bot] wants to merge 16 commits into
mainfrom
release/images-2.1.0-beta.3

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Production Build 2.1.0-beta.3

Version Bump: custom
Services Built: checkout

Changes

  • ✅ SPLUNK-VERSION updated to 2.1.0-beta.3
  • ✅ Source k8s manifests updated with new image references

Note

This is a partial build. Other services will use their existing image versions.

Next Steps

Run the Build Demo Manifest - PRODUCTION workflow to stitch the manifest.


🤖 Generated automatically by GitHub Actions

timhard-splunk and others added 16 commits August 13, 2026 09:04
… flag

Adds a logs-first demo scenario where orders complete successfully at the
wrong price, so APM/RUM golden signals stay green and the revenue loss is
only discoverable in the checkout logs.

- New flagd flag promoDiscountBug sets the fraction of orders that match
  the leftover internal test promo code PROMOTEST100 (100% off). Orders
  that don't match get the live SPRING10 campaign discount (10% off).
- checkout applies the discount only to the amount sent to payment. The
  order items, shipping cost and the OrderResult returned to the customer
  all keep the full price, so the confirmation page, confirmation email
  and accounting DB are unaffected.
- The 'order placed' log record gains app.promo.code,
  app.order.discount.pct and app.order.amount.charged alongside the
  existing app.order.amount, which stays the pre-discount total.

Deliberately not added to span attributes: keeping the signal log-only is
what makes this a logs-first scenario.
The kubelet probes payment's gRPC health service on every liveness (10s) and
readiness (5s) tick, and auto-instrumentation turns each probe into a SERVER
span. Splunk APM counts those as service requests: ~18/min/pod, so ~360 per 5
minutes across four pods against ~15 real Charge calls. The paymentFailure
50/50 A/B split therefore read as a ~2% error rate instead of ~50%.

Add a sampler that drops grpc.health.v1.Health spans and delegates everything
else to the default parent-based sampler. This overlaps the collector-side
filter added later in this branch; keeping both means payment stops exporting
the spans at the source rather than paying to ship them and drop them centrally.

Separately, make the manual `charge` span INTERNAL instead of SERVER and mirror
its attributes onto the auto-instrumented entry span. This does NOT change the
request rate: Splunk APM counts service entry spans only, and the manual span
has a same-service parent, so it never entered the count. It is span-volume
hygiene -- one less exported span per charge, attribute promotion preserved.

Requires @opentelemetry/sdk-trace-base as a direct dependency, pinned to the
2.9.0 already resolved for @splunk/otel so no second copy is installed.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
Register the payment hotfix in .hotfix.yaml via manage-hotfix.py so
stitch-manifests.sh resolves the tag, and point the vA/vB manifests (plus the
legacy payment-k8s.yaml fallback) at
ghcr.io/splunk/opentelemetry-demo/otel-payment:2.0.8-hotfix-payment-1.

Carries the health-probe sampler and INTERNAL charge span fix.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
Every gRPC service in the demo carries a livenessProbe (10s) and a
readinessProbe (5s) against grpc.health.v1.Health/Check, and the language
auto-instrumentation turns each probe into a SERVER span. APM counts those as
service requests: ~18/min/pod of pure noise.

On payment (4 pods) that read as ~390 requests per 5 minutes against ~15 real
Charge calls, so the paymentFailure 50/50 A/B split showed up as a ~2% error
rate instead of ~50%. The same distortion applies to cart, checkout,
product-catalog, ad, recommendation and currency.

Add filter/drop_health_checks to the agent traces pipeline, matching the gRPC
health service on both the RPC attributes and the span name (instrumentations
differ on which they populate), plus anchored HTTP liveness/readiness paths.
error_mode: ignore so IsMatch against an absent attribute is a no-op.

Validated with `otelcol validate` against splunk-otel-collector 0.158.0.

This is the fleet-wide fix; the payment-side sampler in this branch covers the
same probes in-process for that one service.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
Splunk APM renders the endpoint as grpc.grpc.health.v1.Health/Check, so the
raw span name carries a prefix in at least some instrumentations. Match the
substring instead of anchoring at the start; the rpc.service condition already
covers the attribute-based case.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
Copy of the 2.0.8 values, which now carries filter/drop_health_checks, so the
health-probe fix is inherited by the next release rather than depending on the
"Ensure values.yaml exists" clone step in prod-release.yml.

Only the `# Version:` line and the Deploy example's -f filename are changed;
the config itself is identical to 2.0.8.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
Per-hotfix copies of the values file are redundant: prod-release.yml clones one
when a version has none, and the per-version files that matter (2.0.8, 2.0.9)
are carried explicitly.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
The message thrown after all retries are exhausted named the rejected
credential:

  Payment failed after retries: Invalid API Token (test-20e26e90-...)

That string crosses the gRPC boundary, so it surfaced on every failed trace as
grpc.error_message and otel.status_description in checkout's
oteldemo.PaymentService/Charge span, and as exception.message and
exception.stacktrace on the exception events in both checkout and payment.

Use 'Invalid request' instead, matching InvalidRequestError, which is what the
simulated gateway already raises per attempt for a 401.

The token is still logged in the two payment ERROR records that accompany the
failure; those are log records rather than span attributes, and they are the
breadcrumb the demo expects an attendee to follow to root cause.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
…iption

The charge span's status description was the bare status code ("401"), so it
read differently from the auto-instrumented entry span, whose
otel.status_description comes from the thrown error's message.

Mirror the thrown message instead. Both spans now show 'Invalid request' for a
401, and the status code stays available as http.status_code and
rpc.grpc.status_code.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
This reverts commit 267b4ea.

The pin and its .hotfix.yaml entry were for shipping the payment fixes as a
hotfix on top of 2.0.8. This branch releases 2.0.9 instead, where the pipeline
sets every image to the release version and clears hotfix tracking, so both are
stale here: the manifests would ship a 2.0.8 hotfix tag, and manage-hotfix.py
would still report a 2.0.8 hotfix against a 2.0.9 base.

The payment code fixes themselves are untouched.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
Brings in the promoDiscountBug silent revenue leak scenario (checkout + flagd)
alongside the payment health-probe, span-noise and error-message fixes.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
…tics

payment-vB ran with OTEL_LOG_LEVEL=debug, which enables the OpenTelemetry JS
diag logger. That pretty-prints every exported span to stdout as a multi-line
object dump, plus instrumentation patch traces and a machine-id ENOENT on every
start.

The filelog receiver ingests one record per line, and none of those lines carry
a parseable severity, so Splunk showed hundreds of UNKNOWN entries per minute
whose _raw was a fragment like "traceId: '89a4cae...'". On a sample of the last
200 lines from a vB pod, 200 were SDK noise and 0 were application logs.

Drop OTEL_LOG_LEVEL to info, matching payment-vA. LOG_LEVEL stays at debug so
vB remains the more verbose variant where it is useful: pino emits single-line
JSON with severity and trace context, which parses correctly.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
…own"

Every payment log record carried deployment.environment: "unknown". The mixin's
fallback chain missed on all three steps:

  - OTEL_RESOURCE_ATTRIBUTES on these pods is
    "service.name=payment,service.version=...,payment.variant=B" with no
    deployment.environment key, so the regex never matched. In k8s the
    environment is injected downstream by the collector's
    resource/add_environment processor, not passed to the app.
  - ENVIRONMENT is not set; the pods get WORKSHOP_ENV, from workshop-secret
    key "env".
  - So it fell through to the literal string "unknown".

The collector cannot repair this: it adds deployment.environment as a resource
attribute with action: insert, which does not touch a field the app already
wrote into the log body.

Add WORKSHOP_ENV to the chain, and accept deployment.environment.name as well
as deployment.environment so the regex matches current semconv.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
…ot end on the error

For the demo the payment ERROR was the last log line in the trace, so the root
cause was the first thing a viewer saw before investigating anything.

checkout returned from a failed chargeCard straight to the caller with no
logging at all, and payment stopped at its ERROR records. Add breadcrumbs on
the unwind path:

  payment   Charge attempt sequence complete.      (charge.js finally)
            Charge request rejected, returning to caller.
            Leaving payment service.               (index.js catch, after callback)

  checkout  payment declined, unwinding order
            no transaction id returned by payment service
            cart left intact for retry
            shipping order not placed
            order confirmation email not sent
            order not published to orders topic
            order total not recorded for reporting
            leaving PlaceOrder

Ten INFO records after the failure. Each states something that is actually true
about the abandoned order rather than inventing rollback work the service does
not do, so the trail stays honest while the failure sits mid-stream.

checkout uses InfoContext so the records carry trace context and correlate to
the same trace; several existing logger.Info calls in this file use the
context-free form and do not correlate.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
…rder path

Builds on the unwind logging so the payment errors sit in the middle of a long
log stream rather than at the top of the page, making the search exercise in
the demo worth doing.

checkout now brackets every downstream call, so a failed order emits roughly a
dozen INFO records before the error as well:

  entering/leaving cart service          (with item count)
  entering/leaving product catalog service
  entering/leaving shipping service
  entering/leaving currency service
  entering/leaving order preparation
  order total calculated / promo code resolved
  entering/leaving payment service       (leaving carries result=declined)

and the unwind gained one line per order item plus the promo, order id and
return-code records.

payment gains "Entering payment service." on the way in and, on the way out,
"Charge not authorized, no funds captured." and "Payment gateway session
closed." The success path gets a matching leaving pair so the brackets are
symmetric rather than only appearing on failures.

Every line still states something true about the order; none of them invent
rollback work the services do not perform.

Claude-Session: https://claude.ai/code/session_019CsicxrWPhBhfFNmhdkTaM
Version bump: custom

Services: checkout

Updated:

- SPLUNK-VERSION

- Source k8s manifests with new image references

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants