Skip to content

Commit 2dec512

Browse files
authored
80 create server logger (#104)
* Add dependency to Pom * Centralized logger, prints date - time and class. followed by logger level (info/error..) and message * Replace java.util.logger with new logger * Add logger to tcp server * Update tests to match new logger * Switch old util logger to new centralized logger * Update LOG_LEVEL with a fallback * Delete src/main/java/org/example/ServerLogger.java * Change logg -> log * Refactor * Refactor comment
1 parent 80ffccf commit 2dec512

6 files changed

Lines changed: 57 additions & 37 deletions

File tree

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@
7171
<version>1.20.0</version>
7272
</dependency>
7373

74+
<dependency>
75+
<groupId>ch.qos.logback</groupId>
76+
<artifactId>logback-classic</artifactId>
77+
<version>1.5.32</version>
78+
<scope>compile</scope>
79+
</dependency>
80+
7481
</dependencies>
7582
<build>
7683
<plugins>

src/main/java/org/example/TcpServer.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
package org.example;
22

33
import org.example.http.HttpResponseBuilder;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
46

57
import java.io.IOException;
68
import java.io.OutputStream;
7-
import java.io.PrintWriter;
89
import java.net.ServerSocket;
910
import java.net.Socket;
10-
import java.nio.charset.StandardCharsets;
1111
import java.util.Map;
1212

1313
public class TcpServer {
1414

1515
private final int port;
1616
private final ConnectionFactory connectionFactory;
17+
private static final Logger log = LoggerFactory.getLogger(TcpServer.class);
1718

1819
public TcpServer(int port, ConnectionFactory connectionFactory) {
1920
this.port = port;
2021
this.connectionFactory = connectionFactory;
2122
}
2223

2324
public void start() {
24-
System.out.println("Starting TCP server on port " + port);
25+
log.info("Starting TCP server on port {}", port);
2526

2627
try (ServerSocket serverSocket = new ServerSocket(port)) {
2728
while (true) {
2829
Socket clientSocket = serverSocket.accept(); // block
29-
System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress());
30+
log.info("Client connected");
3031
Thread.ofVirtual().start(() -> handleClient(clientSocket));
3132
}
3233
} catch (IOException e) {
@@ -38,16 +39,22 @@ protected void handleClient(Socket client) {
3839
try(client){
3940
processRequest(client);
4041
} catch (Exception e) {
41-
throw new RuntimeException("Failed to close socket", e);
42+
log.error("Failed when handling connection to client", e);
4243
}
4344
}
4445

46+
/*
47+
The connection handler must be run in a try-catch-finally. Otherwise - if we use try-with-resources - the
48+
connection will always be closed when code reaches handleInternalServerError() and there will never
49+
be an error print on the client's webpage.
50+
*/
4551
private void processRequest(Socket client) throws Exception {
4652
ConnectionHandler handler = null;
4753
try{
4854
handler = connectionFactory.create(client);
4955
handler.runConnectionHandler();
5056
} catch (Exception e) {
57+
log.error("Internal Server Error", e);
5158
handleInternalServerError(client);
5259
} finally {
5360
if(handler != null)
@@ -68,7 +75,7 @@ private void handleInternalServerError(Socket client){
6875
out.write(response.build());
6976
out.flush();
7077
} catch (IOException e) {
71-
System.err.println("Failed to send 500 response: " + e.getMessage());
78+
log.error("Client disconnected before 500 response could be sent", e);
7279
}
7380
}
7481
}

src/main/java/org/example/filter/LoggingFilter.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import org.example.http.HttpResponseBuilder;
44
import org.example.httpparser.HttpRequest;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
57

6-
import java.util.logging.Logger;
78

89
public class LoggingFilter implements Filter {
910

10-
private static final Logger logg = Logger.getLogger(LoggingFilter.class.getName());
11+
private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);
1112

1213
@Override
1314
public void init() {
@@ -27,10 +28,8 @@ public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterCh
2728
long endTime = System.nanoTime();
2829
long processingTimeInMs = (endTime - startTime) / 1000000;
2930

30-
String message = String.format("REQUEST: %s %s | STATUS: %s | TIME: %dms",
31+
log.info("REQUEST: {} {} | STATUS: {} | TIME: {}ms",
3132
request.getMethod(), request.getPath(), response.getStatusCode(), processingTimeInMs);
32-
33-
logg.info(message);
3433
}
3534

3635
}

src/main/java/org/example/httpparser/HttpParseRequestLine.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package org.example.httpparser;
22

3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
36
import java.io.BufferedReader;
47
import java.io.IOException;
5-
import java.util.logging.Logger;
68

79
abstract class HttpParseRequestLine {
810
private String method;
911
private String uri;
1012
private String version;
11-
private boolean debug = false;
12-
private static final Logger logger = Logger.getLogger(HttpParseRequestLine.class.getName());
13+
14+
private static final Logger log = LoggerFactory.getLogger(HttpParseRequestLine.class);
1315

1416
public void parseHttpRequest(BufferedReader br) throws IOException {
1517
BufferedReader reader = br;
@@ -31,11 +33,8 @@ public void parseHttpRequest(BufferedReader br) throws IOException {
3133
setVersion(requestLineArray[2]);
3234
}
3335

34-
if(debug) {
35-
logger.info(getMethod());
36-
logger.info(getUri());
37-
logger.info(getVersion());
38-
}
36+
log.debug("METHOD: {} | URI: {} | VERSION: {}",
37+
getMethod(), getUri(), getVersion());
3938
}
4039

4140

src/main/resources/logback.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<configuration>
2+
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder>
4+
<pattern>%d{yyyy-MM-dd HH:mm:ss} %cyan(%logger{36}) %n%highlight(%-5level) - %msg%n</pattern>
5+
</encoder>
6+
</appender>
7+
8+
<root level="${LOG_LEVEL:-INFO}">
9+
<appender-ref ref="CONSOLE" />
10+
</root>
11+
</configuration>

src/test/java/org/example/filter/LoggingFilterTest.java

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,45 @@
11
package org.example.filter;
22

3+
import ch.qos.logback.classic.spi.ILoggingEvent;
4+
import ch.qos.logback.core.read.ListAppender;
35
import org.example.http.HttpResponseBuilder;
46
import org.example.httpparser.HttpRequest;
5-
import org.junit.jupiter.api.AfterEach;
67
import org.junit.jupiter.api.BeforeEach;
78
import org.junit.jupiter.api.Test;
89
import org.junit.jupiter.api.extension.ExtendWith;
9-
import org.mockito.ArgumentCaptor;
1010
import org.mockito.Mock;
1111
import org.mockito.junit.jupiter.MockitoExtension;
12+
import ch.qos.logback.classic.Logger;
13+
import org.slf4j.LoggerFactory;
1214

13-
import java.util.logging.Handler;
14-
import java.util.logging.LogRecord;
15-
import java.util.logging.Logger;
15+
import java.util.List;
1616

1717
import static org.assertj.core.api.Assertions.assertThat;
1818
import static org.mockito.Mockito.*;
1919

2020
@ExtendWith(MockitoExtension.class)
2121
class LoggingFilterTest {
2222

23-
@Mock Handler handler;
2423
@Mock HttpRequest request;
2524
@Mock HttpResponseBuilder response;
2625
@Mock FilterChain chain;
2726

2827
LoggingFilter filter = new LoggingFilter();
29-
Logger logger;
28+
ListAppender<ILoggingEvent> appender;
3029

3130
@BeforeEach
3231
void setup(){
33-
logger = Logger.getLogger(LoggingFilter.class.getName());
34-
logger.addHandler(handler);
32+
Logger logger = (Logger) LoggerFactory.getLogger(LoggingFilter.class);
33+
34+
appender = new ListAppender<>();
35+
appender.start();
36+
37+
logger.addAppender(appender);
3538

3639
when(request.getMethod()).thenReturn("GET");
3740
when(request.getPath()).thenReturn("/index.html");
3841
}
3942

40-
@AfterEach
41-
void tearDown(){
42-
logger.removeHandler(handler);
43-
}
44-
4543
@Test
4644
void loggingWorksWhenChainWorks(){
4745
when(response.getStatusCode()).thenReturn(HttpResponseBuilder.SC_OK);
@@ -79,11 +77,10 @@ void statusChangesFrom200To500WhenErrorOccurs(){
7977
}
8078

8179
private void verifyLogContent(String expectedMessage){
82-
//Use ArgumentCaptor to capture the actual message in the log
83-
ArgumentCaptor<LogRecord> logCaptor = ArgumentCaptor.forClass(LogRecord.class);
84-
verify(handler).publish(logCaptor.capture());
80+
List<ILoggingEvent> logs = appender.list;
8581

86-
String message = logCaptor.getValue().getMessage();
82+
assertThat(logs).isNotEmpty();
83+
String message = logs.getFirst().getFormattedMessage();
8784

8885
assertThat(message).contains(expectedMessage);
8986
}

0 commit comments

Comments
 (0)