Skip to content

Commit 7031310

Browse files
aksOpsclaude
andcommitted
fix: suppress all logging noise in CLI mode for clean output
- Add logback-spring.xml to suppress Spring Boot, Neo4j, MCP, Netty loggers - Disable Spring Boot banner programmatically - Exclude neo4j-slf4j-provider to eliminate duplicate SLF4J provider warning - Fix XML parser to allow DOCTYPE safely (prevents [Fatal Error] on stderr) - Add silent ErrorHandler to XML parser to suppress parse warnings - Exclude MCP auto-configuration in indexing profile - Demote FileDiscovery and Analyzer completion logs from INFO to DEBUG - Improve AnalyzeCommand output: comma-formatted numbers, core count, compact summary Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a8bc0f8 commit 7031310

8 files changed

Lines changed: 116 additions & 22 deletions

File tree

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@
7272
<groupId>org.neo4j</groupId>
7373
<artifactId>neo4j</artifactId>
7474
<version>${neo4j.version}</version>
75+
<exclusions>
76+
<!-- Exclude neo4j's SLF4J provider to avoid "multiple SLF4J providers" warning -->
77+
<exclusion>
78+
<groupId>org.neo4j</groupId>
79+
<artifactId>neo4j-slf4j-provider</artifactId>
80+
</exclusion>
81+
</exclusions>
7582
</dependency>
7683

7784
<!-- Spring AI MCP Server (WebMVC, stateless streamable HTTP) -->

