The log serves as the primary 'UI' of a build tool. If it becomes overly verbose, important warnings and issues can be obscured. However, it is essential to have relevant information to determine if something has gone wrong.

Gradle defines six log levels, detailed in Log levels. In addition to the standard log levels, Gradle introduces two specific levels: QUIET and LIFECYCLE. LIFECYCLE is the default level used to report build progress.

Understanding Log levels

There are 6 log levels in Gradle:

ERROR

Error messages

QUIET

Important information messages

WARNING

Warning messages

LIFECYCLE

Progress information messages

INFO

Information messages

DEBUG

Debug messages

The console’s rich components (build status and work-in-progress area) are displayed regardless of the log level used.

Choosing a log level

You can choose different log levels from the command line switches shown in Log level command-line options.

You can also configure the log level using gradle.properties.

In Stacktrace command-line options you can find the command line switches which affect stacktrace logging.

Log level command-line options:

Option Outputs Log Levels

-q or --quiet

QUIET and higher

-w or --warn

WARN and higher

no logging options

LIFECYCLE and higher

-i or --info

INFO and higher

-d or --debug

DEBUG and higher (that is, all log messages)

Stacktrace command-line options

-s or --stacktrace

Truncated stacktraces are printed. We recommend this over full stacktraces. Groovy full stacktraces are extremely verbose due to the underlying dynamic invocation mechanisms. Yet they usually do not contain relevant information about what has gone wrong in your code. This option renders stacktraces for deprecation warnings.

-S or --full-stacktrace

The full stacktraces are printed out. This option renders stacktraces for deprecation warnings.

<No stacktrace options>

No stacktraces are printed to the console in case of a build error (e.g., a compile error). Only in case of internal exceptions will stacktraces be printed. If the DEBUG log level is chosen, truncated stacktraces are always printed.

Logging Sensitive Information

Running Gradle with the DEBUG log level can potentially expose sensitive information to the console and build log.

This information might include:

  • Environment variables

  • Private repository credentials

  • Build cache and Develocity credentials

  • Plugin Portal publishing credentials

It’s important to avoid using the DEBUG log level when running on public Continuous Integration (CI) services. Build logs on these services are accessible to the public and can expose sensitive information. Even on private CI services, logging sensitive credentials may pose a risk depending on your organization’s threat model. It’s advisable to discuss this with your organization’s security team.

Some CI providers attempt to redact sensitive credentials from logs, but this process is not foolproof and typically only redacts exact matches of pre-configured secrets.

If you suspect that a Gradle Plugin may inadvertently expose sensitive information, please contact security@gradle.com for assistance with disclosure.

Writing your own log messages

A simple option for logging in your build file is to write messages to standard output. Gradle redirects anything written to standard output to its logging system at the QUIET log level:

build.gradle.kts
println("A message which is logged at QUIET level")
build.gradle
println 'A message which is logged at QUIET level'

Gradle also provides a logger property to a build script, which is an instance of Logger. This interface extends the SLF4J Logger interface and adds a few Gradle-specific methods. Below is an example of how this is used in the build script:

build.gradle.kts
logger.quiet("An info log message which is always logged.")
logger.error("An error log message.")
logger.warn("A warning log message.")
logger.lifecycle("A lifecycle info log message.")
logger.info("An info log message.")
logger.debug("A debug log message.")
logger.trace("A trace log message.") // Gradle never logs TRACE level logs
build.gradle
logger.quiet('An info log message which is always logged.')
logger.error('An error log message.')
logger.warn('A warning log message.')
logger.lifecycle('A lifecycle info log message.')
logger.info('An info log message.')
logger.debug('A debug log message.')
logger.trace('A trace log message.') // Gradle never logs TRACE level logs

Use the link typical SLF4J pattern to replace a placeholder with an actual value in the log message.

build.gradle.kts
logger.info("A {} log message", "info")
build.gradle
logger.info('A {} log message', 'info')

You can also hook into Gradle’s logging system from within other classes used in the build (classes from the buildSrc directory, for example) with an SLF4J logger. You can use this logger the same way as you use the provided logger in the build script.

build.gradle.kts
import org.slf4j.LoggerFactory

