Merge pull request #9 from TouchInstinct/logger-spring

Logger spring
This commit is contained in:
Alexander Buntakov 2021-06-07 19:53:05 +03:00 committed by GitHub
commit 86dccf0624
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1034 additions and 0 deletions

View File

@ -60,3 +60,10 @@
* layout
* context
* format
## logger-spring
Встраивание системы логирования в `spring`
* autologging
* serializer

View File

@ -0,0 +1,20 @@
plugins {
id("kotlin")
id("kotlin-spring")
id("maven-publish")
}
dependencies {
api(project(":logger"))
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(project(":common-spring"))
implementation("org.springframework.boot:spring-boot")
implementation("org.springframework.boot:spring-boot-starter-aop")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}

View File

@ -0,0 +1,8 @@
@file:Suppress("unused")
package ru.touchin.logger.spring
import org.springframework.context.annotation.Import
import ru.touchin.logger.spring.configurations.SpringLoggerConfiguration
@Import(value = [SpringLoggerConfiguration::class])
annotation class EnableSpringLogger

View File

@ -0,0 +1,7 @@
package ru.touchin.logger.spring.annotations
@Target(AnnotationTarget.FUNCTION)
annotation class AutoLogging(
val tags: Array<String>,
val preventError: Boolean = false,
)

View File

@ -0,0 +1,7 @@
package ru.touchin.logger.spring.annotations
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
annotation class LogValue(
val prefix: String = "",
val expand: Boolean = false,
)

View File

@ -0,0 +1,119 @@
package ru.touchin.logger.spring.aspects
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.reflect.MethodSignature
import ru.touchin.logger.spring.annotations.AutoLogging
import ru.touchin.logger.spring.annotations.LogValue
import ru.touchin.logger.builder.LogDataItem
import ru.touchin.logger.factory.LogBuilderFactory
import ru.touchin.logger.dto.LogDuration
import ru.touchin.logger.dto.LogError
import ru.touchin.logger.dto.LogValueField
import ru.touchin.logger.spring.serializers.LogValueFieldSerializer
import java.lang.reflect.AnnotatedElement
import java.lang.reflect.Method
@Aspect
class LogAspect(
private val logBuilderFactory: LogBuilderFactory<*>,
private val logValueFieldSerializer: LogValueFieldSerializer,
) {
@Around("@annotation(autoLoggingAnnotation)")
fun logInvocation(pjp: ProceedingJoinPoint, autoLoggingAnnotation: AutoLogging): Any? {
val duration = LogDuration()
val actionResult = runCatching(pjp::proceed)
try {
val method = pjp.method()
logBuilderFactory.create(method.declaringClass)
.setMethod(method.name)
.setDuration(duration)
.addTags(*autoLoggingAnnotation.tags)
.addData(*getDataItems(pjp, actionResult).toTypedArray())
.setError(actionResult.exceptionOrNull())
.build()
.log()
} catch (error: Exception) {
try {
logBuilderFactory.create(this::class.java)
.addTags(LogError.ERROR_TAG, LogError.ERROR_FATAL_TAG)
.setError(error)
.build()
.error()
} catch (logError: Throwable) {
error.printStackTrace()
logError.printStackTrace()
}
}
return if (autoLoggingAnnotation.preventError) {
actionResult.getOrNull()
} else {
actionResult.getOrThrow()
}
}
private fun getDataItems(pjp: ProceedingJoinPoint, result: Result<Any?>): List<LogDataItem> {
return getArgumentsField(pjp) + getResultFields(pjp.method(), result)
}
private fun getArgumentsField(pjp: ProceedingJoinPoint): List<LogDataItem> {
return pjp.method().parameters
.zip(pjp.args)
.filter { (parameter, value) ->
parameter.hasLogValueAnnotation() && value != null
}
.flatMap { (parameter, value) ->
logValueFieldSerializer.invoke(
LogValueField(
name = parameter.name,
value = value,
prefix = parameter.getLogValuePrefix(),
expand = parameter.getLogValueExpand(),
)
)
}
}
private fun getResultFields(method: Method, result: Result<Any?>): List<LogDataItem> {
if (result.isFailure || !method.hasLogValueAnnotation()) {
return emptyList()
}
val returnValue = result.getOrNull()
?: return emptyList()
return logValueFieldSerializer.invoke(
LogValueField (
name = null,
value = returnValue,
prefix = method.getLogValuePrefix(),
expand = method.getLogValueExpand(),
)
)
}
companion object {
private fun AnnotatedElement.hasLogValueAnnotation() = this.isAnnotationPresent(LogValue::class.java)
private fun AnnotatedElement.getLogValuePrefix(): String? {
val prefix = this.getAnnotation(LogValue::class.java).prefix.trim()
if (prefix.isBlank()) {
return null
}
return prefix
}
private fun AnnotatedElement.getLogValueExpand() = this.getAnnotation(LogValue::class.java).expand
private fun ProceedingJoinPoint.method(): Method = (signature as MethodSignature).method
}
}

