Skip to content

Commit 0f8f0dd

Browse files
committed
Add fallback to Common Log Format
Signed-off-by: Dmitry Sulman <[email protected]>
1 parent d1fd902 commit 0f8f0dd

File tree

4 files changed

+55
-50
lines changed

4 files changed

+55
-50
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ HttpServer.create()
8080

8181
The library can be configured in several ways:
8282

83-
1. **Default configuration** uses `logback-access.xml` file on the classpath.
83+
1. **Default configuration** uses the `logback-access.xml` file from the classpath or the current directory, with a fallback to the [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format).
8484
2. **System property.** Set `-Dlogback.access.reactor.netty.config` property to specify configuration file location.
85-
3. **Programmatic configuration.** Provide configuration file filename or URL of the resource directly:
85+
3. **Programmatic configuration.** Provide configuration file filename or URL of the classpath resource directly:
8686
```java
8787
// Using specific configuration file by the filename
8888
var factory = new ReactorNettyAccessLogFactory("/path/to/logback-access.xml");

logback-access-reactor-netty/src/main/kotlin/io/github/dmitrysulman/logback/access/reactor/netty/ReactorNettyAccessLogFactory.kt

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import ch.qos.logback.core.status.ErrorStatus
66
import ch.qos.logback.core.status.InfoStatus
77
import ch.qos.logback.core.status.OnConsoleStatusListener
88
import ch.qos.logback.core.status.Status
9-
import ch.qos.logback.core.status.WarnStatus
109
import ch.qos.logback.core.util.StatusListenerConfigHelper
1110
import ch.qos.logback.core.util.StatusPrinter2
1211
import reactor.netty.http.server.logging.AccessLogArgProvider
@@ -120,19 +119,15 @@ class ReactorNettyAccessLogFactory : AccessLogFactory {
120119
}
121120

122121
private fun initialize(
123-
config: URL?,
122+
config: URL,
124123
joranConfigurator: JoranConfigurator,
125124
debug: Boolean,
126125
) {
127126
try {
128-
if (config != null) {
129-
addStatus(InfoStatus("Start configuring with configuration file [${config.file}]", this::class.java.simpleName))
130-
accessContext.name = config.file
131-
joranConfigurator.context = accessContext
132-
joranConfigurator.doConfigure(config)
133-
} else {
134-
addStatus(WarnStatus("No configuration file provided, skipping configuration", this::class.java.simpleName))
135-
}
127+
addStatus(InfoStatus("Start configuring with configuration file [${config.file}]", this::class.java.simpleName))
128+
accessContext.name = config.file
129+
joranConfigurator.context = accessContext
130+
joranConfigurator.doConfigure(config)
136131
if (debug) {
137132
StatusListenerConfigHelper.addOnConsoleListenerInstance(accessContext, OnConsoleStatusListener())
138133
}
@@ -147,7 +142,7 @@ class ReactorNettyAccessLogFactory : AccessLogFactory {
147142
}
148143
}
149144

150-
private fun getDefaultConfig(): URL? {
145+
private fun getDefaultConfig(): URL {
151146
val fileNameFromSystemProperty =
152147
System.getProperty(CONFIG_FILE_NAME_PROPERTY)?.also {
153148
addStatus(
@@ -173,9 +168,11 @@ class ReactorNettyAccessLogFactory : AccessLogFactory {
173168
)
174169
try {
175170
getConfigFromFileName(DEFAULT_CONFIG_FILE_NAME)
176-
} catch (e: FileNotFoundException) {
177-
addStatus(WarnStatus(e.message, this::class.java.simpleName))
178-
null
171+
} catch (_: FileNotFoundException) {
172+
addStatus(
173+
InfoStatus("Not found [$DEFAULT_CONFIG_FILE_NAME], fallback to the default configuration", this::class.java.simpleName),
174+
)
175+
getResource(DEFAULT_CONFIGURATION)
179176
}
180177
}
181178
}
@@ -187,12 +184,15 @@ class ReactorNettyAccessLogFactory : AccessLogFactory {
187184
file.toURI().toURL()
188185
} else {
189186
addStatus(InfoStatus("Not found file [$fileName], checking resource", this::class.java.simpleName))
190-
this::class.java.classLoader.getResource(fileName)?.also {
191-
addStatus(InfoStatus("Found resource [${it.file}]", this::class.java.simpleName))
192-
} ?: throw FileNotFoundException("Configuration file $fileName not found")
187+
getResource(fileName)
193188
}
194189
}
195190

191+
private fun getResource(fileName: String) =
192+
this::class.java.classLoader.getResource(fileName)?.also {
193+
addStatus(InfoStatus("Found resource [${it.file}]", this::class.java.simpleName))
194+
} ?: throw FileNotFoundException("Configuration file $fileName not found")
195+
196196
private fun addStatus(status: Status) {
197197
accessContext.statusManager.add(status)
198198
}
@@ -215,5 +215,10 @@ class ReactorNettyAccessLogFactory : AccessLogFactory {
215215
* The default configuration file name used for the Logback Access configuration.
216216
*/
217217
const val DEFAULT_CONFIG_FILE_NAME = "logback-access.xml"
218+
219+
/**
220+
* The fallback configuration file path.
221+
*/
222+
const val DEFAULT_CONFIGURATION = "logback-access-reactor-netty/logback-access-default-config.xml"
218223
}
219224
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder>
4+
<pattern>common</pattern>
5+
</encoder>
6+
</appender>
7+
8+
<appender-ref ref="STDOUT" />
9+
</configuration>

logback-access-reactor-netty/src/test/kotlin/io/github/dmitrysulman/logback/access/reactor/netty/ReactorNettyAccessLogFactoryTests.kt

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@ package io.github.dmitrysulman.logback.access.reactor.netty
33
import ch.qos.logback.access.common.joran.JoranConfigurator
44
import ch.qos.logback.core.joran.spi.JoranException
55
import ch.qos.logback.core.status.OnConsoleStatusListener
6-
import ch.qos.logback.core.status.Status
76
import io.github.dmitrysulman.logback.access.reactor.netty.ReactorNettyAccessLogFactory.Companion.CONFIG_FILE_NAME_PROPERTY
7+
import io.github.dmitrysulman.logback.access.reactor.netty.ReactorNettyAccessLogFactory.Companion.DEFAULT_CONFIGURATION
88
import io.github.dmitrysulman.logback.access.reactor.netty.ReactorNettyAccessLogFactory.Companion.DEFAULT_CONFIG_FILE_NAME
99
import io.github.dmitrysulman.logback.access.reactor.netty.integration.EventCaptureAppender
1010
import io.kotest.assertions.throwables.shouldThrowExactly
1111
import io.kotest.matchers.booleans.shouldBeTrue
12-
import io.kotest.matchers.nulls.shouldBeNull
1312
import io.kotest.matchers.nulls.shouldNotBeNull
13+
import io.kotest.matchers.shouldBe
14+
import io.kotest.matchers.string.shouldEndWith
1415
import io.kotest.matchers.types.shouldBeTypeOf
1516
import io.mockk.every
16-
import io.mockk.mockk
1717
import io.mockk.spyk
18-
import io.mockk.verify
1918
import org.junit.jupiter.api.Test
2019
import java.io.FileNotFoundException
2120
import java.net.URL
@@ -36,6 +35,7 @@ class ReactorNettyAccessLogFactoryTests {
3635
val defaultAppender = reactorNettyAccessLogFactory.accessContext.getAppender("PROPERTY")
3736
defaultAppender.shouldNotBeNull()
3837
defaultAppender.shouldBeTypeOf<EventCaptureAppender>()
38+
System.clearProperty(CONFIG_FILE_NAME_PROPERTY)
3939
}
4040

4141
@Test
@@ -113,6 +113,7 @@ class ReactorNettyAccessLogFactoryTests {
113113
fun `test not existing filename from configuration property`() {
114114
System.setProperty(CONFIG_FILE_NAME_PROPERTY, "logback-access-not-exist.xml")
115115
shouldThrowExactly<FileNotFoundException> { ReactorNettyAccessLogFactory() }
116+
System.clearProperty(CONFIG_FILE_NAME_PROPERTY)
116117
}
117118

118119
@Test
@@ -121,36 +122,26 @@ class ReactorNettyAccessLogFactoryTests {
121122
}
122123

123124
@Test
124-
fun `test not existing default config file`() {
125+
fun `test not existing default config file fallback to default configuration`() {
125126
val reactorNettyAccessLogFactory = spyk<ReactorNettyAccessLogFactory>(recordPrivateCalls = true)
126127
every { reactorNettyAccessLogFactory["getConfigFromFileName"](DEFAULT_CONFIG_FILE_NAME) } throws FileNotFoundException()
127128
val getDefaultConfigMethod = reactorNettyAccessLogFactory::class.java.getDeclaredMethod("getDefaultConfig")
128129
getDefaultConfigMethod.trySetAccessible()
129-
val defaultConfigUrl = getDefaultConfigMethod.invoke(reactorNettyAccessLogFactory) as URL?
130-
defaultConfigUrl.shouldBeNull()
131-
}
132-
133-
@Test
134-
fun `test initialization without config`() {
135-
val reactorNettyAccessLogFactory = ReactorNettyAccessLogFactory()
136-
val joranConfigurator = mockk<JoranConfigurator>(relaxed = true)
137-
val initializeMethod =
138-
reactorNettyAccessLogFactory::class.java.getDeclaredMethod(
139-
"initialize",
140-
URL::class.java,
141-
JoranConfigurator::class.java,
142-
Boolean::class.java,
143-
)
144-
initializeMethod.trySetAccessible()
145-
initializeMethod.invoke(reactorNettyAccessLogFactory, null, joranConfigurator, false)
146-
147-
verify(exactly = 0) { joranConfigurator.context }
148-
verify(exactly = 0) { joranConfigurator.doConfigure(any<URL>()) }
149-
150-
reactorNettyAccessLogFactory.accessContext.statusManager.copyOfStatusList
151-
.any {
152-
it.effectiveLevel == Status.WARN &&
153-
it.message == "No configuration file provided, skipping configuration"
154-
}.shouldBeTrue()
130+
val defaultConfigUrl = getDefaultConfigMethod.invoke(reactorNettyAccessLogFactory) as URL
131+
defaultConfigUrl.toString() shouldEndWith DEFAULT_CONFIGURATION
132+
defaultConfigUrl.openStream().reader().use {
133+
it.readText() shouldBe
134+
"""
135+
<configuration>
136+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
137+
<encoder>
138+
<pattern>common</pattern>
139+
</encoder>
140+
</appender>
141+
142+
<appender-ref ref="STDOUT" />
143+
</configuration>
144+
""".trimIndent()
145+
}
155146
}
156147
}

0 commit comments

Comments
 (0)