src/main/java/io/github/randomcodespace/iq/CodeIqApplication.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public int getExitCode() {
4646

4747
public static void main(String[] args) {
4848
var app = new SpringApplication(CodeIqApplication.class);
49+
app.setBannerMode(org.springframework.boot.Banner.Mode.OFF);
4950

5051
// Detect if "serve" is among the arguments
5152
boolean isServe = Arrays.stream(args)

src/main/java/io/github/randomcodespace/iq/analyzer/Analyzer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ public AnalysisResult run(Path repoPath, Consumer<String> onProgress) {
174174
int edgeCount = builder.getEdgeCount();
175175

176176
report.accept("Analysis complete - " + nodeCount + " nodes, " + edgeCount + " edges");
177-
log.info("Analysis complete: {} nodes, {} edges in {}ms",
177+
log.debug("Analysis complete: {} nodes, {} edges in {}ms",
178178
nodeCount, edgeCount, elapsed.toMillis());
179179

180180
return new AnalysisResult(

src/main/java/io/github/randomcodespace/iq/analyzer/FileDiscovery.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public List<DiscoveredFile> discover(Path repoPath) {
6161

6262
// Sort for deterministic ordering
6363
result.sort(Comparator.comparing(f -> f.path().toString()));
64-
log.info("Discovered {} files in {}", result.size(), root);
64+
log.debug("Discovered {} files in {}", result.size(), root);
6565
return result;
6666
}
6767

src/main/java/io/github/randomcodespace/iq/analyzer/StructuredParser.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.springframework.stereotype.Service;
77
import org.yaml.snakeyaml.Yaml;
88

9+
import javax.xml.XMLConstants;
910
import javax.xml.parsers.DocumentBuilderFactory;
1011
import java.io.ByteArrayInputStream;
1112
import java.io.StringReader;
@@ -89,9 +90,19 @@ private Object parseJson(String content) throws Exception {
8990

9091
private Object parseXml(String content, String filePath) throws Exception {
9192
var factory = DocumentBuilderFactory.newInstance();
92-
// Disable external entities for security
93-
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
93+
// Allow DOCTYPE but prevent XXE attacks (avoids [Fatal Error] on stderr)
94+
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false);
95+
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
96+
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
97+
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
98+
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
9499
var builder = factory.newDocumentBuilder();
100+
// Suppress parse warnings/errors from printing to stderr
101+
builder.setErrorHandler(new org.xml.sax.ErrorHandler() {
102+
@Override public void warning(org.xml.sax.SAXParseException e) {}
103+
@Override public void error(org.xml.sax.SAXParseException e) {}
104+
@Override public void fatalError(org.xml.sax.SAXParseException e) throws org.xml.sax.SAXException { throw e; }
105+
});
95106
var doc = builder.parse(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
96107
var root = doc.getDocumentElement();
97108
// Return a simple map with root element info for structured detectors

src/main/java/io/github/randomcodespace/iq/cli/AnalyzeCommand.java

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import picocli.CommandLine.Parameters;
1010

1111
import java.nio.file.Path;
12+
import java.text.NumberFormat;
13+
import java.util.Locale;
1214
import java.util.Map;
1315
import java.util.concurrent.Callable;
1416

@@ -37,6 +39,9 @@ public AnalyzeCommand(Analyzer analyzer, CodeIqConfig config) {
3739
@Override
3840
public Integer call() {
3941
Path root = path.toAbsolutePath().normalize();
42+
NumberFormat nf = NumberFormat.getIntegerInstance(Locale.US);
43+
int cores = Runtime.getRuntime().availableProcessors();
44+
4045
CliOutput.step("\uD83D\uDD0D", "Scanning " + root + " ...");
4146

4247
AnalysisResult result = analyzer.run(root, msg -> {
@@ -45,11 +50,11 @@ public Integer call() {
4550
} else if (msg.startsWith("Found")) {
4651
CliOutput.step("\uD83D\uDCC1", "@|cyan " + msg + "|@");
4752
} else if (msg.startsWith("Analyzing")) {
48-
CliOutput.step("\u2699\uFE0F", msg);
49-
} else if (msg.startsWith("Linking")) {
50-
CliOutput.step("\uD83D\uDD17", msg);
53+
CliOutput.step("\u2699\uFE0F", msg.replace("files...", "files using " + cores + " cores..."));
5154
} else if (msg.startsWith("Building")) {
5255
CliOutput.step("\uD83C\uDFD7\uFE0F", msg);
56+
} else if (msg.startsWith("Linking")) {
57+
CliOutput.step("\uD83D\uDD17", msg);
5358
} else if (msg.startsWith("Classifying")) {
5459
CliOutput.step("\uD83C\uDFF7\uFE0F", msg);
5560
} else if (msg.startsWith("Analysis complete")) {
@@ -59,31 +64,41 @@ public Integer call() {
5964
}
6065
});
6166

67+
long secs = result.elapsed().toSeconds();
68+
String timeStr = secs > 0 ? secs + "s" : result.elapsed().toMillis() + "ms";
69+
6270
System.out.println();
63-
CliOutput.success("\u2705 Analysis complete");
71+
CliOutput.success("\u2705 Analysis complete \u2014 "
72+
+ nf.format(result.nodeCount()) + " nodes, "
73+
+ nf.format(result.edgeCount()) + " edges in " + timeStr);
6474
System.out.println();
65-
CliOutput.info(" Files discovered: " + result.totalFiles());
66-
CliOutput.info(" Files analyzed: " + result.filesAnalyzed());
67-
CliOutput.cyan(" Nodes: " + result.nodeCount());
68-
CliOutput.cyan(" Edges: " + result.edgeCount());
69-
CliOutput.info(" Duration: " + result.elapsed().toMillis() + " ms");
75+
CliOutput.info(" Files: " + nf.format(result.totalFiles()) + " discovered, "
76+
+ nf.format(result.filesAnalyzed()) + " analyzed");
77+
CliOutput.cyan(" Nodes: " + nf.format(result.nodeCount()));
78+
CliOutput.cyan(" Edges: " + nf.format(result.edgeCount()));
79+
CliOutput.info(" Time: " + timeStr);
7080

71-
if (!result.languageBreakdown().isEmpty()) {
81+
if (!result.nodeBreakdown().isEmpty()) {
7282
System.out.println();
73-
CliOutput.bold(" Languages:");
74-
result.languageBreakdown().entrySet().stream()
83+
StringBuilder topNodes = new StringBuilder(" Top node kinds: ");
84+
result.nodeBreakdown().entrySet().stream()
7585
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
7686
.limit(10)
77-
.forEach(e -> CliOutput.info(" " + e.getKey() + ": " + e.getValue()));
87+
.forEach(e -> topNodes.append(e.getKey()).append(" (")
88+
.append(nf.format(e.getValue())).append("), "));
89+
if (topNodes.length() > 2) topNodes.setLength(topNodes.length() - 2);
90+
CliOutput.info(topNodes.toString());
7891
}
7992

80-
if (!result.nodeBreakdown().isEmpty()) {
81-
System.out.println();
82-
CliOutput.bold(" Node kinds:");
83-
result.nodeBreakdown().entrySet().stream()
93+
if (!result.languageBreakdown().isEmpty()) {
94+
StringBuilder langs = new StringBuilder(" Languages: ");
95+
result.languageBreakdown().entrySet().stream()
8496
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
8597
.limit(10)
86-
.forEach(e -> CliOutput.info(" " + e.getKey() + ": " + e.getValue()));
98+
.forEach(e -> langs.append(e.getKey()).append(" (")
99+
.append(nf.format(e.getValue())).append("), "));
100+
if (langs.length() > 2) langs.setLength(langs.length() - 2);
101+
CliOutput.info(langs.toString());
87102
}
88103

89104
return 0;

src/main/resources/application.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
spring:
22
application:
33
name: code-iq
4+
main:
5+
banner-mode: off
46
threads:
57
virtual:
68
enabled: true
@@ -34,6 +36,9 @@ spring:
3436
config:
3537
activate:
3638
on-profile: indexing
39+
autoconfigure:
40+
exclude:
41+
- org.springframework.ai.mcp.server.autoconfigure.McpServerAutoConfiguration
3742

3843
---
3944
spring:
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration>
3+
4+
<!-- Console appender with minimal format (our app uses System.out directly for CLI output) -->
5+
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
6+
<encoder>
7+
<pattern>%msg%n</pattern>
8+
</encoder>
9+
</appender>
10+
11+
<!-- Suppress noisy third-party loggers -->
12+
<logger name="org.neo4j" level="ERROR" />
13+
<logger name="org.springframework.data.neo4j" level="ERROR" />
14+
<logger name="org.springframework.ai.mcp" level="OFF" />
15+
<logger name="org.springframework.boot" level="ERROR" />
16+
<logger name="org.springframework.core.env" level="ERROR" />
17+
<logger name="org.springframework.web" level="WARN" />
18+
<logger name="io.netty" level="ERROR" />
19+
<logger name="org.jctools" level="ERROR" />
20+
<logger name="io.micrometer" level="WARN" />
21+
<logger name="com.hazelcast" level="WARN" />
22+
<logger name="org.apache.catalina" level="WARN" />
23+
<logger name="org.apache.coyote" level="WARN" />
24+
25+
<!-- Our app loggers — suppress Spring's startup messages from our main class -->
26+
<logger name="io.github.randomcodespace.iq.CodeIqApplication" level="WARN" />
27+
<logger name="io.github.randomcodespace.iq" level="INFO" />
28+
29+
<!-- Indexing profile: minimal logging (CLI mode) -->
30+
<springProfile name="indexing">
31+
<root level="ERROR">
32+
<appender-ref ref="CONSOLE" />
33+
</root>
34+
<logger name="io.github.randomcodespace.iq" level="INFO" />
35+
<logger name="io.github.randomcodespace.iq.CodeIqApplication" level="WARN" />
36+
</springProfile>
37+
38+
<!-- Serving profile: normal logging (server mode) -->
39+
<springProfile name="serving">
40+
<root level="WARN">
41+
<appender-ref ref="CONSOLE" />
42+
</root>
43+
<logger name="io.github.randomcodespace.iq" level="INFO" />
44+
</springProfile>
45+
46+
<!-- Default (no profile active) -->
47+
<springProfile name="default">
48+
<root level="WARN">
49+
<appender-ref ref="CONSOLE" />
50+
</root>
51+
<logger name="io.github.randomcodespace.iq" level="INFO" />
52+
<logger name="io.github.randomcodespace.iq.CodeIqApplication" level="WARN" />
53+
</springProfile>
54+
55+
</configuration>

0 commit comments

Comments
 (0)