Skip to content

Commit 2444a9f

Browse files
committed
Issue #34 Implement Refined JUL Logging Strategy with ERROR/PERFORMANCE Prefixes
- Convert ALL logging to lambda expressions for zero runtime overhead - Add 'ERROR:' prefix to SEVERE level messages for cloud monitoring compatibility - Add 'PERFORMANCE WARNING:' prefix for performance threshold issues - Use consistent hierarchical logger naming (fully-qualified class names) - Follow Oracle's JUL level hierarchy with proper target-audience mapping Changes: - JsonSchema.java: Added performance monitoring for validation stack depth - ApiTracker.java: Updated all logging calls to use lambda expressions with ERROR prefixes - JsonTestSuiteSummary.java: Converted warning calls to lambda expressions - JsonTestSuiteTest.java: Updated logging to use lambda expressions - RobustCharDecoder.java: Converted all fine-level logging to lambda expressions All tests pass: ./mvnw test
1 parent 690284e commit 2444a9f

File tree

5 files changed

+61
-39
lines changed

5 files changed

+61
-39
lines changed

json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonTestSuiteSummary.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ private TestResults runTests() throws Exception {
106106
content = Files.readString(file, StandardCharsets.UTF_8);
107107
charContent = content.toCharArray();
108108
} catch (MalformedInputException e) {
109-
LOGGER.warning("UTF-8 failed for " + filename + ", using robust encoding detection");
109+
LOGGER.warning(() -> "UTF-8 failed for " + filename + ", using robust encoding detection");
110110
try {
111111
byte[] rawBytes = Files.readAllBytes(file);
112112
charContent = RobustCharDecoder.decodeToChars(rawBytes, filename);
@@ -123,7 +123,7 @@ private TestResults runTests() throws Exception {
123123
} catch (JsonParseException e) {
124124
parseSucceeded = false;
125125
} catch (StackOverflowError e) {
126-
LOGGER.warning("StackOverflowError on file: " + filename);
126+
LOGGER.warning(() -> "StackOverflowError on file: " + filename);
127127
parseSucceeded = false; // Treat as parse failure
128128
}
129129

json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/RobustCharDecoder.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ class RobustCharDecoder {
2929
/// @param filename filename for logging purposes
3030
/// @return char array representing the content
3131
static char[] decodeToChars(byte[] rawBytes, String filename) {
32-
LOGGER.fine("Attempting robust decoding for " + filename + " (" + rawBytes.length + " bytes)");
32+
LOGGER.fine(() -> "Attempting robust decoding for " + filename + " (" + rawBytes.length + " bytes)");
3333

3434
// Stage 1: BOM Detection
3535
BomResult bom = detectBOM(rawBytes);
3636
if (bom.encoding != null) {
37-
LOGGER.fine("BOM detected for " + filename + ": " + bom.encoding.name());
37+
LOGGER.fine(() -> "BOM detected for " + filename + ": " + bom.encoding.name());
3838
char[] result = tryDecodeWithCharset(rawBytes, bom.offset, bom.encoding, filename);
3939
if (result != null) {
4040
return result;
@@ -53,13 +53,13 @@ static char[] decodeToChars(byte[] rawBytes, String filename) {
5353
for (Charset encoding : encodings) {
5454
char[] result = tryDecodeWithCharset(rawBytes, 0, encoding, filename);
5555
if (result != null) {
56-
LOGGER.fine("Successfully decoded " + filename + " using " + encoding.name());
56+
LOGGER.fine(() -> "Successfully decoded " + filename + " using " + encoding.name());
5757
return result;
5858
}
5959
}
6060

6161
// Stage 3: Byte-level conversion with UTF-8 sequence awareness
62-
LOGGER.fine("Using permissive byte-to-char conversion for " + filename);
62+
LOGGER.fine(() -> "Using permissive byte-to-char conversion for " + filename);
6363
return convertBytesToCharsPermissively(rawBytes);
6464
}
6565

@@ -96,10 +96,10 @@ private static char[] tryDecodeWithCharset(byte[] bytes, int offset, Charset cha
9696
return result;
9797

9898
} catch (CharacterCodingException e) {
99-
LOGGER.fine("Failed to decode " + filename + " with " + charset.name() + ": " + e.getMessage());
99+
LOGGER.fine(() -> "Failed to decode " + filename + " with " + charset.name() + ": " + e.getMessage());
100100
return null;
101101
} catch (Exception e) {
102-
LOGGER.fine("Unexpected error decoding " + filename + " with " + charset.name() + ": " + e.getMessage());
102+
LOGGER.fine(() -> "Unexpected error decoding " + filename + " with " + charset.name() + ": " + e.getMessage());
103103
return null;
104104
}
105105
}

json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/JsonTestSuiteTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private DynamicTest createTest(Path file) {
5151
content = Files.readString(file, StandardCharsets.UTF_8);
5252
charContent = content.toCharArray();
5353
} catch (MalformedInputException e) {
54-
LOGGER.warning("UTF-8 failed for " + filename + ", using robust encoding detection");
54+
LOGGER.warning(() -> "UTF-8 failed for " + filename + ", using robust encoding detection");
5555
try {
5656
byte[] rawBytes = Files.readAllBytes(file);
5757
charContent = RobustCharDecoder.decodeToChars(rawBytes, filename);
@@ -129,7 +129,7 @@ private void testImplementationDefinedSingle(String description, Runnable parseA
129129
// OK - we rejected it
130130
} catch (StackOverflowError e) {
131131
// OK - acceptable for deeply nested structures
132-
LOGGER.warning("StackOverflowError on implementation-defined: " + description);
132+
LOGGER.warning(() -> "StackOverflowError on implementation-defined: " + description);
133133
} catch (Exception e) {
134134
// NOT OK - unexpected exception type
135135
fail("Unexpected exception for %s: %s", description, e);

json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ static String fetchFromUrl(String url) {
9494
/// Discovers all classes in the local JSON API packages
9595
/// @return sorted set of classes from jdk.sandbox.java.util.json and jdk.sandbox.internal.util.json
9696
static Set<Class<?>> discoverLocalJsonClasses() {
97-
LOGGER.info("Starting class discovery for JSON API packages");
97+
LOGGER.info(() -> "Starting class discovery for JSON API packages");
9898
final var classes = new TreeSet<Class<?>>(Comparator.comparing(Class::getName));
9999

100100
// Packages to scan - only public API, not internal implementation
@@ -122,11 +122,11 @@ static Set<Class<?>> discoverLocalJsonClasses() {
122122
}
123123
}
124124
} catch (Exception e) {
125-
LOGGER.log(Level.WARNING, "Error scanning package: " + packageName, e);
125+
LOGGER.warning(() -> "ERROR: Error scanning package: " + packageName + " - " + e.getMessage());
126126
}
127127
}
128128

129-
LOGGER.info("Discovered " + classes.size() + " classes in JSON API packages: " +
129+
LOGGER.info(() -> "Discovered " + classes.size() + " classes in JSON API packages: " +
130130
classes.stream().map(Class::getName).sorted().collect(Collectors.joining(", ")));
131131
return Collections.unmodifiableSet(classes);
132132
}
@@ -151,7 +151,7 @@ static void scanDirectory(java.io.File directory, String packageName, Set<Class<
151151
try {
152152
final var clazz = Class.forName(className);
153153
classes.add(clazz);
154-
LOGGER.info("Found class: " + className);
154+
LOGGER.info(() -> "Found class: " + className);
155155
} catch (ClassNotFoundException | NoClassDefFoundError e) {
156156
LOGGER.fine(() -> "Could not load class: " + className);
157157
}
@@ -189,15 +189,15 @@ static void scanJar(java.net.URL jarUrl, String packageName, Set<Class<?>> class
189189
try {
190190
final var clazz = Class.forName(className);
191191
classes.add(clazz);
192-
LOGGER.info("Found class in JAR: " + className);
192+
LOGGER.info(() -> "Found class in JAR: " + className);
193193
} catch (ClassNotFoundException | NoClassDefFoundError e) {
194194
LOGGER.fine(() -> "Could not load class from JAR: " + className);
195195
}
196196
}
197197
}
198198
}
199199
} catch (Exception e) {
200-
LOGGER.log(Level.WARNING, "Error scanning JAR: " + jarUrl, e);
200+
LOGGER.warning(() -> "ERROR: Error scanning JAR: " + jarUrl + " - " + e.getMessage());
201201
}
202202
}
203203

@@ -206,7 +206,7 @@ static void scanJar(java.net.URL jarUrl, String packageName, Set<Class<?>> class
206206
/// @return map of className to source code (or error message if fetch failed)
207207
static Map<String, String> fetchUpstreamSources(Set<Class<?>> localClasses) {
208208
Objects.requireNonNull(localClasses, "localClasses must not be null");
209-
LOGGER.info("Fetching upstream sources for " + localClasses.size() + " classes");
209+
LOGGER.info(() -> "Fetching upstream sources for " + localClasses.size() + " classes");
210210

211211
final var results = new LinkedHashMap<String, String>();
212212
final var httpClient = HttpClient.newBuilder()
@@ -227,7 +227,7 @@ static Map<String, String> fetchUpstreamSources(Set<Class<?>> localClasses) {
227227
final var upstreamPath = mapToUpstreamPath(className);
228228
final var url = GITHUB_BASE_URL + upstreamPath;
229229

230-
LOGGER.info("Fetching upstream source: " + url);
230+
LOGGER.info(() -> "Fetching upstream source: " + url);
231231

232232
try {
233233
final var request = HttpRequest.newBuilder()
@@ -242,20 +242,20 @@ static Map<String, String> fetchUpstreamSources(Set<Class<?>> localClasses) {
242242
final var body = response.body();
243243
FETCH_CACHE.put(className, body);
244244
results.put(className, body);
245-
LOGGER.info("Successfully fetched " + body.length() + " chars for: " + className);
245+
LOGGER.info(() -> "Successfully fetched " + body.length() + " chars for: " + className);
246246
} else if (response.statusCode() == 404) {
247247
final var error = "NOT_FOUND: Upstream file not found (possibly deleted or renamed)";
248248
results.put(className, error);
249-
LOGGER.info("404 Not Found for upstream: " + className + " at " + url);
249+
LOGGER.info(() -> "404 Not Found for upstream: " + className + " at " + url);
250250
} else {
251251
final var error = "HTTP_ERROR: Status " + response.statusCode();
252252
results.put(className, error);
253-
LOGGER.info("HTTP error " + response.statusCode() + " for " + className + " at " + url);
253+
LOGGER.info(() -> "HTTP error " + response.statusCode() + " for " + className + " at " + url);
254254
}
255255
} catch (Exception e) {
256256
final var error = "FETCH_ERROR: " + e.getMessage();
257257
results.put(className, error);
258-
LOGGER.info("Fetch error for " + className + " at " + url + ": " + e.getMessage());
258+
LOGGER.info(() -> "Fetch error for " + className + " at " + url + ": " + e.getMessage());
259259
}
260260
}
261261

@@ -306,7 +306,7 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
306306
return JsonObject.of(errorMap);
307307
}
308308

309-
LOGGER.info("Extracting upstream API for: " + className);
309+
LOGGER.info(() -> "Extracting upstream API for: " + className);
310310

311311
final var compiler = ToolProvider.getSystemJavaCompiler();
312312
if (compiler == null) {
@@ -367,7 +367,7 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
367367
));
368368

369369
} catch (Exception e) {
370-
LOGGER.log(Level.WARNING, "Error parsing upstream source for " + className, e);
370+
LOGGER.warning(() -> "ERROR: Error parsing upstream source for " + className + " - " + e.getMessage());
371371
return JsonObject.of(Map.of(
372372
"error", JsonString.of("Parse error: " + e.getMessage()),
373373
"className", JsonString.of(className)
@@ -376,7 +376,7 @@ static JsonObject extractApiFromSource(String sourceCode, String className) {
376376
try {
377377
fileManager.close();
378378
} catch (IOException e) {
379-
LOGGER.log(Level.FINE, "Error closing file manager", e);
379+
LOGGER.fine(() -> "Error closing file manager: " + e.getMessage());
380380
}
381381
}
382382
}
@@ -877,7 +877,7 @@ static String normalizeTypeName(String typeName) {
877877
/// Runs source-to-source comparison for fair parameter name comparison
878878
/// @return complete comparison report as JSON
879879
static JsonObject runFullComparison() {
880-
LOGGER.info("Starting full API comparison");
880+
LOGGER.info(() -> "Starting full API comparison");
881881
final var startTime = Instant.now();
882882

883883
final var reportMap = new LinkedHashMap<String, JsonValue>();
@@ -887,7 +887,7 @@ static JsonObject runFullComparison() {
887887

888888
// Discover local classes
889889
final var localClasses = discoverLocalJsonClasses();
890-
LOGGER.info("Found " + localClasses.size() + " local classes");
890+
LOGGER.info(() -> "Found " + localClasses.size() + " local classes");
891891

892892
// Extract and compare APIs
893893
final var differences = new ArrayList<JsonValue>();
@@ -927,7 +927,7 @@ static JsonObject runFullComparison() {
927927
final var duration = Duration.between(startTime, Instant.now());
928928
reportMap.put("durationMs", JsonNumber.of(duration.toMillis()));
929929

930-
LOGGER.info("Comparison completed in " + duration.toMillis() + "ms");
930+
LOGGER.info(() -> "Comparison completed in " + duration.toMillis() + "ms");
931931

932932
return JsonObject.of(reportMap);
933933
}

json-java21-schema/src/main/java/io/github/simbo1905/json/schema/JsonSchema.java

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public sealed interface JsonSchema
5252
JsonSchema.NotSchema,
5353
JsonSchema.RootRef {
5454

55-
Logger LOG = Logger.getLogger(JsonSchema.class.getName());
55+
Logger LOGGER = Logger.getLogger(JsonSchema.class.getName());
5656

5757
/// Prevents external implementations, ensuring all schema types are inner records
5858
enum Nothing implements JsonSchema {
@@ -69,10 +69,15 @@ public ValidationResult validateAt(String path, JsonValue json, Deque<Validation
6969
/// @param schemaJson JSON Schema document as JsonValue
7070
/// @return Immutable JsonSchema instance
7171
/// @throws IllegalArgumentException if schema is invalid
72-
static JsonSchema compile(JsonValue schemaJson) {
73-
Objects.requireNonNull(schemaJson, "schemaJson");
74-
return SchemaCompiler.compile(schemaJson);
75-
}
72+
static JsonSchema compile(JsonValue schemaJson) {
73+
Objects.requireNonNull(schemaJson, "schemaJson");
74+
try {
75+
return SchemaCompiler.compile(schemaJson);
76+
} catch (IllegalArgumentException e) {
77+
LOGGER.severe(() -> "ERROR: Failed to compile JSON schema: " + e.getMessage());
78+
throw e;
79+
}
80+
}
7681

7782
/// Validates JSON document against this schema
7883
///
@@ -84,15 +89,30 @@ default ValidationResult validate(JsonValue json) {
8489
Deque<ValidationFrame> stack = new ArrayDeque<>();
8590
stack.push(new ValidationFrame("", this, json));
8691

92+
final int stackDepthWarningThreshold = 1000;
93+
final java.util.concurrent.atomic.AtomicInteger maxStackDepth = new java.util.concurrent.atomic.AtomicInteger(0);
94+
8795
while (!stack.isEmpty()) {
8896
ValidationFrame frame = stack.pop();
89-
LOG.finest(() -> "POP " + frame.path() +
97+
int currentStackSize = stack.size();
98+
maxStackDepth.updateAndGet(current -> Math.max(current, currentStackSize));
99+
100+
if (currentStackSize > stackDepthWarningThreshold) {
101+
LOGGER.fine(() -> "PERFORMANCE WARNING: Validation stack depth exceeds recommended threshold: " + currentStackSize);
102+
}
103+
104+
LOGGER.finest(() -> "POP " + frame.path() +
90105
" schema=" + frame.schema().getClass().getSimpleName());
91106
ValidationResult result = frame.schema.validateAt(frame.path, frame.json, stack);
92107
if (!result.valid()) {
93108
errors.addAll(result.errors());
94109
}
95110
}
111+
112+
final int finalMaxStackDepth = maxStackDepth.get();
113+
if (finalMaxStackDepth > 100) {
114+
LOGGER.fine(() -> "PERFORMANCE WARNING: Maximum validation stack depth reached: " + finalMaxStackDepth);
115+
}
96116

97117
return errors.isEmpty() ? ValidationResult.success() : ValidationResult.failure(errors);
98118
}
@@ -368,7 +388,7 @@ public ValidationResult validateAt(String path, JsonValue json, Deque<Validation
368388
Deque<ValidationFrame> branchStack = new ArrayDeque<>();
369389
List<ValidationError> branchErrors = new ArrayList<>();
370390

371-
LOG.finest(() -> "BRANCH START: " + schema.getClass().getSimpleName());
391+
LOGGER.finest(() -> "BRANCH START: " + schema.getClass().getSimpleName());
372392
branchStack.push(new ValidationFrame(path, schema, json));
373393

374394
while (!branchStack.isEmpty()) {
@@ -384,7 +404,7 @@ public ValidationResult validateAt(String path, JsonValue json, Deque<Validation
384404
break;
385405
}
386406
collected.addAll(branchErrors);
387-
LOG.finest(() -> "BRANCH END: " + branchErrors.size() + " errors");
407+
LOGGER.finest(() -> "BRANCH END: " + branchErrors.size() + " errors");
388408
}
389409

390410
return anyValid ? ValidationResult.success() : ValidationResult.failure(collected);
@@ -401,7 +421,7 @@ public ValidationResult validateAt(String path, JsonValue json, Deque<Validation
401421
// Step 2 - choose branch
402422
JsonSchema branch = ifResult.valid() ? thenSchema : elseSchema;
403423

404-
LOG.finer(() -> String.format(
424+
LOGGER.finer(() -> String.format(
405425
"Conditional path=%s ifValid=%b branch=%s",
406426
path, ifResult.valid(),
407427
branch == null ? "none" : (ifResult.valid() ? "then" : "else")));
@@ -439,8 +459,8 @@ final class SchemaCompiler {
439459
private static JsonSchema currentRootSchema;
440460

441461
private static void trace(String stage, JsonValue fragment) {
442-
if (LOG.isLoggable(Level.FINER)) {
443-
LOG.finer(() ->
462+
if (LOGGER.isLoggable(Level.FINER)) {
463+
LOGGER.finer(() ->
444464
String.format("[%s] %s", stage, fragment.toString()));
445465
}
446466
}
@@ -460,6 +480,7 @@ private static JsonSchema compileInternal(JsonValue schemaJson) {
460480
}
461481

462482
if (!(schemaJson instanceof JsonObject obj)) {
483+
LOGGER.severe(() -> "ERROR: Schema must be an object or boolean, got: " + schemaJson.getClass().getSimpleName());
463484
throw new IllegalArgumentException("Schema must be an object or boolean");
464485
}
465486

@@ -483,6 +504,7 @@ private static JsonSchema compileInternal(JsonValue schemaJson) {
483504
}
484505
JsonSchema resolved = definitions.get(ref);
485506
if (resolved == null) {
507+
LOGGER.severe(() -> "ERROR: Unresolved $ref: " + ref);
486508
throw new IllegalArgumentException("Unresolved $ref: " + ref);
487509
}
488510
return resolved;

0 commit comments

Comments
 (0)