docs+rig(flow-debugger): step logs v1.4 specs, register rows, FlowLogDemo seed + verification step J
This commit is contained in:
+2
-1
@@ -100,7 +100,8 @@ docker/
|
||||
├── seed-delmia-sim.sh # Seed the in-memory Delmia machine stand-in (DelmiaSim01) on site-a
|
||||
├── seed-delmia-api.sh # Seed the DelmiaRecipeDownload inbound method + a scoped API key (token printed once)
|
||||
├── seed-flow-outbound-demo.sh # Seed the Flow Debugger outbound-calls demo: RigRest external system, RigDb connection, FlowDemo list, OutboundDemo script + FlowOutboundDemo method/key
|
||||
├── verify-delmia-notifier.sh # DelmiaNotifier end-to-end vs the rig: standby relay, active-central crash/takeover, rejoin; step H = Flow Debugger live run + replay (CLI flow-debug); step I = outbound API/DB/notify nodes live + replay
|
||||
├── seed-flow-log-demo.sh # Seed the Flow Debugger STEP-LOGS demo: FlowLogDemo Call script on SimMachine (logs info/debug/warn, writes an attribute, then throws) — idempotent
|
||||
├── verify-delmia-notifier.sh # DelmiaNotifier end-to-end vs the rig: standby relay, active-central crash/takeover, rejoin; step H = Flow Debugger live run + replay (CLI flow-debug); step I = outbound API/DB/notify nodes live + replay; step J = step logs + exception detail live + replay
|
||||
├── teardown.sh # Stop and remove containers
|
||||
├── central-node-a/
|
||||
│ ├── appsettings.Central.json # Central node A configuration
|
||||
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Seed the Flow Debugger STEP-LOGS demo on the docker rig (plan
|
||||
# docs/plans/2026-09-09-flow-debugger-step-logs.md, Task 15): one site Call script that writes a
|
||||
# line at three levels, writes a static attribute, warns, and then throws — so a traced run shows
|
||||
# BOTH halves of the step-logs feature on one graph:
|
||||
#
|
||||
# * the ScriptRun node ends Failed and carries the whole exception (errorDetail = ex.ToString()),
|
||||
# rendered as a red `InvalidOperationException: demo failure` subtitle plus the Log tab's
|
||||
# exception block;
|
||||
# * the info / debug / warn lines fold onto their owning step as ScriptTrace/ScriptLog rows —
|
||||
# the badge reads `✕ 0 ! 1 ≡ 2`-style counts, never separate nodes.
|
||||
#
|
||||
# What it creates:
|
||||
# - Script FlowLogDemo (Call, no parameters) on template SimMachine (docker/seed-delmia-sim.sh),
|
||||
# then redeploys DelmiaSim01 so the site actually has it.
|
||||
#
|
||||
# IDEMPOTENT: re-running UPDATES the script in place (template script update) instead of failing on
|
||||
# the unique name, so this is the script to re-run after a rig rebuild.
|
||||
#
|
||||
# Prerequisites: bash docker/deploy.sh; docker/seed-sites.sh; docker/seed-delmia-sim.sh.
|
||||
#
|
||||
# Try:
|
||||
# scadabridge --url http://localhost:9000 --username multi-role --password password \
|
||||
# flow-debug run-script --instance-id <id> --script FlowLogDemo --show-logs
|
||||
# then open /deployment/flow-debugger?executionId=<rootExecutionId>
|
||||
# Covered by docker/verify-delmia-notifier.sh step J.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CLI="$PROJECT_ROOT/src/ZB.MOM.WW.ScadaBridge.CLI/bin/Debug/net10.0/scadabridge"
|
||||
A=(--url "${SCADABRIDGE_URL:-http://localhost:9000}" --format=json --username "${SCADABRIDGE_USERNAME:-multi-role}" --password "${SCADABRIDGE_PASSWORD:-password}")
|
||||
INSTANCE_NAME="${INSTANCE_NAME:-DelmiaSim01}"
|
||||
TEMPLATE_NAME="${TEMPLATE_NAME:-SimMachine}"
|
||||
SCRIPT_NAME="${SCRIPT_NAME:-FlowLogDemo}"
|
||||
# A static attribute that exists on SimMachine through the composed DelmiaReceiver child, and that
|
||||
# nothing triggers on (RecipeDownloadFlag would fire the simulator script and muddy the graph).
|
||||
WRITE_ATTRIBUTE="${WRITE_ATTRIBUTE:-RecipeProcessResultText}"
|
||||
|
||||
find_id() { # stdin = JSON array, $1 = key, $2 = value
|
||||
python3 -c 'import json,sys; k,v=sys.argv[1],sys.argv[2]; print(next(x["id"] for x in json.load(sys.stdin) if x.get(k)==v))' "$1" "$2"; }
|
||||
|
||||
# The demo body. Log.* is the leveled per-run logging global (design 2026-09-09 D4): every line
|
||||
# always reaches the node ILogger, and becomes a ScriptLog trace row only when the run is traced.
|
||||
read -r -d '' DEMO_CODE <<'CS' || true
|
||||
// Flow Debugger step-logs demo. Three levels, one real static write between them, then a throw:
|
||||
// the lines fold onto the steps that produced them, and the throw gives the ScriptRun node its
|
||||
// exception block. Nothing here is debugger-specific — Log.* is an ordinary script API.
|
||||
Log.Info("FlowLogDemo starting");
|
||||
Log.Debug("about to write");
|
||||
await Children["DelmiaReceiver"].Attributes.SetAsync("RecipeProcessResultText", "42");
|
||||
Log.Warn("demo warning after the write");
|
||||
throw new System.InvalidOperationException("demo failure");
|
||||
CS
|
||||
|
||||
echo "1/3 Locating template $TEMPLATE_NAME..."
|
||||
TEMPLATE_ID=$("$CLI" template list "${A[@]}" | find_id name "$TEMPLATE_NAME")
|
||||
echo " template id=$TEMPLATE_ID"
|
||||
|
||||
echo "2/3 Adding or updating script $SCRIPT_NAME (Call)..."
|
||||
EXISTING_ID=$("$CLI" template get "${A[@]}" --id "$TEMPLATE_ID" | python3 -c '
|
||||
import json, sys
|
||||
t = json.load(sys.stdin)
|
||||
name = sys.argv[1]
|
||||
print(next((s["id"] for s in (t.get("scripts") or []) if s.get("name") == name), ""))' "$SCRIPT_NAME")
|
||||
if [ -n "$EXISTING_ID" ]; then
|
||||
"$CLI" template script update "${A[@]}" --id "$EXISTING_ID" --name "$SCRIPT_NAME" --trigger-type Call --code "$DEMO_CODE" > /dev/null
|
||||
echo " updated script id=$EXISTING_ID"
|
||||
else
|
||||
EXISTING_ID=$("$CLI" template script add "${A[@]}" --template-id "$TEMPLATE_ID" --name "$SCRIPT_NAME" --trigger-type Call --code "$DEMO_CODE" \
|
||||
| python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("id",""))')
|
||||
echo " added script id=$EXISTING_ID"
|
||||
fi
|
||||
|
||||
echo "3/3 Redeploying $INSTANCE_NAME so the site has $SCRIPT_NAME..."
|
||||
INSTANCE_ID=$("$CLI" instance list "${A[@]}" | find_id uniqueName "$INSTANCE_NAME")
|
||||
"$CLI" instance deploy "${A[@]}" --id "$INSTANCE_ID" > /dev/null
|
||||
echo " instance id=$INSTANCE_ID"
|
||||
|
||||
echo
|
||||
echo "FLOW_LOG_DEMO_TEMPLATE_ID=$TEMPLATE_ID FLOW_LOG_DEMO_INSTANCE_ID=$INSTANCE_ID SCRIPT=$SCRIPT_NAME (writes $WRITE_ATTRIBUTE)"
|
||||
echo "Try: $CLI --url http://localhost:9000 --username multi-role --password password flow-debug run-script --instance-id $INSTANCE_ID --script $SCRIPT_NAME --show-logs"
|
||||
@@ -41,6 +41,11 @@ set -euo pipefail
|
||||
# DB write Submitted or — DB reachable, immediate-completion path — already Delivered, the
|
||||
# notification Submitted; replay is retried <= 90 s until the write is Delivered and the
|
||||
# notification has a terminal NotifyDeliver row). Skipped with a note when the demo is not seeded.
|
||||
# J Flow Debugger STEP LOGS (2026-09-09) -> YES (FlowLogDemo — seed-flow-log-demo.sh — logs
|
||||
# info/debug/warn, writes a static attribute, then throws: the ScriptRun step comes back Failed with
|
||||
# errorDetail naming InvalidOperationException, and >= 3 ScriptTrace/ScriptLog rows carry levels
|
||||
# {info,debug,warn}. Replay asserts the same from the central audit store. Skipped with a note when
|
||||
# the demo is not seeded.)
|
||||
|
||||
: "${DELMIA_API_KEY:?export DELMIA_API_KEY=sbk_... first}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
@@ -232,4 +237,69 @@ sys.exit(0 if ok else 1)' "$1"
|
||||
echo " replay: $I_REPLAY"
|
||||
echo "I outbound live+replay -> YES"
|
||||
fi
|
||||
# J — Flow Debugger step logs + exception detail (design 2026-09-09). FlowLogDemo logs at three
|
||||
# levels, writes a static attribute, then throws. Live: the site returns its ScriptRun row (Failed,
|
||||
# errorDetail = the exception's ToString()) and the ScriptLog rows inline on the same reply. Replay:
|
||||
# the same rows are read back from the central AuditLog once the 5 s telemetry push has landed.
|
||||
echo "== J Flow Debugger step logs (live + replay)"
|
||||
J_INSTANCE_ID=$("$CLI" instance list "${H_ARGS[@]}" | python3 -c 'import json,sys; xs=json.load(sys.stdin); print(next((i["id"] for i in xs if i.get("uniqueName")==sys.argv[1]), ""))' "$MACHINE")
|
||||
if [ -z "$J_INSTANCE_ID" ]; then
|
||||
echo "J step logs live+replay -> SKIPPED ($MACHINE not seeded — run docker/seed-delmia-sim.sh)"
|
||||
else
|
||||
j_check() { # $1 = live|replay; stdin = FlowDebugRunResult JSON
|
||||
python3 -c '
|
||||
import json, sys
|
||||
mode = sys.argv[1]
|
||||
r = json.load(sys.stdin)
|
||||
rows = []
|
||||
for e in r.get("events", []):
|
||||
d = json.loads(e["detailsJson"])
|
||||
d["_extra"] = json.loads(d["extra"]) if d.get("extra") else {}
|
||||
rows.append(d)
|
||||
runs = [d for d in rows if d.get("kind") == "ScriptRun"]
|
||||
logs = [d for d in rows if d.get("kind") == "ScriptLog"]
|
||||
levels = sorted({(d["_extra"].get("logLevel") or "?") for d in logs})
|
||||
problems = []
|
||||
if not runs:
|
||||
problems.append("no ScriptRun row")
|
||||
else:
|
||||
failed = [d for d in runs if d.get("status") == "Failed"]
|
||||
if not failed:
|
||||
problems.append("ScriptRun not Failed (" + ",".join(str(d.get("status")) for d in runs) + ")")
|
||||
elif not any("InvalidOperationException" in (d.get("errorDetail") or "") for d in failed):
|
||||
problems.append("no InvalidOperationException in errorDetail")
|
||||
if len(logs) < 3:
|
||||
problems.append("only " + str(len(logs)) + " ScriptLog row(s)")
|
||||
missing = [lv for lv in ("info", "debug", "warn") if lv not in levels]
|
||||
if missing:
|
||||
problems.append("levels missing " + ",".join(missing))
|
||||
ok = not problems
|
||||
print(("YES" if ok else "NO") + " root=" + str(r.get("rootExecutionId")) + " logs=" + str(len(logs))
|
||||
+ " levels=" + ",".join(levels) + " scriptRun=" + ",".join(str(d.get("status")) for d in runs)
|
||||
+ (" problems=" + ";".join(problems) if problems else "") + (" error=" + str(r.get("errorCode")) if r.get("errorCode") else ""))
|
||||
sys.exit(0 if ok else 1)' "$1"
|
||||
}
|
||||
# The script throws by design, so the run reports Completed=false with CALL_FAILED — that is the
|
||||
# expected outcome here, and the CLI still prints the full result. Do not gate on the exit code.
|
||||
set +e; J_RUN=$("$CLI" flow-debug run-script --instance-id "$J_INSTANCE_ID" --script FlowLogDemo "${H_ARGS[@]}"); set -e
|
||||
case "$J_RUN" in
|
||||
*rootExecutionId*) ;;
|
||||
*) echo "J step logs live+replay -> SKIPPED (FlowLogDemo not seeded — run docker/seed-flow-log-demo.sh): $(echo "$J_RUN" | head -2)"; J_RUN=""; ;;
|
||||
esac
|
||||
if [ -n "$J_RUN" ]; then
|
||||
J_LIVE=$(echo "$J_RUN" | j_check live) || { echo "FAIL: live run: $J_LIVE"; exit 1; }
|
||||
echo " live: $J_LIVE"
|
||||
J_ROOT=$(echo "$J_RUN" | python3 -c 'import json,sys; print(json.load(sys.stdin)["rootExecutionId"])')
|
||||
J_REPLAY=""
|
||||
for i in $(seq 1 20); do
|
||||
set +e; J_REPLAY=$("$CLI" flow-debug replay --execution-id "$J_ROOT" "${H_ARGS[@]}" | j_check replay); J_RRC=$?; set -e
|
||||
[ "$J_RRC" -eq 0 ] && break
|
||||
sleep 2
|
||||
done
|
||||
[ "${J_RRC:-1}" -eq 0 ] || { echo "FAIL: replay never showed the logs + exception after 40 s: $J_REPLAY"; exit 1; }
|
||||
echo " replay: $J_REPLAY"
|
||||
echo " root: $J_ROOT (open /deployment/flow-debugger?executionId=$J_ROOT)"
|
||||
echo "J step logs live+replay -> YES"
|
||||
fi
|
||||
fi
|
||||
echo "== done. Notifier log: $RUN_DIR/delmia-notifier.log"
|
||||
|
||||
Reference in New Issue
Block a user