(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:
@@ -1,10 +1,13 @@
|
|||||||
package at.mocode.gateway.config
|
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 at.mocode.shared.config.AppConfig
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.plugins.calllogging.*
|
import io.ktor.server.plugins.calllogging.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
import io.ktor.util.*
|
import io.ktor.util.*
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.slf4j.event.Level
|
import org.slf4j.event.Level
|
||||||
@@ -96,20 +99,9 @@ fun Application.configureLogSampling() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify the CallLogging plugin to respect the sampling decision
|
// Instead of trying to modify CallLogging after installation,
|
||||||
environment.monitor.subscribe(CallLogging.LoggingConfig) { loggingConfig ->
|
// we'll use the interceptor to decide if logging should happen
|
||||||
// Add a filter to the CallLogging plugin
|
// The CallLogging plugin will be configured in MonitoringConfig.kt
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package at.mocode.gateway.config
|
package at.mocode.gateway.config
|
||||||
|
|
||||||
import at.mocode.dto.base.ApiResponse
|
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 at.mocode.shared.config.AppConfig
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
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.plugins.statuspages.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
@@ -125,13 +127,13 @@ fun Application.configureMonitoring() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filtere Pfade, die vom Logging ausgeschlossen werden sollen
|
// Filtere Pfade, die vom Logging ausgeschlossen werden sollen
|
||||||
filter { call ->
|
filter { call: ApplicationCall ->
|
||||||
val path = call.request.path()
|
val path = call.request.path()
|
||||||
!loggingConfig.excludePaths.any { path.startsWith(it) }
|
!loggingConfig.excludePaths.any { path.startsWith(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formatiere Log-Einträge mit erweitertem Format
|
// Formatiere Log-Einträge mit erweitertem Format
|
||||||
format { call ->
|
format { call: ApplicationCall ->
|
||||||
val status = call.response.status()
|
val status = call.response.status()
|
||||||
val httpMethod = call.request.httpMethod.value
|
val httpMethod = call.request.httpMethod.value
|
||||||
val path = call.request.path()
|
val path = call.request.path()
|
||||||
@@ -140,7 +142,7 @@ fun Application.configureMonitoring() {
|
|||||||
val timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
val timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||||
|
|
||||||
// Get the request ID from the call attributes (set by RequestTracingConfig)
|
// 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) {
|
if (loggingConfig.useStructuredLogging) {
|
||||||
// Strukturiertes Logging-Format
|
// Strukturiertes Logging-Format
|
||||||
@@ -167,11 +169,11 @@ fun Application.configureMonitoring() {
|
|||||||
// Log all headers if in debug mode, filtering sensitive data
|
// Log all headers if in debug mode, filtering sensitive data
|
||||||
if (loggingConfig.level.equals("DEBUG", ignoreCase = true)) {
|
if (loggingConfig.level.equals("DEBUG", ignoreCase = true)) {
|
||||||
append("headers={")
|
append("headers={")
|
||||||
call.request.headers.entries().joinTo(this, ", ") {
|
call.request.headers.entries().joinTo(this, ", ") { entry ->
|
||||||
if (isSensitiveHeader(it.key)) {
|
if (isSensitiveHeader(entry.key)) {
|
||||||
"${it.key}=*****"
|
"${entry.key}=*****"
|
||||||
} else {
|
} else {
|
||||||
"${it.key}=${it.value.joinToString(",")}"
|
"${entry.key}=${entry.value.joinToString(",")}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
append("} ")
|
append("} ")
|
||||||
@@ -181,22 +183,24 @@ fun Application.configureMonitoring() {
|
|||||||
// Log Query-Parameter wenn konfiguriert
|
// Log Query-Parameter wenn konfiguriert
|
||||||
if (loggingConfig.logRequestParameters && call.request.queryParameters.entries().isNotEmpty()) {
|
if (loggingConfig.logRequestParameters && call.request.queryParameters.entries().isNotEmpty()) {
|
||||||
append("params={")
|
append("params={")
|
||||||
call.request.queryParameters.entries().joinTo(this, ", ") {
|
call.request.queryParameters.entries().joinTo(this, ", ") { entry ->
|
||||||
if (isSensitiveParameter(it.key)) {
|
if (isSensitiveParameter(entry.key)) {
|
||||||
"${it.key}=*****"
|
"${entry.key}=*****"
|
||||||
} else {
|
} else {
|
||||||
"${it.key}=${it.value.joinToString(",")}"
|
"${entry.key}=${entry.value.joinToString(",")}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
append("} ")
|
append("} ")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userAgent != null) {
|
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
|
// 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
|
val duration = System.currentTimeMillis() - startTime
|
||||||
append("duration=${duration}ms ")
|
append("duration=${duration}ms ")
|
||||||
}
|
}
|
||||||
@@ -215,8 +219,8 @@ fun Application.configureMonitoring() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Einfaches Logging-Format
|
// Einfaches Logging-Format
|
||||||
val duration = call.attributes.getOrNull(REQUEST_START_TIME_KEY)?.let {
|
val duration = call.attributes.getOrNull(REQUEST_START_TIME_KEY)?.let { startTime: Long ->
|
||||||
" - Duration: ${System.currentTimeMillis() - it}ms"
|
" - Duration: ${System.currentTimeMillis() - startTime}ms"
|
||||||
} ?: ""
|
} ?: ""
|
||||||
|
|
||||||
"$timestamp - $status: $httpMethod $path - RequestID: $requestId - $clientIp - $userAgent$duration"
|
"$timestamp - $status: $httpMethod $path - RequestID: $requestId - $clientIp - $userAgent$duration"
|
||||||
@@ -235,9 +239,9 @@ fun Application.configureMonitoring() {
|
|||||||
"propagateRequestId=${loggingConfig.propagateRequestId}")
|
"propagateRequestId=${loggingConfig.propagateRequestId}")
|
||||||
|
|
||||||
install(StatusPages) {
|
install(StatusPages) {
|
||||||
exception<Throwable> { call, cause ->
|
exception<Throwable> { call: ApplicationCall, cause: Throwable ->
|
||||||
// Get the request ID for error logging
|
// 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.application.log.error("Unhandled exception - RequestID: $requestId", cause)
|
||||||
call.respond(
|
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
|
// 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.application.log.warn("Not found - Path: ${call.request.path()} - RequestID: $requestId")
|
||||||
call.respond(
|
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
|
// 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.application.log.warn("Unauthorized access - Path: ${call.request.path()} - RequestID: $requestId")
|
||||||
call.respond(
|
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
|
// 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.application.log.warn("Forbidden access - Path: ${call.request.path()} - RequestID: $requestId")
|
||||||
call.respond(
|
call.respond(
|
||||||
@@ -280,9 +284,9 @@ fun Application.configureMonitoring() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rate limit exceeded
|
// Rate limit exceeded
|
||||||
status(HttpStatusCode.TooManyRequests) { call, status ->
|
status(HttpStatusCode.TooManyRequests) { call: ApplicationCall, status: HttpStatusCode ->
|
||||||
// Get the request ID for error logging
|
// 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.application.log.warn("Rate limit exceeded - Path: ${call.request.path()} - RequestID: $requestId")
|
||||||
call.respond(
|
call.respond(
|
||||||
|
|||||||
Reference in New Issue
Block a user