Skip to content

Commit 5747696

Browse files
committed
chore: rework output logging to make use of picocli abstraction
this allows easier testing as we can now redirect stdout/stderr per commandline invocation and as such read and validate output per test.
1 parent 9ea39ff commit 5747696

File tree

9 files changed

+198
-47
lines changed

9 files changed

+198
-47
lines changed

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

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

18+
import org.jetbrains.annotations.NotNull;
19+
1820
import com.diffplug.spotless.cli.execution.FormatterStepsSupplier;
21+
import com.diffplug.spotless.cli.logging.output.Output;
1922

2023
public interface SpotlessAction extends SpotlessCommand {
21-
Integer executeSpotlessAction(FormatterStepsSupplier formatterSteps);
24+
@NotNull Integer executeSpotlessAction(@NotNull Output output, @NotNull FormatterStepsSupplier formatterSteps);
2225

23-
default void setupLogging() {}
26+
@NotNull Output setupLogging();
2427
}

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.nio.charset.Charset;
2020
import java.nio.file.Path;
2121
import java.util.List;
22+
import java.util.Objects;
2223
import java.util.concurrent.ExecutorService;
2324
import java.util.concurrent.Executors;
2425
import java.util.concurrent.Future;
@@ -40,6 +41,7 @@
4041
import com.diffplug.spotless.cli.execution.SpotlessExecutionStrategy;
4142
import com.diffplug.spotless.cli.help.OptionConstants;
4243
import com.diffplug.spotless.cli.logging.output.LoggingConfigurer;
44+
import com.diffplug.spotless.cli.logging.output.Output;
4345
import com.diffplug.spotless.cli.steps.GoogleJavaFormat;
4446
import com.diffplug.spotless.cli.steps.LicenseHeader;
4547
import com.diffplug.spotless.cli.steps.Prettier;
@@ -195,13 +197,16 @@ LoggingConfigurer.CLIOutputLevel toCliOutputLevel() {
195197
File logFile;
196198

197199
@Override
198-
public void setupLogging() {
200+
public @NotNull Output setupLogging() {
201+
CommandLine commandLine = spec.commandLine();
199202
LoggingConfigurer.CLIOutputLevel outputLevel = loggingLevelOptions != null
200203
? loggingLevelOptions.toCliOutputLevel()
201204
: LoggingConfigurer.CLIOutputLevel.DEFAULT;
202-
LoggingConfigurer.configureLogging(outputLevel, logFile);
205+
Output output =
206+
LoggingConfigurer.configureLogging(outputLevel, logFile, commandLine::getErr, commandLine::getOut);
203207
// the following logs are to make sure that the logging is configured correctly
204208
logMetaStatements();
209+
return output;
205210
}
206211

207212
private static void logMetaStatements() {
@@ -217,7 +222,10 @@ private static void logMetaStatements() {
217222
}
218223

219224
@Override
220-
public Integer executeSpotlessAction(FormatterStepsSupplier formatterSteps) {
225+
public @NotNull Integer executeSpotlessAction(
226+
@NotNull Output output, @NotNull FormatterStepsSupplier formatterSteps) {
227+
Objects.requireNonNull(output);
228+
Objects.requireNonNull(formatterSteps);
221229
validateTargets();
222230
TargetResolver targetResolver = targetResolver();
223231

@@ -240,7 +248,7 @@ public Integer executeSpotlessAction(FormatterStepsSupplier formatterSteps) {
240248
.toList();
241249
ResultType resultType = stepResults.stream()
242250
.map(future -> ThrowingEx.get(future::get))
243-
.map(this::handleResult)
251+
.map(result -> this.handleResult(output, result))
244252
.reduce(ResultType.CLEAN, ResultType::combineWith);
245253
return spotlessMode.translateResultTypeToExitCode(resultType);
246254
}
@@ -262,7 +270,7 @@ private void validateTargets() {
262270
}
263271
}
264272

265-
private ResultType handleResult(Result result) {
273+
private ResultType handleResult(Output output, Result result) {
266274
if (result.lintState().isClean()) {
267275
LOGGER.debug("File is clean: {}", result.target().toFile());
268276
return ResultType.CLEAN;
@@ -271,7 +279,7 @@ private ResultType handleResult(Result result) {
271279
LOGGER.warn("File did not converge: {}", result.target().toFile());
272280
return ResultType.DID_NOT_CONVERGE;
273281
}
274-
return this.spotlessMode.handleResult(result);
282+
return this.spotlessMode.handleResult(output, result);
275283
}
276284

277285
private TargetResolver targetResolver() {

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ enum SpotlessMode {
3232
private static final Logger LOGGER = LoggerFactory.getLogger(SpotlessMode.class);
3333

3434
@Override
35-
ResultType handleResult(Result result) {
35+
ResultType handleResult(Output output, Result result) {
3636
if (result.lintState().isHasLints()) {
37-
Output.eitherDefault(() -> new Output.MessageWithArgs(
37+
output.eitherDefault(() -> new Output.MessageWithArgs(
3838
"File has lints: {} -- {}",
3939
result.target().toFile().getPath(),
4040
result.lintState()
@@ -53,7 +53,7 @@ ResultType handleResult(Result result) {
5353
String original = Files.readString(result.target(), StandardCharsets.UTF_8);
5454

5555
final int diffs = Diff.countLineDifferences(original, cleaned);
56-
Output.eitherDefault(() -> {
56+
output.eitherDefault(() -> {
5757
if (diffs > 0) {
5858
return new Output.MessageWithArgs(
5959
"File needs reformatting: {} -- {} differences", result.target(), diffs);
@@ -92,10 +92,10 @@ Integer translateResultTypeToExitCode(ResultType resultType) {
9292
private static final Logger LOGGER = LoggerFactory.getLogger(SpotlessMode.class);
9393

9494
@Override
95-
ResultType handleResult(Result result) {
95+
ResultType handleResult(Output output, Result result) {
9696
if (result.lintState().isHasLints()) {
9797
// something went wrong, we should not apply the changes
98-
Output.eitherDefault(() -> new Output.MessageWithArgs(
98+
output.eitherDefault(() -> new Output.MessageWithArgs(
9999
"File has lints: {} -- {}",
100100
result.target().toFile().getPath(),
101101
result.lintState()
@@ -127,7 +127,7 @@ Integer translateResultTypeToExitCode(ResultType resultType) {
127127
}
128128
};
129129

130-
abstract ResultType handleResult(Result result);
130+
abstract ResultType handleResult(Output output, Result result);
131131

132132
abstract Integer translateResultTypeToExitCode(ResultType resultType);
133133
}

app/src/main/java/com/diffplug/spotless/cli/execution/SpotlessExecutionStrategy.java

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

18+
import com.diffplug.spotless.cli.SpotlessAction;
1819
import com.diffplug.spotless.cli.core.SpotlessActionContext;
1920
import com.diffplug.spotless.cli.core.SpotlessCommandLineStream;
21+
import com.diffplug.spotless.cli.logging.output.Output;
2022

2123
import picocli.CommandLine;
2224

@@ -34,7 +36,12 @@ public int execute(CommandLine.ParseResult parseResult) throws CommandLine.Execu
3436

3537
private Integer runSpotlessActions(SpotlessCommandLineStream commandLineStream) {
3638
// 0. setup logging
37-
commandLineStream.actions().findFirst().ifPresent(action -> action.setupLogging());
39+
Output output = commandLineStream
40+
.actions()
41+
.findFirst()
42+
.map(SpotlessAction::setupLogging)
43+
.orElseThrow();
44+
3845
// 1. prepare context
3946
SpotlessActionContext context = provideSpotlessActionContext(commandLineStream);
4047

@@ -44,7 +51,7 @@ private Integer runSpotlessActions(SpotlessCommandLineStream commandLineStream)
4451
stepsSupplierFactory.createFormatterStepsSupplier(commandLineStream, context);
4552

4653
// 3. run spotless steps
47-
return executeSpotlessAction(commandLineStream, stepsSupplier);
54+
return executeSpotlessAction(output, commandLineStream, stepsSupplier);
4855
}
4956

5057
private SpotlessActionContext provideSpotlessActionContext(SpotlessCommandLineStream commandLineStream) {
@@ -56,11 +63,11 @@ private SpotlessActionContext provideSpotlessActionContext(SpotlessCommandLineSt
5663
}
5764

5865
private Integer executeSpotlessAction(
59-
SpotlessCommandLineStream commandLineStream, FormatterStepsSupplier stepsSupplier) {
66+
Output output, SpotlessCommandLineStream commandLineStream, FormatterStepsSupplier stepsSupplier) {
6067
return commandLineStream
6168
.actions()
6269
.findFirst()
63-
.map(spotlessAction -> spotlessAction.executeSpotlessAction(stepsSupplier))
70+
.map(spotlessAction -> spotlessAction.executeSpotlessAction(output, stepsSupplier))
6471
.orElse(-1);
6572
}
6673
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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.logging.output;
17+
18+
import java.io.OutputStream;
19+
import java.io.PrintWriter;
20+
21+
final class LogStreams {
22+
23+
static OutputStream asOutputStream(PrintWriter printWriter) {
24+
return new OutputStream() {
25+
@Override
26+
public void write(int b) {
27+
printWriter.write(b);
28+
printWriter.flush();
29+
}
30+
31+
@Override
32+
public void write(byte[] b, int off, int len) {
33+
printWriter.write(new String(b, off, len));
34+
printWriter.flush();
35+
}
36+
};
37+
}
38+
}

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

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
package com.diffplug.spotless.cli.logging.output;
1717

1818
import java.io.File;
19-
import java.util.logging.ConsoleHandler;
19+
import java.io.PrintWriter;
20+
import java.util.function.Supplier;
2021
import java.util.logging.FileHandler;
2122
import java.util.logging.Handler;
2223
import java.util.logging.Level;
@@ -32,17 +33,27 @@
3233

3334
public final class LoggingConfigurer {
3435

35-
public static void configureLogging(@NotNull CLIOutputLevel cliOutputLevel, @Nullable File logFile) {
36-
configureJdkLogging(cliOutputLevel, logFile);
36+
public static Output configureLogging(
37+
@NotNull CLIOutputLevel cliOutputLevel,
38+
@Nullable File logFile,
39+
@NotNull Supplier<PrintWriter> stdErr,
40+
@NotNull Supplier<PrintWriter> stdOut) {
41+
return configureJdkLogging(cliOutputLevel, logFile, stdErr, stdOut);
3742
}
3843

39-
private static void configureJdkLogging(@NotNull CLIOutputLevel cliOutputLevel, @Nullable File logFile) {
44+
private static Output configureJdkLogging(
45+
@NotNull CLIOutputLevel cliOutputLevel,
46+
@Nullable File logFile,
47+
@NotNull Supplier<PrintWriter> stdErr,
48+
@NotNull Supplier<PrintWriter> stdOut) {
49+
// Set the output to Output
50+
Output output = new Output().with(stdErr.get());
4051

4152
// Reset the logging configuration to remove any default handlers
4253
LogManager.getLogManager().reset();
4354

4455
// Create a new console handler
45-
final Handler rootHandler = createRootHandler(logFile);
56+
final Handler rootHandler = createRootHandler(logFile, stdErr);
4657

4758
// Set the root logger level to OFF
4859
final Logger rootLogger = Logger.getLogger("");
@@ -55,53 +66,52 @@ private static void configureJdkLogging(@NotNull CLIOutputLevel cliOutputLevel,
5566
// set the logging level per logger
5667
switch (cliOutputLevel) {
5768
case QUIET -> {
58-
Output.setLevel(Level.SEVERE);
69+
output = output.with(Level.SEVERE);
5970
spotlessCliLogger.setLevel(Level.SEVERE);
6071
spotlessLibLogger.setLevel(Level.SEVERE);
6172
rootLogger.setLevel(Level.SEVERE);
6273
}
6374
case DEFAULT -> {
64-
Output.setLevel(Level.INFO);
6575
spotlessCliLogger.setLevel(Level.WARNING);
6676
spotlessLibLogger.setLevel(Level.WARNING);
6777
rootLogger.setLevel(Level.SEVERE);
6878
}
6979
case V -> {
70-
Output.setLevel(Level.INFO);
7180
spotlessCliLogger.setLevel(Level.INFO);
7281
spotlessLibLogger.setLevel(Level.WARNING);
7382
rootLogger.setLevel(Level.SEVERE);
7483
}
7584
case VV -> {
76-
Output.setLevel(Level.INFO);
7785
spotlessLibLogger.setLevel(Level.INFO);
7886
spotlessCliLogger.setLevel(Level.INFO);
7987
rootLogger.setLevel(Level.SEVERE);
8088
}
8189
case VVV -> {
82-
Output.setLevel(Level.ALL);
90+
output = output.with(Level.ALL);
8391
spotlessCliLogger.setLevel(Level.ALL);
8492
spotlessLibLogger.setLevel(Level.ALL);
8593
rootLogger.setLevel(Level.SEVERE);
8694
}
8795
case VVVV -> {
88-
Output.setLevel(Level.ALL);
96+
output = output.with(Level.ALL);
8997
spotlessCliLogger.setLevel(Level.ALL);
9098
spotlessLibLogger.setLevel(Level.ALL);
9199
rootLogger.setLevel(Level.INFO);
92100
}
93101
case VVVVV -> {
94-
Output.setLevel(Level.ALL);
102+
output = output.with(Level.ALL);
95103
spotlessCliLogger.setLevel(Level.ALL);
96104
spotlessLibLogger.setLevel(Level.ALL);
97105
rootLogger.setLevel(Level.ALL);
98106
}
99107
}
108+
109+
return output;
100110
}
101111

102-
private static @NotNull Handler createRootHandler(@Nullable File logFile) {
112+
private static @NotNull Handler createRootHandler(@Nullable File logFile, @NotNull Supplier<PrintWriter> stdErr) {
103113
if (logFile == null) {
104-
ConsoleHandler consoleHandler = new ConsoleHandler();
114+
PicocliConsoleHandler consoleHandler = new PicocliConsoleHandler(stdErr);
105115
consoleHandler.setLevel(Level.ALL); // Set logging level
106116
LogfmtFormatter.KeyDecorator keyDecorator = CommandLine.Help.Ansi.AUTO.enabled()
107117
? LogfmtFormatter.KeyDecorator.MULTI_COLOR

0 commit comments

Comments
 (0)