val slf4jLogger = LoggerFactory.getLogger("some-logger")
slf4jLogger.info("An info log message logged using SLF4j")
build.gradle
import org.slf4j.LoggerFactory

def slf4jLogger = LoggerFactory.getLogger('some-logger')
slf4jLogger.info('An info log message logged using SLF4j')

Logging from external tools and libraries

Internally, Gradle uses Ant and Ivy. Both have their own logging system. Gradle redirects their logging output into the Gradle logging system.

There is a 1:1 mapping from the Ant/Ivy log levels to the Gradle log levels, except the Ant/Ivy TRACE log level, which is mapped to the Gradle DEBUG log level. This means the default Gradle log level will not show any Ant/Ivy output unless it is an error or a warning.

Many tools out there still use the standard output for logging. By default, Gradle redirects standard output to the QUIET log level and standard error to the ERROR level. This behavior is configurable.

The project object provides a LoggingManager, which allows you to change the log levels that standard out or error are redirected to when your build script is evaluated.

build.gradle.kts
logging.captureStandardOutput(LogLevel.INFO)
println("A message which is logged at INFO level")
build.gradle
logging.captureStandardOutput LogLevel.INFO
println 'A message which is logged at INFO level'

To change the log level for standard out or error during task execution, use a LoggingManager.

build.gradle.kts
tasks.register("logInfo") {
    logging.captureStandardOutput(LogLevel.INFO)
    doFirst {
        println("A task message which is logged at INFO level")
    }
}
build.gradle
tasks.register('logInfo') {
    logging.captureStandardOutput LogLevel.INFO
    doFirst {
        println 'A task message which is logged at INFO level'
    }
}

Gradle also integrates with the Java Util Logging, Jakarta Commons Logging and Log4j logging toolkits. Any log messages your build classes write using these logging toolkits will be redirected to Gradle’s logging system.

Changing what Gradle logs

This feature is deprecated and will be removed in the next major version without a replacement.

The configuration cache limits the ability to customize Gradle’s logging UI. The custom logger can only implement supported listener interfaces. These interfaces do not receive events when the configuration cache entry is reused because the configuration phase is skipped.

You can replace much of Gradle’s logging UI with your own. You could do this if you want to customize the UI somehow - to log more or less information or to change the formatting. Simply replace the logging using the Gradle.useLogger(java.lang.Object) method. This is accessible from a build script, an init script, or via the embedding API. Note that this completely disables Gradle’s default output. Below is an example init script that changes how task execution and build completion are logged:

customLogger.init.gradle.kts
useLogger(CustomEventLogger())

@Suppress("deprecation")
class CustomEventLogger() : BuildAdapter(), TaskExecutionListener {

    override fun beforeExecute(task: Task) {
        println("[${task.name}]")
    }

    override fun afterExecute(task: Task, state: TaskState) {
        println()
    }

    override fun buildFinished(result: BuildResult) {
        println("build completed")
        if (result.failure != null) {
            (result.failure as Throwable).printStackTrace()
        }
    }
}
customLogger.init.gradle
useLogger(new CustomEventLogger())

@SuppressWarnings("deprecation")
class CustomEventLogger extends BuildAdapter implements TaskExecutionListener {

    void beforeExecute(Task task) {
        println "[$task.name]"
    }

    void afterExecute(Task task, TaskState state) {
        println()
    }
    
    void buildFinished(BuildResult result) {
        println 'build completed'
        if (result.failure != null) {
            result.failure.printStackTrace()
        }
    }
}
$ gradle -I customLogger.init.gradle.kts build

> Task :compile
[compile]
compiling source

> Task :testCompile
[testCompile]
compiling test source

> Task :test
[test]
running unit tests

> Task :build
[build]

build completed
3 actionable tasks: 3 executed
$ gradle -I customLogger.init.gradle build

> Task :compile
[compile]
compiling source

> Task :testCompile
[testCompile]
compiling test source

> Task :test
[test]
running unit tests

> Task :build
[build]

build completed
3 actionable tasks: 3 executed

Your logger can implement any of the listener interfaces listed below. When you register a logger, only the logging for the interfaces it implements is replaced. Logging for the other interfaces is left untouched. You can find out more about the listener interfaces in Build lifecycle events.


1. Not compatible with the configuration cache.