From 37db201f871987b7ce046e727eb065cd5834bfbe Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 11 Aug 2023 15:47:25 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E3=83=88=E3=83=A9=E3=83=B3=E3=82=B6?= =?UTF-8?q?=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=81=8C=E3=81=82=E3=82=8B=E7=A8=8B=E5=BA=A6=E7=99=BA=E7=94=9F?= =?UTF-8?q?=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/usbharu/hideout/Application.kt | 4 +- .../usbharu/hideout/plugins/ActivityPub.kt | 37 ++++++++++-------- .../dev/usbharu/hideout/plugins/Routing.kt | 4 +- .../routing/wellknown/WebfingerRouting.kt | 6 +-- .../ActivityPubCreateServiceImpl.kt | 12 ++++-- .../activitypub/ActivityPubLikeServiceImpl.kt | 22 ++++++----- .../ActivityPubReceiveFollowServiceImpl.kt | 38 ++++++++++--------- .../activitypub/ActivityPubUndoServiceImpl.kt | 15 +++++--- .../activitypub/ActivityPubUserServiceImpl.kt | 12 ++++-- .../service/api/WebFingerApiService.kt | 7 ++++ .../service/api/WebFingerApiServiceImpl.kt | 16 ++++++++ .../auth/HttpSignatureVerifyServiceImpl.kt | 8 +++- src/main/resources/logback.xml | 2 +- 13 files changed, 119 insertions(+), 64 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiServiceImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 2c9fc9b5..dc4d2788 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -20,6 +20,7 @@ import dev.usbharu.hideout.service.activitypub.ActivityPubUserService import dev.usbharu.hideout.service.api.IPostApiService import dev.usbharu.hideout.service.api.IUserApiService import dev.usbharu.hideout.service.api.UserAuthApiService +import dev.usbharu.hideout.service.api.WebFingerApiService import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService import dev.usbharu.hideout.service.core.* import dev.usbharu.hideout.service.job.JobQueueParentService @@ -75,7 +76,7 @@ fun Application.parent() { level = LogLevel.INFO } install(httpSignaturePlugin) { - keyMap = KtorKeyMap(get()) + keyMap = KtorKeyMap(get(), get()) } expectSuccess = true } @@ -116,6 +117,7 @@ fun Application.parent() { userQueryService = inject().value, followerQueryService = inject().value, userAuthApiService = inject().value, + webFingerApiService = inject().value, transaction = inject().value ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt index 669c01ec..bb30773b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout.plugins import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ap.JsonLd import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.user.UserAuthService import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.client.* @@ -164,19 +165,21 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo } } -class KtorKeyMap(private val userQueryService: UserQueryService) : KeyMap { +class KtorKeyMap(private val userQueryService: UserQueryService, private val transaction: Transaction) : KeyMap { override fun getPublicKey(keyId: String?): PublicKey = runBlocking { val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey") .substringAfterLast("/") val publicBytes = Base64.getDecoder().decode( - userQueryService.findByNameAndDomain( - username, - Config.configData.domain - ).run { - publicKey - .replace("-----BEGIN PUBLIC KEY-----", "") - .replace("-----END PUBLIC KEY-----", "") - .replace("\n", "") + transaction.transaction { + userQueryService.findByNameAndDomain( + username, + Config.configData.domain + ).run { + publicKey + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replace("\n", "") + } } ) val x509EncodedKeySpec = X509EncodedKeySpec(publicBytes) @@ -187,13 +190,15 @@ class KtorKeyMap(private val userQueryService: UserQueryService) : KeyMap { val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey") .substringAfterLast("/") val publicBytes = Base64.getDecoder().decode( - userQueryService.findByNameAndDomain( - username, - Config.configData.domain - ).privateKey?.run { - replace("-----BEGIN PRIVATE KEY-----", "") - .replace("-----END PRIVATE KEY-----", "") - .replace("\n", "") + transaction.transaction { + userQueryService.findByNameAndDomain( + username, + Config.configData.domain + ).privateKey?.run { + replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replace("\n", "") + } } ) val x509EncodedKeySpec = PKCS8EncodedKeySpec(publicBytes) diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt index a4d5d3e8..5fcd18de 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt @@ -14,6 +14,7 @@ import dev.usbharu.hideout.service.activitypub.ActivityPubUserService import dev.usbharu.hideout.service.api.IPostApiService import dev.usbharu.hideout.service.api.IUserApiService import dev.usbharu.hideout.service.api.UserAuthApiService +import dev.usbharu.hideout.service.api.WebFingerApiService import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.user.IUserService @@ -32,6 +33,7 @@ fun Application.configureRouting( userQueryService: UserQueryService, followerQueryService: FollowerQueryService, userAuthApiService: UserAuthApiService, + webFingerApiService: WebFingerApiService, transaction: Transaction ) { install(AutoHeadResponse) @@ -39,7 +41,7 @@ fun Application.configureRouting( inbox(httpSignatureVerifyService, activityPubService) outbox() usersAP(activityPubUserService, userQueryService, followerQueryService, transaction) - webfinger(userQueryService) + webfinger(webFingerApiService) route("/api/internal/v1") { posts(postService) users(userService, userApiService) diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt index 38e342b3..9642afd0 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt @@ -4,14 +4,14 @@ import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.wellknown.WebFinger import dev.usbharu.hideout.exception.IllegalParameterException import dev.usbharu.hideout.exception.ParameterNotExistException -import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.api.WebFingerApiService import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* -fun Routing.webfinger(userQueryService: UserQueryService) { +fun Routing.webfinger(webFingerApiService: WebFingerApiService) { route("/.well-known/webfinger") { get { val acct = call.request.queryParameters["resource"]?.decodeURLPart() @@ -25,7 +25,7 @@ fun Routing.webfinger(userQueryService: UserQueryService) { .substringAfter("acct:") .trimStart('@') - val userEntity = userQueryService.findByNameAndDomain(accountName, Config.configData.domain) + val userEntity = webFingerApiService.findByNameAndDomain(accountName, Config.configData.domain) val webFinger = WebFinger( subject = acct, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubCreateServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubCreateServiceImpl.kt index b73e3747..85e88b57 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubCreateServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubCreateServiceImpl.kt @@ -5,12 +5,14 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse import dev.usbharu.hideout.domain.model.ap.Create import dev.usbharu.hideout.domain.model.ap.Note import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException +import dev.usbharu.hideout.service.core.Transaction import io.ktor.http.* import org.koin.core.annotation.Single @Single class ActivityPubCreateServiceImpl( - private val activityPubNoteService: ActivityPubNoteService + private val activityPubNoteService: ActivityPubNoteService, + private val transaction: Transaction ) : ActivityPubCreateService { override suspend fun receiveCreate(create: Create): ActivityPubResponse { val value = create.`object` ?: throw IllegalActivityPubObjectException("object is null") @@ -18,8 +20,10 @@ class ActivityPubCreateServiceImpl( throw IllegalActivityPubObjectException("object is not Note") } - val note = value as Note - activityPubNoteService.fetchNote(note) - return ActivityPubStringResponse(HttpStatusCode.OK, "Created") + return transaction.transaction { + val note = value as Note + activityPubNoteService.fetchNote(note) + ActivityPubStringResponse(HttpStatusCode.OK, "Created") + } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt index f73a5e37..a9f12cfa 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt @@ -6,6 +6,7 @@ import dev.usbharu.hideout.domain.model.ap.Like import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException import dev.usbharu.hideout.query.PostQueryService import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.reaction.IReactionService import io.ktor.http.* import org.koin.core.annotation.Single @@ -16,23 +17,26 @@ class ActivityPubLikeServiceImpl( private val activityPubUserService: ActivityPubUserService, private val activityPubNoteService: ActivityPubNoteService, private val userQueryService: UserQueryService, - private val postQueryService: PostQueryService + private val postQueryService: PostQueryService, + private val transaction: Transaction ) : ActivityPubLikeService { override suspend fun receiveLike(like: Like): ActivityPubResponse { val actor = like.actor ?: throw IllegalActivityPubObjectException("actor is null") val content = like.content ?: throw IllegalActivityPubObjectException("content is null") like.`object` ?: throw IllegalActivityPubObjectException("object is null") - val person = activityPubUserService.fetchPerson(actor) - activityPubNoteService.fetchNote(like.`object`!!) + transaction.transaction { + val person = activityPubUserService.fetchPerson(actor) + activityPubNoteService.fetchNote(like.`object`!!) - val user = userQueryService.findByUrl( - person.url - ?: throw IllegalActivityPubObjectException("actor is not found") - ) + val user = userQueryService.findByUrl( + person.url + ?: throw IllegalActivityPubObjectException("actor is not found") + ) - val post = postQueryService.findByUrl(like.`object`!!) + val post = postQueryService.findByUrl(like.`object`!!) - reactionService.receiveReaction(content, actor.substringAfter("://").substringBefore("/"), user.id, post.id) + reactionService.receiveReaction(content, actor.substringAfter("://").substringBefore("/"), user.id, post.id) + } return ActivityPubStringResponse(HttpStatusCode.OK, "") } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImpl.kt index 00b51262..94392412 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImpl.kt @@ -9,6 +9,7 @@ import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob import dev.usbharu.hideout.plugins.postAp import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.job.JobQueueParentService import dev.usbharu.hideout.service.user.IUserService import io.ktor.client.* @@ -22,7 +23,8 @@ class ActivityPubReceiveFollowServiceImpl( private val activityPubUserService: ActivityPubUserService, private val userService: IUserService, private val httpClient: HttpClient, - private val userQueryService: UserQueryService + private val userQueryService: UserQueryService, + private val transaction: Transaction ) : ActivityPubReceiveFollowService { override suspend fun receiveFollow(follow: Follow): ActivityPubResponse { // TODO: Verify HTTP Signature @@ -35,24 +37,26 @@ class ActivityPubReceiveFollowServiceImpl( } override suspend fun receiveFollowJob(props: JobProps) { - val actor = props[ReceiveFollowJob.actor] - val targetActor = props[ReceiveFollowJob.targetActor] - val person = activityPubUserService.fetchPerson(actor, targetActor) - val follow = Config.configData.objectMapper.readValue(props[ReceiveFollowJob.follow]) - httpClient.postAp( - urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), - username = "$targetActor#pubkey", - jsonLd = Accept( - name = "Follow", - `object` = follow, - actor = targetActor + transaction.transaction { + val actor = props[ReceiveFollowJob.actor] + val targetActor = props[ReceiveFollowJob.targetActor] + val person = activityPubUserService.fetchPerson(actor, targetActor) + val follow = Config.configData.objectMapper.readValue(props[ReceiveFollowJob.follow]) + httpClient.postAp( + urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), + username = "$targetActor#pubkey", + jsonLd = Accept( + name = "Follow", + `object` = follow, + actor = targetActor + ) ) - ) - val targetEntity = userQueryService.findByUrl(targetActor) - val followActorEntity = - userQueryService.findByUrl(follow.actor ?: throw java.lang.IllegalArgumentException("Actor is null")) + val targetEntity = userQueryService.findByUrl(targetActor) + val followActorEntity = + userQueryService.findByUrl(follow.actor ?: throw java.lang.IllegalArgumentException("Actor is null")) - userService.followRequest(targetEntity.id, followActorEntity.id) + userService.followRequest(targetEntity.id, followActorEntity.id) + } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUndoServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUndoServiceImpl.kt index 16a8ecec..612146a6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUndoServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUndoServiceImpl.kt @@ -5,6 +5,7 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.domain.model.ap.Undo import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.user.IUserService import io.ktor.http.* import org.koin.core.annotation.Single @@ -14,7 +15,8 @@ import org.koin.core.annotation.Single class ActivityPubUndoServiceImpl( private val userService: IUserService, private val activityPubUserService: ActivityPubUserService, - private val userQueryService: UserQueryService + private val userQueryService: UserQueryService, + private val transaction: Transaction ) : ActivityPubUndoService { override suspend fun receiveUndo(undo: Undo): ActivityPubResponse { if (undo.actor == null) { @@ -33,11 +35,12 @@ class ActivityPubUndoServiceImpl( if (follow.`object` == null) { return ActivityPubStringResponse(HttpStatusCode.BadRequest, "object.object is null") } - - activityPubUserService.fetchPerson(undo.actor!!, follow.`object`) - val follower = userQueryService.findByUrl(undo.actor!!) - val target = userQueryService.findByUrl(follow.`object`!!) - userService.unfollow(target.id, follower.id) + transaction.transaction { + activityPubUserService.fetchPerson(undo.actor!!, follow.`object`) + val follower = userQueryService.findByUrl(undo.actor!!) + val target = userQueryService.findByUrl(follow.`object`!!) + userService.unfollow(target.id, follower.id) + } } else -> {} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt index 3a0cc4b7..a67dc361 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt @@ -6,10 +6,10 @@ import dev.usbharu.hideout.domain.model.ap.Image import dev.usbharu.hideout.domain.model.ap.Key import dev.usbharu.hideout.domain.model.ap.Person import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto -import dev.usbharu.hideout.exception.UserNotFoundException import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException import dev.usbharu.hideout.plugins.getAp import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.user.IUserService import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.client.* @@ -22,13 +22,16 @@ import org.koin.core.annotation.Single class ActivityPubUserServiceImpl( private val userService: IUserService, private val httpClient: HttpClient, - private val userQueryService: UserQueryService + private val userQueryService: UserQueryService, + private val transaction: Transaction ) : ActivityPubUserService { override suspend fun getPersonByName(name: String): Person { + val userEntity = transaction.transaction { + userQueryService.findByNameAndDomain(name, Config.configData.domain) + } // TODO: JOINで書き直し - val userEntity = userQueryService.findByNameAndDomain(name, Config.configData.domain) val userUrl = "${Config.configData.url}/users/$name" return Person( type = emptyList(), @@ -81,7 +84,7 @@ class ActivityPubUserServiceImpl( publicKeyPem = userEntity.publicKey ) ) - } catch (ignore: UserNotFoundException) { + } catch (ignore: NoSuchElementException) { val httpResponse = if (targetActor != null) { httpClient.getAp(url, "$targetActor#pubkey") } else { @@ -108,5 +111,6 @@ class ActivityPubUserServiceImpl( ) person } + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiService.kt new file mode 100644 index 00000000..cdda80b7 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiService.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.service.api + +import dev.usbharu.hideout.domain.model.hideout.entity.User + +interface WebFingerApiService { + suspend fun findByNameAndDomain(name: String, domain: String): User +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiServiceImpl.kt new file mode 100644 index 00000000..66934625 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiServiceImpl.kt @@ -0,0 +1,16 @@ +package dev.usbharu.hideout.service.api + +import dev.usbharu.hideout.domain.model.hideout.entity.User +import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction +import org.koin.core.annotation.Single + +@Single +class WebFingerApiServiceImpl(private val transaction: Transaction, private val userQueryService: UserQueryService) : + WebFingerApiService { + override suspend fun findByNameAndDomain(name: String, domain: String): User { + return transaction.transaction { + userQueryService.findByNameAndDomain(name, domain) + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyServiceImpl.kt index d64d0fd4..e9282d34 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyServiceImpl.kt @@ -2,14 +2,18 @@ package dev.usbharu.hideout.service.auth import dev.usbharu.hideout.plugins.KtorKeyMap import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction import io.ktor.http.* import org.koin.core.annotation.Single import tech.barbero.http.message.signing.SignatureHeaderVerifier @Single -class HttpSignatureVerifyServiceImpl(private val userQueryService: UserQueryService) : HttpSignatureVerifyService { +class HttpSignatureVerifyServiceImpl( + private val userQueryService: UserQueryService, + private val transaction: Transaction +) : HttpSignatureVerifyService { override fun verify(headers: Headers): Boolean { - val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService)).build() + val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService, transaction)).build() return true // build.verify(object : HttpMessage { // override fun headerValues(name: String?): MutableList { diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index ad457f2b..9129b1b2 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -4,7 +4,7 @@ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - +