(fix) Umbau zu SCS

### API-Gateway erweitern
- Bestehenden API-Gateway-Service mit zusätzlichen Funktionen ausstatten:
    - Rate Limiting implementieren
    - Request/Response Logging verbessern
    - Cross-Service Tracing mit eindeutigen Request-IDs einführen
This commit is contained in:
stefan
2025-07-21 17:41:00 +02:00
parent f8eade8091
commit 834c92dcb2
2 changed files with 36 additions and 40 deletions
@@ -1,10 +1,13 @@
package at.mocode.gateway.config
import at.mocode.gateway.config.REQUEST_ID_KEY
import at.mocode.gateway.config.REQUEST_START_TIME_KEY
import at.mocode.shared.config.AppConfig
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.calllogging.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.util.*
import org.slf4j.LoggerFactory
import org.slf4j.event.Level
@@ -96,20 +99,9 @@ fun Application.configureLogSampling() {
}
}
// Modify the CallLogging plugin to respect the sampling decision
environment.monitor.subscribe(CallLogging.LoggingConfig) { loggingConfig ->
// Add a filter to the CallLogging plugin
loggingConfig.filter { call ->
// Check if the request should be logged based on sampling
val shouldLog = call.attributes.getOrNull(SHOULD_LOG_REQUEST_KEY) ?: true
// Apply the original filter as well (exclude paths)
val originalFilter = !AppConfig.logging.excludePaths.any { call.request.path().startsWith(it) }
// Only log if both filters pass
shouldLog && originalFilter
}
}
// Instead of trying to modify CallLogging after installation,
// we'll use the interceptor to decide if logging should happen
// The CallLogging plugin will be configured in MonitoringConfig.kt
}
/**
@@ -1,10 +1,12 @@
package at.mocode.gateway.config
import at.mocode.dto.base.ApiResponse
import at.mocode.gateway.config.REQUEST_ID_KEY
import at.mocode.gateway.config.REQUEST_START_TIME_KEY
import at.mocode.shared.config.AppConfig
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.callloging.*
import io.ktor.server.plugins.calllogging.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.server.request.*
import io.ktor.server.response.*
@@ -125,13 +127,13 @@ fun Application.configureMonitoring() {
}
// Filtere Pfade, die vom Logging ausgeschlossen werden sollen
filter { call ->
filter { call: ApplicationCall ->
val path = call.request.path()
!loggingConfig.excludePaths.any { path.startsWith(it) }
}
// Formatiere Log-Einträge mit erweitertem Format
format { call ->
format { call: ApplicationCall ->
val status = call.response.status()
val httpMethod = call.request.httpMethod.value
val path = call.request.path()
@@ -140,7 +142,7 @@ fun Application.configureMonitoring() {
val timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
// Get the request ID from the call attributes (set by RequestTracingConfig)
val requestId = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
val requestId: String = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
if (loggingConfig.useStructuredLogging) {
// Strukturiertes Logging-Format
@@ -167,11 +169,11 @@ fun Application.configureMonitoring() {
// Log all headers if in debug mode, filtering sensitive data
if (loggingConfig.level.equals("DEBUG", ignoreCase = true)) {
append("headers={")
call.request.headers.entries().joinTo(this, ", ") {
if (isSensitiveHeader(it.key)) {
"${it.key}=*****"
call.request.headers.entries().joinTo(this, ", ") { entry ->
if (isSensitiveHeader(entry.key)) {
"${entry.key}=*****"
} else {
"${it.key}=${it.value.joinToString(",")}"
"${entry.key}=${entry.value.joinToString(",")}"
}
}
append("} ")
@@ -181,22 +183,24 @@ fun Application.configureMonitoring() {
// Log Query-Parameter wenn konfiguriert
if (loggingConfig.logRequestParameters && call.request.queryParameters.entries().isNotEmpty()) {
append("params={")
call.request.queryParameters.entries().joinTo(this, ", ") {
if (isSensitiveParameter(it.key)) {
"${it.key}=*****"
call.request.queryParameters.entries().joinTo(this, ", ") { entry ->
if (isSensitiveParameter(entry.key)) {
"${entry.key}=*****"
} else {
"${it.key}=${it.value.joinToString(",")}"
"${entry.key}=${entry.value.joinToString(",")}"
}
}
append("} ")
}
if (userAgent != null) {
append("userAgent=\"${userAgent.replace("\"", "\\\"")}\" ")
// Use a simpler approach to avoid escape sequence issues
val escapedUserAgent = userAgent.replace("\"", "\\\"")
append("userAgent=\"$escapedUserAgent\" ")
}
// Log response time if available from RequestTracingConfig
call.attributes.getOrNull(REQUEST_START_TIME_KEY)?.let { startTime ->
call.attributes.getOrNull(REQUEST_START_TIME_KEY)?.let { startTime: Long ->
val duration = System.currentTimeMillis() - startTime
append("duration=${duration}ms ")
}
@@ -215,8 +219,8 @@ fun Application.configureMonitoring() {
}
} else {
// Einfaches Logging-Format
val duration = call.attributes.getOrNull(REQUEST_START_TIME_KEY)?.let {
" - Duration: ${System.currentTimeMillis() - it}ms"
val duration = call.attributes.getOrNull(REQUEST_START_TIME_KEY)?.let { startTime: Long ->
" - Duration: ${System.currentTimeMillis() - startTime}ms"
} ?: ""
"$timestamp - $status: $httpMethod $path - RequestID: $requestId - $clientIp - $userAgent$duration"
@@ -235,9 +239,9 @@ fun Application.configureMonitoring() {
"propagateRequestId=${loggingConfig.propagateRequestId}")
install(StatusPages) {
exception<Throwable> { call, cause ->
exception<Throwable> { call: ApplicationCall, cause: Throwable ->
// Get the request ID for error logging
val requestId = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
val requestId: String = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
call.application.log.error("Unhandled exception - RequestID: $requestId", cause)
call.respond(
@@ -246,9 +250,9 @@ fun Application.configureMonitoring() {
)
}
status(HttpStatusCode.NotFound) { call, status ->
status(HttpStatusCode.NotFound) { call: ApplicationCall, status: HttpStatusCode ->
// Get the request ID for error logging
val requestId = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
val requestId: String = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
call.application.log.warn("Not found - Path: ${call.request.path()} - RequestID: $requestId")
call.respond(
@@ -257,9 +261,9 @@ fun Application.configureMonitoring() {
)
}
status(HttpStatusCode.Unauthorized) { call, status ->
status(HttpStatusCode.Unauthorized) { call: ApplicationCall, status: HttpStatusCode ->
// Get the request ID for error logging
val requestId = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
val requestId: String = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
call.application.log.warn("Unauthorized access - Path: ${call.request.path()} - RequestID: $requestId")
call.respond(
@@ -268,9 +272,9 @@ fun Application.configureMonitoring() {
)
}
status(HttpStatusCode.Forbidden) { call, status ->
status(HttpStatusCode.Forbidden) { call: ApplicationCall, status: HttpStatusCode ->
// Get the request ID for error logging
val requestId = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
val requestId: String = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
call.application.log.warn("Forbidden access - Path: ${call.request.path()} - RequestID: $requestId")
call.respond(
@@ -280,9 +284,9 @@ fun Application.configureMonitoring() {
}
// Rate limit exceeded
status(HttpStatusCode.TooManyRequests) { call, status ->
status(HttpStatusCode.TooManyRequests) { call: ApplicationCall, status: HttpStatusCode ->
// Get the request ID for error logging
val requestId = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
val requestId: String = call.attributes.getOrNull(REQUEST_ID_KEY) ?: "no-request-id"
call.application.log.warn("Rate limit exceeded - Path: ${call.request.path()} - RequestID: $requestId")
call.respond(