Skip to content

Commit ddf40fc

Browse files
authored
Merge pull request #94 from AptFox/docker-image-optimizations
Docker image and log polish
2 parents f94405a + 2ec932a commit ddf40fc

5 files changed

Lines changed: 77 additions & 4 deletions

File tree

Dockerfile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@ COPY src ./src
1818
RUN ./gradlew bootJar --no-daemon
1919

2020
# Stage 2: Create the production image
21-
FROM eclipse-temurin:21-jdk
21+
FROM eclipse-temurin:21-jre-jammy
22+
23+
# Create non-root user
24+
RUN groupadd -r spring && useradd -r -g spring spring
2225

2326
# Set the working directory inside the container
2427
WORKDIR /app
2528

2629
# Copy the built JAR file from the builder stage
2730
COPY --from=builder /app/build/libs/harmony-backend-*.jar app.jar
31+
RUN chown spring:spring app.jar
32+
33+
# Switch to non-root user
34+
USER spring
2835

2936
# Define the entrypoint for running the application
30-
ENTRYPOINT ["java", "-Xmx1g", "-Xss512k", "-Xms512m", "-XX:MaxJavaStackTraceDepth=20", "-XX:+UseContainerSupport", "-jar", "app.jar"]
37+
ENTRYPOINT ["java", "-XX:InitialRAMPercentage=25", "-XX:MaxRAMPercentage=60", "-Xss512k", "-XX:MaxJavaStackTraceDepth=20", "-XX:+UseContainerSupport", "-jar", "app.jar"]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package iterative.harmony.backend.config
2+
3+
import iterative.harmony.backend.util.getLogger
4+
import java.lang.management.ManagementFactory
5+
import java.lang.management.MemoryMXBean
6+
import java.util.concurrent.TimeUnit
7+
import org.springframework.scheduling.annotation.Scheduled
8+
import org.springframework.stereotype.Component
9+
10+
@Component
11+
class JvmMemoryLogger(private val memoryBean: MemoryMXBean = ManagementFactory.getMemoryMXBean()) {
12+
private val log = getLogger()
13+
14+
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES)
15+
fun logMemoryUsage() {
16+
val heap = memoryBean.heapMemoryUsage
17+
val nonHeap = memoryBean.nonHeapMemoryUsage
18+
19+
log.info(
20+
"JVM memory | heap: used={}MB committed={}MB max={}MB | non-heap: used={}MB committed={}MB",
21+
toMb(heap.used),
22+
toMb(heap.committed),
23+
toMb(heap.max),
24+
toMb(nonHeap.used),
25+
toMb(nonHeap.committed),
26+
)
27+
}
28+
29+
private fun toMb(bytes: Long): Long = bytes / 1024 / 1024
30+
}

src/main/kotlin/iterative/harmony/backend/config/LoggingFilter.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,63 @@
11
package iterative.harmony.backend.config
22

33
import iterative.harmony.backend.util.Utils
4+
import iterative.harmony.backend.util.getLogger
45
import iterative.harmony.backend.util.setUserIdInLogs
56
import jakarta.servlet.FilterChain
67
import jakarta.servlet.http.HttpServletRequest
78
import jakarta.servlet.http.HttpServletResponse
9+
import java.util.UUID
810
import org.slf4j.MDC
911
import org.springframework.stereotype.Component
1012
import org.springframework.web.filter.OncePerRequestFilter
1113

1214
@Component
1315
class LoggingFilter : OncePerRequestFilter() {
16+
17+
private val log = getLogger()
18+
19+
private val ignoredExactPaths =
20+
setOf("/robots.txt", "/favicon.ico", "/.env", "/.git", "/.git/config")
21+
22+
private val ignoredPathPrefixes =
23+
listOf("/.git/", "/.well-known/", "/wp-", "/phpmyadmin", "/phpinfo.php")
24+
25+
override fun shouldNotFilter(request: HttpServletRequest): Boolean {
26+
val path = request.requestURI
27+
28+
if (path in ignoredExactPaths && request.method == "GET") return true
29+
30+
return request.method == "GET" && ignoredPathPrefixes.any { path.startsWith(it) }
31+
}
32+
1433
override fun doFilterInternal(
1534
request: HttpServletRequest,
1635
response: HttpServletResponse,
1736
filterChain: FilterChain,
1837
) {
38+
val start = System.nanoTime()
39+
val requestId = UUID.randomUUID().toString().take(8)
40+
MDC.put("requestId", requestId)
1941
try {
2042
val userId = Utils().getUserIdFromSecurityContext()
2143
setUserIdInLogs(userId)
2244

2345
filterChain.doFilter(request, response)
2446
} finally {
47+
val requestUri = request.requestURI
48+
val query = request.queryString?.let { "?$it" } ?: ""
49+
val requestPath = "$requestUri$query"
50+
val durationMs = (System.nanoTime() - start) / 1_000_000
51+
val clientIp =
52+
request.getHeader("X-Forwarded-For")?.split(",")?.first() ?: request.remoteAddr
53+
log.info(
54+
"{} {} {} {}ms ip={}",
55+
request.method,
56+
requestPath,
57+
response.status,
58+
durationMs,
59+
clientIp,
60+
)
2561
MDC.clear() // Clear logging context
2662
}
2763
}

src/main/kotlin/iterative/harmony/backend/util/Constants.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ object SecurityConstants {
2525
const val COOKIE_EXPIRATION_IN_SECONDS = 60 * 60 * 48 // 48 hours
2626
const val AUTH_PREFIX: String = "auth"
2727
const val NON_AUTH_PREFIX: String = "nonAuth"
28-
const val ANON_USER_ID: String = "ANON_USER"
28+
const val ANON_USER_ID: String = "ANON"
2929
const val ANON_USER_AGENT: String = "ANON_BROWSER"
3030
const val ANON_REQUEST_IP: String = "0.0.0.0"
3131
}

src/main/resources/application.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ sentry.max-request-body-size=none
5757

5858
# Logging
5959
# Log pattern: timestamp, log level, classname - message
60-
logging.pattern.console=%d{HH:mm:ss:SSS} %-5level %logger{1}: user=%X{userId} - %msg%n%rEx{10}
60+
logging.pattern.console=%d{HH:mm:ss:SSS} %-5level %logger{1}: requestId=%X{requestId} user=%X{userId} - %msg%n%rEx{10}
6161
spring.output.ansi.enabled=ALWAYS
6262
logging.level.web=WARN
6363
logging.level.org.springframework=WARN

0 commit comments

Comments
 (0)