Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#115 Collect Spring logs to Loki #119

Merged
merged 5 commits into from
Jan 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package ru.ifmo.se.dating.authik.api
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import ru.ifmo.se.dating.authik.api.generated.AuthApiDelegate
import ru.ifmo.se.dating.authik.external.telegram.TelegramInitDataParser
import ru.ifmo.se.dating.authik.logic.AuthService
import ru.ifmo.se.dating.authik.model.generated.AuthGrantMessage
import ru.ifmo.se.dating.authik.model.generated.TelegramInitDataMessage
import ru.ifmo.se.dating.authik.external.telegram.TelegramInitDataParser
import ru.ifmo.se.dating.exception.AuthenticationException
import ru.ifmo.se.dating.exception.GenericException
import ru.ifmo.se.dating.logging.Log.Companion.autoLog
Expand All @@ -28,7 +28,7 @@ class HttpAuthApi(
return ResponseEntity.ok(response)
}

private fun parseInitData(telegramInitDataMessage: TelegramInitDataMessage) =
private suspend fun parseInitData(telegramInitDataMessage: TelegramInitDataMessage) =
try {
telegramParser.parse(telegramInitDataMessage)
} catch (error: GenericException) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ru.ifmo.se.dating.authik.logic.basic

import kotlinx.coroutines.runBlocking
import ru.ifmo.se.dating.authik.logic.AuthService
import ru.ifmo.se.dating.authik.model.generated.TelegramWebAppInitDataMessage
import ru.ifmo.se.dating.authik.security.auth.TokenIssuer
Expand All @@ -17,8 +18,10 @@ class BasicAuthService(
private val log = autoLog()

init {
val adamToken = issuer.issue(AccessToken.Payload(User.Id(ADAM_ID)))
log.info("Issued adam token: '${adamToken.text}'")
runBlocking {
val adamToken = issuer.issue(AccessToken.Payload(User.Id(ADAM_ID)))
log.info("Issued adam token: '${adamToken.text}'")
}
}

override suspend fun authenticate(telegram: TelegramWebAppInitDataMessage): AccessToken =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ class AuthikSecuredPaths : SpringSecuredPaths {
Path("/api/**"),
Not(Path("/api/auth/telegram/web-app", HttpMethod.GET)),
Not(Path("/api/monitoring/healthcheck", HttpMethod.PUT)),
Not(Path("/actuator/**", HttpMethod.GET)),
Not(Path("/actuator/prometheus", HttpMethod.GET)),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class JwtTokenIssuer(
) : TokenIssuer {
private val duration: JavaDuration = duration.toJavaDuration()

override fun issue(payload: AccessToken.Payload): AccessToken {
override suspend fun issue(payload: AccessToken.Payload): AccessToken {
val now = clock.instant()
return Jwts.builder()
.claims(Jwt.serialize(payload))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ru.ifmo.se.dating.security.auth.AccessToken
class LoggingTokenIssuer(private val origin: TokenIssuer) : TokenIssuer {
private val log = autoLog()

override fun issue(payload: AccessToken.Payload): AccessToken =
override suspend fun issue(payload: AccessToken.Payload): AccessToken =
runCatching { origin.issue(payload) }
.onSuccess { log.info("Issued access token for user with id ${payload.userId}") }
.onFailure { e ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package ru.ifmo.se.dating.authik.security.auth
import ru.ifmo.se.dating.security.auth.AccessToken

interface TokenIssuer {
fun issue(payload: AccessToken.Payload): AccessToken
suspend fun issue(payload: AccessToken.Payload): AccessToken
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class LoggingTransactionalOutbox<E, Id>(
) : TransactionalOutbox<E, Id> by origin {
private val log = Log.forClass(origin.javaClass)

override fun publishable(): Flow<Id> =
override suspend fun publishable(): Flow<Id> =
runCatching { origin.publishable() }
.onSuccess { log.info("Retrieved publishable events") }
.onFailure { log.warn("Failed to retrieve publishable events") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ru.ifmo.se.dating.storage.TxEnv
@Suppress("ComplexInterface")
interface TransactionalOutbox<E, Id> {
val tx: TxEnv
fun publishable(): Flow<Id>
suspend fun publishable(): Flow<Id>
suspend fun acquireById(id: Id): E
suspend fun isPublished(event: E): Boolean
suspend fun doProcess(event: E)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class JwtTokenDecoder(
private val clock: Clock,
private val publicSignKey: PublicKey,
) : TokenDecoder {
override fun decode(token: AccessToken): AccessToken.Payload =
override suspend fun decode(token: AccessToken): AccessToken.Payload =
try {
Jwts.parser()
.verifyWith(publicSignKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ru.ifmo.se.dating.logging.Log.Companion.autoLog
class LoggingTokenDecoder(private val origin: TokenDecoder) : TokenDecoder {
private val log = autoLog()

override fun decode(token: AccessToken): AccessToken.Payload =
override suspend fun decode(token: AccessToken): AccessToken.Payload =
runCatching { origin.decode(token) }
.onSuccess { log.debug("Decoded a token of user with id ${it.userId}") }
.onFailure { log.warn("Failed to decode an auth token") }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package ru.ifmo.se.dating.security.auth

interface TokenDecoder {
fun decode(token: AccessToken): AccessToken.Payload
suspend fun decode(token: AccessToken): AccessToken.Payload
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
import ru.ifmo.se.dating.exception.AuthenticationException
import ru.ifmo.se.dating.exception.GenericException
import ru.ifmo.se.dating.logging.Log.Companion.autoLog
import ru.ifmo.se.dating.spring.exception.SpringGenericExceptionHandler

@Component
Expand Down Expand Up @@ -42,8 +41,6 @@ class SpringJwtContextRepository(
private val headerName = HttpHeaders.AUTHORIZATION
private val bearerPrefix = "Bearer "

private val log = autoLog()

override fun save(
exchange: ServerWebExchange,
context: SecurityContext,
Expand All @@ -64,7 +61,7 @@ class SpringJwtContextRepository(
null
}

private fun extractBearer(exchange: ServerWebExchange): String = runCatching {
private suspend fun extractBearer(exchange: ServerWebExchange): String {
val count = exchange.request.headers.getOrEmpty(headerName).size
if (count != 1) {
throw AuthenticationException(
Expand All @@ -79,9 +76,6 @@ class SpringJwtContextRepository(
)
}

bearer.substringAfter(bearerPrefix)
return bearer.substringAfter(bearerPrefix)
}
.onSuccess { log.debug("Extracted a bearer token") }
.onFailure { e -> log.warn("Failed to extract a bearer token: ${e.message}") }
.getOrThrow()
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ru.ifmo.se.dating.spring.storage

import kotlinx.coroutines.runBlocking
import org.springframework.beans.factory.annotation.Value
import org.springframework.jdbc.datasource.SingleConnectionDataSource
import org.springframework.stereotype.Component
Expand Down Expand Up @@ -27,7 +28,7 @@ class SpringLiquibaseMigration(
private val log = autoLog()

init {
log.info("Running a liquibase migration...")
runBlocking { log.info("Running a liquibase migration...") }

val suppressClose = false
SingleConnectionDataSource(url, username, password, suppressClose).use {
Expand All @@ -38,6 +39,6 @@ class SpringLiquibaseMigration(
).run()
}

log.info("Liquibase migration was completed successfully")
runBlocking { log.info("Liquibase migration was completed successfully") }
}
}
4 changes: 4 additions & 0 deletions backend/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ org-jetbrains-kotlinx-kotlinx-coroutines = "1.9.0"
io-projectreactor-kotlin-reactor-kotlin-extensions = "1.2.3"

io-micrometer = "1.14.3"
com-github-loki4j = "1.6.0"
io-github-numichi-reactive-logger = "6.0.3"

io-projectreactor-reactor-test = "3.6.11"
junit-junit = "4.13.2"
Expand Down Expand Up @@ -79,6 +81,8 @@ org-postgresql-postgresql = { module = "org.postgresql:postgresql", version.ref
org-postgresql-r2dbc-postgresql = { module = "org.postgresql:r2dbc-postgresql", version.ref = "org-postgresql-r2dbc-postgresql" }

io-micrometer-micrometer-registry-prometheus = { module = "io.micrometer:micrometer-registry-prometheus", version.ref = "io-micrometer" }
com-github-loki4j-loki-logback-appender = { module = "com.github.loki4j:loki-logback-appender", version.ref = "com-github-loki4j" }
io-github-numichi-reactive-logger = { module = "io.github.numichi:reactive-logger", version.ref = "io-github-numichi-reactive-logger" }

junit-junit = { module = "junit:junit", version.ref = "junit-junit" }
org-testcontainers-postgresql = { module = "org.testcontainers:postgresql", version.ref = "org-testcontainers" }
Expand Down
7 changes: 7 additions & 0 deletions backend/grafana/provisioning/datasources/all.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
apiVersion: 1
datasources:

- name: Prometheus
label: Prometheus
type: prometheus
access: proxy
url: http://${ITMO_DATING_PROMETHEUS_HOST}:9090
isDefault: true

- name: Loki
label: Loki
type: loki
access: proxy
url: http://${ITMO_DATING_LOKI_HOST}:3100
3 changes: 3 additions & 0 deletions backend/loki/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM grafana/loki

COPY ./config.yaml /etc/loki/local-config.yaml
40 changes: 40 additions & 0 deletions backend/loki/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
auth_enabled: false

server:
http_listen_port: 3100

common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory

compactor:
working_directory: /loki/retention
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 32
delete_request_store: filesystem

schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h

limits_config:
retention_period: 72h

ruler:
alertmanager_url: http://localhost:9093
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ru.ifmo.se.dating.matchmaker.api

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import ru.ifmo.se.dating.matchmaker.api.generated.StatisticsApiDelegate
Expand All @@ -14,8 +15,9 @@ typealias StatisticsAttitudesResponse =

@Controller
class HttpStatisticsApi(private val service: StatisticsService) : StatisticsApiDelegate {
override fun statisticsAttitudesGet(): StatisticsAttitudesResponse =
override fun statisticsAttitudesGet(): StatisticsAttitudesResponse = runBlocking {
service.selectAttitudesByPerson()
.map { it.toMessage() }
.let { ResponseEntity.ok(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ import ru.ifmo.se.dating.security.auth.User

interface AttitudeService {
suspend fun express(attitude: Attitude)
fun matches(client: User.Id): Flow<User.Id>
fun suggestions(client: User.Id, limit: Int): Flow<User.Id>
suspend fun matches(client: User.Id): Flow<User.Id>
suspend fun suggestions(client: User.Id, limit: Int): Flow<User.Id>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import kotlinx.coroutines.flow.Flow
import ru.ifmo.se.dating.matchmaker.model.AttitudesStatistics

interface StatisticsService {
fun selectAttitudesByPerson(): Flow<AttitudesStatistics>
suspend fun selectAttitudesByPerson(): Flow<AttitudesStatistics>
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ class BasicAttitudeService(
throw NotFoundException("source or target ids does not exist", exception)
}

override fun matches(client: User.Id): Flow<User.Id> =
override suspend fun matches(client: User.Id): Flow<User.Id> =
storage.selectLikedBack(client)

override fun suggestions(client: User.Id, limit: Int): Flow<User.Id> =
override suspend fun suggestions(client: User.Id, limit: Int): Flow<User.Id> =
storage.selectUnknownFor(client, limit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ import ru.ifmo.se.dating.matchmaker.model.AttitudesStatistics
import ru.ifmo.se.dating.matchmaker.storage.StatisticsStorage

class BasicStatisticsService(private val storage: StatisticsStorage) : StatisticsService {
override fun selectAttitudesByPerson(): Flow<AttitudesStatistics> =
override suspend fun selectAttitudesByPerson(): Flow<AttitudesStatistics> =
storage.selectAttitudesByPerson()
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class LoggingAttitudeService(private val origin: AttitudeService) : AttitudeServ
}
.getOrThrow()

override fun matches(client: User.Id): Flow<User.Id> =
override suspend fun matches(client: User.Id): Flow<User.Id> =
runCatching { origin.matches(client) }
.onSuccess { log.debug("Got matches for client with id ${client.number}") }
.onFailure { e ->
Expand All @@ -40,7 +40,7 @@ class LoggingAttitudeService(private val origin: AttitudeService) : AttitudeServ
}
.getOrThrow()

override fun suggestions(client: User.Id, limit: Int): Flow<User.Id> =
override suspend fun suggestions(client: User.Id, limit: Int): Flow<User.Id> =
runCatching { origin.suggestions(client, limit) }
.onSuccess {
log.debug("Got no more than $limit suggestions for client with id $client")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import ru.ifmo.se.dating.matchmaker.model.AttitudesStatistics
class LoggingStatisticsService(private val origin: StatisticsService) : StatisticsService {
private val log = autoLog()

override fun selectAttitudesByPerson(): Flow<AttitudesStatistics> =
override suspend fun selectAttitudesByPerson(): Flow<AttitudesStatistics> =
runCatching { origin.selectAttitudesByPerson() }
.onSuccess { log.warn("Someone got attitudes by person statistics") }
.onFailure { e -> log.warn("Failed to got statistics: ${e.message}") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ class MatchmakerSecuredPaths : SpringSecuredPaths {
Not(Path("/api/people/{person_id}", HttpMethod.PUT)),
Not(Path("/api/monitoring/healthcheck", HttpMethod.GET)),
Not(Path("/api/suggestions", HttpMethod.OPTIONS)),
Not(Path("/api/monitoring/healthcheck", HttpMethod.PUT)),
Not(Path("/actuator/**", HttpMethod.GET)),
Not(Path("/actuator/prometheus", HttpMethod.GET)),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ru.ifmo.se.dating.people.api

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import ru.ifmo.se.dating.people.api.generated.FacultiesApiDelegate
Expand All @@ -11,7 +12,8 @@ import ru.ifmo.se.dating.people.model.generated.FacultyMessage

@Controller
class HttpFacultiesApi(private val service: FacultyService) : FacultiesApiDelegate {
override fun facultiesGet(): ResponseEntity<Flow<FacultyMessage>> =
override fun facultiesGet(): ResponseEntity<Flow<FacultyMessage>> = runBlocking {
service.getAll().map { it.toMessage() }
.let { ResponseEntity.ok(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ru.ifmo.se.dating.people.api

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import ru.ifmo.se.dating.people.api.generated.LocationsApiDelegate
Expand All @@ -11,7 +12,8 @@ import ru.ifmo.se.dating.people.model.generated.LocationMessage

@Controller
class HttpLocationsApi(private val service: LocationService) : LocationsApiDelegate {
override fun locationsGet(): ResponseEntity<Flow<LocationMessage>> =
override fun locationsGet(): ResponseEntity<Flow<LocationMessage>> = runBlocking {
service.getAll().map { it.toMessage() }
.let { ResponseEntity.ok(it) }
}
}
Loading
Loading