diff --git a/build.gradle.kts b/build.gradle.kts index b768fcfd..2f176a8b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ val javaImageScalingVersion = "0.8.6" val firebaseVersion = "9.5.0" val simpleJavaMailVersion = "8.12.6" val jacksonVersion = "2.19.2" +val openTelemetry = "1.52.0" val jdaVersion = "6.0.0-preview" val twitter4jVersion = "4.0.7" @@ -91,6 +92,13 @@ dependencies { implementation("com.google.firebase:firebase-admin:$firebaseVersion") implementation("org.simplejavamail:simple-java-mail:$simpleJavaMailVersion") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion") + + implementation("io.opentelemetry:opentelemetry-api:$openTelemetry") + implementation("io.opentelemetry:opentelemetry-sdk:$openTelemetry") + implementation("io.opentelemetry:opentelemetry-exporter-otlp:$openTelemetry") + implementation("io.opentelemetry:opentelemetry-exporter-logging:$openTelemetry") + implementation("io.opentelemetry:opentelemetry-context:$openTelemetry") + implementation("net.dv8tion:JDA:$jdaVersion") implementation("org.twitter4j:twitter4j-core:$twitter4jVersion") implementation("twitter4j-v2:twitter4j-v2-support") { diff --git a/src/main/kotlin/fr/shikkanime/Application.kt b/src/main/kotlin/fr/shikkanime/Application.kt index 9354de60..ec4f75d3 100644 --- a/src/main/kotlin/fr/shikkanime/Application.kt +++ b/src/main/kotlin/fr/shikkanime/Application.kt @@ -20,6 +20,7 @@ private val logger = LoggerFactory.getLogger(Constant.NAME) fun main(args: Array) { logger.info("Starting ${Constant.NAME}...") + TelemetryConfig.initialize() logger.info("Testing Playwright installation...") checkPlaywrightInstallation() diff --git a/src/main/kotlin/fr/shikkanime/controllers/api/AnimeController.kt b/src/main/kotlin/fr/shikkanime/controllers/api/AnimeController.kt index cb3b58c0..15958f77 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/api/AnimeController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/api/AnimeController.kt @@ -7,6 +7,8 @@ import fr.shikkanime.entities.enums.LangType import fr.shikkanime.services.caches.AnimeCacheService import fr.shikkanime.services.caches.MemberFollowAnimeCacheService import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import fr.shikkanime.utils.atStartOfWeek import fr.shikkanime.utils.routes.* import fr.shikkanime.utils.routes.method.Get @@ -17,6 +19,7 @@ import java.util.* @Controller("/api/v1/animes") class AnimeController : HasPageableRoute() { + private val tracer = TelemetryConfig.getTracer("AnimeController") @Inject private lateinit var animeCacheService: AnimeCacheService @Inject private lateinit var memberFollowAnimeCacheService: MemberFollowAnimeCacheService @@ -46,17 +49,17 @@ class AnimeController : HasPageableRoute() { desc ) - return Response.ok( - if (memberUuid != null) { - memberFollowAnimeCacheService.findAllBy(memberUuid, page, limit) - } else { - if (!name.isNullOrBlank()) { + return tracer.trace { + Response.ok( + if (memberUuid != null) { + memberFollowAnimeCacheService.findAllBy(memberUuid, page, limit) + } else if (!name.isNullOrBlank()) { animeCacheService.findAllByName(countryCode, name, page, limit, searchTypes) } else { animeCacheService.findAllBy(countryCode, simulcastUuid, sortParams, page, limit, searchTypes) } - } - ) + ) + } } @Path("/weekly") @@ -74,7 +77,7 @@ class AnimeController : HasPageableRoute() { return Response.badRequest(MessageDto.error("Invalid week format")) }.atStartOfWeek() - return Response.ok(animeCacheService.getWeeklyAnimes(country, memberUuid, startOfWeekDay, searchTypes)) + return tracer.trace { Response.ok(animeCacheService.getWeeklyAnimes(country, memberUuid, startOfWeekDay, searchTypes)) } } @Path("/missed") @@ -86,6 +89,6 @@ class AnimeController : HasPageableRoute() { @QueryParam("limit", "9") limitParam: Int ): Response { val (page, limit, _) = pageableRoute(pageParam, limitParam, null, null) - return Response.ok(memberFollowAnimeCacheService.getMissedAnimes(uuid, page, limit)) + return tracer.trace { Response.ok(memberFollowAnimeCacheService.getMissedAnimes(uuid, page, limit)) } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/controllers/api/AttachmentController.kt b/src/main/kotlin/fr/shikkanime/controllers/api/AttachmentController.kt index f85783a5..4d080a1b 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/api/AttachmentController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/api/AttachmentController.kt @@ -6,6 +6,8 @@ import fr.shikkanime.entities.enums.ImageType import fr.shikkanime.services.AttachmentService import fr.shikkanime.services.caches.AttachmentCacheService import fr.shikkanime.utils.Constant +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import fr.shikkanime.utils.routes.Cached import fr.shikkanime.utils.routes.Controller import fr.shikkanime.utils.routes.Path @@ -17,6 +19,7 @@ import java.util.* @Controller("/api/v1/attachments") class AttachmentController { + private val tracer = TelemetryConfig.getTracer("AttachmentController") @Inject private lateinit var attachmentService: AttachmentService @Inject private lateinit var attachmentCacheService: AttachmentCacheService @@ -27,29 +30,31 @@ class AttachmentController { @QueryParam uuid: UUID?, @QueryParam("type") typeString: String? ): Response { - if (uuid == null || runCatching { UUID.fromString(uuid.toString()) }.isFailure) - Response.badRequest(MessageDto.error("UUID is required")) + return tracer.trace { + if (uuid == null || runCatching { UUID.fromString(uuid.toString()) }.isFailure) + return@trace Response.badRequest(MessageDto.error("UUID is required")) - val type = ImageType.entries.find { it.name.equals(typeString, true) } - ?: return Response.badRequest(MessageDto.error("Invalid type")) + val type = ImageType.entries.find { it.name.equals(typeString, true) } + ?: return@trace Response.badRequest(MessageDto.error("Invalid type")) - // Get attachment from cache or database - val attachment = attachmentCacheService.findByEntityUuidTypeAndActive(uuid!!, type) - ?: return Response.notFound(MessageDto.error("Attachment not found")) + // Get attachment from cache or database + val attachment = attachmentCacheService.findByEntityUuidTypeAndActive(uuid, type) + ?: return@trace Response.notFound(MessageDto.error("Attachment not found")) - // Check if image is in the memory cache - val imageBytes = attachmentService.getContentFromCache(attachment) ?: run { - val file = attachmentService.getFile(attachment) + // Check if image is in the memory cache + val imageBytes = attachmentService.getContentFromCache(attachment) ?: run { + val file = attachmentService.getFile(attachment) - if (!file.exists() || file.length() <= 0 || attachment.uuid in attachmentService.inProgressAttachments) - return Response.notFound(MessageDto.error("Attachment not found")) + if (!file.exists() || file.length() <= 0 || attachment.uuid in attachmentService.inProgressAttachments) + return@trace Response.notFound(MessageDto.error("Attachment not found")) - // Read the file and store it in the cache - val bytes = file.readBytes() - attachmentService.setContentInCache(attachment, bytes) - bytes - } + // Read the file and store it in the cache + val bytes = file.readBytes() + attachmentService.setContentInCache(attachment, bytes) + bytes + } - return Response.multipart(imageBytes, ContentType.parse("image/webp")) + return@trace Response.multipart(imageBytes, ContentType.parse("image/webp")) + } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/controllers/api/EpisodeMappingController.kt b/src/main/kotlin/fr/shikkanime/controllers/api/EpisodeMappingController.kt index eba03cc6..b46cced7 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/api/EpisodeMappingController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/api/EpisodeMappingController.kt @@ -8,6 +8,8 @@ import fr.shikkanime.services.caches.EpisodeMappingCacheService import fr.shikkanime.services.caches.MemberFollowEpisodeCacheService import fr.shikkanime.utils.EncryptionManager import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import fr.shikkanime.utils.routes.* import fr.shikkanime.utils.routes.method.Get import fr.shikkanime.utils.routes.param.QueryParam @@ -18,6 +20,7 @@ import javax.imageio.ImageIO @Controller("/api/v1/episode-mappings") class EpisodeMappingController : HasPageableRoute() { + private val tracer = TelemetryConfig.getTracer("EpisodeMappingController") @Inject private lateinit var episodeMappingCacheService: EpisodeMappingCacheService @Inject private lateinit var episodeVariantService: EpisodeVariantService @Inject private lateinit var memberFollowEpisodeCacheService: MemberFollowEpisodeCacheService @@ -42,13 +45,15 @@ class EpisodeMappingController : HasPageableRoute() { desc ) - return Response.ok( - if (memberUuid != null) { - memberFollowEpisodeCacheService.findAllBy(memberUuid, page, limit) - } else { - episodeMappingCacheService.findAllBy(countryCode, animeUuid, season, sortParameters, page, limit) - } - ) + return tracer.trace { + Response.ok( + if (memberUuid != null) { + memberFollowEpisodeCacheService.findAllBy(memberUuid, page, limit) + } else { + episodeMappingCacheService.findAllBy(countryCode, animeUuid, season, sortParameters, page, limit) + } + ) + } } @Path("/media-image") diff --git a/src/main/kotlin/fr/shikkanime/controllers/api/MemberActionController.kt b/src/main/kotlin/fr/shikkanime/controllers/api/MemberActionController.kt index 62583633..e9ec22ad 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/api/MemberActionController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/api/MemberActionController.kt @@ -5,6 +5,8 @@ import fr.shikkanime.dtos.MessageDto import fr.shikkanime.entities.Member import fr.shikkanime.services.MemberActionService import fr.shikkanime.utils.MapCache +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import fr.shikkanime.utils.routes.Controller import fr.shikkanime.utils.routes.JWTAuthenticated import fr.shikkanime.utils.routes.Path @@ -16,6 +18,7 @@ import java.util.* @Controller("/api/v1/member-actions") class MemberActionController { + private val tracer = TelemetryConfig.getTracer("MemberActionController") @Inject private lateinit var memberActionService: MemberActionService @Path("/validate") @@ -31,7 +34,7 @@ class MemberActionController { return Response.badRequest(MessageDto.error("Action is required")) try { - memberActionService.validateAction(uuid!!, action) + tracer.trace { memberActionService.validateAction(uuid!!, action) } MapCache.invalidate(Member::class.java) return Response.ok() } catch (e: Exception) { diff --git a/src/main/kotlin/fr/shikkanime/controllers/api/MemberController.kt b/src/main/kotlin/fr/shikkanime/controllers/api/MemberController.kt index 68bf646a..145c71cf 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/api/MemberController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/api/MemberController.kt @@ -10,6 +10,8 @@ import fr.shikkanime.services.MemberService import fr.shikkanime.services.caches.MemberCacheService import fr.shikkanime.utils.MapCache import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import fr.shikkanime.utils.routes.* import fr.shikkanime.utils.routes.method.Delete import fr.shikkanime.utils.routes.method.Get @@ -25,6 +27,7 @@ import java.util.* @Controller("/api/v1/members") class MemberController : HasPageableRoute() { + private val tracer = TelemetryConfig.getTracer("MemberController") @Inject private lateinit var memberService: MemberService @Inject private lateinit var memberCacheService: MemberCacheService @Inject private lateinit var memberFollowAnimeService: MemberFollowAnimeService @@ -33,15 +36,17 @@ class MemberController : HasPageableRoute() { @Path("/register") @Post private fun registerMember(): Response { - var identifier: String + return tracer.trace { + var identifier: String - do { - identifier = StringUtils.generateRandomString(12) - } while (memberService.findByIdentifier(identifier) != null) + do { + identifier = StringUtils.generateRandomString(12) + } while (memberService.findByIdentifier(identifier) != null) - memberService.register(identifier) - MapCache.invalidate(Member::class.java) - return Response.created(mapOf("identifier" to identifier)) + memberService.register(identifier) + MapCache.invalidate(Member::class.java) + Response.created(mapOf("identifier" to identifier)) + } } @Path("/login") @@ -51,9 +56,11 @@ class MemberController : HasPageableRoute() { @HttpHeader("X-Device") device: String?, @HttpHeader("X-Locale") locale: String?, @BodyParam identifier: String - ) = memberService.login(identifier, appVersion, device, locale)?.let { Response.ok(it) } ?: runBlocking { - delay(1000) - Response.notFound(MessageDto.error("Member not found")) + ) = tracer.trace { + memberService.login(identifier, appVersion, device, locale)?.let { Response.ok(it) } ?: runBlocking { + delay(1000) + Response.notFound(MessageDto.error("Member not found")) + } } @Path("/associate-email") @@ -99,7 +106,7 @@ class MemberController : HasPageableRoute() { private fun followAnime( @JWTUser memberUuid: UUID, @BodyParam anime: GenericDto - ) = memberFollowAnimeService.follow(memberUuid, anime) + ) = tracer.trace { memberFollowAnimeService.follow(memberUuid, anime) } @Path("/animes") @Delete @@ -107,7 +114,7 @@ class MemberController : HasPageableRoute() { private fun unfollowAnime( @JWTUser memberUuid: UUID, @BodyParam anime: GenericDto - ) = memberFollowAnimeService.unfollow(memberUuid, anime) + ) = tracer.trace { memberFollowAnimeService.unfollow(memberUuid, anime) } @Path("/follow-all-episodes") @Put @@ -115,7 +122,7 @@ class MemberController : HasPageableRoute() { private fun followAllEpisodes( @JWTUser memberUuid: UUID, @BodyParam anime: GenericDto - ) = memberFollowEpisodeService.followAll(memberUuid, anime) + ) = tracer.trace { memberFollowEpisodeService.followAll(memberUuid, anime) } @Path("/episodes") @Put @@ -123,7 +130,7 @@ class MemberController : HasPageableRoute() { private fun followEpisode( @JWTUser memberUuid: UUID, @BodyParam episode: GenericDto - ) = memberFollowEpisodeService.follow(memberUuid, episode) + ) = tracer.trace { memberFollowEpisodeService.follow(memberUuid, episode) } @Path("/episodes") @Delete @@ -131,7 +138,7 @@ class MemberController : HasPageableRoute() { private fun unfollowEpisode( @JWTUser memberUuid: UUID, @BodyParam episode: GenericDto - ) = memberFollowEpisodeService.unfollow(memberUuid, episode) + ) = tracer.trace { memberFollowEpisodeService.unfollow(memberUuid, episode) } @Path("/image") @Post @@ -140,14 +147,16 @@ class MemberController : HasPageableRoute() { @JWTUser memberUuid: UUID, @BodyParam multiPartData: MultiPartData ): Response { - try { - runBlocking { memberService.changeProfileImage(memberCacheService.find(memberUuid)!!, multiPartData) } - } catch (e: Exception) { - return Response.badRequest(MessageDto.error(e.message ?: "Invalid file format")) - } - - MapCache.invalidate(Member::class.java) - return Response.ok() + return tracer.trace { + try { + runBlocking { memberService.changeProfileImage(memberCacheService.find(memberUuid)!!, multiPartData) } + } catch (e: Exception) { + return@trace Response.badRequest(MessageDto.error(e.message ?: "Invalid file format")) + } + + MapCache.invalidate(Member::class.java) + Response.ok() + } } @Path("/refresh") @@ -157,7 +166,9 @@ class MemberController : HasPageableRoute() { @JWTUser memberUuid: UUID, @QueryParam("limit", "9") limitParam: Int ): Response { - val (_, limit, _) = pageableRoute(null, limitParam, null, null) - return Response.ok(memberCacheService.getRefreshMember(memberUuid, limit) ?: return Response.notFound()) + return tracer.trace { + val (_, limit, _) = pageableRoute(null, limitParam, null, null) + Response.ok(memberCacheService.getRefreshMember(memberUuid, limit) ?: return@trace Response.notFound()) + } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/controllers/api/SimulcastController.kt b/src/main/kotlin/fr/shikkanime/controllers/api/SimulcastController.kt index 7024348d..52855a75 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/api/SimulcastController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/api/SimulcastController.kt @@ -2,6 +2,8 @@ package fr.shikkanime.controllers.api import com.google.inject.Inject import fr.shikkanime.services.caches.SimulcastCacheService +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import fr.shikkanime.utils.routes.Controller import fr.shikkanime.utils.routes.Path import fr.shikkanime.utils.routes.Response @@ -9,9 +11,10 @@ import fr.shikkanime.utils.routes.method.Get @Controller("/api/v1/simulcasts") class SimulcastController { + private val tracer = TelemetryConfig.getTracer("SimulcastController") @Inject private lateinit var simulcastCacheService: SimulcastCacheService @Path @Get - private fun getAll() = Response.ok(simulcastCacheService.findAll()) + private fun getAll() = tracer.trace { Response.ok(simulcastCacheService.findAll()) } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/controllers/api/v2/EpisodeMappingController.kt b/src/main/kotlin/fr/shikkanime/controllers/api/v2/EpisodeMappingController.kt index 40603a90..89ba25fc 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/api/v2/EpisodeMappingController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/api/v2/EpisodeMappingController.kt @@ -3,6 +3,8 @@ package fr.shikkanime.controllers.api.v2 import com.google.inject.Inject import fr.shikkanime.entities.enums.CountryCode import fr.shikkanime.services.caches.EpisodeMappingCacheService +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import fr.shikkanime.utils.routes.Controller import fr.shikkanime.utils.routes.HasPageableRoute import fr.shikkanime.utils.routes.Path @@ -12,6 +14,7 @@ import fr.shikkanime.utils.routes.param.QueryParam @Controller("/api/v2/episode-mappings") class EpisodeMappingController : HasPageableRoute() { + private val tracer = TelemetryConfig.getTracer("EpisodeMappingController") @Inject private lateinit var episodeMappingCacheService: EpisodeMappingCacheService @Path @@ -22,13 +25,13 @@ class EpisodeMappingController : HasPageableRoute() { @QueryParam("limit", "9") limitParam: Int, @QueryParam("sort") sortParam: String?, @QueryParam("desc") descParam: String? - ): Response { + ) = tracer.trace { val (page, limit, sortParameters) = if (sortParam.isNullOrBlank() && descParam.isNullOrBlank()) { pageableRoute(pageParam, limitParam, "releaseDateTime,animeName,season,episodeType,number", "releaseDateTime,animeName,season,episodeType,number") } else { pageableRoute(pageParam, limitParam, sortParam, descParam) } - return Response.ok(episodeMappingCacheService.findAllGroupedBy(country, sortParameters, page, limit)) + Response.ok(episodeMappingCacheService.findAllGroupedBy(country, sortParameters, page, limit)) } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt b/src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt index f5db48bc..4dc18a07 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt @@ -9,6 +9,8 @@ import fr.shikkanime.services.caches.ConfigCacheService import fr.shikkanime.services.caches.EpisodeMappingCacheService import fr.shikkanime.services.caches.SimulcastCacheService import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import fr.shikkanime.utils.atStartOfWeek import fr.shikkanime.utils.routes.Controller import fr.shikkanime.utils.routes.Path @@ -22,6 +24,7 @@ import java.time.format.DateTimeFormatter @Controller("/") class SiteController { + private val tracer = TelemetryConfig.getTracer("SiteController") @Inject private lateinit var animeCacheService: AnimeCacheService @Inject private lateinit var episodeMappingCacheService: EpisodeMappingCacheService @Inject private lateinit var simulcastCacheService: SimulcastCacheService @@ -57,46 +60,49 @@ class SiteController { @Path @Get - private fun home() = Response.template( - Link.HOME, - mutableMapOf( - "animes" to getFullAnimesSimulcast(), - "groupedEpisodes" to episodeMappingCacheService.findAllGroupedBy( - CountryCode.FR, - listOf( + private fun home() = tracer.trace { + Response.template( + Link.HOME, + mutableMapOf( + "animes" to getFullAnimesSimulcast(), + "groupedEpisodes" to episodeMappingCacheService.findAllGroupedBy( + CountryCode.FR, + listOf( SortParameter("releaseDateTime", SortParameter.Order.DESC), SortParameter("animeName", SortParameter.Order.DESC), SortParameter("season", SortParameter.Order.DESC), SortParameter("episodeType", SortParameter.Order.DESC), SortParameter("number", SortParameter.Order.DESC), - ), - 1, - 8 - ).data, + ),1, + 8 + ).data, + ) ) - ) + } @Path("catalog/{slug}") @Get private fun catalogSimulcast(@PathParam slug: String): Response { - val findAll = simulcastCacheService.findAll() - val selectedSimulcast = findAll.firstOrNull { it.slug == slug } ?: return Response.notFound() - - return Response.template( - Link.CATALOG.template, - selectedSimulcast.label, - mutableMapOf( - "simulcasts" to findAll, - "selectedSimulcast" to selectedSimulcast, - "animes" to animeCacheService.findAllBy( - CountryCode.FR, - selectedSimulcast.uuid, - listOf(SortParameter("name", SortParameter.Order.ASC)), - 1, - 102 - ).data, + return tracer.trace { + val findAll = simulcastCacheService.findAll() + val selectedSimulcast = findAll.firstOrNull { it.slug == slug } ?: return@trace Response.notFound() + + Response.template( + Link.CATALOG.template, + selectedSimulcast.label, + mutableMapOf( + "simulcasts" to findAll, + "selectedSimulcast" to selectedSimulcast, + "animes" to animeCacheService.findAllBy( + CountryCode.FR, + selectedSimulcast.uuid, + listOf(SortParameter("name", SortParameter.Order.ASC)), + 1, + 102 + ).data, + ) ) - ) + } } private fun getAnimeDetail(slug: String, season: Int? = null, page: Int? = null, sort: String? = null): Response { @@ -139,7 +145,7 @@ class SiteController { @Path("animes/{slug}") @Get - private fun animeDetail(@PathParam slug: String) = getAnimeDetail(slug) + private fun animeDetail(@PathParam slug: String) = tracer.trace { getAnimeDetail(slug) } @Path("animes/{slug}/season-{season}") @Get @@ -148,7 +154,7 @@ class SiteController { @PathParam season: Int, @QueryParam page: Int?, @QueryParam sort: String?, - ) = getAnimeDetail(slug, season, page, sort) + ) = tracer.trace { getAnimeDetail(slug, season, page, sort) } @Path("animes/{slug}/season-{season}/{episodeSlug}") @Get @@ -157,33 +163,35 @@ class SiteController { @PathParam season: Int, @PathParam episodeSlug: String ): Response { - val dto = animeCacheService.findBySlug(CountryCode.FR, slug) ?: return Response.notFound() - if (dto.seasons.isNullOrEmpty() || dto.seasons!!.none { it.number == season }) return Response.redirect("/animes/$slug") - - val match = "(${EpisodeType.entries.joinToString("|") { it.slug }})-(-?\\d+)".toRegex().find(episodeSlug) ?: return Response.notFound() - val episodeType = EpisodeType.fromSlug(match.groupValues[1]) - val episodeNumber = match.groupValues[2].toInt() - - val (previousDto, currentDto, nextDto) = episodeMappingCacheService.findPreviousAndNextBy( - dto.uuid!!, - season, - episodeType, - episodeNumber - ) ?: return Response.redirect("/animes/$slug/season-$season") - - val title = - currentDto.anime!!.shortName + " - ${StringUtils.toEpisodeMappingString(currentDto, separator = false)}" - - return Response.template( - "/site/episodeDetails.ftl", - title, - mutableMapOf( - "description" to currentDto.description, - "episodeMapping" to currentDto, - "previousEpisode" to previousDto, - "nextEpisode" to nextDto, + return tracer.trace { + val dto = animeCacheService.findBySlug(CountryCode.FR, slug) ?: return@trace Response.notFound() + if (dto.seasons.isNullOrEmpty() || dto.seasons!!.none { it.number == season }) return@trace Response.redirect("/animes/$slug") + + val match = "(${EpisodeType.entries.joinToString("|") { it.slug }})-(-?\\d+)".toRegex().find(episodeSlug) ?: return@trace Response.notFound() + val episodeType = EpisodeType.fromSlug(match.groupValues[1]) + val episodeNumber = match.groupValues[2].toInt() + + val (previousDto, currentDto, nextDto) = episodeMappingCacheService.findPreviousAndNextBy( + dto.uuid!!, + season, + episodeType, + episodeNumber + ) ?: return@trace Response.redirect("/animes/$slug/season-$season") + + val title = + currentDto.anime!!.shortName + " - ${StringUtils.toEpisodeMappingString(currentDto, separator = false)}" + + Response.template( + "/site/episodeDetails.ftl", + title, + mutableMapOf( + "description" to currentDto.description, + "episodeMapping" to currentDto, + "previousEpisode" to previousDto, + "nextEpisode" to nextDto, + ) ) - ) + } } @Path("search") @@ -207,27 +215,29 @@ class SiteController { @QueryParam date: String?, @QueryParam searchTypes: Array?, ): Response { - val startOfWeekDay = try { - date?.let { LocalDate.parse(it, DateTimeFormatter.ISO_LOCAL_DATE) } ?: LocalDate.now() - } catch (_: Exception) { - LocalDate.now() - }.atStartOfWeek() - - val minimalReleaseDate = episodeMappingCacheService.findMinimalReleaseDateTime().toLocalDate().atStartOfWeek() - - if (startOfWeekDay < minimalReleaseDate) - return Response.notFound() - - return Response.template( - Link.CALENDAR, - mutableMapOf( - "weeklyAnimes" to animeCacheService.getWeeklyAnimes(CountryCode.FR, null, startOfWeekDay, searchTypes), - "previousWeek" to startOfWeekDay.minusDays(7).takeIf { it >= minimalReleaseDate }, - "searchTypes" to searchTypes.orEmpty().joinToString(StringUtils.COMMA_STRING), - "currentWeek" to startOfWeekDay, - "nextWeek" to startOfWeekDay.plusDays(7).takeIf { it <= ZonedDateTime.now().toLocalDate() } + return tracer.trace { + val startOfWeekDay = try { + date?.let { LocalDate.parse(it, DateTimeFormatter.ISO_LOCAL_DATE) } ?: LocalDate.now() + } catch (_: Exception) { + LocalDate.now() + }.atStartOfWeek() + + val minimalReleaseDate = episodeMappingCacheService.findMinimalReleaseDateTime().toLocalDate().atStartOfWeek() + + if (startOfWeekDay < minimalReleaseDate) + return@trace Response.notFound() + + Response.template( + Link.CALENDAR, + mutableMapOf( + "weeklyAnimes" to animeCacheService.getWeeklyAnimes(CountryCode.FR, null, startOfWeekDay, searchTypes), + "previousWeek" to startOfWeekDay.minusDays(7).takeIf { it >= minimalReleaseDate }, + "searchTypes" to searchTypes.orEmpty().joinToString(StringUtils.COMMA_STRING), + "currentWeek" to startOfWeekDay, + "nextWeek" to startOfWeekDay.plusDays(7).takeIf { it <= ZonedDateTime.now().toLocalDate() } + ) ) - ) + } } @Path("presentation") diff --git a/src/main/kotlin/fr/shikkanime/dtos/PageableDto.kt b/src/main/kotlin/fr/shikkanime/dtos/PageableDto.kt index 5a79fac0..227f1325 100644 --- a/src/main/kotlin/fr/shikkanime/dtos/PageableDto.kt +++ b/src/main/kotlin/fr/shikkanime/dtos/PageableDto.kt @@ -2,6 +2,8 @@ package fr.shikkanime.dtos import fr.shikkanime.entities.miscellaneous.Pageable import fr.shikkanime.factories.IGenericFactory +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace data class PageableDto( val data: Set, @@ -10,6 +12,8 @@ data class PageableDto( val total: Long, ) { companion object { + val tracer = TelemetryConfig.getTracer("PageableDto") + fun empty(): PageableDto { return PageableDto( data = emptySet(), @@ -20,12 +24,14 @@ data class PageableDto( } inline fun fromPageable(pageable: Pageable, factory: IGenericFactory): PageableDto { - return PageableDto( - data = pageable.data.asSequence().map { factory.toDto(it) }.toSet(), - page = pageable.page, - limit = pageable.limit, - total = pageable.total, - ) + return tracer.trace("PageableDto.fromPageable") { + PageableDto( + data = pageable.data.asSequence().map { factory.toDto(it) }.toSet(), + page = pageable.page, + limit = pageable.limit, + total = pageable.total, + ) + } } inline fun fromPageable(pageable: Pageable, fn: (T) -> R): PageableDto { diff --git a/src/main/kotlin/fr/shikkanime/factories/impl/EpisodeVariantFactory.kt b/src/main/kotlin/fr/shikkanime/factories/impl/EpisodeVariantFactory.kt index 725740bb..04bc5d96 100644 --- a/src/main/kotlin/fr/shikkanime/factories/impl/EpisodeVariantFactory.kt +++ b/src/main/kotlin/fr/shikkanime/factories/impl/EpisodeVariantFactory.kt @@ -7,11 +7,8 @@ import fr.shikkanime.factories.IEpisodeVariantFactory import fr.shikkanime.utils.withUTCString class EpisodeVariantFactory : IEpisodeVariantFactory { - @Inject - private lateinit var episodeMappingFactory: EpisodeMappingFactory - - @Inject - private lateinit var platformFactory: PlatformFactory + @Inject private lateinit var episodeMappingFactory: EpisodeMappingFactory + @Inject private lateinit var platformFactory: PlatformFactory override fun toDto( entity: EpisodeVariant, diff --git a/src/main/kotlin/fr/shikkanime/factories/impl/MemberFactory.kt b/src/main/kotlin/fr/shikkanime/factories/impl/MemberFactory.kt index 057edacc..1fc9ab7d 100644 --- a/src/main/kotlin/fr/shikkanime/factories/impl/MemberFactory.kt +++ b/src/main/kotlin/fr/shikkanime/factories/impl/MemberFactory.kt @@ -6,19 +6,19 @@ import fr.shikkanime.dtos.member.TokenDto import fr.shikkanime.entities.Member import fr.shikkanime.entities.enums.ImageType import fr.shikkanime.factories.IGenericFactory -import fr.shikkanime.services.AttachmentService import fr.shikkanime.services.MemberFollowAnimeService import fr.shikkanime.services.MemberFollowEpisodeService +import fr.shikkanime.services.caches.AttachmentCacheService import fr.shikkanime.utils.withUTCString class MemberFactory : IGenericFactory { @Inject private lateinit var memberFollowAnimeService: MemberFollowAnimeService @Inject private lateinit var memberFollowEpisodeService: MemberFollowEpisodeService - @Inject private lateinit var attachmentService: AttachmentService + @Inject private lateinit var attachmentCacheService: AttachmentCacheService override fun toDto(entity: Member): MemberDto { val seenAndUnseenDuration = memberFollowEpisodeService.getSeenAndUnseenDuration(entity) - val attachment = attachmentService.findByEntityUuidTypeAndActive(entity.uuid!!, ImageType.MEMBER_PROFILE) + val attachment = attachmentCacheService.findByEntityUuidTypeAndActive(entity.uuid!!, ImageType.MEMBER_PROFILE) return MemberDto( uuid = entity.uuid, diff --git a/src/main/kotlin/fr/shikkanime/repositories/EpisodeVariantRepository.kt b/src/main/kotlin/fr/shikkanime/repositories/EpisodeVariantRepository.kt index d1beedbe..66cce743 100644 --- a/src/main/kotlin/fr/shikkanime/repositories/EpisodeVariantRepository.kt +++ b/src/main/kotlin/fr/shikkanime/repositories/EpisodeVariantRepository.kt @@ -64,6 +64,19 @@ class EpisodeVariantRepository : AbstractRepository() { } } + fun findAllByMappings(vararg mappingUUIDs: UUID): List { + return database.entityManager.use { + val cb = it.criteriaBuilder + val query = cb.createQuery(getEntityClass()) + val root = query.from(getEntityClass()) + + query.where(root[EpisodeVariant_.mapping][EpisodeMapping_.uuid].`in`(mappingUUIDs.toSet())) + + createReadOnlyQuery(it, query) + .resultList + } + } + fun findAllVariantReleases( countryCode: CountryCode, member: Member?, diff --git a/src/main/kotlin/fr/shikkanime/services/AnimeService.kt b/src/main/kotlin/fr/shikkanime/services/AnimeService.kt index d344dd74..21dfb21b 100644 --- a/src/main/kotlin/fr/shikkanime/services/AnimeService.kt +++ b/src/main/kotlin/fr/shikkanime/services/AnimeService.kt @@ -8,18 +8,15 @@ import fr.shikkanime.entities.* import fr.shikkanime.entities.enums.CountryCode import fr.shikkanime.entities.enums.EpisodeType import fr.shikkanime.entities.enums.LangType -import fr.shikkanime.entities.miscellaneous.Pageable import fr.shikkanime.entities.miscellaneous.SortParameter import fr.shikkanime.factories.impl.AnimeFactory import fr.shikkanime.factories.impl.EpisodeMappingFactory import fr.shikkanime.factories.impl.PlatformFactory import fr.shikkanime.repositories.AnimeRepository import fr.shikkanime.services.caches.EpisodeVariantCacheService -import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.* import fr.shikkanime.utils.StringUtils.capitalizeWords -import fr.shikkanime.utils.atEndOfWeek -import fr.shikkanime.utils.withUTC -import fr.shikkanime.utils.withUTCString +import fr.shikkanime.utils.TelemetryConfig.trace import java.time.LocalDate import java.time.LocalTime import java.time.ZoneId @@ -28,6 +25,7 @@ import java.time.format.DateTimeFormatter import java.util.* class AnimeService : AbstractService() { + private val tracer = TelemetryConfig.getTracer("AnimeService") @Inject private lateinit var animeRepository: AnimeRepository @Inject private lateinit var simulcastService: SimulcastService @Inject private lateinit var episodeMappingService: EpisodeMappingService @@ -49,7 +47,7 @@ class AnimeService : AbstractService() { page: Int, limit: Int, searchTypes: Array?, - ) = animeRepository.findAllBy(countryCode, simulcast, sort, page, limit, searchTypes) + ) = tracer.trace { animeRepository.findAllBy(countryCode, simulcast, sort, page, limit, searchTypes) } fun findAllByName( countryCode: CountryCode?, @@ -57,8 +55,8 @@ class AnimeService : AbstractService() { page: Int, limit: Int, searchTypes: Array? - ): Pageable { - return if (name.length == 1) { + ) = tracer.trace { + if (name.length == 1) { animeRepository.findAllByFirstLetterCategory(countryCode, name, page, limit, searchTypes) } else { animeRepository.findAllByName(countryCode, name, page, limit, searchTypes) @@ -73,7 +71,7 @@ class AnimeService : AbstractService() { fun preIndex() = animeRepository.preIndex() - fun findBySlug(countryCode: CountryCode, slug: String) = animeRepository.findBySlug(countryCode, slug) + fun findBySlug(countryCode: CountryCode, slug: String) = tracer.trace { animeRepository.findBySlug(countryCode, slug) } fun findByName(countryCode: CountryCode, name: String?) = animeRepository.findByName(countryCode, name) @@ -84,24 +82,26 @@ class AnimeService : AbstractService() { startOfWeekDay: LocalDate, searchTypes: Array? = null, ): List { - val zoneId = ZoneId.of(countryCode.timezone) - val dayCountryPattern = DateTimeFormatter.ofPattern("EEEE", Locale.forLanguageTag(countryCode.locale)) - val endOfWeekDay = startOfWeekDay.atEndOfWeek() - val weekRange = startOfWeekDay..endOfWeekDay - - val variantReleaseDtos = episodeVariantCacheService.findAllVariantReleases( - countryCode, - member, - startOfWeekDay, - zoneId, - searchTypes - ) + return tracer.trace { + val zoneId = ZoneId.of(countryCode.timezone) + val dayCountryPattern = DateTimeFormatter.ofPattern("EEEE", Locale.forLanguageTag(countryCode.locale)) + val endOfWeekDay = startOfWeekDay.atEndOfWeek() + val weekRange = startOfWeekDay..endOfWeekDay + + val variantReleaseDtos = episodeVariantCacheService.findAllVariantReleases( + countryCode, + member, + startOfWeekDay, + zoneId, + searchTypes + ) - val releases = processReleases(variantReleaseDtos, zoneId, weekRange).let { releases -> - releases.filterNot { isReleaseInCurrentWeek(it, releases, weekRange) } - } + val releases = processReleases(variantReleaseDtos, zoneId, weekRange).let { releases -> + tracer.trace("AnimeService.isReleaseInCurrentWeek") { releases.filterNot { isReleaseInCurrentWeek(it, releases, weekRange) } } + } - return groupAndSortReleases(startOfWeekDay, releases.map { it.first }, zoneId, dayCountryPattern) + groupAndSortReleases(startOfWeekDay, releases.map { it.first }, zoneId, dayCountryPattern) + } } private fun isTimeInRange(timeToCheck: LocalTime, referenceTime: LocalTime, toleranceHours: Long): Boolean { @@ -120,29 +120,31 @@ class AnimeService : AbstractService() { zoneId: ZoneId, weekRange: ClosedRange ): List> { - val groups = mutableMapOf, UUID>() + val groups = mutableMapOf, UUID>() val isCurrentWeek: (VariantReleaseDto) -> Boolean = { it.releaseDateTime.withZoneSameInstant(zoneId).toLocalDate() in weekRange } - val dayPattern = DateTimeFormatter.ofPattern("EEEE") - - return variantReleaseDtos.groupBy { variantReleaseDto -> - variantReleaseDto.releaseDateTime.format(dayPattern) to variantReleaseDto.anime - }.flatMap { (pair, values) -> - val key = "${pair.first}-${pair.second.uuid}" - values.groupBy { variantReleaseDto -> - val groupKey = groups.entries.firstOrNull { it.key.first == key && isTimeInRange(variantReleaseDto.releaseDateTime.toLocalTime(), it.key.second.toLocalTime(), 1) } - groupKey?.value ?: UUID.randomUUID().also { groups[key to variantReleaseDto.releaseDateTime] = it } + return tracer.trace { + variantReleaseDtos.filter(isCurrentWeek) + .map { it.episodeMapping } + .distinctBy { it.uuid!! } + .apply { episodeVariantCacheService.findAllByMappings(*this.toTypedArray()) } + + variantReleaseDtos.groupBy { variantReleaseDto -> + val key = "${variantReleaseDto.releaseDateTime.dayOfWeek}-${variantReleaseDto.anime.uuid}" + val releaseTime = variantReleaseDto.releaseDateTime.toLocalTime() + val groupKey = groups.entries.firstOrNull { it.key.first == key && isTimeInRange(releaseTime, it.key.second, 1) } + groupKey?.value ?: UUID.randomUUID().also { groups[key to releaseTime] = it } }.flatMap { (_, hourValues) -> val filter = hourValues.filter(isCurrentWeek) - createWeeklyAnimeDtos(filter, hourValues, pair) + createWeeklyAnimeDtos(hourValues.first().anime, filter, hourValues) } } } private fun createWeeklyAnimeDtos( + anime: Anime, filter: List, - hourValues: List, - pair: Pair + hourValues: List ): List> { val mappings = filter.asSequence() .map { it.episodeMapping } @@ -153,41 +155,48 @@ class AnimeService : AbstractService() { return mappings.groupBy { it.episodeType } .ifEmpty { mapOf(null to mappings) } .map { (episodeType, episodeMappings) -> - createWeeklyAnimeDto(filter, hourValues, pair, episodeType, episodeMappings.toSet()) + createWeeklyAnimeDto(anime, filter, hourValues, episodeType, episodeMappings.toSet()) } } private fun createWeeklyAnimeDto( + anime: Anime, filter: List, hourValues: List, - pair: Pair, episodeType: EpisodeType?, episodeMappings: Set ): Triple { val platforms = filter.map { it.platform }.ifEmpty { hourValues.map { it.platform } }.sortedBy { it.name }.toSet() val releaseDateTime = filter.minOfOrNull { it.releaseDateTime } ?: hourValues.minOf { it.releaseDateTime } val langTypes = filter.map { - LangType.fromAudioLocale(pair.second.countryCode!!, it.audioLocale) + LangType.fromAudioLocale(anime.countryCode!!, it.audioLocale) } .sorted() .ifEmpty { - hourValues.map { LangType.fromAudioLocale(pair.second.countryCode!!, it.audioLocale) } + hourValues.map { LangType.fromAudioLocale(anime.countryCode!!, it.audioLocale) } .sorted() }.toSet() return Triple( WeeklyAnimeDto( - animeFactory.toDto(pair.second), + animeFactory.toDto(anime), platforms.map { platformFactory.toDto(it) }.toSet(), releaseDateTime.withUTCString(), buildString { - append("/animes/${pair.second.slug}") - - episodeMappings.takeIf { it.isNotEmpty() }?.let { - if (it.minOf { it.season!! } == it.maxOf { it.season!! }) { - append("/season-${it.first().season}") - if (it.size <= 1) append("/${it.first().episodeType!!.slug}-${it.first().number}") + append("/animes/${anime.slug}") + + episodeMappings.takeIf { it.isNotEmpty() }?.let { mappings -> + val season = mappings.first().season + if (mappings.all { it.season == season }) { + append("/season-$season") + if (mappings.size == 1) { + val episode = mappings.first() + append("/${episode.episodeType!!.slug}-${episode.number}") + } } + } ?: run { + val season = hourValues.map { it.episodeMapping.season }.distinct().singleOrNull() + season?.let { append("/season-$it") } } }, langTypes, @@ -233,21 +242,24 @@ class AnimeService : AbstractService() { zoneId: ZoneId, dateFormatter: DateTimeFormatter ): List { - return (0..6).map { dayOffset -> - val date = startOfWeekDay.plusDays(dayOffset.toLong()) - val tuplesDay = releases.filter { - ZonedDateTime.parse(it.releaseDateTime).withZoneSameInstant(zoneId).dayOfWeek.value == dayOffset + 1 - } + return tracer.trace { + (1..7).map { dayOffset -> + val date = startOfWeekDay.plusDays(dayOffset.toLong()) - WeeklyAnimesDto( - date.format(dateFormatter).capitalizeWords(), - tuplesDay.sortedWith( - compareBy( - { ZonedDateTime.parse(it.releaseDateTime).withZoneSameInstant(zoneId).toLocalTime() }, - { it.anime.shortName } - ) - ).toSet() - ) + val tuplesDay = releases.filter { + ZonedDateTime.parse(it.releaseDateTime).withZoneSameInstant(zoneId).dayOfWeek.value == dayOffset + } + + WeeklyAnimesDto( + date.format(dateFormatter).capitalizeWords(), + tuplesDay.sortedWith( + compareBy( + { ZonedDateTime.parse(it.releaseDateTime).withZoneSameInstant(zoneId).toLocalTime() }, + { it.anime.shortName } + ) + ).toSet() + ) + } } } diff --git a/src/main/kotlin/fr/shikkanime/services/AttachmentService.kt b/src/main/kotlin/fr/shikkanime/services/AttachmentService.kt index 02d970c2..de2bcf00 100644 --- a/src/main/kotlin/fr/shikkanime/services/AttachmentService.kt +++ b/src/main/kotlin/fr/shikkanime/services/AttachmentService.kt @@ -6,6 +6,7 @@ import fr.shikkanime.entities.TraceAction import fr.shikkanime.entities.enums.ImageType import fr.shikkanime.repositories.AttachmentRepository import fr.shikkanime.utils.* +import fr.shikkanime.utils.TelemetryConfig.trace import io.ktor.client.statement.* import io.ktor.http.* import kotlinx.coroutines.runBlocking @@ -30,6 +31,7 @@ class AttachmentService : AbstractService() { private val httpRequest = HttpRequest() private val imageCache = LRUCache(100) val inProgressAttachments: MutableSet = Collections.synchronizedSet(HashSet()) + private val tracer = TelemetryConfig.getTracer("AttachmentService") @Inject private lateinit var attachmentRepository: AttachmentRepository @Inject private lateinit var traceActionService: TraceActionService @@ -43,7 +45,7 @@ class AttachmentService : AbstractService() { fun findAllNeededUpdate(lastUpdateDateTime: ZonedDateTime) = attachmentRepository.findAllNeededUpdate(lastUpdateDateTime) - fun findByEntityUuidTypeAndActive(entityUuid: UUID, type: ImageType) = attachmentRepository.findByEntityUuidTypeAndActive(entityUuid, type) + fun findByEntityUuidTypeAndActive(entityUuid: UUID, type: ImageType) = tracer.trace { attachmentRepository.findByEntityUuidTypeAndActive(entityUuid, type) } fun findAllActiveWithUrl() = attachmentRepository.findAllActiveWithUrl() @@ -136,9 +138,7 @@ class AttachmentService : AbstractService() { fun getFile(attachment: Attachment) = File(Constant.imagesFolder, getFileName(attachment)) - fun getContentFromCache(attachment: Attachment): ByteArray? { - return imageCache[attachment.uuid!!] - } + fun getContentFromCache(attachment: Attachment) = imageCache[attachment.uuid!!] fun setContentInCache(attachment: Attachment, bytes: ByteArray) { imageCache[attachment.uuid!!] = bytes diff --git a/src/main/kotlin/fr/shikkanime/services/EpisodeMappingService.kt b/src/main/kotlin/fr/shikkanime/services/EpisodeMappingService.kt index 5b89ed50..e113ea7d 100644 --- a/src/main/kotlin/fr/shikkanime/services/EpisodeMappingService.kt +++ b/src/main/kotlin/fr/shikkanime/services/EpisodeMappingService.kt @@ -9,10 +9,13 @@ import fr.shikkanime.entities.enums.EpisodeType import fr.shikkanime.entities.enums.Platform import fr.shikkanime.entities.miscellaneous.SortParameter import fr.shikkanime.repositories.EpisodeMappingRepository +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import java.time.ZonedDateTime import java.util.* class EpisodeMappingService : AbstractService() { + private val tracer = TelemetryConfig.getTracer("EpisodeMappingService") @Inject private lateinit var episodeMappingRepository: EpisodeMappingRepository @Inject private lateinit var episodeVariantService: EpisodeVariantService @Inject private lateinit var memberFollowEpisodeService: MemberFollowEpisodeService @@ -27,11 +30,11 @@ class EpisodeMappingService : AbstractService, page: Int, limit: Int, - ) = episodeMappingRepository.findAllBy(countryCode, anime, season, sort, page, limit) + ) = tracer.trace { episodeMappingRepository.findAllBy(countryCode, anime, season, sort, page, limit) } - fun findAllByAnime(anime: Anime) = episodeMappingRepository.findAllByAnime(anime.uuid!!) + fun findAllByAnime(animeUuid: UUID) = tracer.trace { episodeMappingRepository.findAllByAnime(animeUuid) } - fun findAllByAnime(animeUuid: UUID) = episodeMappingRepository.findAllByAnime(animeUuid) + fun findAllByAnime(anime: Anime) = findAllByAnime(anime.uuid!!) fun findAllNeedUpdate(lastUpdateDateTime: ZonedDateTime, lastImageUpdateDateTime: ZonedDateTime) = episodeMappingRepository.findAllNeedUpdate(lastUpdateDateTime, lastImageUpdateDateTime) @@ -41,7 +44,7 @@ class EpisodeMappingService : AbstractService, ignoreAudioLocale: String) = episodeMappingRepository.findAllSimulcasted(ignoreEpisodeTypes, ignoreAudioLocale) - fun findAllGroupedBy(countryCode: CountryCode?, sort: List, page: Int, limit: Int) = episodeMappingRepository.findAllGroupedBy(countryCode, sort, page, limit) + fun findAllGroupedBy(countryCode: CountryCode?, sort: List, page: Int, limit: Int) = tracer.trace { episodeMappingRepository.findAllGroupedBy(countryCode, sort, page, limit) } fun findLastNumber(anime: Anime, episodeType: EpisodeType, season: Int, platform: Platform, audioLocale: String) = episodeMappingRepository.findLastNumber(anime, episodeType, season, platform, audioLocale) @@ -52,7 +55,7 @@ class EpisodeMappingService : AbstractService() { + private val tracer = TelemetryConfig.getTracer("EpisodeVariantService") @Inject private lateinit var episodeVariantRepository: EpisodeVariantRepository @Inject private lateinit var simulcastCacheService: SimulcastCacheService @Inject private lateinit var configCacheService: ConfigCacheService @@ -31,10 +34,12 @@ class EpisodeVariantService : AbstractService? = null) = episodeVariantRepository.findAllVariantReleases(countryCode, member, startZonedDateTime, endZonedDateTime, searchTypes) diff --git a/src/main/kotlin/fr/shikkanime/services/MemberActionService.kt b/src/main/kotlin/fr/shikkanime/services/MemberActionService.kt index c97988d3..a302662a 100644 --- a/src/main/kotlin/fr/shikkanime/services/MemberActionService.kt +++ b/src/main/kotlin/fr/shikkanime/services/MemberActionService.kt @@ -7,10 +7,8 @@ import fr.shikkanime.entities.MemberAction import fr.shikkanime.entities.TraceAction import fr.shikkanime.entities.enums.Action import fr.shikkanime.repositories.MemberActionRepository -import fr.shikkanime.utils.Constant -import fr.shikkanime.utils.EncryptionManager -import fr.shikkanime.utils.LoggerFactory -import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.* +import fr.shikkanime.utils.TelemetryConfig.trace import java.time.ZonedDateTime import java.util.* import java.util.logging.Level @@ -22,7 +20,7 @@ class MemberActionService : AbstractService { - memberAction.member!!.email = memberAction.email - memberService.update(memberAction.member!!) - traceActionService.createTraceAction(memberAction.member!!, TraceAction.Action.UPDATE) - - mailService.save( - Mail( - recipient = memberAction.email!!, - title = "${Constant.NAME} - Adresse email validée", - body = mailService.getFreemarkerContent("/mail/email-associated.ftl").toString() + tracer.trace { + when (memberAction.action) { + Action.VALIDATE_EMAIL -> { + memberAction.member!!.email = memberAction.email + memberService.update(memberAction.member!!) + traceActionService.createTraceAction(memberAction.member!!, TraceAction.Action.UPDATE) + + mailService.save( + Mail( + recipient = memberAction.email!!, + title = "${Constant.NAME} - Adresse email validée", + body = mailService.getFreemarkerContent("/mail/email-associated.ftl").toString() + ) ) - ) - } + } - Action.FORGOT_IDENTIFIER -> { - var identifier: String + Action.FORGOT_IDENTIFIER -> { + var identifier: String - do { - identifier = StringUtils.generateRandomString(12) - } while (memberService.findByIdentifier(identifier) != null) + do { + identifier = StringUtils.generateRandomString(12) + } while (memberService.findByIdentifier(identifier) != null) - memberAction.member!!.username = EncryptionManager.toSHA512(identifier) - memberService.update(memberAction.member!!) - traceActionService.createTraceAction(memberAction.member!!, TraceAction.Action.UPDATE) + memberAction.member!!.username = EncryptionManager.toSHA512(identifier) + memberService.update(memberAction.member!!) + traceActionService.createTraceAction(memberAction.member!!, TraceAction.Action.UPDATE) - mailService.save( - Mail( - recipient = memberAction.email!!, - title = "${Constant.NAME} - Votre nouvel identifiant", - body = mailService.getFreemarkerContent("/mail/your-new-identifier.ftl", identifier).toString() + mailService.save( + Mail( + recipient = memberAction.email!!, + title = "${Constant.NAME} - Votre nouvel identifiant", + body = mailService.getFreemarkerContent("/mail/your-new-identifier.ftl", identifier).toString() + ) ) - ) + } + + else -> TODO("Action not implemented") } - else -> TODO("Action not implemented") + memberAction.validated = true + memberAction.updateDateTime = ZonedDateTime.now() + memberActionRepository.update(memberAction) } - - memberAction.validated = true - memberAction.updateDateTime = ZonedDateTime.now() - memberActionRepository.update(memberAction) } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/MemberFollowAnimeService.kt b/src/main/kotlin/fr/shikkanime/services/MemberFollowAnimeService.kt index 631d49d2..99cd7d21 100644 --- a/src/main/kotlin/fr/shikkanime/services/MemberFollowAnimeService.kt +++ b/src/main/kotlin/fr/shikkanime/services/MemberFollowAnimeService.kt @@ -9,10 +9,13 @@ import fr.shikkanime.entities.TraceAction import fr.shikkanime.repositories.MemberFollowAnimeRepository import fr.shikkanime.services.caches.MemberCacheService import fr.shikkanime.utils.MapCache +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import fr.shikkanime.utils.routes.Response import java.util.* class MemberFollowAnimeService : AbstractService() { + private val tracer = TelemetryConfig.getTracer("MemberFollowAnimeService") @Inject private lateinit var memberFollowAnimeRepository: MemberFollowAnimeRepository @Inject private lateinit var memberCacheService: MemberCacheService @Inject private lateinit var animeService: AnimeService @@ -20,22 +23,24 @@ class MemberFollowAnimeService : AbstractService() { + private val tracer = TelemetryConfig.getTracer("MemberFollowEpisodeCacheService") @Inject private lateinit var memberFollowEpisodeRepository: MemberFollowEpisodeRepository @Inject private lateinit var memberCacheService: MemberCacheService @Inject private lateinit var episodeMappingService: EpisodeMappingService @@ -24,20 +27,26 @@ class MemberFollowEpisodeService : AbstractService) = + tracer.trace { memberFollowEpisodeRepository.findAllFollowedEpisodesByMemberAndEpisodes(member, episodes) } + + fun existsByMemberAndEpisode(member: Member, episode: EpisodeMapping) = tracer.trace { memberFollowEpisodeRepository.existsByMemberAndEpisode(member, episode) } + + fun findByMemberAndEpisode(member: Member, episode: EpisodeMapping) = tracer.trace { memberFollowEpisodeRepository.findByMemberAndEpisode(member, episode) } fun followAll(memberUuid: UUID, anime: GenericDto): Response { val member = memberCacheService.find(memberUuid) ?: return Response.notFound() - val elements = episodeMappingService.findAllByAnime(animeService.find(anime.uuid) ?: return Response.notFound()) - .filter { it.episodeType != EpisodeType.SUMMARY } - val followed = memberFollowEpisodeRepository.findAllFollowedEpisodesByMemberAndEpisodes(member, elements) + val elements = episodeMappingService.findAllByAnime(anime.uuid).filter { it.episodeType != EpisodeType.SUMMARY } + val followed = findAllFollowedEpisodesByMemberAndEpisodes(member, elements) val now = ZonedDateTime.now() val filtered = elements.filter { it.uuid !in followed }.map { MemberFollowEpisode(followDateTime = now, member = member, episode = it) } @@ -52,7 +61,7 @@ class MemberFollowEpisodeService : AbstractService() { private val logger = LoggerFactory.getLogger(javaClass) - + private val tracer = TelemetryConfig.getTracer("MemberService") @Inject private lateinit var memberRepository: MemberRepository @Inject private lateinit var memberActionService: MemberActionService @Inject private lateinit var traceActionService: TraceActionService @@ -45,7 +43,7 @@ class MemberService : AbstractService() { memberRepository.findByUsernameAndPassword(username, EncryptionManager.generate(password)) fun findByIdentifier(identifier: String) = - memberRepository.findByIdentifier(EncryptionManager.toSHA512(identifier)) + tracer.trace { memberRepository.findByIdentifier(EncryptionManager.toSHA512(identifier)) } fun findByEmail(email: String) = memberRepository.findByEmail(email) @@ -71,32 +69,35 @@ class MemberService : AbstractService() { } fun register(identifier: String): Member { - val saved = save( - Member( - isPrivate = true, - username = EncryptionManager.toSHA512(identifier), - encryptedPassword = byteArrayOf() + return tracer.trace { + val saved = save( + Member( + isPrivate = true, + username = EncryptionManager.toSHA512(identifier), + encryptedPassword = byteArrayOf() + ) ) - ) - traceActionService.createTraceAction(saved, TraceAction.Action.CREATE) - return saved + traceActionService.createTraceAction(saved, TraceAction.Action.CREATE) + saved + } } fun login(identifier: String, appVersion: String? = null, device: String? = null, locale: String? = null): MemberDto? { - val member = findByIdentifier(identifier) ?: return null - - val traceData = if (!appVersion.isNullOrBlank() && !device.isNullOrBlank() && !locale.isNullOrBlank()) { - ObjectParser.toJson(mapOf( - "appVersion" to appVersion, - "device" to device, - "locale" to locale - )) - } else null - - traceActionService.createTraceAction(member, TraceAction.Action.LOGIN, traceData) - - return memberFactory.toDto(member) + return tracer.trace { + val member = findByIdentifier(identifier) ?: return@trace null + + val traceData = if (!appVersion.isNullOrBlank() && !device.isNullOrBlank() && !locale.isNullOrBlank()) { + ObjectParser.toJson(mapOf( + "appVersion" to appVersion, + "device" to device, + "locale" to locale + )) + } else null + + traceActionService.createTraceAction(member, TraceAction.Action.LOGIN, traceData) + memberFactory.toDto(member) + } } fun associateEmail(memberUuid: UUID, email: String): UUID { diff --git a/src/main/kotlin/fr/shikkanime/services/TraceActionService.kt b/src/main/kotlin/fr/shikkanime/services/TraceActionService.kt index 7a98a73f..2b71211d 100644 --- a/src/main/kotlin/fr/shikkanime/services/TraceActionService.kt +++ b/src/main/kotlin/fr/shikkanime/services/TraceActionService.kt @@ -4,11 +4,13 @@ import com.google.inject.Inject import fr.shikkanime.entities.ShikkEntity import fr.shikkanime.entities.TraceAction import fr.shikkanime.repositories.TraceActionRepository +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import java.time.ZonedDateTime class TraceActionService : AbstractService() { - @Inject - private lateinit var traceActionRepository: TraceActionRepository + private val tracer = TelemetryConfig.getTracer("TraceActionService") + @Inject private lateinit var traceActionRepository: TraceActionRepository override fun getRepository() = traceActionRepository @@ -16,13 +18,15 @@ class TraceActionService : AbstractService() fun getLoginCountsAfter(date: ZonedDateTime) = traceActionRepository.getLoginCountsAfter(date) - fun createTraceAction(shikkEntity: ShikkEntity, action: TraceAction.Action, additionalData: String? = null) = save( - TraceAction( - actionDateTime = ZonedDateTime.now(), - entityType = shikkEntity::class.java.simpleName, - entityUuid = shikkEntity.uuid, - action = action, - additionalData = additionalData + fun createTraceAction(shikkEntity: ShikkEntity, action: TraceAction.Action, additionalData: String? = null) = tracer.trace { + save( + TraceAction( + actionDateTime = ZonedDateTime.now(), + entityType = shikkEntity::class.java.simpleName, + entityUuid = shikkEntity.uuid, + action = action, + additionalData = additionalData + ) ) - ) + } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt index 6ccb647e..4cb7de48 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt @@ -16,10 +16,13 @@ import fr.shikkanime.factories.impl.AnimeFactory import fr.shikkanime.services.AnimeService import fr.shikkanime.utils.MapCache import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import java.time.LocalDate import java.util.* class AnimeCacheService : ICacheService { + private val tracer = TelemetryConfig.getTracer("AnimeCacheService") @Inject private lateinit var animeService: AnimeService @Inject private lateinit var simulcastCacheService: SimulcastCacheService @Inject private lateinit var memberCacheService: MemberCacheService @@ -42,7 +45,7 @@ class AnimeCacheService : ICacheService { "AnimeCacheService.findAllBy", classes = listOf(Anime::class.java, EpisodeMapping::class.java, EpisodeVariant::class.java), key = CountryCodeUUIDSortPaginationKeyCache(countryCode, uuid, sort, page, limit, searchTypes), - ) { + ) { tracer.trace { PageableDto.fromPageable( animeService.findAllBy( it.countryCode, @@ -54,57 +57,55 @@ class AnimeCacheService : ICacheService { ), animeFactory ) - } + } } fun findAllByName(countryCode: CountryCode?, name: String, page: Int, limit: Int, searchTypes: Array?) = MapCache.getOrCompute( "AnimeCacheService.findAllByName", classes = listOf(Anime::class.java, EpisodeMapping::class.java, EpisodeVariant::class.java), key = CountryCodeNamePaginationKeyCache(countryCode, name, page, limit, searchTypes), - ) { + ) { tracer.trace { PageableDto.fromPageable( animeService.findAllByName(it.countryCode, it.name, it.page, it.limit, it.searchTypes), animeFactory ) - } + } } fun getAudioLocales(anime: Anime) = MapCache.getOrCompute( "AnimeCacheService.getAudioLocales", classes = listOf(Anime::class.java, EpisodeMapping::class.java, EpisodeVariant::class.java), key = StringUtils.EMPTY_STRING, - ) { animeService.findAllAudioLocales() }[anime.uuid!!] + ) { tracer.trace { animeService.findAllAudioLocales() } }[anime.uuid!!] fun getSeasons(anime: Anime) = MapCache.getOrCompute( "AnimeCacheService.getSeasons", classes = listOf(Anime::class.java, EpisodeMapping::class.java, EpisodeVariant::class.java), key = StringUtils.EMPTY_STRING, - ) { animeService.findAllSeasons() }[anime.uuid!!] + ) { tracer.trace { animeService.findAllSeasons() } }[anime.uuid!!] fun find(uuid: UUID) = MapCache.getOrComputeNullable( "AnimeCacheService.find", classes = listOf(Anime::class.java), key = uuid, - ) { animeService.find(it) } + ) { tracer.trace { animeService.find(it) } } fun findBySlug(countryCode: CountryCode, slug: String) = MapCache.getOrComputeNullable( "AnimeCacheService.findBySlug", classes = listOf(Anime::class.java, EpisodeMapping::class.java, EpisodeVariant::class.java), key = countryCode to slug, - ) { - animeService.findBySlug(it.first, it.second)?.let { anime -> animeFactory.toDto(anime) } - } + ) { tracer.trace { animeService.findBySlug(it.first, it.second)?.let { anime -> animeFactory.toDto(anime) } } } fun getWeeklyAnimes(countryCode: CountryCode, memberUuid: UUID?, startOfWeekDay: LocalDate, searchTypes: Array? = null) = MapCache.getOrCompute( "AnimeCacheService.getWeeklyAnimes", classes = listOf(Anime::class.java, EpisodeMapping::class.java, EpisodeVariant::class.java, MemberFollowAnime::class.java), key = CountryCodeLocalDateKeyCache(countryCode, memberUuid, startOfWeekDay, searchTypes), - ) { + ) { tracer.trace { animeService.getWeeklyAnimes( it.countryCode, it.member?.let { uuid -> memberCacheService.find(uuid) }, it.localDate, it.searchTypes, ) - } + } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/caches/AnimePlatformCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/AnimePlatformCacheService.kt index 5eb1d8c8..62303877 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/AnimePlatformCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/AnimePlatformCacheService.kt @@ -5,13 +5,16 @@ import fr.shikkanime.entities.Anime import fr.shikkanime.services.AnimePlatformService import fr.shikkanime.utils.MapCache import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace class AnimePlatformCacheService : ICacheService { + private val tracer = TelemetryConfig.getTracer("AnimePlatformCacheService") @Inject private lateinit var animePlatformService: AnimePlatformService fun getAll(anime: Anime) = MapCache.getOrCompute( "AnimePlatformCacheService.getAll", classes = listOf(Anime::class.java), key = StringUtils.EMPTY_STRING, - ) { animePlatformService.findAll().groupBy { it.anime!!.uuid } }[anime.uuid] ?: emptyList() + ) { tracer.trace { animePlatformService.findAll().groupBy { it.anime!!.uuid } } }[anime.uuid] ?: emptyList() } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/caches/AttachmentCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/AttachmentCacheService.kt index 17e5e454..ff12038f 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/AttachmentCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/AttachmentCacheService.kt @@ -5,14 +5,17 @@ import fr.shikkanime.entities.Attachment import fr.shikkanime.entities.enums.ImageType import fr.shikkanime.services.AttachmentService import fr.shikkanime.utils.MapCache +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import java.util.* class AttachmentCacheService : ICacheService { + private val tracer = TelemetryConfig.getTracer("AttachmentCacheService") @Inject private lateinit var attachmentService: AttachmentService fun findByEntityUuidTypeAndActive(uuid: UUID, type: ImageType) = MapCache.getOrComputeNullable( "AttachmentCacheService.findByEntityUuidTypeAndActive", classes = listOf(Attachment::class.java), key = uuid to type, - ) { attachmentService.findByEntityUuidTypeAndActive(it.first, it.second) } + ) { tracer.trace { attachmentService.findByEntityUuidTypeAndActive(it.first, it.second) } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/caches/ConfigCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/ConfigCacheService.kt index 218d3791..f592c2c4 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/ConfigCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/ConfigCacheService.kt @@ -6,15 +6,18 @@ import fr.shikkanime.entities.enums.ConfigPropertyKey import fr.shikkanime.services.ConfigService import fr.shikkanime.utils.MapCache import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace class ConfigCacheService : ICacheService { + private val tracer = TelemetryConfig.getTracer("ConfigCacheService") @Inject private lateinit var configService: ConfigService fun findByName(name: String) = MapCache.getOrCompute( "ConfigCacheService.findAll", classes = listOf(Config::class.java), key = StringUtils.EMPTY_STRING, - ) { configService.findAll() } + ) { tracer.trace { configService.findAll() } } .find { it.propertyKey == name } fun getValueAsString(configPropertyKey: ConfigPropertyKey) = findByName(configPropertyKey.key)?.propertyValue diff --git a/src/main/kotlin/fr/shikkanime/services/caches/EpisodeMappingCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/EpisodeMappingCacheService.kt index 7d55fc00..c9e138cb 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/EpisodeMappingCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/EpisodeMappingCacheService.kt @@ -15,13 +15,17 @@ import fr.shikkanime.factories.impl.GroupedEpisodeFactory import fr.shikkanime.services.EpisodeMappingService import fr.shikkanime.utils.MapCache import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import java.util.* class EpisodeMappingCacheService : ICacheService { + private val tracer = TelemetryConfig.getTracer("EpisodeMappingCacheService") @Inject private lateinit var episodeMappingService: EpisodeMappingService @Inject private lateinit var animeCacheService: AnimeCacheService @Inject private lateinit var episodeMappingFactory: EpisodeMappingFactory @Inject private lateinit var groupedEpisodeFactory: GroupedEpisodeFactory + @Inject private lateinit var episodeVariantCacheService: EpisodeVariantCacheService fun findAllBy( countryCode: CountryCode?, @@ -34,7 +38,7 @@ class EpisodeMappingCacheService : ICacheService { "EpisodeMappingCacheService.findAllBy", classes = listOf(EpisodeMapping::class.java, EpisodeVariant::class.java), key = CountryCodeUUIDSeasonSortPaginationKeyCache(countryCode, anime, season, sort, page, limit), - ) { + ) { tracer.trace { PageableDto.fromPageable( episodeMappingService.findAllBy( it.countryCode, @@ -43,10 +47,10 @@ class EpisodeMappingCacheService : ICacheService { it.sort, it.page, it.limit, - ), + ).apply { episodeVariantCacheService.findAllByMappings(*data.toTypedArray()) }, episodeMappingFactory ) - } + } } fun findAllGroupedBy( countryCode: CountryCode?, @@ -57,12 +61,12 @@ class EpisodeMappingCacheService : ICacheService { "EpisodeMappingCacheService.findAllGroupedBy", classes = listOf(Anime::class.java, EpisodeMapping::class.java, EpisodeVariant::class.java), key = CountryCodeSortPaginationKeyCache(countryCode, sort,page, limit), - ) { + ) { tracer.trace { PageableDto.fromPageable( episodeMappingService.findAllGroupedBy(it.countryCode, it.sort, it.page, it.limit), groupedEpisodeFactory ) - } + } } fun findAllSeo() = MapCache.getOrCompute( "EpisodeMappingCacheService.findAllSeo", @@ -79,8 +83,9 @@ class EpisodeMappingCacheService : ICacheService { "EpisodeMappingCacheService.findPreviousAndNextBy", classes = listOf(EpisodeMapping::class.java), key = animeUuid, - ) { + ) { tracer.trace { val result = episodeMappingService.findAllByAnime(it) + episodeVariantCacheService.findAllByMappings(*result.toTypedArray()) result.mapIndexed { index, current -> Triple(current.season!!, current.episodeType!!, current.number!!) to Triple( @@ -89,11 +94,11 @@ class EpisodeMappingCacheService : ICacheService { result.getOrNull(index + 1)?.let { episodeMappingFactory.toDto(it) } ) }.toMap() - }[Triple(season, episodeType, number)] + } }[Triple(season, episodeType, number)] fun findMinimalReleaseDateTime() = MapCache.getOrCompute( "EpisodeMappingCacheService.findMinimalReleaseDateTime", classes = listOf(EpisodeMapping::class.java), key = StringUtils.EMPTY_STRING, - ) { episodeMappingService.findMinimalReleaseDateTime() } + ) { tracer.trace { episodeMappingService.findMinimalReleaseDateTime() } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/caches/EpisodeVariantCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/EpisodeVariantCacheService.kt index 2cd5881b..65d470df 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/EpisodeVariantCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/EpisodeVariantCacheService.kt @@ -12,25 +12,37 @@ import fr.shikkanime.entities.enums.LangType import fr.shikkanime.entities.enums.Platform import fr.shikkanime.factories.impl.EpisodeVariantFactory import fr.shikkanime.services.EpisodeVariantService -import fr.shikkanime.utils.MapCache -import fr.shikkanime.utils.StringUtils -import fr.shikkanime.utils.atEndOfTheDay -import fr.shikkanime.utils.atEndOfWeek +import fr.shikkanime.utils.* +import fr.shikkanime.utils.TelemetryConfig.trace import java.time.LocalDate import java.time.ZoneId import java.time.ZonedDateTime import java.util.* class EpisodeVariantCacheService : ICacheService { + private val tracer = TelemetryConfig.getTracer("EpisodeVariantCacheService") @Inject private lateinit var episodeVariantService: EpisodeVariantService @Inject private lateinit var memberCacheService: MemberCacheService @Inject private lateinit var episodeVariantFactory: EpisodeVariantFactory - - fun findAllByMapping(episodeMapping: EpisodeMapping) = MapCache.getOrCompute( + + private val byMappingCache = MapCache>( "EpisodeVariantCacheService.findAllByMapping", classes = listOf(EpisodeMapping::class.java, EpisodeVariant::class.java), - key = episodeMapping.uuid!!, - ) { episodeVariantService.findAllByMapping(it) }.toSet() + ) { tracer.trace { episodeVariantService.findAllByMapping(it).toSet() } } + + fun findAllByMapping(episodeMapping: EpisodeMapping) = byMappingCache[episodeMapping.uuid!!] ?: emptySet() + + fun findAllByMappings(vararg episodeMappings: EpisodeMapping) { + val notFetched = episodeMappings.filterNot { byMappingCache.containsKeyAndValid(it.uuid!!) } + + if (notFetched.isNotEmpty()) { + episodeVariantService.findAllByMappings(*notFetched.mapNotNull { it.uuid }.toTypedArray()) + .groupBy { it.mapping!!.uuid!! } + .forEach { byMappingCache.setIfNotExists(it.key, it.value.toSet()) } + } + + episodeMappings.forEach { byMappingCache[it.uuid!!]!! } + } fun findAllVariantReleases( countryCode: CountryCode, @@ -42,7 +54,7 @@ class EpisodeVariantCacheService : ICacheService { "EpisodeVariantCacheService.findAllVariantReleases", classes = listOf(EpisodeVariant::class.java, MemberFollowAnime::class.java), key = CountryCodeMemberUUIDWeekKeyCache(countryCode, member?.uuid, startOfWeekDay.minusWeeks(1).atStartOfDay(zoneId), startOfWeekDay.atEndOfWeek().atEndOfTheDay(zoneId), searchTypes), - ) { + ) { tracer.trace { episodeVariantService.findAllVariantReleases( it.countryCode, it.member?.let { uuid -> memberCacheService.find(uuid) }, @@ -50,7 +62,7 @@ class EpisodeVariantCacheService : ICacheService { it.endZonedDateTime, it.searchTypes ) - } + } } fun findAllVariantsByCountryCodeAndPlatformAndReleaseDateTimeBetween( countryCode: CountryCode, diff --git a/src/main/kotlin/fr/shikkanime/services/caches/MemberCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/MemberCacheService.kt index 806fe81c..72aa9b8d 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/MemberCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/MemberCacheService.kt @@ -6,9 +6,12 @@ import fr.shikkanime.entities.* import fr.shikkanime.factories.impl.RefreshMemberFactory import fr.shikkanime.services.MemberService import fr.shikkanime.utils.MapCache +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import java.util.* class MemberCacheService : ICacheService { + private val tracer = TelemetryConfig.getTracer("MemberCacheService") @Inject private lateinit var memberService: MemberService @Inject private lateinit var refreshMemberFactory: RefreshMemberFactory @@ -16,11 +19,11 @@ class MemberCacheService : ICacheService { "MemberCacheService.find", classes = listOf(Member::class.java), key = uuid - ) { memberService.find(it) } + ) { tracer.trace { memberService.find(it) } } fun getRefreshMember(uuid: UUID, limit: Int) = MapCache.getOrComputeNullable( "MemberCacheService.getRefreshMember", classes = listOf(Member::class.java, Anime::class.java, MemberFollowAnime::class.java, EpisodeMapping::class.java, MemberFollowEpisode::class.java), key = UUIDPaginationKeyCache(uuid, 1, limit) - ) { find(it.uuid)?.let { member -> refreshMemberFactory.toDto(member, it.limit) } } + ) { tracer.trace { find(it.uuid)?.let { member -> refreshMemberFactory.toDto(member, it.limit) } } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/caches/MemberFollowAnimeCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/MemberFollowAnimeCacheService.kt index 080576b2..f12018be 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/MemberFollowAnimeCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/MemberFollowAnimeCacheService.kt @@ -11,9 +11,12 @@ import fr.shikkanime.factories.impl.AnimeFactory import fr.shikkanime.factories.impl.MissedAnimeFactory import fr.shikkanime.services.MemberFollowAnimeService import fr.shikkanime.utils.MapCache +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import java.util.* class MemberFollowAnimeCacheService : ICacheService { + private val tracer = TelemetryConfig.getTracer("MemberFollowAnimeCacheService") @Inject private lateinit var memberCacheService: MemberCacheService @Inject private lateinit var memberFollowAnimeService: MemberFollowAnimeService @Inject private lateinit var animeFactory: AnimeFactory @@ -23,29 +26,29 @@ class MemberFollowAnimeCacheService : ICacheService { "MemberFollowAnimeCacheService.getMissedAnimes", classes = listOf(Anime::class.java, EpisodeMapping::class.java, MemberFollowAnime::class.java, MemberFollowEpisode::class.java), key = UUIDPaginationKeyCache(member, page, limit), - ) { + ) { tracer.trace { PageableDto.fromPageable( memberFollowAnimeService.findAllMissedAnimes( - memberCacheService.find(it.uuid) ?: return@getOrCompute PageableDto.empty(), + memberCacheService.find(it.uuid) ?: return@trace PageableDto.empty(), it.page, it.limit ), missedAnimeFactory ) - } + } } fun findAllBy(member: UUID, page: Int, limit: Int) = MapCache.getOrCompute( "MemberFollowAnimeCacheService.findAllBy", classes = listOf(Anime::class.java, MemberFollowAnime::class.java), key = UUIDPaginationKeyCache(member, page, limit), - ) { + ) { tracer.trace { PageableDto.fromPageable( memberFollowAnimeService.findAllFollowedAnimes( - memberCacheService.find(it.uuid) ?: return@getOrCompute PageableDto.empty(), + memberCacheService.find(it.uuid) ?: return@trace PageableDto.empty(), it.page, it.limit ), animeFactory ) - } + } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/caches/MemberFollowEpisodeCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/MemberFollowEpisodeCacheService.kt index ba23ebf6..b2cc3fc6 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/MemberFollowEpisodeCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/MemberFollowEpisodeCacheService.kt @@ -8,9 +8,12 @@ import fr.shikkanime.entities.MemberFollowEpisode import fr.shikkanime.factories.impl.EpisodeMappingFactory import fr.shikkanime.services.MemberFollowEpisodeService import fr.shikkanime.utils.MapCache +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import java.util.* class MemberFollowEpisodeCacheService : ICacheService { + private val tracer = TelemetryConfig.getTracer("MemberFollowEpisodeCacheService") @Inject private lateinit var memberCacheService: MemberCacheService @Inject private lateinit var memberFollowEpisodeService: MemberFollowEpisodeService @Inject private lateinit var episodeMappingFactory: EpisodeMappingFactory @@ -19,14 +22,14 @@ class MemberFollowEpisodeCacheService : ICacheService { "MemberFollowEpisodeCacheService.findAllBy", classes = listOf(EpisodeMapping::class.java, MemberFollowEpisode::class.java), key = UUIDPaginationKeyCache(member, page, limit), - ) { + ) { tracer.trace { PageableDto.fromPageable( memberFollowEpisodeService.findAllFollowedEpisodes( - memberCacheService.find(it.uuid) ?: return@getOrCompute PageableDto.empty(), + memberCacheService.find(it.uuid) ?: return@trace PageableDto.empty(), it.page, it.limit ), - episodeMappingFactory + episodeMappingFactory ) - } + } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/caches/SimulcastCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/SimulcastCacheService.kt index e1fae138..4d6141ef 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/SimulcastCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/SimulcastCacheService.kt @@ -8,22 +8,25 @@ import fr.shikkanime.entities.enums.Season import fr.shikkanime.services.SimulcastService import fr.shikkanime.utils.MapCache import fr.shikkanime.utils.StringUtils +import fr.shikkanime.utils.TelemetryConfig +import fr.shikkanime.utils.TelemetryConfig.trace import java.util.* class SimulcastCacheService : ICacheService { + private val tracer = TelemetryConfig.getTracer("SimulcastCacheService") @Inject private lateinit var simulcastService: SimulcastService fun findAll() = MapCache.getOrCompute( "SimulcastCacheService.findAll", classes = listOf(Simulcast::class.java, Anime::class.java), key = StringUtils.EMPTY_STRING - ) { simulcastService.findAllModified() } + ) { tracer.trace { simulcastService.findAllModified() } } fun find(uuid: UUID) = MapCache.getOrComputeNullable( "SimulcastCacheService.find", classes = listOf(Simulcast::class.java), key = uuid - ) { simulcastService.find(it) } + ) { tracer.trace { simulcastService.find(it) } } fun findBySeasonAndYear(season: Season, year: Int) = MapCache.getOrComputeNullable( "SimulcastCacheService.findBySeasonAndYear", diff --git a/src/main/kotlin/fr/shikkanime/utils/MapCache.kt b/src/main/kotlin/fr/shikkanime/utils/MapCache.kt index 0a71c349..b2add3be 100644 --- a/src/main/kotlin/fr/shikkanime/utils/MapCache.kt +++ b/src/main/kotlin/fr/shikkanime/utils/MapCache.kt @@ -19,6 +19,8 @@ class MapCache( fun containsKey(key: K) = cache.containsKey(key) + fun containsKeyAndValid(key: K) = cache.containsKey(key) && !needInvalidation(key) + private fun needInvalidation(key: K): Boolean { val (timestamp, _) = cache[key] ?: return false return System.currentTimeMillis() - timestamp > duration.toMillis() diff --git a/src/main/kotlin/fr/shikkanime/utils/TelemetryConfig.kt b/src/main/kotlin/fr/shikkanime/utils/TelemetryConfig.kt new file mode 100644 index 00000000..74796e60 --- /dev/null +++ b/src/main/kotlin/fr/shikkanime/utils/TelemetryConfig.kt @@ -0,0 +1,87 @@ +package fr.shikkanime.utils + +import io.opentelemetry.api.GlobalOpenTelemetry +import io.opentelemetry.api.OpenTelemetry +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.StatusCode +import io.opentelemetry.api.trace.Tracer +import io.opentelemetry.context.Context +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.resources.Resource +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor +import io.opentelemetry.sdk.trace.samplers.Sampler + +object TelemetryConfig { + private var isInitialized = false + private lateinit var openTelemetry: OpenTelemetry + + fun initialize() { + if (isInitialized) { + return + } + + val resource = Resource.getDefault() + .merge(Resource.create(Attributes.of( + AttributeKey.stringKey("service.name"), Constant.NAME, + AttributeKey.stringKey("service.version"), this::class.java.`package`.implementationVersion ?: "unknown" + ))) + + val spanExporter = OtlpGrpcSpanExporter.builder() + .setEndpoint("http://localhost:4317") + .build() + + val tracerProvider = SdkTracerProvider.builder() + .addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build()) + .setResource(resource) + .setSampler(Sampler.alwaysOn()) + .build() + + openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(tracerProvider) + .build() + + GlobalOpenTelemetry.resetForTest() + GlobalOpenTelemetry.set(openTelemetry) + + isInitialized = true + } + + fun getTracer(name: String): Tracer? { + if (!isInitialized) { + return null + } + + return openTelemetry.getTracer(name) + } + + private fun getName(): String { + val line = Exception().stackTraceToString().split("\n").drop(1).firstOrNull { !it.contains("TelemetryConfig") } ?: "" + val split = line.substringBefore("(").split(".") + val className = split.dropLast(1).last() + val methodName = split.last().substringBefore("$") + return "$className.$methodName" + } + + fun Tracer.traceWithAttributes(name: String? = null, fn: (Span) -> T): T { + val span = this.spanBuilder(if (name.isNullOrBlank()) getName() else name).setParent(Context.current()).startSpan() + val scope = span.makeCurrent() + + return try { + fn(span) + } catch (e: Exception) { + span.recordException(e) + span.setStatus(StatusCode.ERROR, e.localizedMessage) + throw e + } finally { + scope.close() + span.end() + } + } + + // Surcharge pour compatibilité avec le code existant + fun Tracer?.trace(name: String? = null, fn: () -> T): T = if (this != null) traceWithAttributes(name) { _ -> fn() } else fn() +} \ No newline at end of file