skip to content

Inside a custom plugin or task class (not a build script), how do you obtain a logger correctly?

level: seniorimportance: should knowfreq 20%

answer

  1. Task -> inherited logger
  2. Plugin.apply -> project.logger
  3. elsewhere -> Logging.getLogger(Class)
  4. SLF4J works, loses lifecycle/quiet
  5. don't stash Project for config-cache

basics

~10 s

In a Task subclass, just use the inherited logger. In a plain plugin class with no project handy, call org.gradle.api.logging.Logging.getLogger(MyClass::class.java) to get a Gradle Logger.

solid answer

~40 s

How you get a logger depends on context. A custom `Task` (subclass of `DefaultTask`) inherits a per-task `logger` property — use it directly. A `Plugin<Project>`'s `apply` method receives the `Project`, so `project.logger` works there. But helper classes, services, or anything without a project reference should call `org.gradle.api.logging.Logging.getLogger(MyHelper::class.java)` (or by name), which returns a Gradle `Logger` (with `lifecycle`/`quiet`). You can also declare a regular SLF4J `LoggerFactory.getLogger(...)`; since Gradle routes SLF4J through its system, the messages still appear at the right levels — you just lose the `lifecycle`/`quiet` convenience methods. The anti-pattern is capturing `project` into a field to log later: that defies configuration-cache rules. Prefer `Logging.getLogger(...)` for stateless logging in plugin internals.

code

kotlin · 8 lines
kotlin
import org.gradle.api.logging.Logging

class ManifestWriter {
    private val logger = Logging.getLogger(ManifestWriter::class.java)
    fun write() {
        logger.lifecycle("Writing manifest") // Gradle-specific level still available
    }
}

go deeper

for a junior

Likely beyond scope; at most know tasks have an inherited logger.

for a middle

Know to use the inherited Task logger and project.logger in apply().

for a senior

Explain Logging.getLogger for context-free classes and that SLF4J also routes through Gradle.

for a principal

Set conventions for stateless logging in plugin internals that stay configuration-cache compatible.

## The contexts ### Custom Task ```kotlin abstract class GenerateTask : DefaultTask() { @TaskAction fun run() { logger.lifecycle("Generating...") // inherited per-task logger } } ``` Every `Task` exposes a `logger` of type `org.gradle.api.logging.Logger`. No setup needed. ### Plugin apply method ```kotlin class MyPlugin : Plugin<Project> { override fun apply(project: Project) { project.logger.info("Applying MyPlugin") } } ``` The `Project` is the parameter, so `project.logger` is right there. ### A class with no Project/Task For helpers, build services, or static utilities, use the factory: ```kotlin import org.gradle.api.logging.Logging class DependencyScanner { private val logger = Logging.getLogger(DependencyScanner::class.java) fun scan() { logger.info("Scanning") } } ``` `Logging.getLogger(Class)` and `Logging.getLogger(String)` return a Gradle `Logger`, so you keep `lifecycle`/`quiet`. ### Plain SLF4J also works ```kotlin private val log = org.slf4j.LoggerFactory.getLogger(MyClass::class.java) ``` Because Gradle binds SLF4J to its own logging backend, these messages flow into Gradle's console at the mapped levels. The only loss is the Gradle-specific `lifecycle`/`quiet` methods. ## Why not stash the Project? Capturing `project` (or `project.logger`'s enclosing state) into a field used during execution can break the **configuration cache**, which forbids holding live `Project` references at execution time. A logger obtained via `Logging.getLogger(...)` carries no such state, so it is the safe choice inside reusable plugin internals. ## Summary table - In a Task: `logger`. - In `Plugin.apply`: `project.logger`. - Anywhere else: `Logging.getLogger(Class)` (Gradle logger) or `LoggerFactory.getLogger(Class)` (SLF4J).

  • If you use org.slf4j.LoggerFactory inside a plugin, do messages still reach Gradle's console?
    Yes. Gradle binds SLF4J to its own backend, so SLF4J messages appear at the mapped levels. You only lose the lifecycle/quiet convenience methods.
  • Why is capturing project into a field to log later a problem?
    Holding a live Project reference at execution time violates configuration-cache rules. Logging.getLogger(...) gives a stateless logger that sidesteps this.

saying these in an interview costs you the question

  • Using println in plugin code because 'there is no logger here' — Logging.getLogger exists.
  • Storing a Project reference in a field purely to access its logger during execution.
  • Assuming SLF4J loggers in a plugin are silenced by Gradle — they are routed through it.

context