The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
249 lines
23 KiB
XML
249 lines
23 KiB
XML
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="2636" viewBox="0 0 1200 2636" role="img">
|
|
<title>terminal evidence</title>
|
|
<desc>Terminal-style rendering generated from retained command output. Sensitive-looking values are redacted in the visual asset.</desc>
|
|
<rect x="1" y="1" width="1198" height="2634" rx="14" fill="#0d1117" stroke="#30363d"/>
|
|
<rect x="1" y="1" width="1198" height="44" rx="14" fill="#161b22"/>
|
|
<rect x="1" y="30" width="1198" height="14" fill="#161b22"/>
|
|
<circle cx="24" cy="22" r="6" fill="#ff5f57"/>
|
|
<circle cx="44" cy="22" r="6" fill="#febc2e"/>
|
|
<circle cx="64" cy="22" r="6" fill="#28c840"/>
|
|
<text x="92" y="27" fill="#8b949e" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">terminal evidence</text>
|
|
<text x="24" y="68" fill="#c9d1d9" font-size="15" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">$ set -e
|
|
set -o pipefail
|
|
D=$(mktemp -d); trap 'rm -rf "$D"' EXIT
|
|
C=/shared/codebase/clean-architecture-backend-template
|
|
</tspan><tspan x="24" dy="22">N=$C/src/adapter/outbound/notification
|
|
CACHE=/root/.gradle/caches/modules-2/files-2.1
|
|
java -version 2>&1 | head -1
|
|
cp=""</tspan><tspan x="24" dy="22">
|
|
while IFS= read -r line; do
|
|
coord=${line%%=*}; confs=${line#*=}
|
|
case ",$confs," in *",runtimeClasspath,"*) ;; *) co</tspan><tspan x="24" dy="22">ntinue ;; esac
|
|
g=${coord%%:*}; rest=${coord#*:}; n=${rest%%:*}; v=${rest##*:}
|
|
jar=$(find "$CACHE/$g/$n/$v" -name '*.</tspan><tspan x="24" dy="22">jar' ! -name '*sources*' ! -name '*javadoc*' 2>/dev/null | head -1)
|
|
[ -n "$jar" ] && cp="$cp:$jar"
|
|
done < <(grep -E '^</tspan><tspan x="24" dy="22">[a-zA-Z0-9._-]+:[^=]+=' "$N/gradle.lockfile")
|
|
# jackson-core 는 이 프로브가 부르지 않는 valueMasker() 를 컴파일하려고만 </tspan><tspan x="24" dy="22">붙인다.
|
|
# 여기서 재는 mask(String) 은 java.util.regex 만 쓴다.
|
|
JACKSON=$(find "$CACHE/com.fasterxml.jackson.core/jackson</tspan><tspan x="24" dy="22">-core" -name 'jackson-core-*.jar' ! -name '*sources*' | head -1)
|
|
LOGSTASH=$(find "$CACHE/net.logstash.logback/logstash-l</tspan><tspan x="24" dy="22">ogback-encoder/8.0" -name '*.jar' ! -name '*sources*' | head -1)
|
|
CP=$(find "$N/build/libs" "$C/src/application-core/buil</tspan><tspan x="24" dy="22">d/libs" -name '*+21234e38cdb9.jar' | tr '\n' ':')$JACKSON:$LOGSTASH${cp}
|
|
# 마스킹 카탈로그는 빌드 산출물이 아니라 고정 </tspan><tspan x="24" dy="22">리비전 소스에서 바로 컴파일한다.
|
|
javac -encoding UTF-8 -cp "$CP" -d "$D" \
|
|
"$C/src/app-bootstrap/src/main/java/dev/cas</tspan><tspan x="24" dy="22">keleton/bootstrap/logging/LogMaskingPatterns.java"
|
|
cat > "$D/RenderFailureProbe.java" <<'JAVA'
|
|
import ch.qos.logback.cla</tspan><tspan x="24" dy="22">ssic.LoggerContext;
|
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
|
import ch.qos.logback.classic.spi.ThrowableProxyUti</tspan><tspan x="24" dy="22">l;
|
|
import ch.qos.logback.core.read.ListAppender;
|
|
import dev.caskeleton.adapter.outbound.notification.platform.template.T</tspan><tspan x="24" dy="22">emplateSlotMode;
|
|
import dev.caskeleton.adapter.outbound.notification.platform.template.ThymeleafStringTemplateEngine;
|
|
im</tspan><tspan x="24" dy="22">port dev.caskeleton.bootstrap.logging.LogMaskingPatterns;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.u</tspan><tspan x="24" dy="22">til.Map;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
/** 같은 식을 두 오버로드에 넣고, 호출자가 받는 것과 로그에 남는 것을 따로 잰</tspan><tspan x="24" dy="22">다. */
|
|
public final class RenderFailureProbe {
|
|
|
|
private static final String CODE = "483920";
|
|
|
|
/** 값 하나가 아니라 </tspan><tspan x="24" dy="22">값 객체가 변수로 들어오는 경우를 보려고 둔 합성 레코드. */
|
|
record Recipient(String email, String code) {}
|
|
|
|
private s</tspan><tspan x="24" dy="22">tatic final Recipient RECIPIENT = new Recipient("alice@example.com", CODE);
|
|
private static final Map<String, Object> V</tspan><tspan x="24" dy="22">ARIABLES =
|
|
Map.of("code", CODE, "recipient", RECIPIENT);
|
|
|
|
private static final ThymeleafStringTemplateEngine ENG</tspan><tspan x="24" dy="22">INE = new ThymeleafStringTemplateEngine();
|
|
|
|
private static ListAppender<ILoggingEvent> sink;
|
|
|
|
/** 한 번 렌더한 결과:</tspan><tspan x="24" dy="22"> 호출자가 값을 보는가, 로그에 남는가, 마스킹이 지우는가. */
|
|
private record Seen(boolean caller, boolean logged, boolea</tspan><tspan x="24" dy="22">n masked) {}
|
|
|
|
/** 원인 사슬 전체를 잇는다. 최상위 메시지만 보면 아래에 담긴 값을 놓친다. */
|
|
private static String ch</tspan><tspan x="24" dy="22">ain(Throwable failure) {
|
|
StringBuilder joined = new StringBuilder();
|
|
for (Throwable step = failure; step != null</tspan><tspan x="24" dy="22">; step = step.getCause()) {
|
|
joined.append(step.getClass().getSimpleName()).append(": ").append(step.getMessage());</tspan><tspan x="24" dy="22">
|
|
}
|
|
return joined.toString();
|
|
}
|
|
|
|
/** logback 이 실제로 찍을 형태: 메시지 + 예외 사슬. */
|
|
private static Str</tspan><tspan x="24" dy="22">ing drainLog() {
|
|
StringBuilder printed = new StringBuilder();
|
|
for (ILoggingEvent event : List.copyOf(sink.list))</tspan><tspan x="24" dy="22"> {
|
|
printed.append(event.getFormattedMessage());
|
|
if (event.getThrowableProxy() != null) {
|
|
printed.app</tspan><tspan x="24" dy="22">end(ThrowableProxyUtil.asString(event.getThrowableProxy()));
|
|
}
|
|
}
|
|
sink.list.clear();
|
|
return printed.toS</tspan><tspan x="24" dy="22">tring();
|
|
}
|
|
|
|
private static boolean carries(String text) {
|
|
return text.contains(CODE) || text.contains("alice@exa</tspan><tspan x="24" dy="22">mple.com");
|
|
}
|
|
|
|
private static Seen measure(Runnable call) {
|
|
sink.list.clear();
|
|
String caught = "";
|
|
try {</tspan><tspan x="24" dy="22">
|
|
call.run();
|
|
} catch (RuntimeException failure) {
|
|
caught = chain(failure);
|
|
}
|
|
String logged = dra</tspan><tspan x="24" dy="22">inLog();
|
|
return new Seen(carries(caught), carries(logged), carries(LogMaskingPatterns.mask(logged)));
|
|
}
|
|
|
|
private</tspan><tspan x="24" dy="22"> static String mark(boolean seen) {
|
|
return seen ? "있음" : "없음";
|
|
}
|
|
|
|
/** 두 오버로드를 나란히 재고 한 줄로 적는</tspan><tspan x="24" dy="22">다. 로그 쪽이 갈리면 그렇게 적는다. */
|
|
private static void shape(String source) {
|
|
Seen plain = measure(() -> ENGIN</tspan><tspan x="24" dy="22">E.render(source, VARIABLES));
|
|
Seen moded = measure(() -> ENGINE.render(TemplateSlotMode.TEXT, source, VARIABLES));
|
|
</tspan><tspan x="24" dy="22"> String logged = plain.logged() == moded.logged() ? mark(moded.logged()) : "두 쪽이 다름";
|
|
String masked = plain.m</tspan><tspan x="24" dy="22">asked() == moded.masked() ? mark(moded.masked()) : "두 쪽이 다름";
|
|
System.out.println(source);
|
|
System.out.printf</tspan><tspan x="24" dy="22">(" 호출자 모드없음 %s · 모드있음* %s · 로그 %s · 마스킹 뒤 %s%n",
|
|
mark(plain.caller()), mark(moded.caller())</tspan><tspan x="24" dy="22">, logged, masked);
|
|
}
|
|
|
|
/** OGNL 이나 JDK 가 만든 첫 절. Thymeleaf 의 포장 절은 건너뛴다. */
|
|
private static void cla</tspan><tspan x="24" dy="22">use(String label, Runnable call) {
|
|
try {
|
|
call.run();
|
|
System.out.printf(" %-30s (실패하지 않음)%n", labe</tspan><tspan x="24" dy="22">l);
|
|
} catch (RuntimeException failure) {
|
|
for (Throwable step = failure; step != null; step = step.getCause()) </tspan><tspan x="24" dy="22">{
|
|
String type = step.getClass().getName();
|
|
if (!type.startsWith("ognl.") && !type.startsWith("java.")) {</tspan><tspan x="24" dy="22">
|
|
continue;
|
|
}
|
|
// toString 을 재정의하지 않은 객체는 실행마다 다른 주소를 남긴다. 여기서 재는 것</tspan><tspan x="24" dy="22">은
|
|
// 값이 적히는지 여부이므로 주소만 고정 표시로 바꾼다.
|
|
String message = String.valueOf(step.getMessag</tspan><tspan x="24" dy="22">e()).replaceAll("@[0-9a-f]{4,}", "@<주소>");
|
|
System.out.printf(" %-30s %s: %s%n", label, step.getClass().getSim</tspan><tspan x="24" dy="22">pleName(), message);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void clauseOf(String source) {
|
|
clause(sourc</tspan><tspan x="24" dy="22">e, () -> ENGINE.render(TemplateSlotMode.TEXT, source, VARIABLES));
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
L</tspan><tspan x="24" dy="22">oggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
|
// 기본 콘솔 어펜더를 뗀다. 남기면 재려는 </tspan><tspan x="24" dy="22">로그가 잰 결과와 섞인다.
|
|
context.getLogger("ROOT").detachAndStopAllAppenders();
|
|
sink = new ListAppender<>();
|
|
</tspan><tspan x="24" dy="22"> sink.setContext(context);
|
|
sink.start();
|
|
context.getLogger("ROOT").addAppender(sink);
|
|
|
|
System.out.println();</tspan><tspan x="24" dy="22">
|
|
System.out.println("code=" + CODE + ", recipient=" + RECIPIENT + " 를 넣고 렌더한다.");
|
|
System.out.println("* </tspan><tspan x="24" dy="22">표시가 프로덕션이 부르는 오버로드다.");
|
|
System.out.println();
|
|
|
|
String[] leaking = {
|
|
"[[${code.substring(99</tspan><tspan x="24" dy="22">)}]]",
|
|
"[[${code.noSuchMethod()}]]",
|
|
"[[${recipient.noSuchMethod()}]]",
|
|
"[[${recipient.email.substring</tspan><tspan x="24" dy="22">(99)}]]",
|
|
"[[${#numbers.formatDecimal(code, 1, 2)}]]",
|
|
"[[${code[recipient]}]]",
|
|
"[[${recipient * 2}]]</tspan><tspan x="24" dy="22">",
|
|
};
|
|
String[] quiet = {
|
|
"[[${#strings.substring(code, 99, 200)}]]",
|
|
"[[${code.noSuchProperty}]]",
|
|
</tspan><tspan x="24" dy="22"> "[[${code.length() / 0}]]",
|
|
};
|
|
|
|
System.out.println("[실패한 자리에 변수가 놓인 식]");
|
|
for (String sourc</tspan><tspan x="24" dy="22">e : leaking) {
|
|
shape(source);
|
|
}
|
|
System.out.println();
|
|
|
|
System.out.println("[실패한 자리에 변수가 없는 </tspan><tspan x="24" dy="22">식]");
|
|
for (String source : quiet) {
|
|
shape(source);
|
|
}
|
|
System.out.println("[[${code}]] (이 줄만 변수를</tspan><tspan x="24" dy="22"> 하나도 주지 않는다)");
|
|
Seen plain = measure(() -> ENGINE.render("[[${code}]]", new HashMap<>()));
|
|
Seen moded =
|
|
</tspan><tspan x="24" dy="22"> measure(() -> ENGINE.render(TemplateSlotMode.TEXT, "[[${code}]]", new HashMap<>()));
|
|
System.out.printf(" 호</tspan><tspan x="24" dy="22">출자 모드없음 %s · 모드있음* %s · 로그 %s · 마스킹 뒤 %s%n",
|
|
mark(plain.caller()), mark(moded.caller()), mark</tspan><tspan x="24" dy="22">(moded.logged()), mark(moded.masked()));
|
|
System.out.println();
|
|
|
|
System.out.println("[OGNL 이나 JDK 가 만든 절]")</tspan><tspan x="24" dy="22">;
|
|
for (String source : leaking) {
|
|
clauseOf(source);
|
|
}
|
|
for (String source : quiet) {
|
|
clauseOf(sou</tspan><tspan x="24" dy="22">rce);
|
|
}
|
|
}
|
|
}
|
|
JAVA
|
|
javac -encoding UTF-8 -cp "$CP:$D" -d "$D" "$D/RenderFailureProbe.java"
|
|
java -Dstdout.encoding=UT</tspan><tspan x="24" dy="22">F-8 -cp "$CP:$D" RenderFailureProbe
|
|
</tspan></text>
|
|
<text x="24" y="1542" fill="#8b949e" font-size="13" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">cwd: /shared/codebase/clean-architecture-backend-template/src</tspan></text>
|
|
<text x="24" y="1564" fill="#8b949e" font-size="13" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">time: 2026-09-02T16:07:05+00:00 · exit 0</tspan></text>
|
|
<line x1="24" y1="1578" x2="1176" y2="1578" stroke="#30363d"/>
|
|
<text x="24" y="1618" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">openjdk version "21.0.12" 2026-07-21</tspan></text>
|
|
<text x="24" y="1640" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
|
|
<text x="24" y="1662" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">code=483920, recipient=Recipient[email=alice@example.com, code=483920] 를 넣고 렌더한다.</tspan></text>
|
|
<text x="24" y="1684" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">* 표시가 프로덕션이 부르는 오버로드다.</tspan></text>
|
|
<text x="24" y="1706" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
|
|
<text x="24" y="1728" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[실패한 자리에 변수가 놓인 식]</tspan></text>
|
|
<text x="24" y="1750" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${code.substring(99)}]]</tspan></text>
|
|
<text x="24" y="1772" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 있음 · 로그 있음 · 마스킹 뒤 있음</tspan></text>
|
|
<text x="24" y="1794" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${code.noSuchMethod()}]]</tspan></text>
|
|
<text x="24" y="1816" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 있음 · 로그 있음 · 마스킹 뒤 있음</tspan></text>
|
|
<text x="24" y="1838" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${recipient.noSuchMethod()}]]</tspan></text>
|
|
<text x="24" y="1860" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 있음 · 로그 있음 · 마스킹 뒤 있음</tspan></text>
|
|
<text x="24" y="1882" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${recipient.email.substring(99)}]]</tspan></text>
|
|
<text x="24" y="1904" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 있음 · 로그 있음 · 마스킹 뒤 있음</tspan></text>
|
|
<text x="24" y="1926" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${#numbers.formatDecimal(code, 1, 2)}]]</tspan></text>
|
|
<text x="24" y="1948" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 있음 · 로그 있음 · 마스킹 뒤 있음</tspan></text>
|
|
<text x="24" y="1970" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${code[recipient]}]]</tspan></text>
|
|
<text x="24" y="1992" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 있음 · 로그 있음 · 마스킹 뒤 있음</tspan></text>
|
|
<text x="24" y="2014" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${recipient * 2}]]</tspan></text>
|
|
<text x="24" y="2036" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 있음 · 로그 있음 · 마스킹 뒤 있음</tspan></text>
|
|
<text x="24" y="2058" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
|
|
<text x="24" y="2080" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[실패한 자리에 변수가 없는 식]</tspan></text>
|
|
<text x="24" y="2102" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${#strings.substring(code, 99, 200)}]]</tspan></text>
|
|
<text x="24" y="2124" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 없음 · 로그 없음 · 마스킹 뒤 없음</tspan></text>
|
|
<text x="24" y="2146" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${code.noSuchProperty}]]</tspan></text>
|
|
<text x="24" y="2168" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 없음 · 로그 없음 · 마스킹 뒤 없음</tspan></text>
|
|
<text x="24" y="2190" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${code.length() / 0}]]</tspan></text>
|
|
<text x="24" y="2212" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 없음 · 로그 없음 · 마스킹 뒤 없음</tspan></text>
|
|
<text x="24" y="2234" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[[${code}]] (이 줄만 변수를 하나도 주지 않는다)</tspan></text>
|
|
<text x="24" y="2256" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 호출자 모드없음 없음 · 모드있음* 없음 · 로그 없음 · 마스킹 뒤 없음</tspan></text>
|
|
<text x="24" y="2278" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
|
|
<text x="24" y="2300" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[OGNL 이나 JDK 가 만든 절]</tspan></text>
|
|
<text x="24" y="2322" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> [[${code.substring(99)}]] MethodFailedException: Method "substring" failed for object 483920</tspan></text>
|
|
<text x="24" y="2344" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> [[${code.noSuchMethod()}]] MethodFailedException: Method "noSuchMethod" failed for object 483920</tspan></text>
|
|
<text x="24" y="2366" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> [[${recipient.noSuchMethod()}]] MethodFailedException: Method "noSuchMethod" failed for object Recipient[email=alice@example.c</tspan><tspan x="24" dy="22">om, code=483920]</tspan></text>
|
|
<text x="24" y="2410" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> [[${recipient.email.substring(99)}]] MethodFailedException: Method "substring" failed for object alice@example.com</tspan></text>
|
|
<text x="24" y="2432" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> [[${#numbers.formatDecimal(code, 1, 2)}]] IllegalArgumentException: Unable to convert type java.lang.String of 483920 to type </tspan><tspan x="24" dy="22">of java.lang.Number</tspan></text>
|
|
<text x="24" y="2476" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> [[${code[recipient]}]] NoSuchPropertyException: java.lang.String.Recipient[email=alice@example.com, code=483920]</tspan></text>
|
|
<text x="24" y="2498" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> [[${recipient * 2}]] NumberFormatException: For input string: "Recipient[email=alice@example.com, code=483920]"</tspan></text>
|
|
<text x="24" y="2520" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> [[${#strings.substring(code, 99, 200)}]] MethodFailedException: Method "substring" failed for object org.thymeleaf.expression.</tspan><tspan x="24" dy="22">Strings@<주소></tspan></text>
|
|
<text x="24" y="2564" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> [[${code.noSuchProperty}]] NoSuchPropertyException: java.lang.String.noSuchProperty</tspan></text>
|
|
<text x="24" y="2586" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> [[${code.length() / 0}]] ArithmeticException: / by zero</tspan></text>
|
|
</svg>
|