docs(keycloak-session-store): import the session-storage lab as a new project
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
43bccd08a8
commit
b2963105a8
+248
@@ -0,0 +1,248 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 23 KiB |
Reference in New Issue
Block a user