commit
5c154e034b
|
|
@ -52,3 +52,11 @@
|
|||
## common-spring-test-jpa
|
||||
|
||||
Утилиты для тестирования репозиториев
|
||||
|
||||
## logger
|
||||
|
||||
Основные компоненты логирования:
|
||||
|
||||
* layout
|
||||
* context
|
||||
* format
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
plugins {
|
||||
id("kotlin")
|
||||
id("maven-publish")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
|
||||
api(project(":common"))
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation("ch.qos.logback.contrib:logback-jackson")
|
||||
implementation("ch.qos.logback.contrib:logback-json-classic")
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package ru.touchin.logger.builder
|
||||
|
||||
import ru.touchin.logger.context.LogExecutionContextData
|
||||
import ru.touchin.logger.dto.LogData
|
||||
import ru.touchin.logger.dto.LogDuration
|
||||
import ru.touchin.logger.dto.LogLevel
|
||||
import ru.touchin.logger.log.Log
|
||||
|
||||
typealias LogDataItem = Pair<String, Any>
|
||||
|
||||
interface LogBuilder<T> {
|
||||
|
||||
fun addTags(vararg tags: String): LogBuilder<T>
|
||||
fun setError(error: Throwable?): LogBuilder<T>
|
||||
fun setDuration(duration: LogDuration): LogBuilder<T>
|
||||
fun addData(vararg items: LogDataItem): LogBuilder<T>
|
||||
fun setMethod(method: String): LogBuilder<T>
|
||||
fun setContext(context: LogExecutionContextData): LogBuilder<LogData>
|
||||
fun build(): Log<T>
|
||||
fun isEnabled(logLevel: LogLevel): Boolean
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package ru.touchin.logger.builder
|
||||
|
||||
import ru.touchin.logger.context.LogExecutionContextData
|
||||
import ru.touchin.logger.context.LoggerExecutionContext
|
||||
import ru.touchin.logger.log.Log
|
||||
import ru.touchin.logger.dto.LogLevel
|
||||
import ru.touchin.logger.dto.LogData
|
||||
import ru.touchin.logger.dto.LogDuration
|
||||
import ru.touchin.logger.dto.LogError
|
||||
|
||||
class LogBuilderImpl(
|
||||
val createLog: (LogData) -> Log<LogData>
|
||||
) : LogBuilder<LogData> {
|
||||
|
||||
private val logData: LogData = LogData()
|
||||
|
||||
override fun addTags(vararg tags: String): LogBuilder<LogData> = also {
|
||||
logData.tags.addAll(tags)
|
||||
}
|
||||
|
||||
override fun setError(error: Throwable?): LogBuilder<LogData> = also {
|
||||
logData.error = error
|
||||
|
||||
if (error != null) {
|
||||
addTags(LogError.ERROR_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDuration(duration: LogDuration): LogBuilder<LogData> = also {
|
||||
logData.duration = duration.getDurationInMs()
|
||||
}
|
||||
|
||||
override fun addData(vararg items: LogDataItem): LogBuilder<LogData> = also {
|
||||
logData.data.putAll(items)
|
||||
}
|
||||
|
||||
override fun setMethod(method: String): LogBuilder<LogData> = also {
|
||||
logData.method = method
|
||||
}
|
||||
|
||||
override fun setContext(context: LogExecutionContextData): LogBuilder<LogData> = also {
|
||||
logData.ctx = LoggerExecutionContext.current.get()
|
||||
}
|
||||
|
||||
override fun build(): Log<LogData> {
|
||||
if (logData.ctx == null) {
|
||||
logData.ctx = LoggerExecutionContext.current.get()
|
||||
}
|
||||
|
||||
return createLog(logData)
|
||||
}
|
||||
|
||||
override fun isEnabled(logLevel: LogLevel): Boolean {
|
||||
return createLog(LogData()).isEnabled(logLevel)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.touchin.logger.context
|
||||
|
||||
@Suppress("unused", "EnumEntryName")
|
||||
enum class DefaultContextFields {
|
||||
id,
|
||||
host,
|
||||
path,
|
||||
httpMethod,
|
||||
ipAddress,
|
||||
appCode,
|
||||
deviceId,
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
@file:Suppress("unused")
|
||||
package ru.touchin.logger.context
|
||||
|
||||
import ru.touchin.common.context.ExecutionContext
|
||||
|
||||
typealias LogExecutionContextData = Map<String, Any>
|
||||
|
||||
object LoggerExecutionContext {
|
||||
|
||||
val current = ExecutionContext<LogExecutionContextData>(
|
||||
init = { emptyMap() }
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.touchin.logger.creator
|
||||
|
||||
import ru.touchin.logger.log.Log
|
||||
import ru.touchin.logger.log.JsonLogImpl
|
||||
import ru.touchin.logger.dto.LogData
|
||||
|
||||
class JsonLogCreatorImpl : LogCreator<LogData> {
|
||||
|
||||
override fun create(clazz: Class<*>): Log<LogData> {
|
||||
return JsonLogImpl(clazz)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.touchin.logger.creator
|
||||
|
||||
import ru.touchin.logger.log.Log
|
||||
|
||||
interface LogCreator<T> {
|
||||
|
||||
fun create(clazz: Class<*>): Log<T>
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.touchin.logger.creator
|
||||
|
||||
import ru.touchin.logger.log.Log
|
||||
import ru.touchin.logger.log.SimpleLogImpl
|
||||
import ru.touchin.logger.dto.LogData
|
||||
|
||||
class SimpleLogCreatorImpl : LogCreator<LogData> {
|
||||
|
||||
override fun create(clazz: Class<*>): Log<LogData> {
|
||||
return SimpleLogImpl(clazz)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.touchin.logger.dto
|
||||
|
||||
import ru.touchin.logger.context.LogExecutionContextData
|
||||
|
||||
class LogData {
|
||||
|
||||
val tags = mutableSetOf<String>()
|
||||
|
||||
var error: Throwable? = null
|
||||
|
||||
var duration: Long? = null
|
||||
|
||||
val data = mutableMapOf<String, Any>()
|
||||
|
||||
var method: String? = null
|
||||
|
||||
var ctx: LogExecutionContextData? = null
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.touchin.logger.dto
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class LogDuration {
|
||||
|
||||
private val startTime = Duration.ofNanos(System.nanoTime())
|
||||
private var duration: Duration? = null
|
||||
|
||||
fun getDurationInMs(): Long {
|
||||
if (this.duration == null) {
|
||||
this.duration = Duration.ofNanos(System.nanoTime())
|
||||
.minus(startTime)
|
||||
}
|
||||
|
||||
return this.duration!!.toMillis()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
@file:Suppress("unused")
|
||||
package ru.touchin.logger.dto
|
||||
|
||||
data class LogError(
|
||||
val message: String?,
|
||||
val trace: String?
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val ERROR_TAG = "error"
|
||||
const val ERROR_FATAL_TAG = "errorFatal"
|
||||
const val ERROR_BASE_TAG = "errorBase"
|
||||
|
||||
fun Throwable.stackTraceAsString() = this.stackTrace?.fold("") { a, b ->
|
||||
"$a$b\n"
|
||||
}
|
||||
|
||||
fun Throwable.toLogError() = LogError(
|
||||
message = this.message,
|
||||
trace = this.stackTraceAsString()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package ru.touchin.logger.dto
|
||||
|
||||
enum class LogLevel {
|
||||
Trace, Info, Error
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package ru.touchin.logger.dto
|
||||
|
||||
class LogValueField(
|
||||
val name: String?,
|
||||
val value: Any,
|
||||
val prefix: String? = null,
|
||||
val expand: Boolean = false,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.touchin.logger.factory
|
||||
|
||||
import ru.touchin.logger.builder.LogBuilder
|
||||
|
||||
interface LogBuilderFactory<T> {
|
||||
|
||||
fun create(clazz: Class<*>): LogBuilder<T>
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package ru.touchin.logger.factory
|
||||
|
||||
import ru.touchin.logger.builder.LogBuilder
|
||||
import ru.touchin.logger.builder.LogBuilderImpl
|
||||
import ru.touchin.logger.creator.LogCreator
|
||||
import ru.touchin.logger.dto.LogData
|
||||
|
||||
class LogBuilderFactoryImpl(
|
||||
private val logCreator: LogCreator<LogData>
|
||||
) : LogBuilderFactory<LogData> {
|
||||
|
||||
override fun create(clazz: Class<*>): LogBuilder<LogData> {
|
||||
return LogBuilderImpl(
|
||||
createLog = { logData ->
|
||||
logCreator.create(clazz)
|
||||
.also { log ->
|
||||
log.logData = logData
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.touchin.logger.layouts
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent
|
||||
import ch.qos.logback.contrib.json.classic.JsonLayout as ContribJsonLayout
|
||||
|
||||
private const val MESSAGE_TYPE_ATTR_NAME = "messageType"
|
||||
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
class JsonLayout : ContribJsonLayout() {
|
||||
|
||||
var messageType: String? = null
|
||||
|
||||
override fun addCustomDataToJsonMap(map: MutableMap<String, Any>?, event: ILoggingEvent?) {
|
||||
messageType?.let {
|
||||
map?.put(MESSAGE_TYPE_ATTR_NAME, it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package ru.touchin.logger.log
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import ru.touchin.logger.dto.LogLevel
|
||||
import ru.touchin.logger.dto.LogData
|
||||
|
||||
abstract class AbstractLog(clazz: Class<*>) : Log<LogData> {
|
||||
|
||||
class LogMessage (
|
||||
val message: String,
|
||||
val error: Throwable? = null
|
||||
)
|
||||
|
||||
private val logger = LoggerFactory.getLogger(clazz)
|
||||
?: throw IllegalStateException("unable to retrieve Logger")
|
||||
|
||||
override var logData: LogData = LogData()
|
||||
|
||||
protected abstract fun getMessage(): LogMessage
|
||||
|
||||
override fun trace() {
|
||||
if (logger.isTraceEnabled) {
|
||||
val logMessage = getMessage()
|
||||
|
||||
logger.trace(logMessage.message, logMessage.error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun info() {
|
||||
if (logger.isInfoEnabled) {
|
||||
val logMessage = getMessage()
|
||||
|
||||
logger.info(logMessage.message, logMessage.error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun error() {
|
||||
if (logger.isErrorEnabled) {
|
||||
val logMessage = getMessage()
|
||||
|
||||
logger.error(logMessage.message, logMessage.error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun log() {
|
||||
if (logData.error == null) {
|
||||
info()
|
||||
} else {
|
||||
error()
|
||||
}
|
||||
}
|
||||
|
||||
override fun isEnabled(level: LogLevel): Boolean {
|
||||
return when(level) {
|
||||
LogLevel.Trace -> logger.isTraceEnabled
|
||||
LogLevel.Info -> logger.isInfoEnabled
|
||||
LogLevel.Error -> logger.isErrorEnabled
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package ru.touchin.logger.log
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import ru.touchin.logger.dto.LogError
|
||||
import ru.touchin.logger.dto.LogError.Companion.stackTraceAsString
|
||||
import ru.touchin.logger.dto.LogError.Companion.toLogError
|
||||
|
||||
open class JsonLogImpl(clazz: Class<*>) : AbstractLog(clazz) {
|
||||
|
||||
companion object {
|
||||
val objectMapper: ObjectMapper = ObjectMapper()
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
}
|
||||
|
||||
private fun toJson(): JsonNode {
|
||||
val result = objectMapper.convertValue(logData, ObjectNode::class.java)
|
||||
|
||||
logData.error
|
||||
?.toLogError()
|
||||
?.let {
|
||||
objectMapper.convertValue(it, JsonNode::class.java)
|
||||
}
|
||||
?.let {
|
||||
result.remove("error")
|
||||
result.set<ObjectNode>("error", it)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun getMessage(): LogMessage {
|
||||
val message = runCatching {
|
||||
objectMapper.writeValueAsString(toJson())
|
||||
}.getOrElse { throwable ->
|
||||
"""
|
||||
{
|
||||
"tags": ["logParse", "${LogError.ERROR_TAG}", "${LogError.ERROR_FATAL_TAG}"],
|
||||
"error": {
|
||||
"message": "${throwable.message}",
|
||||
"trace": "${throwable.stackTraceAsString()}"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
return LogMessage(message)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package ru.touchin.logger.log
|
||||
|
||||
import ru.touchin.logger.dto.LogLevel
|
||||
|
||||
interface Log<T> {
|
||||
|
||||
var logData: T
|
||||
|
||||
fun trace()
|
||||
fun info()
|
||||
fun error()
|
||||
|
||||
/***
|
||||
* Use loglevel error is logData has error and use info level otherwise
|
||||
*/
|
||||
fun log()
|
||||
|
||||
fun isEnabled(level: LogLevel): Boolean
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package ru.touchin.logger.log
|
||||
|
||||
class SimpleLogImpl(clazz: Class<*>): AbstractLog(clazz) {
|
||||
|
||||
override fun getMessage(): LogMessage {
|
||||
val builder = StringBuilder()
|
||||
|
||||
builder.append("\n\ttags: ${logData.tags.joinToString(",")}")
|
||||
|
||||
if (logData.duration != null) {
|
||||
builder.append("\n\tduration: ${logData.duration}ms")
|
||||
}
|
||||
|
||||
return LogMessage(
|
||||
message = builder.toString(),
|
||||
error = logData.error
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -25,3 +25,4 @@ include("common-spring-jpa")
|
|||
include("common-spring-web")
|
||||
include("common-spring-test")
|
||||
include("common-spring-test-jpa")
|
||||
include("logger")
|
||||
|
|
|
|||
Loading…
Reference in New Issue