This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new d36e119481 GROOVY-11886: support JDK platform logging instead of
System.err (improve doco and improve Ivy integration)
d36e119481 is described below
commit d36e119481bd8150dd196de8d9f6da3d996b4f83
Author: Paul King <[email protected]>
AuthorDate: Tue Mar 31 13:16:45 2026 +1000
GROOVY-11886: support JDK platform logging instead of System.err (improve
doco and improve Ivy integration)
---
.../org/codehaus/groovy/tools/GroovyStarter.java | 27 ++-
src/spec/doc/grape.adoc | 34 +++-
src/spec/doc/tools-groovy.adoc | 204 +++++++++++++++++++++
subprojects/groovy-grape-ivy/build.gradle | 6 +
.../main/groovy/groovy/grape/ivy/GrapeIvy.groovy | 22 +--
.../grape/ivy/PlatformLoggingMessageLogger.java | 93 ++++++++++
6 files changed, 365 insertions(+), 21 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java
b/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java
index 41063810f0..4e0f94856a 100644
--- a/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java
+++ b/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java
@@ -18,9 +18,11 @@
*/
package org.codehaus.groovy.tools;
+import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
+import java.util.logging.LogManager;
import static java.lang.System.Logger.Level.ERROR;
@@ -29,7 +31,11 @@ import static java.lang.System.Logger.Level.ERROR;
*/
public class GroovyStarter {
- private static final System.Logger LOGGER =
System.getLogger(GroovyStarter.class.getName());
+ // Logger is in a holder class so it is not initialized until after
+ // configuration (including logging properties) has been loaded.
+ private static class LogHolder {
+ static final System.Logger LOGGER =
System.getLogger(GroovyStarter.class.getName());
+ }
static void printUsage() {
System.out.println("possible programs are
'groovyc','groovy','console', and 'groovysh'");
@@ -102,10 +108,25 @@ public class GroovyStarter {
try {
lc.configure(new FileInputStream(conf));
} catch (Exception e) {
- LOGGER.log(ERROR, "Exception while configuring main class
loader");
+ LogHolder.LOGGER.log(ERROR, "Exception while configuring main
class loader");
exit(e);
}
}
+
+ // Auto-discover user logging configuration from
~/.groovy/logging.properties
+ // if no explicit logging config was set via -D or groovy-starter.conf.
+ // This must happen before any logger in LogHolder is accessed.
+ if (System.getProperty("java.util.logging.config.file") == null) {
+ File loggingConfig = new File(System.getProperty("user.home"),
".groovy/logging.properties");
+ if (loggingConfig.isFile()) {
+ System.setProperty("java.util.logging.config.file",
loggingConfig.getAbsolutePath());
+ try {
+ LogManager.getLogManager().readConfiguration();
+ } catch (Exception ignore) {
+ }
+ }
+ }
+
// create loader and execute main class
ClassLoader loader = getLoader(lc);
Method m = null;
@@ -132,7 +153,7 @@ public class GroovyStarter {
}
private static void exit(String text) {
- LOGGER.log(ERROR, text);
+ LogHolder.LOGGER.log(ERROR, text);
System.exit(1);
}
}
diff --git a/src/spec/doc/grape.adoc b/src/spec/doc/grape.adoc
index f90f6f21ac..fd3788ee1c 100644
--- a/src/spec/doc/grape.adoc
+++ b/src/spec/doc/grape.adoc
@@ -127,11 +127,35 @@ JAVA_OPTS = -Dhttp.proxyHost=yourproxy
-Dhttp.proxyPort=8080
[[Grape-Logging]]
=== Logging
-Logging configuration depends on which Grape implementation is active.
+The Grape facade (`groovy.grape.Grape`) uses
+https://docs.oracle.com/en/java/javase/17/docs/api/java.logging/java/lang/System.Logger.html[JDK
Platform Logging]
+and can be configured through `~/.groovy/logging.properties`
+(see <<section-groovy-commandline>> for details).
+
+Logging of the underlying engine depends on which Grape implementation is
active.
==== GrapeIvy logging
-If you want to see what Grape is doing set the system property
+GrapeIvy routes Ivy's internal logging through JDK Platform Logging
+under the logger name `groovy.grape.ivy`. To control the verbosity,
+set the level in `~/.groovy/logging.properties`:
+
+[source,properties]
+----
+# Show Ivy info messages (INFO, WARNING, ERROR)
+groovy.grape.ivy.level=INFO
+
+# Show verbose Ivy output (adds dependency resolution details)
+groovy.grape.ivy.level=FINE
+
+# Show all Ivy debug output
+groovy.grape.ivy.level=FINEST
+----
+
+The `grape` command's `-q`, `-w`, `-i`, `-V`, and `-d` flags also
+adjust the Ivy logging level for that invocation.
+
+If you want to see download progress specifically, set the system property
`groovy.grape.report.downloads` to `true` (e.g. add
`-Dgroovy.grape.report.downloads=true` to invocation or JAVA_OPTS) and Grape
will
print the following infos to System.error:
@@ -141,15 +165,11 @@ print the following infos to System.error:
* Retrying download of an artifact
* Download size and time for downloaded artifacts
-To log with even more verbosity, increase the Ivy log level
-(defaults to `-1`). For example `-Divy.message.logger.level=4`.
-
==== GrapeMaven logging
When using the GrapeMaven engine, set `-Dgroovy.grape.debug=true` to
enable verbose Maven Resolver logging to `System.err`. The
-`groovy.grape.report.downloads` and `ivy.message.logger.level`
-properties have no effect with this engine.
+`groovy.grape.report.downloads` property has no effect with this engine.
[[Grape-Detail]]
== Detail
diff --git a/src/spec/doc/tools-groovy.adoc b/src/spec/doc/tools-groovy.adoc
index c106789cff..8c9d61eacb 100644
--- a/src/spec/doc/tools-groovy.adoc
+++ b/src/spec/doc/tools-groovy.adoc
@@ -61,3 +61,207 @@ int (disable any int based optimizations) |
| -pa | --parameters | Generates metadata for reflection on method parameter
names on JDK 8 and above. Defaults to false. | groovy --parameters Person.groovy
| -pr | --enable-preview | Enable preview Java features (jdk12+ only). |
groovy --enable-preview Person.groovy
|=======================================================================
+
+=== Configuring logging
+
+Groovy's command-line tools use
https://docs.oracle.com/en/java/javase/17/docs/api/java.logging/java/lang/System.Logger.html[JDK
Platform Logging] (`System.Logger`)
+for diagnostic messages. By default, this is backed by `java.util.logging`
(JUL).
+
+==== Logging configuration file
+
+JUL reads its configuration from exactly one source.
+The source is resolved in the following order:
+
+1. The file specified by `-Djava.util.logging.config.file=...` on the command
line (via `JAVA_OPTS`).
+2. The file `~/.groovy/logging.properties`, if it exists. Groovy automatically
loads this at startup.
+3. The JDK default at `$JAVA_HOME/conf/logging.properties`.
+
+The first source found is used and the rest are ignored.
+
+==== Example configuration
+
+[source,properties]
+----
+# ~/.groovy/logging.properties
+.level=INFO
+handlers=java.util.logging.ConsoleHandler
+java.util.logging.ConsoleHandler.level=ALL
+java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format=%1$tT %4$s [%3$s] %5$s%6$s%n
+
+# Show Grape provider discovery details
+groovy.grape.Grape.level=FINE
+----
+
+==== Logger reference
+
+The following table lists all classes that emit log messages,
+grouped by module. The _JUL level_ column shows the `java.util.logging`
+level to set in `logging.properties` (the Platform Logging level is shown in
parentheses).
+
+===== Core (`groovy`)
+
+[cols="<3,<1,<3",options="header"]
+|===
+| Logger name | JUL level | What is logged
+
+| `groovy.grape.Grape`
+| FINE (DEBUG), WARNING, SEVERE (ERROR)
+| GrapeEngine provider discovery, selection, and instantiation failures
+
+| `groovy.ui.GroovyMain`
+| SEVERE (ERROR)
+| Script execution errors and uncaught exceptions
+
+| `org.apache.groovy.antlr.LexerFrame`
+| SEVERE (ERROR)
+| Parser errors in the lexer debugging tool
+
+| `org.codehaus.groovy.classgen.asm.OperandStack`
+| SEVERE (ERROR)
+| Stack operation errors during bytecode generation
+
+| `org.codehaus.groovy.classgen.asm.util.LoggableTextifier`
+| FINE (DEBUG)
+| ASM bytecode disassembly output
+
+| `org.codehaus.groovy.control.XStreamUtils`
+| FINE (DEBUG), WARNING
+| XStream serialization availability and failures
+
+| `org.codehaus.groovy.tools.DgmConverter`
+| INFO
+| DGM method stub generation progress
+
+| `org.codehaus.groovy.tools.FileSystemCompiler`
+| WARNING, SEVERE (ERROR)
+| Compilation errors and warnings
+
+| `org.codehaus.groovy.tools.GroovyStarter`
+| SEVERE (ERROR)
+| Startup configuration errors
+
+| `org.codehaus.groovy.tools.javac.JavacJavaCompiler`
+| INFO
+| Joint compilation progress
+|===
+
+===== Grape Ivy (`groovy-grape-ivy`)
+
+[cols="<3,<1,<3",options="header"]
+|===
+| Logger name | JUL level | What is logged
+
+| `groovy.grape.ivy`
+| INFO, FINE (DEBUG), FINEST (TRACE)
+| Ivy dependency resolution, downloads, and cache operations
+|===
+
+===== Ant tasks (`groovy-ant`)
+
+[cols="<3,<1,<3",options="header"]
+|===
+| Logger name | JUL level | What is logged
+
+| `org.codehaus.groovy.ant.Groovyc`
+| WARNING
+| Ant task compilation warnings
+
+| `org.codehaus.groovy.ant.Groovydoc`
+| WARNING
+| Ant task groovydoc warnings
+|===
+
+===== Console (`groovy-console`)
+
+[cols="<3,<1,<3",options="header"]
+|===
+| Logger name | JUL level | What is logged
+
+| `groovy.console.ui.ConsoleTextEditor`
+| WARNING
+| Text editor component warnings
+|===
+
+===== GroovyDoc (`groovy-groovydoc`)
+
+[cols="<3,<1,<3",options="header"]
+|===
+| Logger name | JUL level | What is logged
+
+| `org.codehaus.groovy.tools.groovydoc.FileOutputTool`
+| WARNING
+| File output warnings
+
+| `org.codehaus.groovy.tools.groovydoc.antlr4.GroovyDocParser`
+| WARNING
+| Doc parsing warnings
+
+| `org.codehaus.groovy.tools.groovydoc.GroovyDocTemplateEngine`
+| WARNING, SEVERE (ERROR)
+| Template processing errors
+
+| `org.codehaus.groovy.tools.groovydoc.GroovyRootDocBuilder`
+| WARNING
+| Doc building warnings
+|===
+
+===== JSON (`groovy-json`)
+
+[cols="<3,<1,<3",options="header"]
+|===
+| Logger name | JUL level | What is logged
+
+| `org.apache.groovy.json.internal.Exceptions`
+| SEVERE (ERROR)
+| JSON parsing exception details
+
+| `org.apache.groovy.json.internal.Sys`
+| WARNING
+| System property access warnings
+|===
+
+===== Servlet (`groovy-servlet`)
+
+[cols="<3,<1,<3",options="header"]
+|===
+| Logger name | JUL level | What is logged
+
+| `groovy.servlet.GroovyServlet`
+| SEVERE (ERROR)
+| Servlet script execution errors
+|===
+
+===== Swing (`groovy-swing`)
+
+[cols="<3,<1,<3",options="header"]
+|===
+| Logger name | JUL level | What is logged
+
+| `groovy.swing.table.TableSorter`
+| WARNING
+| Table sorting comparison warnings
+|===
+
+===== Testing (`groovy-test`, `groovy-test-junit5`)
+
+[cols="<3,<1,<3",options="header"]
+|===
+| Logger name | JUL level | What is logged
+
+| `groovy.test.GroovyTestSuite`
+| INFO
+| Test suite class loading info
+
+| `groovy.junit5.plugin.JUnit5Runner`
+| WARNING
+| JUnit5 runner warnings
+|===
+
+TIP: To enable all diagnostic logging, set `.level=FINE` in your configuration
file.
+To focus on a specific area, set the logger name to `FINE` (or
`FINER`/`FINEST` for trace-level detail)
+and leave the root level at `INFO` or `WARNING`.
+
+NOTE: If a different `System.LoggerFinder` is on the classpath (e.g., via
SLF4J/Logback or Log4j2),
+platform logging calls are routed to that backend instead, and
`logging.properties` has no effect.
+Configure the alternative backend using its own mechanism (e.g., `logback.xml`
or `log4j2.xml`).
diff --git a/subprojects/groovy-grape-ivy/build.gradle
b/subprojects/groovy-grape-ivy/build.gradle
index 7283b13bc0..1eebf420e7 100644
--- a/subprojects/groovy-grape-ivy/build.gradle
+++ b/subprojects/groovy-grape-ivy/build.gradle
@@ -27,3 +27,9 @@ dependencies {
}
testImplementation projects.groovyTest
}
+
+// The only Java source is a package-private implementation class;
+// exclude it from javadoc to avoid "no public classes found" error.
+tasks.withType(Javadoc).configureEach {
+ exclude '**/PlatformLoggingMessageLogger.java'
+}
diff --git
a/subprojects/groovy-grape-ivy/src/main/groovy/groovy/grape/ivy/GrapeIvy.groovy
b/subprojects/groovy-grape-ivy/src/main/groovy/groovy/grape/ivy/GrapeIvy.groovy
index 70cb1117f4..3ef2531c82 100644
---
a/subprojects/groovy-grape-ivy/src/main/groovy/groovy/grape/ivy/GrapeIvy.groovy
+++
b/subprojects/groovy-grape-ivy/src/main/groovy/groovy/grape/ivy/GrapeIvy.groovy
@@ -49,7 +49,6 @@ import org.apache.ivy.plugins.matcher.PatternMatcher
import org.apache.ivy.plugins.resolver.ChainResolver
import org.apache.ivy.plugins.resolver.IBiblioResolver
import org.apache.ivy.plugins.resolver.ResolverSettings
-import org.apache.ivy.util.DefaultMessageLogger
import org.apache.ivy.util.Message
import org.codehaus.groovy.reflection.ReflectionUtils
import org.w3c.dom.Element
@@ -89,7 +88,7 @@ class GrapeIvy implements GrapeEngine {
final Set<IvyGrabRecord> grabRecordsForCurrDependencies = [] as
LinkedHashSet
GrapeIvy() {
- Message.setDefaultLogger(new
DefaultMessageLogger(System.getProperty('ivy.message.logger.level', '-1') as
int))
+ Message.setDefaultLogger(new PlatformLoggingMessageLogger())
settings = new IvySettings()
settings.setVariable('user.home.url', new
File(System.getProperty('user.home')).toURI().toURL() as String)
@@ -637,29 +636,30 @@ class GrapeIvy implements GrapeEngine {
@Override
void setLoggingLevel(int level) {
- // Map numeric level to Ivy logging level
+ // Map numeric level (from grape -q/-w/-i/-V/-d flags) to JUL level
+ // for the platform logging backed Ivy logger.
// 0=quiet/errors only, 1=warn, 2=info, 3=verbose, 4=debug
- int ivyLevel
+ java.util.logging.Level julLevel
switch (level) {
case 0:
- ivyLevel = Message.MSG_ERR
+ julLevel = java.util.logging.Level.SEVERE
break
case 1:
- ivyLevel = Message.MSG_WARN
+ julLevel = java.util.logging.Level.WARNING
break
case 2:
- ivyLevel = Message.MSG_INFO
+ julLevel = java.util.logging.Level.INFO
break
case 3:
- ivyLevel = Message.MSG_VERBOSE
+ julLevel = java.util.logging.Level.FINE
break
case 4:
- ivyLevel = Message.MSG_DEBUG
+ julLevel = java.util.logging.Level.FINEST
break
default:
- ivyLevel = Message.MSG_INFO
+ julLevel = java.util.logging.Level.INFO
}
- Message.setDefaultLogger(new DefaultMessageLogger(ivyLevel))
+
java.util.logging.Logger.getLogger('groovy.grape.ivy').setLevel(julLevel)
}
}
diff --git
a/subprojects/groovy-grape-ivy/src/main/java/groovy/grape/ivy/PlatformLoggingMessageLogger.java
b/subprojects/groovy-grape-ivy/src/main/java/groovy/grape/ivy/PlatformLoggingMessageLogger.java
new file mode 100644
index 0000000000..da1ede1ee7
--- /dev/null
+++
b/subprojects/groovy-grape-ivy/src/main/java/groovy/grape/ivy/PlatformLoggingMessageLogger.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.grape.ivy;
+
+import org.apache.ivy.util.AbstractMessageLogger;
+import org.apache.ivy.util.Message;
+
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.ERROR;
+import static java.lang.System.Logger.Level.INFO;
+import static java.lang.System.Logger.Level.TRACE;
+import static java.lang.System.Logger.Level.WARNING;
+
+/**
+ * An Ivy {@link org.apache.ivy.util.MessageLogger} that delegates to JDK
Platform Logging
+ * ({@code System.Logger}), so that Ivy log output can be controlled through
+ * {@code logging.properties} alongside other Groovy diagnostic messages.
+ *
+ * <p>Ivy message levels are mapped to Platform Logging levels as follows:</p>
+ * <ul>
+ * <li>{@code MSG_ERR} → {@code ERROR}</li>
+ * <li>{@code MSG_WARN} → {@code WARNING}</li>
+ * <li>{@code MSG_INFO} → {@code INFO}</li>
+ * <li>{@code MSG_VERBOSE} → {@code DEBUG}</li>
+ * <li>{@code MSG_DEBUG} → {@code TRACE}</li>
+ * </ul>
+ *
+ * @since 6.0.0
+ */
+class PlatformLoggingMessageLogger extends AbstractMessageLogger {
+
+ private static final String LOGGER_NAME = "groovy.grape.ivy";
+ private static final System.Logger LOGGER = System.getLogger(LOGGER_NAME);
+
+ PlatformLoggingMessageLogger() {
+ // Default to WARNING to match the previous behaviour
(DefaultMessageLogger
+ // at level -1 suppressed everything). Users can raise verbosity via
+ // ~/.groovy/logging.properties or the grape -i/-V/-d flags.
+ var julLogger = java.util.logging.Logger.getLogger(LOGGER_NAME);
+ if (julLogger.getLevel() == null) {
+ julLogger.setLevel(java.util.logging.Level.WARNING);
+ }
+ }
+
+ @Override
+ public void log(String msg, int level) {
+ LOGGER.log(toSystemLevel(level), msg);
+ }
+
+ @Override
+ public void rawlog(String msg, int level) {
+ LOGGER.log(toSystemLevel(level), msg);
+ }
+
+ @Override
+ protected void doProgress() {
+ // no-op — progress dots are not useful in structured logging
+ }
+
+ @Override
+ protected void doEndProgress(String msg) {
+ if (msg != null && !msg.isEmpty()) {
+ LOGGER.log(DEBUG, msg);
+ }
+ }
+
+ private static System.Logger.Level toSystemLevel(int ivyLevel) {
+ switch (ivyLevel) {
+ case Message.MSG_ERR: return ERROR;
+ case Message.MSG_WARN: return WARNING;
+ case Message.MSG_INFO: return INFO;
+ case Message.MSG_VERBOSE: return DEBUG;
+ case Message.MSG_DEBUG: return TRACE;
+ default: return INFO;
+ }
+ }
+}