View File

@ -0,0 +1,49 @@
package ru.touchin.logger.spring.configurations
import org.springframework.beans.factory.config.ConfigurableBeanFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import org.springframework.context.annotation.Profile
import org.springframework.context.annotation.Scope
import ru.touchin.logger.spring.aspects.LogAspect
import ru.touchin.logger.factory.LogBuilderFactory
import ru.touchin.logger.creator.LogCreator
import ru.touchin.logger.creator.JsonLogCreatorImpl
import ru.touchin.logger.factory.LogBuilderFactoryImpl
import ru.touchin.logger.creator.SimpleLogCreatorImpl
import ru.touchin.logger.dto.LogData
import ru.touchin.logger.spring.serializers.LogValueFieldSerializer
@Configuration
@ComponentScan("ru.touchin.logger.spring.serializers", "ru.touchin.logger.spring.listeners")
class SpringLoggerConfiguration {
@Bean
@Primary
@Profile("json-log")
fun jsonLogCreator(): LogCreator<LogData> {
return JsonLogCreatorImpl()
}
@Bean
fun localLogCreator(): LogCreator<LogData> {
return SimpleLogCreatorImpl()
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
fun logBuilderFactory(logCreator: LogCreator<LogData>): LogBuilderFactory<LogData> {
return LogBuilderFactoryImpl(logCreator)
}
@Bean
fun logAspect(
logBuilderFactory: LogBuilderFactory<LogData>,
logValueFieldSerializer: LogValueFieldSerializer
): LogAspect {
return LogAspect(logBuilderFactory, logValueFieldSerializer)
}
}

View File

@ -0,0 +1,39 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.listeners
import org.springframework.boot.context.event.ApplicationStartedEvent
import org.springframework.context.event.ContextClosedEvent
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component
import ru.touchin.logger.factory.LogBuilderFactory
import ru.touchin.logger.dto.LogData
import ru.touchin.logger.dto.LogDuration
private const val APPLICATION = "application"
@Component
class ApplicationLifeCycleEventListener(
private val logBuilderFactory: LogBuilderFactory<LogData>
) {
lateinit var duration: LogDuration
@EventListener(ApplicationStartedEvent::class)
fun onApplicationStart() {
duration = LogDuration()
logBuilderFactory.create(this::class.java)
.addTags(APPLICATION, "started")
.build()
.log()
}
@EventListener(ContextClosedEvent::class)
fun onApplicationStopped() {
logBuilderFactory.create(this::class.java)
.addTags(APPLICATION, "stopped")
.setDuration(duration)
.build()
.log()
}
}

View File

@ -0,0 +1,10 @@
package ru.touchin.logger.spring.serializers
import ru.touchin.logger.builder.LogDataItem
import ru.touchin.logger.dto.LogValueField
interface LogValueFieldSerializer {
operator fun invoke(field: LogValueField): List<LogDataItem>
}

View File

@ -0,0 +1,72 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers
import org.springframework.stereotype.Component
import ru.touchin.logger.builder.LogDataItem
import ru.touchin.logger.dto.LogValueField
import ru.touchin.logger.spring.serializers.resolvers.LogValueResolver
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
import kotlin.reflect.full.declaredMemberProperties
@Component
class LogValueFieldSerializerImpl(
resolversList: List<LogValueResolver<*>>
) : LogValueFieldSerializer {
private val resolvers = resolversList.asSequence()
private fun resolveFieldName(field: LogValueField, resolvedValue: ResolvedValue<*>): String {
val prefix = field.prefix
if (prefix != null) {
return prefix
}
var suffix = ""
if (resolvedValue.typeName.isNotBlank()) {
suffix = "_${resolvedValue.typeName}"
}
return (field.name ?: DEFAULT_RETURN_FIELD_NAME) + suffix
}
private fun serialize(field: LogValueField): LogDataItem? {
val resolvedValue = resolvers
.mapNotNull { it.invoke(field.value) }
.firstOrNull()
if (resolvedValue?.value == null) {
return null
}
return resolveFieldName(field, resolvedValue) to resolvedValue.value
}
private fun expand(field: LogValueField): List<LogValueField> {
if (!field.expand) {
return listOf(field)
}
return field.value::class.declaredMemberProperties
.mapNotNull { property ->
property.getter.call(field.value)
?.let { propertyValue ->
LogValueField(
name = property.name,
value = propertyValue,
prefix = field.prefix?.let { "$it.${property.name}" }
)
}
}
}
override operator fun invoke(field: LogValueField): List<LogDataItem> {
return expand(field).mapNotNull(this::serialize)
}
companion object {
private const val DEFAULT_RETURN_FIELD_NAME = "result"
}
}

View File

@ -0,0 +1,24 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
@Order(Ordered.NORMAL)
@Component
class BooleanLogValueResolverImpl : LogValueResolver<Boolean> {
override operator fun invoke(value: Any): ResolvedValue<Boolean>? {
if (value !is Boolean) {
return null
}
return ResolvedValue(
value = value,
typeName = "boolean"
)
}
}

View File

@ -0,0 +1,25 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
import java.time.temporal.Temporal
@Order(Ordered.NORMAL)
@Component
class DateLogValueResolverImpl : LogValueResolver<String> {
override fun invoke(value: Any): ResolvedValue<String>? {
if (value !is Temporal) {
return null
}
return ResolvedValue(
value = value.toString(),
typeName = "date",
)
}
}

View File

@ -0,0 +1,24 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
import java.io.File
@Order(Ordered.NORMAL)
@Component
class FileLogValueResolverImpl : LogValueResolver<String> {
override operator fun invoke(value: Any): ResolvedValue<String>? {
if (value !is File) {
return null
}
return ResolvedValue(
value = value.toString(),
)
}
}

View File

@ -0,0 +1,21 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
@Order(Ordered.NORMAL)
@Component
class FunctionLogValueResolverImpl : LogValueResolver<String> {
override operator fun invoke(value: Any): ResolvedValue<String>? {
if (value !is Function<*>) {
return null
}
return ResolvedValue.SKIP_VALUE
}
}

View File

@ -0,0 +1,10 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
interface LogValueResolver<out T: Any> {
operator fun invoke(value: Any): ResolvedValue<T>?
}

View File

@ -0,0 +1,24 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
@Order(Ordered.NORMAL)
@Component
class NumberLogValueResolverImpl : LogValueResolver<Number> {
override operator fun invoke(value: Any): ResolvedValue<Number>? {
if (value !is Number) {
return null
}
return ResolvedValue(
value = value,
typeName = "number"
)
}
}

View File

@ -0,0 +1,24 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
@Order(Ordered.LOW)
@Component
class ObjectLogValueResolverImpl : LogValueResolver<String> {
private val objectMapper = ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
override operator fun invoke(value: Any): ResolvedValue<String> {
return ResolvedValue(
value = objectMapper.writeValueAsString(value)
)
}
}

View File

@ -0,0 +1,22 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
@Order(Ordered.NORMAL)
@Component
class StringLogValueResolverImpl : LogValueResolver<String> {
override fun invoke(value: Any): ResolvedValue<String>? {
if (value !is String) {
return null
}
return ResolvedValue(
value = value
)
}
}

View File

@ -0,0 +1,24 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
import java.util.*
@Order(Ordered.NORMAL)
@Component
class UUIDLogValueResolverImpl : LogValueResolver<String> {
override operator fun invoke(value: Any): ResolvedValue<String>? {
if (value !is UUID) {
return null
}
return ResolvedValue(
value = value.toString(),
)
}
}

View File

@ -0,0 +1,21 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
@Order(Ordered.NORMAL)
@Component
class UnitLogValueResolverImpl : LogValueResolver<String> {
override operator fun invoke(value: Any): ResolvedValue<String>? {
if (value !is Unit) {
return null
}
return ResolvedValue.SKIP_VALUE
}
}

View File

@ -0,0 +1,25 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
import java.net.URI
import java.net.URL
@Order(Ordered.NORMAL)
@Component
class UrlLogValueResolverImpl : LogValueResolver<String> {
override operator fun invoke(value: Any): ResolvedValue<String>? {
if (value !is URL && value !is URI) {
return null
}
return ResolvedValue(
value = value.toString(),
)
}
}

View File

@ -0,0 +1,13 @@
@file:Suppress("unused")
package ru.touchin.logger.spring.serializers.resolvers.dto
class ResolvedValue<out T: Any>(
val value: T?,
val typeName: String = ""
) {
companion object {
val SKIP_VALUE = ResolvedValue(value = null)
}
}

View File

@ -0,0 +1,6 @@
package ru.touchin.logger
import org.springframework.boot.autoconfigure.SpringBootApplication
@SpringBootApplication
class TestApplication

View File

@ -0,0 +1,432 @@
package ru.touchin.logger.serializers.impl
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import ru.touchin.logger.dto.LogValueField
import ru.touchin.logger.spring.serializers.LogValueFieldSerializer
import java.io.File
import java.math.BigDecimal
import java.net.URI
import java.time.LocalDate
import java.time.ZonedDateTime
import java.util.*
@SpringBootTest
internal class LogValueFieldResolverImplTest {
private val birthDate = "2020-05-15"
@Autowired
private lateinit var logValueFieldSerializer: LogValueFieldSerializer
private val objectMapper = ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
@Suppress("unused")
class Pet(
val nickname: String,
val weight: Double,
val hasTail: Boolean,
)
@Suppress("unused")
class User(
val name: String,
val age: Int?,
val birthDate: LocalDate = LocalDate.parse("2020-05-12"),
val pet: Pet? = null
)
@Test
@DisplayName("Префикс должен быть приоритетнее имени")
fun shouldUsePrefixInsteadName() {
val value = 25
val logValueField = LogValueField(
name = "myAge",
value = value,
prefix = "prefixName",
)
val result = logValueFieldSerializer.invoke(logValueField)
assertEquals(1, result.size)
val field = result.first()
assertEquals(logValueField.prefix, field.first)
}
@Test
@DisplayName("Если имя не указано, то название параметра должно быть result")
fun shouldBeFieldNameResultIfNameOmit() {
val value = "john"
val logValueField = LogValueField(
name = null,
value = value,
prefix = null,
)
val result = logValueFieldSerializer.invoke(logValueField)
assertEquals(1, result.size)
val field = result.first()
assertEquals("result", field.first)
}
@Test
@DisplayName("Для примитивных аргументов должен добавляться суффикс с типом")
fun shouldBeSuffixTypeForPrimitiveArguments() {
val value = 25
val logValueField = LogValueField(
name = "age",
value = value,
prefix = null,
)
val result = logValueFieldSerializer.invoke(logValueField)
assertEquals(1, result.size)
val field = result.first()
assertTrue(field.first == "age_number")
}
@Test
@DisplayName("Для примитивных значение в return должен добавляться префикс")
fun shouldBeSuffixTypeForPrimitiveResult() {
val value = true
val logValueField = LogValueField(
name = null,
value = value,
prefix = null,
)
val result = logValueFieldSerializer.invoke(logValueField)
assertEquals(1, result.size)
val field = result.first()
assertEquals("result_boolean", field.first)
}
@Test
@DisplayName("Объект должен быть строкой, если не указан expand")
fun shouldSerializeObjectValue() {
val value = User(
name = "john",
age = 15,
birthDate = LocalDate.parse(birthDate)
)
val logValueField = LogValueField(
name = "user",
value = value,
)
val result = logValueFieldSerializer.invoke(logValueField)
assertEquals(1, result.size)
val field = result.first()
assertEquals("user", field.first)
assertTrue(field.second is String)
assertEquals(objectMapper.writeValueAsString(value), field.second)
}
@Test
@DisplayName("Объекты со значением Unit должны отфильтровываться")
fun shouldOmitUnit() {
val logValueField = LogValueField(
name = "null",
value = Unit,
prefix = "prefixName",
)
val result = logValueFieldSerializer.invoke(logValueField)
assertTrue(result.isEmpty())
}
@Test
@DisplayName("Объекты со значением Function должны отфильтровываться")
fun shouldOmitFunction() {
val logValueField = LogValueField(
name = "null",
value = {},
prefix = "prefixName",
)
val result = logValueFieldSerializer.invoke(logValueField)
assertTrue(result.isEmpty())
}
@Test
@DisplayName("Если указан expand для объекта, то его поля поднимаются на один уровень с аргументами")
fun shouldExpandObjectValues() {
val value = User(
name = "john",
age = 15,
birthDate = LocalDate.parse(birthDate)
)
val logValueField = LogValueField(
name = "user",
value = value,
expand = true
)
val result = logValueFieldSerializer.invoke(logValueField).sortedBy { it.first }
assertEquals(3, result.size)
val (ageItem, birthDateItem, nameItem) = result
assertEquals("age_number", ageItem.first)
assertEquals(value.age, ageItem.second)
assertEquals("birthDate_date", birthDateItem.first)
assertEquals(birthDate, birthDateItem.second)
assertEquals("name", nameItem.first)
assertEquals(value.name, nameItem.second)
}
@Test
@DisplayName("Если expand объекта есть поле-объект, то это поле сериализуется в строку")
fun shouldSerializeObjectIfParentExpand() {
val value = User(
name = "john",
age = 15,
birthDate = LocalDate.parse(birthDate),
pet = Pet("Rex", 56.13, false)
)
val logValueField = LogValueField(
name = "user",
value = value,
expand = true
)
val result = logValueFieldSerializer.invoke(logValueField).sortedBy { it.first }
assertEquals(4, result.size)
val (_, _, _, petItem) = result
assertEquals("pet", petItem.first)
assertTrue(petItem.second is String)
assertEquals(objectMapper.writeValueAsString(value.pet), petItem.second)
}
@Test
@DisplayName("Если указан expand с префиксом, то названия полей формируется как `prefix.field`")
fun shouldExpandObjectWithPrefix() {
val value = User(
name = "john",
age = 15,
birthDate = LocalDate.parse(birthDate),
pet = Pet("Rex", 56.13, false)
)
val logValueField = LogValueField(
name = "object",
value = value,
prefix = "user",
expand = true
)
val result = logValueFieldSerializer
.invoke(logValueField)
.map { it.first }
.sorted()
assertEquals(4, result.size)
assertEquals(listOf("user.age", "user.birthDate", "user.name", "user.pet"), result)
}
@Test
@DisplayName("Если указан expand для return, то его поля попадают в список")
fun shouldExpandReturnValues() {
val value = User(
name = "john",
age = 15,
birthDate = LocalDate.parse(birthDate)
)
val logValueField = LogValueField(
name = null,
value = value,
expand = true
)
val result = logValueFieldSerializer.invoke(logValueField).sortedBy { it.first }
assertEquals(3, result.size)
val (ageItem, birthDateItem, nameItem) = result
assertEquals("age_number", ageItem.first)
assertEquals(value.age, ageItem.second)
assertEquals("birthDate_date", birthDateItem.first)
assertEquals(birthDate, birthDateItem.second)
assertEquals("name", nameItem.first)
assertEquals(value.name, nameItem.second)
}
@Test
@DisplayName("Если указан expand для return и префикс, то его поля попадают в список с названием `prefix.field`")
fun shouldExpandReturnWithPrefix() {
val value = User(
name = "john",
age = 15,
birthDate = LocalDate.parse(birthDate),
pet = Pet("Rex", 56.13, false)
)
val logValueField = LogValueField(
name = null,
value = value,
prefix = "user",
expand = true
)
val result = logValueFieldSerializer
.invoke(logValueField)
.map { it.first }
.sorted()
assertEquals(4, result.size)
assertEquals(listOf("user.age", "user.birthDate", "user.name", "user.pet"), result)
}
@Test
@DisplayName("Даты должны сериализоваться в строку")
fun shouldBeDateString() {
val date = "2020-12-21T12:09:12Z"
val value = ZonedDateTime.parse(date)
val logValueField = LogValueField(
name = "birthdate",
value = value,
)
val result = logValueFieldSerializer.invoke(logValueField)
assertEquals(1, result.size)
val field = result.first()
assertEquals("birthdate_date", field.first)
assertEquals(date, field.second)
}
@Test
@DisplayName("URI должны сериализоваться в строку")
fun shouldBeUriString() {
val uri = URI.create("http://example.com/test")
val logValueField = LogValueField(
name = "url",
value = uri,
)
val result = logValueFieldSerializer.invoke(logValueField)
assertEquals(1, result.size)
val field = result.first()
assertEquals("url", field.first)
assertEquals(field.second, uri.toString())
}
@Test
@DisplayName("URL должны сериализоваться в строку")
fun shouldBeUrlString() {
val url = URI.create("http://example.com/test").toURL()
val logValueField = LogValueField(
name = "url",
value = url,
)
val result = logValueFieldSerializer.invoke(logValueField)
assertEquals(1, result.size)
val field = result.first()
assertEquals("url", field.first)
assertEquals(field.second, url.toString())
}
@Test
@DisplayName("Files должны сериализоваться в строку")
fun shouldFilePath() {
val path = "/tmp/filename"
val file = File(path)
val logValueField = LogValueField(
name = "file",
value = file,
)
val result = logValueFieldSerializer.invoke(logValueField)
assertEquals(1, result.size)
val field = result.first()
assertEquals("file", field.first)
assertEquals(field.second, path)
}
@Test
@DisplayName("UUID должны сериализоваться в строку")
fun shouldBeUUIDString() {
val uuidString = "f1036cc5-2d86-4dde-b27c-820fb11f1974"
val uuid = UUID.fromString(uuidString)
val logValueField = LogValueField(
name = "uuid",
value = uuid,
)
val result = logValueFieldSerializer.invoke(logValueField)
assertEquals(1, result.size)
val field = result.first()
assertEquals("uuid", field.first)
assertEquals(field.second, uuidString)
}
@Test
@DisplayName("Number не должен сериализоваться в строку")
fun shouldBeInt() {
val b: Byte = 128.toByte()
val s: Short = 256
val i = 512
val l = 1024L
val f = 1.2f
val d = 12.13
val bd: BigDecimal = BigDecimal.valueOf(12.10)
val values = listOf(
b, s, i, l, f, d, bd
)
values.forEach { value ->
val logValueField = LogValueField(
name = null,
value = value,
)
val result = logValueFieldSerializer.invoke(logValueField).also {
assertEquals(1, it.size)
}
val field = result.first()
assertEquals("result_number", field.first)
assertEquals(value, field.second)
}
}
}

View File

@ -26,3 +26,4 @@ include("common-spring-web")
include("common-spring-test")
include("common-spring-test-jpa")
include("logger")
include("logger-spring")