Skip to content

Commit 639b274

Browse files
committed
pre tidy-up
1 parent c608234 commit 639b274

2 files changed

Lines changed: 86 additions & 17 deletions

File tree

AGENTS.md

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ PY
6363
- You MUST add a JUL log statement at INFO level at the top of every test method announcing execution.
6464
- You MUST have all new tests extend a helper such as `JsonSchemaLoggingConfig` so environment variables configure JUL levels compatibly with `./mvn-test-no-boilerplate.sh`.
6565
- You MUST NOT guess root causes; add targeted logging or additional tests. Treat observability as the path to the fix.
66+
- YOU MUST Use exactly one logger for the JSON Schema subsystem and use appropriate logging to debug as below.
6667

6768
### Script Usage (Required)
6869
- You MUST prefer the `./mvn-test-no-boilerplate.sh` wrapper for every Maven invocation. Direct `mvn` or `mvnd` calls require additional authorization and skip the curated output controls.
@@ -181,19 +182,6 @@ mvn exec:java -pl json-compatibility-suite -Dexec.args="--json"
181182

182183
## Common Workflows
183184

184-
### Adding New JSON Type Support
185-
1. Add an interface extending `JsonValue`.
186-
2. Implement the type within `jdk.sandbox.internal.util.json`.
187-
3. Update `Json.fromUntyped()` and `Json.toUntyped()`.
188-
4. Extend parser support inside `JsonParser`.
189-
5. Add comprehensive test coverage.
190-
191-
### Debugging Parser Issues
192-
1. Enable FINER logging: `-Djava.util.logging.ConsoleHandler.level=FINER`.
193-
2. Use `./mvn-test-no-boilerplate.sh` for curated output.
194-
3. Target a single test, for example `-Dtest=JsonParserTests#testMethod`, with `FINEST` logging when needed.
195-
4. Cross-check behaviour with the JSON Compatibility Suite.
196-
197185
### API Compatibility Testing
198186
1. Run the compatibility suite: `mvn exec:java -pl json-compatibility-suite`.
199187
2. Inspect reports for regressions relative to upstream expectations.
@@ -230,11 +218,11 @@ mvn exec:java -pl json-compatibility-suite -Dexec.args="--json"
230218
- All prohibitions on output filtering apply. Do not pipe logs unless you must constrain an infinite stream, and even then examine a large sample (thousands of lines).
231219
- Remote location of `./mvn-test-no-boilerplate.sh` is the repository root; pass module selectors through it for schema-only runs.
232220

233-
#### JUL Logging and ERROR Prefix (Schema Module)
221+
#### JUL Logging
234222
- For SEVERE logs, prefix the message with `ERROR` to align with SLF4J-centric filters.
235223
- Continue using the standard hierarchy (SEVERE through FINEST) for clarity.
236-
237-
#### Performance Warning Convention (Schema Module)
224+
- You MUST Use exactly one logger for the JSON Schema subsystem and use appropriate logging to debug as below.
225+
- You MUST NOT create per-class loggers. Collaborating classes must reuse the same logger.
238226
- Potential performance issues log at FINE with the `PERFORMANCE WARNING:` prefix shown earlier.
239227

240228
## Security Notes
@@ -332,7 +320,7 @@ git push -u origin "rel-$VERSION" && echo "✅ Success" || echo "🛑 Unable to
332320
- `pom.xml` (parent) holds the Central Publishing plugin configuration shared across modules.
333321

334322

335-
#### Minimum Viable Future (MVF) Architecture
323+
#### Minimum Viable (MVF) Architecture
336324
1. **Restatement of the approved whiteboard sketch**
337325
- Compile-time uses a LIFO work stack of schema sources (URIs). Begin with the initial source. Each pop parses/builds the root and scans `$ref` tokens, tagging each as LOCAL (same document) or REMOTE (different document). REMOTE targets are pushed when unseen (dedup by normalized document URI). The Roots Registry maps `docUri → Root`.
338326
- Runtime stays unchanged; validation uses only the first root (initial document). Local `$ref` behaviour remains byte-for-byte identical.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package io.github.simbo1905.json.schema;
2+
3+
import java.util.Map;
4+
import java.util.Objects;
5+
import java.util.concurrent.ConcurrentHashMap;
6+
import java.util.concurrent.atomic.AtomicLong;
7+
import java.util.logging.Level;
8+
import java.util.logging.Logger;
9+
10+
/// Package-private helper for structured JUL logging with simple sampling.
11+
/// Produces concise key=value pairs prefixed by event=NAME.
12+
final class StructuredLog {
13+
private static final Map<String, AtomicLong> COUNTERS = new ConcurrentHashMap<>();
14+
15+
static void fine(Logger log, String event, Object... kv) {
16+
if (log.isLoggable(Level.FINE)) log.fine(() -> ev(event, kv));
17+
}
18+
19+
static void finer(Logger log, String event, Object... kv) {
20+
if (log.isLoggable(Level.FINER)) log.finer(() -> ev(event, kv));
21+
}
22+
23+
static void finest(Logger log, String event, Object... kv) {
24+
if (log.isLoggable(Level.FINEST)) log.finest(() -> ev(event, kv));
25+
}
26+
27+
/// Log at FINEST but only every Nth occurrence per event key.
28+
static void finestSampled(Logger log, String event, int everyN, Object... kv) {
29+
if (!log.isLoggable(Level.FINEST)) return;
30+
if (everyN <= 1) {
31+
log.finest(() -> ev(event, kv));
32+
return;
33+
}
34+
long n = COUNTERS.computeIfAbsent(event, k -> new AtomicLong()).incrementAndGet();
35+
if (n % everyN == 0L) {
36+
log.finest(() -> ev(event, kv("sample", n, kv)));
37+
}
38+
}
39+
40+
private static Object[] kv(String k, Object v, Object... rest) {
41+
Object[] out = new Object[2 + rest.length];
42+
out[0] = k; out[1] = v;
43+
System.arraycopy(rest, 0, out, 2, rest.length);
44+
return out;
45+
}
46+
47+
static String ev(String event, Object... kv) {
48+
StringBuilder sb = new StringBuilder(64);
49+
sb.append("event=").append(sanitize(event));
50+
for (int i = 0; i + 1 < kv.length; i += 2) {
51+
Object key = kv[i];
52+
Object val = kv[i + 1];
53+
if (key == null) continue;
54+
String k = key.toString();
55+
String v = val == null ? "null" : sanitize(val.toString());
56+
sb.append(' ').append(k).append('=');
57+
// quote if contains whitespace
58+
if (needsQuotes(v)) sb.append('"').append(v).append('"'); else sb.append(v);
59+
}
60+
return sb.toString();
61+
}
62+
63+
private static boolean needsQuotes(String s) {
64+
for (int i = 0; i < s.length(); i++) {
65+
char c = s.charAt(i);
66+
if (Character.isWhitespace(c)) return true;
67+
if (c == '"') return true;
68+
}
69+
return false;
70+
}
71+
72+
private static String sanitize(String s) {
73+
if (s == null) return "null";
74+
// Trim overly long payloads to keep logs readable
75+
final int MAX = 256;
76+
String trimmed = s.length() > MAX ? s.substring(0, MAX) + "…" : s;
77+
// Collapse newlines and tabs
78+
return trimmed.replace('\n', ' ').replace('\r', ' ').replace('\t', ' ');
79+
}
80+
}
81+

0 commit comments

Comments
 (0)