Skip to content

Commit b584dcb

Browse files
committed
chore: test logging/output and adapt where not as expected
1 parent 36f1820 commit b584dcb

File tree

11 files changed

+385
-48
lines changed

11 files changed

+385
-48
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies {
1212
implementation 'org.apache.commons:commons-text'
1313
testImplementation project(':testlib')
1414
implementation libs.bundles.spotless.libs
15+
implementation libs.diff.utils
1516

1617
// these are fixed versions of the otherwise dynamic dependencies for spotless
1718
// this is necessary to allow for native compilation where reflective access to dynamic jars is not possible

app/src/main/java/com/diffplug/spotless/cli/SpotlessCLI.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,10 @@ public void setParallelity(int parallelity) {
152152
@CommandLine.ArgGroup(exclusive = true, multiplicity = "0..1")
153153
LoggingLevelOptions loggingLevelOptions;
154154

155-
class LoggingLevelOptions {
155+
public static class LoggingLevelOptions {
156+
157+
@CommandLine.Spec
158+
CommandLine.Model.CommandSpec spec; // injected by picocli
156159

157160
private boolean[] verbosity;
158161

@@ -172,7 +175,7 @@ public void setVerbose(boolean[] verbosity) {
172175
names = {"--quiet", "-q"},
173176
description = "Disable as much output as possible.",
174177
arity = "0")
175-
boolean quiet;
178+
public boolean quiet;
176179

177180
LoggingConfigurer.CLIOutputLevel toCliOutputLevel() {
178181
if (quiet) {
@@ -193,11 +196,24 @@ LoggingConfigurer.CLIOutputLevel toCliOutputLevel() {
193196

194197
@Override
195198
public void setupLogging() {
196-
LoggingConfigurer.configureLogging(
197-
loggingLevelOptions != null
198-
? loggingLevelOptions.toCliOutputLevel()
199-
: LoggingConfigurer.CLIOutputLevel.DEFAULT,
200-
logFile);
199+
LoggingConfigurer.CLIOutputLevel outputLevel = loggingLevelOptions != null
200+
? loggingLevelOptions.toCliOutputLevel()
201+
: LoggingConfigurer.CLIOutputLevel.DEFAULT;
202+
LoggingConfigurer.configureLogging(outputLevel, logFile);
203+
// the following logs are to make sure that the logging is configured correctly
204+
logMetaStatements();
205+
}
206+
207+
private static void logMetaStatements() {
208+
Logger spotlessCliLogger = LoggerFactory.getLogger("com.diffplug.spotless.cli.meta");
209+
spotlessCliLogger.info("Meta: spotless cli loggers on level info enabled.");
210+
spotlessCliLogger.debug("Meta: spotless cli loggers on level debug enabled.");
211+
Logger spotlessLibLogger = LoggerFactory.getLogger("com.diffplug.spotless.meta");
212+
spotlessLibLogger.info("Meta: spotless loggers on level info enabled.");
213+
spotlessLibLogger.debug("Meta: spotless loggers on level debug enabled.");
214+
Logger nonSpotlessLogger = LoggerFactory.getLogger("meta");
215+
nonSpotlessLogger.info("Meta: non-spotless loggers on level info enabled.");
216+
nonSpotlessLogger.debug("Meta: non-spotless loggers on level debug enabled.");
201217
}
202218

203219
@Override

app/src/main/java/com/diffplug/spotless/cli/SpotlessMode.java

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@
1515
*/
1616
package com.diffplug.spotless.cli;
1717

18+
import java.io.ByteArrayOutputStream;
19+
import java.io.IOException;
20+
import java.nio.charset.StandardCharsets;
21+
import java.nio.file.Files;
22+
1823
import org.slf4j.Logger;
1924
import org.slf4j.LoggerFactory;
2025

2126
import com.diffplug.spotless.ThrowingEx;
27+
import com.diffplug.spotless.cli.core.Diff;
2228
import com.diffplug.spotless.cli.logging.output.Output;
2329

2430
enum SpotlessMode {
@@ -28,7 +34,6 @@ enum SpotlessMode {
2834
@Override
2935
ResultType handleResult(Result result) {
3036
if (result.lintState().isHasLints()) {
31-
3237
Output.eitherDefault(() -> new Output.MessageWithArgs(
3338
"File has lints: {} -- {}",
3439
result.target().toFile().getPath(),
@@ -40,10 +45,38 @@ ResultType handleResult(Result result) {
4045
result.lintState()
4146
.asStringDetailed(result.target().toFile(), result.formatter())))
4247
.write();
43-
} else {
44-
LOGGER.debug(
45-
"Check-Result - File is clean: {}",
46-
result.target().toFile().getPath());
48+
return ResultType.DIRTY;
49+
}
50+
51+
try (ByteArrayOutputStream cleanedOutputStream = new ByteArrayOutputStream()) {
52+
result.lintState().getDirtyState().writeCanonicalTo(cleanedOutputStream);
53+
String cleaned = cleanedOutputStream.toString(StandardCharsets.UTF_8);
54+
String original = Files.readString(result.target(), StandardCharsets.UTF_8);
55+
56+
final int diffs = Diff.countLineDifferences(original, cleaned);
57+
Output.eitherDefault(() -> {
58+
if (diffs > 0) {
59+
return new Output.MessageWithArgs(
60+
"File needs reformatting: {} -- {} differences", result.target(), diffs);
61+
}
62+
return new Output.MessageWithArgs("File is clean: {}", result.target());
63+
})
64+
.orDetail(() -> {
65+
String diffString = Diff.createDiffString(original, cleaned, result.target());
66+
String delim = "*".repeat(80);
67+
if (diffs > 0) {
68+
return new Output.MessageWithArgs(
69+
"File needs reformatting: {}\n{}\n{}\n{}",
70+
result.target(),
71+
delim,
72+
diffString,
73+
delim);
74+
}
75+
return new Output.MessageWithArgs("File is clean: {}", result.target());
76+
})
77+
.write();
78+
} catch (IOException e) {
79+
throw ThrowingEx.asRuntime(e);
4780
}
4881
return ResultType.DIRTY;
4982
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2025 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.cli.core;
17+
18+
import java.nio.file.Path;
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
23+
import difflib.DiffUtils;
24+
import difflib.Patch;
25+
26+
public final class Diff {
27+
28+
public static final int DEFAULT_MAX_PATCHES = 3;
29+
public static final int DEFAULT_CONTEXT = 0;
30+
31+
private Diff() {
32+
// no instance
33+
}
34+
35+
public static int countLineDifferences(String dirty, String clean) {
36+
return DiffUtils.diff(lines(dirty), lines(clean)).getDeltas().size();
37+
}
38+
39+
public static String createDiffString(String dirty, String clean, Path file) {
40+
return createDiffString(dirty, clean, file, Limits.defaultLimits());
41+
}
42+
43+
public static String createDiffString(String dirty, String clean, Path file, Limits limits) {
44+
List<String> dirtyLines = lines(dirty);
45+
List<String> cleanLines = lines(clean);
46+
47+
final Patch<String> patch = DiffUtils.diff(dirtyLines, cleanLines);
48+
49+
// limit to 3 diffs to avoid too much output
50+
final Patch<String> limitedPatch = new Patch<>();
51+
patch.getDeltas().stream().limit(limits.maxPatches()).forEachOrdered(limitedPatch::addDelta);
52+
53+
final List<String> diff = new ArrayList<>(DiffUtils.generateUnifiedDiff(
54+
file.toString(), file + "(cleaned)", dirtyLines, limitedPatch, limits.diffContext()));
55+
if (limitedPatch.getDeltas().size() != patch.getDeltas().size()) {
56+
diff.add("[...]"); // indicate that we have more diffs
57+
}
58+
return String.join("\n", diff);
59+
}
60+
61+
private static List<String> lines(String string) {
62+
return Arrays.asList(string.split("\r?\n"));
63+
}
64+
65+
public record Limits(int maxPatches, int diffContext) {
66+
static Limits defaultLimits() {
67+
return new Limits(DEFAULT_MAX_PATCHES, DEFAULT_CONTEXT);
68+
}
69+
}
70+
}

app/src/main/java/com/diffplug/spotless/cli/logging/output/LogfmtFormatter.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ public String format(LogRecord record) {
5353

5454
Map<String, String> attributes = new LinkedHashMap<>();
5555
intoMap(record, attributes);
56-
return toString(attributes);
56+
String logLine = toString(attributes);
57+
if (logLine != null && !logLine.isEmpty()) {
58+
return logLine + "\n";
59+
}
60+
return logLine;
5761
}
5862

5963
private void intoMap(LogRecord record, Map<String, String> attributes) {

app/src/main/java/com/diffplug/spotless/cli/logging/output/LoggingConfigurer.java

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
import com.diffplug.spotless.ThrowingEx;
3030

31+
import picocli.CommandLine;
32+
3133
public final class LoggingConfigurer {
3234

3335
public static void configureLogging(@NotNull CLIOutputLevel cliOutputLevel, @Nullable File logFile) {
@@ -40,51 +42,79 @@ private static void configureJdkLogging(@NotNull CLIOutputLevel cliOutputLevel,
4042
LogManager.getLogManager().reset();
4143

4244
// Create a new console handler
43-
Handler rootHandler = createRootHandler(logFile);
45+
final Handler rootHandler = createRootHandler(logFile);
4446

4547
// Set the root logger level to OFF
46-
Logger rootLogger = Logger.getLogger("");
48+
final Logger rootLogger = Logger.getLogger("");
4749
rootLogger.setLevel(Level.OFF); // only enable specifics
4850
rootLogger.addHandler(rootHandler); // Add the configured handler
4951

50-
Logger spotlessLibLogger = Logger.getLogger("com.diffplug.spotless");
51-
Logger spotlessCliLogger = Logger.getLogger("com.diffplug.spotless.cli");
52+
final Logger spotlessLibLogger = Logger.getLogger("com.diffplug.spotless");
53+
final Logger spotlessCliLogger = Logger.getLogger("com.diffplug.spotless.cli");
5254

53-
ConsoleHandler outputConsoleHandler = new ConsoleHandler();
54-
outputConsoleHandler.setLevel(Level.ALL); // Set logging level
55+
final ConsoleHandler outputConsoleHandler = new ConsoleHandler();
56+
outputConsoleHandler.setLevel(Level.ALL); // make sure everything is logged
5557
outputConsoleHandler.setFormatter(new PlainMessageFormatter()); // Set formatter
5658

57-
Logger outputLogger = Logger.getLogger(Output.OUTPUT_LOGGER_NAME);
58-
outputLogger.setLevel(Level.ALL);
59+
final Logger outputLogger = Logger.getLogger(Output.OUTPUT_LOGGER_NAME);
5960
outputLogger.setUseParentHandlers(false);
6061
outputLogger.addHandler(outputConsoleHandler);
6162

62-
if (cliOutputLevel == CLIOutputLevel.VVVVV) {
63-
rootLogger.setLevel(Level.ALL);
64-
} else if (cliOutputLevel == CLIOutputLevel.VVVV) {
65-
rootLogger.setLevel(Level.INFO);
66-
spotlessLibLogger.setLevel(Level.ALL);
67-
} else if (cliOutputLevel == CLIOutputLevel.VVV) {
68-
spotlessLibLogger.setLevel(Level.ALL);
69-
} else if (cliOutputLevel == CLIOutputLevel.VV) {
70-
spotlessLibLogger.setLevel(Level.INFO);
71-
} else if (cliOutputLevel == CLIOutputLevel.V) {
72-
// spotlessLibLogger.setLevel(Level.OFF);
73-
spotlessCliLogger.setLevel(Level.INFO);
74-
} else if (cliOutputLevel == CLIOutputLevel.DEFAULT) {
75-
// spotlessLibLogger.setLevel(Level.OFF);
76-
spotlessCliLogger.setLevel(Level.WARNING);
77-
} else if (cliOutputLevel == CLIOutputLevel.QUIET) {
78-
// spotlessLibLogger.setLevel(Level.OFF);
79-
spotlessCliLogger.setLevel(Level.SEVERE);
63+
// set the logging level per logger
64+
switch (cliOutputLevel) {
65+
case QUIET -> {
66+
outputLogger.setLevel(Level.SEVERE);
67+
spotlessCliLogger.setLevel(Level.SEVERE);
68+
spotlessLibLogger.setLevel(Level.SEVERE);
69+
rootLogger.setLevel(Level.SEVERE);
70+
}
71+
case DEFAULT -> {
72+
outputLogger.setLevel(Level.INFO);
73+
spotlessCliLogger.setLevel(Level.WARNING);
74+
spotlessLibLogger.setLevel(Level.WARNING);
75+
rootLogger.setLevel(Level.SEVERE);
76+
}
77+
case V -> {
78+
outputLogger.setLevel(Level.INFO);
79+
spotlessCliLogger.setLevel(Level.INFO);
80+
spotlessLibLogger.setLevel(Level.WARNING);
81+
rootLogger.setLevel(Level.SEVERE);
82+
}
83+
case VV -> {
84+
outputLogger.setLevel(Level.INFO);
85+
spotlessLibLogger.setLevel(Level.INFO);
86+
spotlessCliLogger.setLevel(Level.INFO);
87+
rootLogger.setLevel(Level.SEVERE);
88+
}
89+
case VVV -> {
90+
outputLogger.setLevel(Level.ALL);
91+
spotlessCliLogger.setLevel(Level.ALL);
92+
spotlessLibLogger.setLevel(Level.ALL);
93+
rootLogger.setLevel(Level.SEVERE);
94+
}
95+
case VVVV -> {
96+
outputLogger.setLevel(Level.ALL);
97+
spotlessCliLogger.setLevel(Level.ALL);
98+
spotlessLibLogger.setLevel(Level.ALL);
99+
rootLogger.setLevel(Level.INFO);
100+
}
101+
case VVVVV -> {
102+
outputLogger.setLevel(Level.ALL);
103+
spotlessCliLogger.setLevel(Level.ALL);
104+
spotlessLibLogger.setLevel(Level.ALL);
105+
rootLogger.setLevel(Level.ALL);
106+
}
80107
}
81108
}
82109

83110
private static @NotNull Handler createRootHandler(@Nullable File logFile) {
84111
if (logFile == null) {
85112
ConsoleHandler consoleHandler = new ConsoleHandler();
86113
consoleHandler.setLevel(Level.ALL); // Set logging level
87-
consoleHandler.setFormatter(new LogfmtFormatter(LogfmtFormatter.KeyDecorator.MULTI_COLOR)); // Set formatter
114+
LogfmtFormatter.KeyDecorator keyDecorator = CommandLine.Help.Ansi.AUTO.enabled()
115+
? LogfmtFormatter.KeyDecorator.MULTI_COLOR
116+
: LogfmtFormatter.KeyDecorator.NONE;
117+
consoleHandler.setFormatter(new LogfmtFormatter(keyDecorator)); // Set formatter
88118
return consoleHandler;
89119
}
90120
FileHandler fileHandler = ThrowingEx.get(() -> new FileHandler(logFile.getAbsolutePath(), false));

app/src/main/java/com/diffplug/spotless/cli/logging/output/Output.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
public final class Output {
2525

26-
public static final String OUTPUT_LOGGER_NAME = "com.diffplug.spotless.cli.outputs.FORCED_OUTPUT";
26+
public static final String OUTPUT_LOGGER_NAME = "CLI_OUTPUT";
2727
static final Logger CLI_OUTPUT = LoggerFactory.getLogger(OUTPUT_LOGGER_NAME);
2828

2929
public static void write(String message, Object... args) {
@@ -52,12 +52,14 @@ public ToOutputWriter orDetail(@NotNull Supplier<MessageWithArgs> detailMessageW
5252
@Override
5353
public void write() {
5454
if (CLI_OUTPUT.isDebugEnabled()) {
55-
MessageWithArgs messageWithArgs = defaultMessageWithArgs.get();
55+
MessageWithArgs messageWithArgs = detailMessageWithArgs.get();
5656
CLI_OUTPUT.debug(messageWithArgs.message(), messageWithArgs.args());
5757
return;
5858
}
59-
MessageWithArgs messageWithArgs = detailMessageWithArgs.get();
60-
CLI_OUTPUT.info(messageWithArgs.message(), messageWithArgs.args());
59+
if (CLI_OUTPUT.isInfoEnabled()) {
60+
MessageWithArgs messageWithArgs = defaultMessageWithArgs.get();
61+
CLI_OUTPUT.info(messageWithArgs.message(), messageWithArgs.args());
62+
}
6163
}
6264
}
6365

app/src/test/java/com/diffplug/spotless/cli/CLIIntegrationHarness.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ protected SpotlessCLIRunner cliRunner() {
5050
return createRunnerForTag().withWorkingDir(rootFolder());
5151
}
5252

53-
private SpotlessCLIRunner createRunnerForTag() {
53+
protected SpotlessCLIRunner createRunnerForTag() {
5454
CliProcessTest cliProcessTest = getClass().getAnnotation(CliProcessTest.class);
5555
CliProcessNpmTest cliProcessNpmTest = getClass().getAnnotation(CliProcessNpmTest.class);
5656
if (cliProcessTest != null || cliProcessNpmTest != null) {

0 commit comments

Comments
 (0)