diff --git a/backends/jul-backend/src/main/java/io/spine/logging/backend/jul/AbstractJulBackend.java b/backends/jul-backend/src/main/java/io/spine/logging/backend/jul/AbstractJulBackend.java index 876d154d2..3b7cb2f8d 100644 --- a/backends/jul-backend/src/main/java/io/spine/logging/backend/jul/AbstractJulBackend.java +++ b/backends/jul-backend/src/main/java/io/spine/logging/backend/jul/AbstractJulBackend.java @@ -29,12 +29,10 @@ import io.spine.logging.Level; import io.spine.logging.backend.LoggerBackend; -import java.util.logging.Filter; -import java.util.logging.Handler; import java.util.logging.LogRecord; import java.util.logging.Logger; -import static io.spine.logging.JvmLoggerKt.toJavaLogging; +import static io.spine.logging.Levels.toJavaLogging; /** * An abstract implementation of {@code java.util.logging} (JUL) based backend. @@ -130,7 +128,7 @@ public final void log(LogRecord record, boolean wasForced) { // // In all cases we still call the filter (if one exists) even though we ignore the result. // Use a local variable to avoid race conditions where the filter can be unset at any time. - Filter filter = logger.getFilter(); + var filter = logger.getFilter(); if (filter != null) { filter.isLoggable(record); } @@ -169,7 +167,7 @@ private static void publish(Logger logger, LogRecord record) { // nothing much we can do about this (and there could be synchronization issues even if we // could access things directly because handlers can be changed at any time). Most of the // time this returns the singleton empty array however, so it's not as bad as all that. - for (Handler handler : logger.getHandlers()) { + for (var handler : logger.getHandlers()) { handler.publish(record); } if (logger.getUseParentHandlers()) { @@ -185,7 +183,7 @@ private static void publish(Logger logger, LogRecord record) { // subclasses of Logger to be used. void forceLoggingViaChildLogger(LogRecord record) { // Assume that nobody else will configure or manipulate loggers with this "secret" name. - Logger forcingLogger = getForcingLogger(logger); + var forcingLogger = getForcingLogger(logger); // This logger can be garbage collected at any time, so we must always reset any configuration. // This code is subject to a bunch of unlikely race conditions if the logger is manipulated diff --git a/backends/jul-backend/src/main/java/io/spine/logging/backend/jul/AbstractJulRecord.java b/backends/jul-backend/src/main/java/io/spine/logging/backend/jul/AbstractJulRecord.java index c681c79b5..a8745681d 100644 --- a/backends/jul-backend/src/main/java/io/spine/logging/backend/jul/AbstractJulRecord.java +++ b/backends/jul-backend/src/main/java/io/spine/logging/backend/jul/AbstractJulRecord.java @@ -38,7 +38,7 @@ import java.util.ResourceBundle; import java.util.logging.LogRecord; -import static io.spine.logging.JvmLoggerKt.toJavaLogging; +import static io.spine.logging.Levels.toJavaLogging; import static java.time.Instant.ofEpochMilli; import static java.util.Objects.requireNonNullElse; import static java.util.concurrent.TimeUnit.NANOSECONDS; diff --git a/backends/log4j2-backend/src/main/java/io/spine/logging/backend/log4j2/Log4j2LogEventUtil.java b/backends/log4j2-backend/src/main/java/io/spine/logging/backend/log4j2/Log4j2LogEventUtil.java index 1a4ca75a3..a042919a8 100644 --- a/backends/log4j2-backend/src/main/java/io/spine/logging/backend/log4j2/Log4j2LogEventUtil.java +++ b/backends/log4j2-backend/src/main/java/io/spine/logging/backend/log4j2/Log4j2LogEventUtil.java @@ -49,7 +49,7 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import static io.spine.logging.JvmLoggerKt.toLevel; +import static io.spine.logging.Levels.toLevel; import static io.spine.logging.backend.MetadataProcessor.forScopeAndLogSite; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.NANOSECONDS; diff --git a/dependencies.md b/dependencies.md index c49adee64..f71a5ed75 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-logging-context-tests:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-context-tests:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.0.2. @@ -413,14 +413,14 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-fixtures:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-fixtures:2.0.0-SNAPSHOT.400` ## Runtime ## Compile, tests, and tooling @@ -1148,14 +1148,14 @@ This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:54 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-grpc-context:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-grpc-context:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -1982,14 +1982,14 @@ This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jul-backend:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-jul-backend:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -2800,14 +2800,14 @@ This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-default-platform:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-jvm-default-platform:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -3662,14 +3662,14 @@ This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-jul-backend-grpc-context:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-jvm-jul-backend-grpc-context:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -4520,14 +4520,14 @@ This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-jul-backend-std-context:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-jvm-jul-backend-std-context:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -5370,14 +5370,14 @@ This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-log4j2-backend-std-context:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-jvm-log4j2-backend-std-context:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -6220,14 +6220,14 @@ This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-slf4j-jdk14-backend-std-context:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-jvm-slf4j-jdk14-backend-std-context:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -7078,14 +7078,14 @@ This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-jvm-slf4j-reload4j-backend-std-context:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-jvm-slf4j-reload4j-backend-std-context:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -7940,14 +7940,14 @@ This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-log4j2-backend:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-log4j2-backend:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -8782,14 +8782,14 @@ This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging:2.0.0-SNAPSHOT.400` ## Runtime ## Compile, tests, and tooling @@ -9521,14 +9521,14 @@ This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:spine-logging-testlib:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine.tools:spine-logging-testlib:2.0.0-SNAPSHOT.400` ## Runtime ## Compile, tests, and tooling @@ -10256,14 +10256,14 @@ This report was generated on **Mon Sep 15 12:08:01 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:54 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-probe-backend:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-probe-backend:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.auto.service. **Name** : auto-service-annotations. **Version** : 1.1.1. @@ -11122,14 +11122,14 @@ This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-smoke-test:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-smoke-test:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.18.3. @@ -12008,14 +12008,14 @@ This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-logging-std-context:2.0.0-SNAPSHOT.390` +# Dependencies of `io.spine:spine-logging-std-context:2.0.0-SNAPSHOT.400` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -12826,6 +12826,6 @@ This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Sep 15 12:08:00 WEST 2025** using +This report was generated on **Mon Sep 15 16:45:53 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/logging/src/commonMain/kotlin/io/spine/logging/AbstractLogger.kt b/logging/src/commonMain/kotlin/io/spine/logging/AbstractLogger.kt index 08c60ddea..31fd7d6b3 100644 --- a/logging/src/commonMain/kotlin/io/spine/logging/AbstractLogger.kt +++ b/logging/src/commonMain/kotlin/io/spine/logging/AbstractLogger.kt @@ -98,6 +98,11 @@ public abstract class AbstractLogger> protected constructo */ public fun atSevere(): API = at(Level.SEVERE) + /** + * A convenience method for at([Level.ERROR]). + */ + public fun atError(): API = at(Level.ERROR) + /** * A convenience method for at([Level.WARNING]). */ @@ -118,11 +123,21 @@ public abstract class AbstractLogger> protected constructo */ public fun atFine(): API = at(Level.FINE) + /** + * A convenience method for at([Level.DEBUG]). + */ + public fun atDebug(): API = at(Level.DEBUG) + /** * A convenience method for at([Level.FINER]). */ public fun atFiner(): API = at(Level.FINER) + /** + * A convenience method for at([Level.TRACE]). + */ + public fun atTrace(): API = at(Level.TRACE) + /** * A convenience method for at([Level.FINEST]). */ diff --git a/logging/src/commonMain/kotlin/io/spine/logging/LogContext.kt b/logging/src/commonMain/kotlin/io/spine/logging/LogContext.kt index a7beaa429..18ac4ab80 100644 --- a/logging/src/commonMain/kotlin/io/spine/logging/LogContext.kt +++ b/logging/src/commonMain/kotlin/io/spine/logging/LogContext.kt @@ -91,6 +91,8 @@ protected constructor( */ private var literalArg: String? = null + private var loggingDomain: LoggingDomain? = null + /** * Creates a logging context with the specified level, and with a timestamp obtained from the * configured logging [Platform]. @@ -411,7 +413,8 @@ protected constructor( * This method takes a single string parameter as it's always called with one string argument. */ private fun logImpl(arg: String?) { - this.literalArg = arg + val prefix = loggingDomain?.messagePrefix ?: "" + this.literalArg = prefix + arg // Right at the end of processing add any tags injected by the platform. // Any tags supplied at the log site are merged with the injected tags @@ -465,7 +468,10 @@ protected constructor( return wasForced() || getLogger().doIsLoggable(level) } - public override fun withLoggingDomain(domain: LoggingDomain): API = api() + public override fun withLoggingDomain(domain: LoggingDomain): API { + this.loggingDomain = domain + return api() + } public final override fun with(key: MetadataKey, value: T?): API { if (value != null) { diff --git a/logging/src/commonMain/kotlin/io/spine/logging/Logger.kt b/logging/src/commonMain/kotlin/io/spine/logging/Logger.kt index 8a042ba71..4c8fcfaa3 100644 --- a/logging/src/commonMain/kotlin/io/spine/logging/Logger.kt +++ b/logging/src/commonMain/kotlin/io/spine/logging/Logger.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, TeamDev. All rights reserved. + * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,84 +26,98 @@ package io.spine.logging +import io.spine.annotation.VisibleForTesting import io.spine.logging.LoggingFactory.loggingDomainOf +import io.spine.logging.backend.LoggerBackend +import io.spine.logging.backend.Platform import kotlin.reflect.KClass /** - * Base class for fluent API loggers. + * The default implementation of [AbstractLogger] which returns the basic [LoggingApi] + * and uses the default parser and system configured backend. * - * A logger is a factory of fluent logging [API] instances, - * which allow building log statements via method chaining. + * Note that when extending the logging API or specifying a new parser, you will need to create a + * new logger class (rather than extending this one). Unlike the [LogContext] class, + * which must be extended in order to modify the logging API, this class is not generified and thus + * cannot be modified to produce a different logging API. * - * @param [API] The logging API provided by this logger. - * @param [cls] The class which is going to perform the logging operations using this logger. - * @see [LoggingApi] + * The choice to prevent direct extension of loggers was made to ensure that users + * of a specific logger implementation always get the same behavior. + * + * @property cls The class for which this logger works. + * + * @param backend The backend for this logger. + * + * @see + * Original Java code for historical context. */ -public abstract class Logger>( - protected val cls: KClass<*> -) { +public class Logger( + private val cls: KClass<*>, + backend: LoggerBackend +) : AbstractLogger(backend) { + /** - * Returns a fluent logging [API] for the specified level of logging. + * The non-wildcard, fully specified, logging API for this logger. Fluent logger implementations + * should specify a non-wildcard API like this with which to generify the abstract logger. * - * If the specified level of logging is disabled at this point, the method - * returns a "no-op" instance which silently ignores further calls to the logging [API]. + * It is possible to add methods to this logger-specific API directly, but it is recommended that + * a separate top-level API and LogContext is created, allowing it to be shared by other + * implementations. */ - public fun at(level: Level): API { - val api = createApi(level) - if (!api.isEnabled()) { - return api - } - val loggingDomain = loggingDomainOf(cls) - return if (loggingDomain.name.isEmpty()) { - api - } else { - api.withLoggingDomain(loggingDomain) - } - } + public interface Api : LoggingApi /** - * Creates a new [API] instance with the given level. - * - * If a logger implementation determines that logging is definitely disabled - * at this point, the implementation should return an instance of a class - * extending [LoggingApi.NoOp] which would be a non-wildcard, fully specified, no-op - * implementation of the [API] type. + * The non-wildcard, fully specified, no-op API implementation. This is required to provide a + * no-op implementation whose type is compatible with this logger's API. */ - protected abstract fun createApi(level: Level): API + internal class NoOp : LoggingApi.NoOp(), Api - /* - * IMPLEMENTATION NOTE - * - * The following methods are not implemented as extension functions in order to - * preserve the calling site, which is supposed to be set by `createApi()` method - * of the concrete logger implementation. - * - * Had we implemented these methods as extension functions, the calling site would - * be `LoggerKt` class, which is not what we want. - */ + public companion object { - /** - * A convenience method for `at(Level.TRACE)`. - */ - public fun atTrace(): API = at(Level.TRACE) + /** + * Singleton instance of the no-op API. + * + * This variable is purposefully declared as an instance of the [NoOp] type + * instead of the [Api] type. This helps ProGuard optimization recognize the type of + * this field easier. This allows ProGuard to strip away low-level logs in Android apps in + * fewer optimization passes. Do not change this to 'Api', or any less specific type. + */ + @VisibleForTesting + @JvmField + internal val NO_OP = NoOp() + } - /** - * A convenience method for `at(Level.DEBUG)`. - */ - public fun atDebug(): API = at(Level.DEBUG) + override fun at(level: Level): Api { + val loggerName = getName() + val mappedLevel = Platform.getMappedLevel(loggerName) + if (Level.OFF == mappedLevel) { + return NO_OP + } + val isLoggable = isLoggable(level) + val isForced = Platform.shouldForceLogging(loggerName, level, isLoggable) + return if (isLoggable || isForced) { + Context(level, isForced).also { + val loggingDomain = loggingDomainOf(cls) + if (loggingDomain.name.isNotEmpty()) { + it.withLoggingDomain(loggingDomain) + } + } + } else { + NO_OP + } + } /** - * A convenience method for `at(Level.INFO)`. + * Logging context implementing the fully specified API for this logger. */ - public fun atInfo(): API = at(Level.INFO) + @VisibleForTesting + internal inner class Context(level: Level, isForced: Boolean) : + LogContext(level, isForced), Api { - /** - * A convenience method for `at(Level.WARNING)`. - */ - public fun atWarning(): API = at(Level.WARNING) + override fun getLogger(): Logger = this@Logger - /** - * A convenience method for `at(Level.ERROR)`. - */ - public fun atError(): API = at(Level.ERROR) + override fun api(): Api = this + + override fun noOp(): Api = NO_OP + } } diff --git a/logging/src/commonMain/kotlin/io/spine/logging/LoggingFactory.kt b/logging/src/commonMain/kotlin/io/spine/logging/LoggingFactory.kt index eb22a2d26..1b829273e 100644 --- a/logging/src/commonMain/kotlin/io/spine/logging/LoggingFactory.kt +++ b/logging/src/commonMain/kotlin/io/spine/logging/LoggingFactory.kt @@ -38,14 +38,14 @@ public expect object LoggingFactory { * * The implementation should return the same logger instance for the same class. */ - public fun > forEnclosingClass(): Logger + public fun forEnclosingClass(): Logger /** * Obtains the logger for the given class. * * The implementation should return the same logger instance for the same class. */ - public fun > loggerFor(cls: KClass<*>): Logger + public fun loggerFor(cls: KClass<*>): Logger /** * Obtains a logging domain for the given class. diff --git a/logging/src/commonMain/kotlin/io/spine/logging/WithLogging.kt b/logging/src/commonMain/kotlin/io/spine/logging/WithLogging.kt index 2587eb86b..8e0618302 100644 --- a/logging/src/commonMain/kotlin/io/spine/logging/WithLogging.kt +++ b/logging/src/commonMain/kotlin/io/spine/logging/WithLogging.kt @@ -36,47 +36,78 @@ import io.spine.logging.LoggingFactory.loggerFor * Usage example: * * ```kotlin - * import io.spine.logging.WithLogging - * * class MyClass : WithLogging { * fun doAction() { * logger.atInfo().log { "Action is in progress." } - * } - * } - * ``` - * - * ### Note for actual implementations * - * Actual implementations are meant to take a logger from [LoggingFactory]: + * // Or, via the level shortcut property: * - * ```kotlin - * import io.spine.logging.LoggingFactory.loggerFor - * - * public actual interface WithLogging { - * public actual val logger: Logger<*> - * get() = loggerFor(this::class) + * atInfo.log { "Action is in progress." } + * } * } * ``` - * - * Indeed, this interface could have a default implementation of [WithLogging.logger] - * if default implementations for expected interfaces have been supported. - * Take a look at [KT-20427](https://youtrack.jetbrains.com/issue/KT-20427/Allow-expect-declarations-with-a-default-implementation) - * for details. - * - * As for now, providing a default implementation for a property makes it - * impossible to customize accessing of a logger in target implementations. */ public interface WithLogging { /** * Returns the logger created for this class. */ - public val logger: Logger<*> + public val logger: Logger get() = loggerFor(this::class) /** * Convenience method for obtaining the logger created for this class * when calling from Java code, avoiding the `get` prefix. */ - public fun logger(): Logger<*> = logger + public fun logger(): Logger = logger + + /** + * The shortcut for `logger.atError()`. + */ + public val atError: Logger.Api get() = logger.atError() + + /** + * The shortcut for `logger.atSevere()`. + */ + public val atSevere: Logger.Api get() = logger.atSevere() + + /** + * The shortcut for `logger.atWarning()`. + */ + public val atWarning: Logger.Api get() = logger.atWarning() + + /** + * The shortcut for `logger.atInfo()`. + */ + public val atInfo: Logger.Api get() = logger.atInfo() + + /** + * The shortcut for `logger.atConfig()`. + */ + public val atConfig: Logger.Api get() = logger.atConfig() + + /** + * The shortcut for `logger.atDebug()`. + */ + public val atDebug: Logger.Api get() = logger.atDebug() + + /** + * The shortcut for `logger.atFine()`. + */ + public val atFine: Logger.Api get() = logger.atFine() + + /** + * The shortcut for `logger.atFiner()`. + */ + public val atFiner: Logger.Api get() = logger.atFiner() + + /** + * The shortcut for `logger.atTrace()`. + */ + public val atTrace: Logger.Api get() = logger.atTrace() + + /** + * The shortcut for `logger.atFinest()`. + */ + public val atFinest: Logger.Api get() = logger.atFinest() } diff --git a/logging/src/commonTest/kotlin/io/spine/logging/WithLoggingSpec.kt b/logging/src/commonTest/kotlin/io/spine/logging/WithLoggingSpec.kt index 61634605b..037abed55 100644 --- a/logging/src/commonTest/kotlin/io/spine/logging/WithLoggingSpec.kt +++ b/logging/src/commonTest/kotlin/io/spine/logging/WithLoggingSpec.kt @@ -26,13 +26,14 @@ package io.spine.logging +import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.types.shouldBeSameInstanceAs import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @DisplayName("`WithLogging` interface should") -internal class WithLoggingSpec { +internal class WithLoggingSpec : WithLogging { @Test fun `provide the same logger associated with a class`() { @@ -42,5 +43,21 @@ internal class WithLoggingSpec { usedLogger shouldNotBe null usedLogger shouldBeSameInstanceAs instance.logger } + + @Test + fun `provide shortcut properties for logging level methods of 'Logger'`() { + (atError is Logger.NoOp) shouldBe false + (atSevere is Logger.NoOp) shouldBe false + (atWarning is Logger.NoOp) shouldBe false + (atInfo is Logger.NoOp) shouldBe false + + // Levels below `INFO`. + (atConfig is Logger.NoOp) shouldBe true + (atDebug is Logger.NoOp) shouldBe true + (atFine is Logger.NoOp) shouldBe true + (atFiner is Logger.NoOp) shouldBe true + (atTrace is Logger.NoOp) shouldBe true + (atFinest is Logger.NoOp) shouldBe true + } } diff --git a/logging/src/commonTest/kotlin/io/spine/logging/given/EnclosingMembers.kt b/logging/src/commonTest/kotlin/io/spine/logging/given/EnclosingMembers.kt index f0c4489ef..0b0933875 100644 --- a/logging/src/commonTest/kotlin/io/spine/logging/given/EnclosingMembers.kt +++ b/logging/src/commonTest/kotlin/io/spine/logging/given/EnclosingMembers.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023, TeamDev. All rights reserved. + * Copyright 2025, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,7 +59,7 @@ internal class SealedOverridingChild : EnclosingSealedClass() { } internal data class EnclosingDataClass( - val logger: Logger<*> = LoggingFactory.forEnclosingClass() + val logger: Logger = LoggingFactory.forEnclosingClass() ) @Suppress("unused") diff --git a/logging/src/jvmMain/kotlin/io/spine/logging/JvmLogger.kt b/logging/src/jvmMain/kotlin/io/spine/logging/JvmLogger.kt deleted file mode 100644 index 79a304958..000000000 --- a/logging/src/jvmMain/kotlin/io/spine/logging/JvmLogger.kt +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2025, TeamDev. All rights reserved. - * - * Licensed 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 - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.logging - -import com.google.errorprone.annotations.CheckReturnValue -import io.spine.logging.jvm.Middleman -import kotlin.reflect.KClass -import kotlin.time.DurationUnit -import java.util.logging.Level as JLevel - -/** - * Implements [Logger] using [Middleman] as the underlying implementation. - */ -@CheckReturnValue -public class JvmLogger( - cls: KClass<*>, - internal val delegate: Middleman -) : Logger(cls) { - - /** - * The non-wildcard logging API for the [JvmLogger]. - */ - public interface Api: LoggingApi - - override fun createApi(level: Level): Api { - val floggerApi = delegate.at(level) - return if (floggerApi.isEnabled()) { - ApiImpl(floggerApi) - } else { - NoOp - } - } - - /** - * A no-op singleton implementation of [Api]. - */ - private object NoOp: LoggingApi.NoOp(), Api -} - -/** - * Implements [LoggingApi] wrapping [Middleman.Api]. - */ -@Suppress("TooManyFunctions") -private class ApiImpl(private val delegate: Middleman.Api): JvmLogger.Api { - - private var loggingDomain: LoggingDomain? = null - - override fun withLoggingDomain(domain: LoggingDomain): JvmLogger.Api { - this.loggingDomain = domain - return this - } - - override fun withCause(cause: Throwable?): JvmLogger.Api { - if (cause != null) { - delegate.withCause(cause) - } - return this - } - - override fun withInjectedLogSite(logSite: LogSite): JvmLogger.Api { - delegate.withInjectedLogSite(logSite) - return this - } - - override fun withInjectedLogSite( - internalClassName: String, - methodName: String, - encodedLineNumber: Int, - sourceFileName: String? - ): JvmLogger.Api { - val logSite = injectedLogSite( - internalClassName, methodName, encodedLineNumber, sourceFileName - ) - return withInjectedLogSite(logSite) - } - - override fun with(key: MetadataKey, value: T?): JvmLogger.Api { - delegate.with(key, value) - return this - } - - override fun with(key: MetadataKey): JvmLogger.Api { - delegate.with(key) - return this - } - - override fun withStackTrace(size: StackSize): JvmLogger.Api { - delegate.withStackTrace(size) - return this - } - - override fun every(n: Int): JvmLogger.Api { - delegate.every(n) - return this - } - - override fun onAverageEvery(n: Int): JvmLogger.Api { - delegate.onAverageEvery(n) - return this - } - - override fun atMostEvery(n: Int, unit: DurationUnit): JvmLogger.Api { - delegate.atMostEvery(n, unit) - return this - } - - override fun per(key: Enum<*>?): JvmLogger.Api { - if (key != null) { - delegate.per(key) - } - return this - } - - override fun per(key: T?, strategy: LogPerBucketingStrategy): JvmLogger.Api { - if (key != null) { - delegate.per(key, strategy) - } - return this - } - - override fun per(scopeProvider: LoggingScopeProvider): JvmLogger.Api { - delegate.per(scopeProvider) - return this - } - - override fun isEnabled(): Boolean = delegate.isEnabled() - - override fun log() = - delegate - .withInjectedLogSite(LogSiteLookup.callerOf(ApiImpl::class)) - .log() - - override fun log(message: () -> String?) { - if (isEnabled()) { - val prefix = loggingDomain.messagePrefix - delegate - .withInjectedLogSite(LogSiteLookup.callerOf(ApiImpl::class)) - .log { prefix + message.invoke() } - } - } -} - -/** - * Maps [Level] values to its Java logging counterparts. - * - * @see [JLevel.toLevel] - */ -public fun Level.toJavaLogging(): JLevel = when (this) { - Level.DEBUG -> JLevel.FINE - Level.INFO -> JLevel.INFO - Level.WARNING -> JLevel.WARNING - Level.ERROR -> JLevel.SEVERE - Level.ALL -> JLevel.ALL - else -> ConvertedLevel(this) -} - -/** - * Opens the constructor of [JLevel] for creating converting instance. - */ -@Suppress("serial") -private class ConvertedLevel(level: Level): JLevel(level.name, level.value) - -/** - * Converts Java logging level to [Level]. - * - * @see [Level.toJavaLogging] - */ -public fun JLevel.toLevel(): Level = when (this) { - JLevel.FINE -> Level.DEBUG - JLevel.INFO -> Level.INFO - JLevel.WARNING -> Level.WARNING - JLevel.SEVERE -> Level.ERROR - else -> Level(name, intValue()) -} - -/** - * Compares Java logging levels using their [values][JLevel.intValue]. - */ -public operator fun JLevel.compareTo(other: JLevel): Int = - intValue().compareTo(other.intValue()) diff --git a/logging/src/jvmMain/kotlin/io/spine/logging/LevelExts.kt b/logging/src/jvmMain/kotlin/io/spine/logging/LevelExts.kt new file mode 100644 index 000000000..f8f378dda --- /dev/null +++ b/logging/src/jvmMain/kotlin/io/spine/logging/LevelExts.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Licensed 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +@file:JvmName("Levels") + +package io.spine.logging + +import java.util.logging.Level as JLevel + +/** + * Maps [Level] values to its Java logging counterparts. + * + * @see [JLevel.toLevel] + */ +public fun Level.toJavaLogging(): JLevel = when (this) { + Level.DEBUG -> JLevel.FINE + Level.INFO -> JLevel.INFO + Level.WARNING -> JLevel.WARNING + Level.ERROR -> JLevel.SEVERE + Level.ALL -> JLevel.ALL + else -> ConvertedLevel(this) +} + +/** + * Opens the constructor of [JLevel] for creating converting instance. + */ +@Suppress("serial") +private class ConvertedLevel(level: Level) : JLevel(level.name, level.value) + +/** + * Converts Java logging level to [Level]. + * + * @see [Level.toJavaLogging] + */ +public fun JLevel.toLevel(): Level = when (this) { + JLevel.FINE -> Level.DEBUG + JLevel.INFO -> Level.INFO + JLevel.WARNING -> Level.WARNING + JLevel.SEVERE -> Level.ERROR + else -> Level(name, intValue()) +} + +/** + * Compares Java logging levels using their [values][JLevel.intValue]. + */ +public operator fun JLevel.compareTo(other: JLevel): Int = + intValue().compareTo(other.intValue()) diff --git a/logging/src/jvmMain/kotlin/io/spine/logging/LoggingFactory.kt b/logging/src/jvmMain/kotlin/io/spine/logging/LoggingFactory.kt index 17b8c14d7..9ee77f2fa 100644 --- a/logging/src/jvmMain/kotlin/io/spine/logging/LoggingFactory.kt +++ b/logging/src/jvmMain/kotlin/io/spine/logging/LoggingFactory.kt @@ -27,26 +27,29 @@ package io.spine.logging import io.spine.logging.backend.Platform -import io.spine.logging.jvm.Middleman import io.spine.reflect.CallerFinder import kotlin.reflect.KClass /** - * Obtains a [JvmLogger] for a given class. + * Obtains a [Logger] for a given class. */ -public actual object LoggingFactory: ClassValue() { +public actual object LoggingFactory { + + /** + * Associates a [Logger] with a Java class. + */ + public object LoggerClassValue : ClassValue() { + override fun computeValue(cls: Class<*>): Logger = + createForClass(cls) + } @JvmStatic @JvmName("getLogger") // Set the name explicitly to avoid the synthetic `$logging` suffix. - public actual fun > loggerFor(cls: KClass<*>): Logger { - @Suppress("UNCHECKED_CAST") // Safe as `JvmLogger.Api` - val result = get(cls.java) as Logger + public actual fun loggerFor(cls: KClass<*>): Logger { + val result = LoggerClassValue.get(cls.java) return result } - override fun computeValue(cls: Class<*>): JvmLogger = - createForClass(cls) - @JvmStatic public actual fun loggingDomainOf(cls: KClass<*>): LoggingDomain = LoggingDomainClassValue.get(cls) @@ -71,21 +74,18 @@ public actual object LoggingFactory: ClassValue() { public fun repeatedMetadataKey(label: String, type: Class): MetadataKey = repeatedMetadataKey(label, type.kotlin) - private fun createForClass(cls: Class<*>): JvmLogger { + private fun createForClass(cls: Class<*>): Logger { val loggerBackend = Platform.getBackend(cls.name) - val loggerImpl = Middleman(loggerBackend) - // As for now, `JvmLogger` just delegates actual work to Flogger. - return JvmLogger(cls.kotlin, loggerImpl) + return Logger(cls.kotlin, loggerBackend) } @JvmStatic - @Suppress("UNCHECKED_CAST") // `JvmLogger` is casted to `Logger`. - public actual fun > forEnclosingClass(): Logger { + public actual fun forEnclosingClass(): Logger { val factoryClass = LoggingFactory::class.java val callerStackElement = CallerFinder.findCallerOf(factoryClass, 0) val callerClassName = callerStackElement!!.className val callerClass = Class.forName(callerClassName) - val result = get(callerClass) as Logger + val result = LoggerClassValue.get(callerClass) return result } } diff --git a/logging/src/jvmMain/kotlin/io/spine/logging/jvm/Middleman.kt b/logging/src/jvmMain/kotlin/io/spine/logging/jvm/Middleman.kt deleted file mode 100644 index 613ffc734..000000000 --- a/logging/src/jvmMain/kotlin/io/spine/logging/jvm/Middleman.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2023, The Flogger Authors; 2025, TeamDev. All rights reserved. - * - * Licensed 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 - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.logging.jvm - -import io.spine.annotation.VisibleForTesting -import io.spine.logging.AbstractLogger -import io.spine.logging.Level -import io.spine.logging.LogContext -import io.spine.logging.LoggingApi -import io.spine.logging.backend.LoggerBackend -import io.spine.logging.backend.Platform - -/** - * The default implementation of [AbstractLogger] which returns the basic [LoggingApi] - * and uses the default parser and system configured backend. - * - * Note that when extending the logging API or specifying a new parser, you will need to create a - * new logger class (rather than extending this one). Unlike the [LogContext] class, - * which must be extended in order to modify the logging API, this class is not generified and thus - * cannot be modified to produce a different logging API. - * - * The choice to prevent direct extension of loggers was made to ensure that users - * of a specific logger implementation always get the same behavior. - * - * ### API Note - * - * It is expected that this class is going to be merged with `io.spine.logging.JvmLogger` of - * the `logging` module. - * - * @see - * Original Java code for historical context. - */ -public class Middleman(backend: LoggerBackend) : AbstractLogger(backend) { - - /** - * The non-wildcard, fully specified, logging API for this logger. Fluent logger implementations - * should specify a non-wildcard API like this with which to generify the abstract logger. - * - * It is possible to add methods to this logger-specific API directly, but it is recommended that - * a separate top-level API and LogContext is created, allowing it to be shared by other - * implementations. - */ - public interface Api : LoggingApi - - /** - * The non-wildcard, fully specified, no-op API implementation. This is required to provide a - * no-op implementation whose type is compatible with this logger's API. - */ - internal class NoOp : LoggingApi.NoOp(), Api - - public companion object { - - /** - * Singleton instance of the no-op API. - * - * This variable is purposefully declared as an instance of the [NoOp] type - * instead of the [Api] type. This helps ProGuard optimization recognize the type of - * this field easier. This allows ProGuard to strip away low-level logs in Android apps in - * fewer optimization passes. Do not change this to 'Api', or any less specific type. - */ - @VisibleForTesting - @JvmField - internal val NO_OP = NoOp() - - /** - * Returns a new logger instance which parses log messages using `printf` format - * for the enclosing class using the system default logging backend. - */ - @JvmStatic - public fun forEnclosingClass(): Middleman { - // NOTE: It is _vital_ that the call to "caller finder" is made directly - // inside the static factory method. - // See `getCallerFinder()` for more information. - val loggingClass = Platform.getCallerFinder().findLoggingClass(Middleman::class) - return Middleman(Platform.getBackend(loggingClass)) - } - } - - override fun at(level: Level): Api { - val loggerName = getName() - val mappedLevel = Platform.getMappedLevel(loggerName) - if (Level.OFF == mappedLevel) { - return NO_OP - } - val isLoggable = isLoggable(level) - val isForced = Platform.shouldForceLogging(loggerName, level, isLoggable) - return if (isLoggable || isForced) Context(level, isForced) else NO_OP - } - - /** - * Logging context implementing the fully specified API for this logger. - */ - @VisibleForTesting - internal inner class Context(level: Level, isForced: Boolean) : - LogContext(level, isForced), Api { - - override fun getLogger(): Middleman = this@Middleman - - override fun api(): Api = this - - override fun noOp(): Api = NO_OP - } -} diff --git a/logging/src/jvmTest/java/io/spine/logging/JavaLoggingFactoryTest.java b/logging/src/jvmTest/java/io/spine/logging/JavaLoggingFactoryTest.java index 44fe60541..79dc5e2c7 100644 --- a/logging/src/jvmTest/java/io/spine/logging/JavaLoggingFactoryTest.java +++ b/logging/src/jvmTest/java/io/spine/logging/JavaLoggingFactoryTest.java @@ -92,7 +92,7 @@ void enumClass() { void anonymousClass() { var anonymous = new Supplier<>() { @Override - public Logger get() { + public Logger get() { return LoggingFactory.forEnclosingClass(); } }; diff --git a/logging/src/jvmTest/java/io/spine/logging/given/LoggingEnum.java b/logging/src/jvmTest/java/io/spine/logging/given/LoggingEnum.java index 02e913d61..57ee580e5 100644 --- a/logging/src/jvmTest/java/io/spine/logging/given/LoggingEnum.java +++ b/logging/src/jvmTest/java/io/spine/logging/given/LoggingEnum.java @@ -37,12 +37,12 @@ public enum LoggingEnum { ONE, TWO, THREE; - private static final Logger logger = LoggingFactory.forEnclosingClass(); + private static final Logger logger = LoggingFactory.forEnclosingClass(); /** * Returns a logger used by this enum. */ - public static Logger usedLogger() { + public static Logger usedLogger() { return logger; } } diff --git a/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtility.java b/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtility.java index 429dbc54f..701dbae42 100644 --- a/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtility.java +++ b/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtility.java @@ -35,7 +35,7 @@ */ public final class LoggingUtility { - private static final Logger logger = LoggingFactory.forEnclosingClass(); + private static final Logger logger = LoggingFactory.forEnclosingClass(); /** * Prevents instantiation of this utility class. @@ -46,7 +46,7 @@ private LoggingUtility() { /** * Returns a logger used by this utility. */ - public static Logger usedLogger() { + public static Logger usedLogger() { return logger; } @@ -56,7 +56,7 @@ public static Logger usedLogger() { */ public static final class NestedUtility { - private static final Logger logger = LoggingFactory.forEnclosingClass(); + private static final Logger logger = LoggingFactory.forEnclosingClass(); /** * Prevents instantiation of this utility class. @@ -67,7 +67,7 @@ private NestedUtility() { /** * Returns a logger used by this utility. */ - public static Logger usedLogger() { + public static Logger usedLogger() { return logger; } } diff --git a/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtilityA.java b/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtilityA.java index 73a202bec..2e93b0aef 100644 --- a/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtilityA.java +++ b/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtilityA.java @@ -35,7 +35,7 @@ */ public final class LoggingUtilityA { - private static final Logger logger = LoggingFactory.forEnclosingClass(); + private static final Logger logger = LoggingFactory.forEnclosingClass(); /** * Prevents instantiation of this utility class. @@ -46,7 +46,7 @@ private LoggingUtilityA() { /** * Returns a logger used by this utility. */ - public static Logger usedLogger() { + public static Logger usedLogger() { return logger; } } diff --git a/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtilityB.java b/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtilityB.java index 94116e995..1b98c7ab3 100644 --- a/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtilityB.java +++ b/logging/src/jvmTest/java/io/spine/logging/given/LoggingUtilityB.java @@ -35,7 +35,7 @@ */ public final class LoggingUtilityB { - private static final Logger logger = LoggingFactory.forEnclosingClass(); + private static final Logger logger = LoggingFactory.forEnclosingClass(); /** * Prevents instantiation of this utility class. @@ -46,7 +46,7 @@ private LoggingUtilityB() { /** * Returns a logger used by this utility. */ - public static Logger usedLogger() { + public static Logger usedLogger() { return logger; } } diff --git a/logging/src/jvmTest/kotlin/io/spine/logging/JvmLoggerSpec.kt b/logging/src/jvmTest/kotlin/io/spine/logging/JvmLoggerSpec.kt index 835d6cd53..a1ed9fe0a 100644 --- a/logging/src/jvmTest/kotlin/io/spine/logging/JvmLoggerSpec.kt +++ b/logging/src/jvmTest/kotlin/io/spine/logging/JvmLoggerSpec.kt @@ -59,12 +59,12 @@ internal class JvmLoggerSpec { @Test fun `create no-op instance when logging level is too low`() { // The default level (with Java logging as the backend) is `INFO`. - (logger.atTrace() is LoggingApi.NoOp) shouldBe true - (logger.atDebug() is LoggingApi.NoOp) shouldBe true + (logger.atTrace() is Logger.NoOp) shouldBe true + (logger.atDebug() is Logger.NoOp) shouldBe true - (logger.atInfo() is LoggingApi.NoOp) shouldBe false - (logger.atWarning() is LoggingApi.NoOp) shouldBe false - (logger.atError() is LoggingApi.NoOp) shouldBe false + (logger.atInfo() is Logger.NoOp) shouldBe false + (logger.atWarning() is Logger.NoOp) shouldBe false + (logger.atError() is Logger.NoOp) shouldBe false } @Test diff --git a/logging/src/jvmTest/kotlin/io/spine/logging/JvmWithLoggingSpec.kt b/logging/src/jvmTest/kotlin/io/spine/logging/JvmWithLoggingSpec.kt index ce219d1fa..c38170e9a 100644 --- a/logging/src/jvmTest/kotlin/io/spine/logging/JvmWithLoggingSpec.kt +++ b/logging/src/jvmTest/kotlin/io/spine/logging/JvmWithLoggingSpec.kt @@ -38,7 +38,7 @@ internal class JvmWithLoggingSpec { class LoggingClass : WithLogging val instance = LoggingClass() val usedLogger = instance.logger - usedLogger::class shouldBe JvmLogger::class + usedLogger::class shouldBe Logger::class } } diff --git a/logging/src/jvmTest/kotlin/io/spine/logging/LogContextSpec.kt b/logging/src/jvmTest/kotlin/io/spine/logging/LogContextSpec.kt index 4d26970db..18f6ec7b2 100644 --- a/logging/src/jvmTest/kotlin/io/spine/logging/LogContextSpec.kt +++ b/logging/src/jvmTest/kotlin/io/spine/logging/LogContextSpec.kt @@ -51,7 +51,6 @@ import io.spine.logging.given.ConfigurableLogger import io.spine.logging.given.FakeLogSite import io.spine.logging.given.iterate import io.spine.logging.given.shouldHaveMessage -import io.spine.logging.jvm.Middleman import java.lang.System.currentTimeMillis import kotlin.time.DurationUnit.MILLISECONDS import kotlin.time.DurationUnit.SECONDS @@ -70,7 +69,7 @@ import org.junit.jupiter.api.Test internal class LogContextSpec { private val backend = MemoizingLoggerBackend() - private val logger = Middleman(backend) + private val logger = Logger(this::class, backend) companion object { private val REPEATED_KEY = MetadataKey.repeated("str") @@ -83,7 +82,7 @@ internal class LogContextSpec { // In normal use, the logger would never need to be passed in, // and you'd use `logVarargs()`. private fun logHelper( - logger: Middleman, + logger: Logger, logSite: LogSite, n: Int, message: String diff --git a/logging/src/jvmTest/kotlin/io/spine/logging/LoggerSpec.kt b/logging/src/jvmTest/kotlin/io/spine/logging/LoggerSpec.kt index a697497d0..87e5ac08f 100644 --- a/logging/src/jvmTest/kotlin/io/spine/logging/LoggerSpec.kt +++ b/logging/src/jvmTest/kotlin/io/spine/logging/LoggerSpec.kt @@ -30,12 +30,11 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf import io.kotest.matchers.types.shouldNotBeInstanceOf import io.spine.logging.backend.probe.MemoizingLoggerBackend -import io.spine.logging.jvm.Middleman import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test /** - * Tests for [io.spine.logging.jvm.Middleman]. + * Tests for [io.spine.logging.jvm.Logger]. * * Fluent loggers are typically very simple classes whose only real * responsibility is to be a factory for specific API implementations. @@ -50,25 +49,10 @@ import org.junit.jupiter.api.Test @DisplayName("`Middleman` should") internal class LoggerSpec { - @Test - fun `create a logger for enclosing class`() { - val logger = Middleman.Companion.forEnclosingClass() - val enclosingClass = this::class.java.name - logger.getName() shouldBe enclosingClass - - // Note that this one-to-one binding of loggers and backends is not - // strictly necessary, and in the future it is plausible that a configured - // backend factory might return backends shared with many loggers. - // In that situation, the logger name must still be the enclosing class name - // (held separately by the logger itself) while the backend name could differ. - val backend = logger.getBackend() - backend.loggerName shouldBe enclosingClass - } - @Test fun `provide a no-op API for disabled levels`() { val backend = MemoizingLoggerBackend() - val logger = Middleman(backend) + val logger = Logger(this::class, backend) backend.setLevel(Level.INFO) // Down to and including the configured log level are not no-op instances. @@ -76,9 +60,9 @@ internal class LoggerSpec { logger.atWarning().shouldNotBeInstanceOf>() logger.atInfo().shouldNotBeInstanceOf>() - logger.atSevere().shouldBeInstanceOf() - logger.atWarning().shouldBeInstanceOf() - logger.atInfo().shouldBeInstanceOf() + logger.atSevere().shouldBeInstanceOf() + logger.atWarning().shouldBeInstanceOf() + logger.atInfo().shouldBeInstanceOf() // Below the configured log level, you only get no-op instances. logger.atFine().shouldBeInstanceOf>() diff --git a/pom.xml b/pom.xml index 4f439c547..4c0958059 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine spine-logging -2.0.0-SNAPSHOT.390 +2.0.0-SNAPSHOT.400 2015 diff --git a/tests/fixtures/src/jvmMain/kotlin/io/spine/logging/context/LoggerTest.kt b/tests/fixtures/src/jvmMain/kotlin/io/spine/logging/context/LoggerTest.kt index 4ac153acf..c2ff42285 100644 --- a/tests/fixtures/src/jvmMain/kotlin/io/spine/logging/context/LoggerTest.kt +++ b/tests/fixtures/src/jvmMain/kotlin/io/spine/logging/context/LoggerTest.kt @@ -49,7 +49,7 @@ public abstract class LoggerTest( /** * The logger under the test, which is created for the [given class][cls]. */ - protected val logger: Logger<*> = LoggingFactory.forEnclosingClass() + protected val logger: Logger = LoggingFactory.forEnclosingClass() init { @Suppress("LeakingThis") diff --git a/tests/smoke-test/src/main/kotlin/io/spine/logging/AbstractLoggingSmokeTest.kt b/tests/smoke-test/src/main/kotlin/io/spine/logging/AbstractLoggingSmokeTest.kt index 06e3de85f..dcdc1ef7f 100644 --- a/tests/smoke-test/src/main/kotlin/io/spine/logging/AbstractLoggingSmokeTest.kt +++ b/tests/smoke-test/src/main/kotlin/io/spine/logging/AbstractLoggingSmokeTest.kt @@ -86,7 +86,7 @@ public abstract class AbstractLoggingSmokeTest { public fun `log using instance of 'JvmLogger'`() { val loggingClass = AbstractLoggingSmokeTest::class val logger = LoggingFactory.loggerFor(loggingClass).also { - it::class shouldBe JvmLogger::class + it::class shouldBe Logger::class } val output = tapJavaLogging { logger.atInfo() diff --git a/version.gradle.kts b/version.gradle.kts index 5a3f3015a..3047396fd 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -24,4 +24,4 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -val versionToPublish: String by extra("2.0.0-SNAPSHOT.390") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.400")