diff --git a/spring-boot-modules/spring-boot-logging-loki/README.md b/spring-boot-modules/spring-boot-logging-loki/README.md new file mode 100644 index 0000000000..e320af31b4 --- /dev/null +++ b/spring-boot-modules/spring-boot-logging-loki/README.md @@ -0,0 +1 @@ +### Relevant Articles: \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-logging-loki/pom.xml b/spring-boot-modules/spring-boot-logging-loki/pom.xml new file mode 100644 index 0000000000..fa568119fb --- /dev/null +++ b/spring-boot-modules/spring-boot-logging-loki/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + spring-boot-logging-loki + 0.1-SNAPSHOT + loki + + + com.baeldung.spring-boot-modules + spring-boot-modules + 1.0.0-SNAPSHOT + + + + + com.github.loki4j + loki-logback-appender + ${loki-logback.version} + + + org.springframework.boot + spring-boot-starter-test + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + 1.4.2 + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-logging-loki/src/main/java/com/baeldung/loki/DemoService.java b/spring-boot-modules/spring-boot-logging-loki/src/main/java/com/baeldung/loki/DemoService.java new file mode 100644 index 0000000000..236e5b530c --- /dev/null +++ b/spring-boot-modules/spring-boot-logging-loki/src/main/java/com/baeldung/loki/DemoService.java @@ -0,0 +1,15 @@ +package com.baeldung.loki; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class DemoService { + + private final Logger LOG = LoggerFactory.getLogger(DemoService.class); + + public void log() { + LOG.info("DemoService.log invoked!!!"); + } +} diff --git a/spring-boot-modules/spring-boot-logging-loki/src/main/java/com/baeldung/loki/SpringBootLogbackExtensionsApplication.java b/spring-boot-modules/spring-boot-logging-loki/src/main/java/com/baeldung/loki/SpringBootLogbackExtensionsApplication.java new file mode 100644 index 0000000000..28e455a7ea --- /dev/null +++ b/spring-boot-modules/spring-boot-logging-loki/src/main/java/com/baeldung/loki/SpringBootLogbackExtensionsApplication.java @@ -0,0 +1,22 @@ +package com.baeldung.extensions; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootLogbackExtensionsApplication { + + private static final Logger logger = LoggerFactory.getLogger(SpringBootLogbackExtensionsApplication.class); + + public static void main(String[] args) { + SpringApplication.run(SpringBootLogbackExtensionsApplication.class, args); + + logger.debug("Debug log message"); + logger.info("Info log message"); + logger.error("Error log message"); + logger.warn("Warn log message"); + logger.trace("Trace log message"); + } +} diff --git a/spring-boot-modules/spring-boot-logging-loki/src/main/resources/logback-spring.xml b/spring-boot-modules/spring-boot-logging-loki/src/main/resources/logback-spring.xml new file mode 100644 index 0000000000..c4711c1680 --- /dev/null +++ b/spring-boot-modules/spring-boot-logging-loki/src/main/resources/logback-spring.xml @@ -0,0 +1,48 @@ + + + + + + + + + { + "level":"%level", + "class":"%logger{36}", + "thread":"%thread", + "message": "%message", + "requestId": "%X{X-Request-ID}" + } + + + + + + + http://localhost:3100/loki/api/v1/push + + + + + + { + "level":"%level", + "class":"%logger{36}", + "thread":"%thread", + "message": "%message", + "requestId": "%X{X-Request-ID}" + } + + + + + + + + + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-logging-loki/src/test/java/com/baeldung/loki/DemoServiceLiveTest.java b/spring-boot-modules/spring-boot-logging-loki/src/test/java/com/baeldung/loki/DemoServiceLiveTest.java new file mode 100644 index 0000000000..cf6091c2aa --- /dev/null +++ b/spring-boot-modules/spring-boot-logging-loki/src/test/java/com/baeldung/loki/DemoServiceLiveTest.java @@ -0,0 +1,86 @@ +package com.baeldung.loki; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.verify; + +import java.net.URI; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +@SpringBootTest(classes = DemoService.class) +public class DemoServiceLiveTest { + + @Test + public void givenLokiContainerRunning_whenDemoServiceInvoked_thenLokiAppenderCollectLogs() throws JsonProcessingException, InterruptedException { + DemoService service = new DemoService(); + service.log(); + Thread.sleep(2000); + String baseUrl = "http://localhost:3100/loki/api/v1/query_range"; + // Set up query parameters + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + String query = "{level=\"INFO\"} |= `DemoService.log invoked!!!`"; + // Get current time in UTC + LocalDateTime currentDateTime = LocalDateTime.now(ZoneOffset.UTC); + String current_time_est = currentDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")); + + LocalDateTime tenMinsAgo = currentDateTime.minusMinutes(10); + String start_time_est = tenMinsAgo.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")); + System.out.println(start_time_est); + + URI uri = UriComponentsBuilder.fromUriString(baseUrl) + .queryParam("query", query) + .queryParam("start", start_time_est) + .queryParam("end", current_time_est) + .build() + .toUri(); + + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.exchange( + uri, + HttpMethod.GET, + new HttpEntity<>(headers), + String.class + ); + + List messages = new ArrayList<>(); + ObjectMapper objectMapper = new ObjectMapper(); + if (response.getStatusCode() == HttpStatus.OK) { + String responseBody = response.getBody(); + JsonNode jsonNode = objectMapper.readTree(responseBody); + JsonNode result = jsonNode.get("data").get("result").get(0).get("values"); + result.iterator().forEachRemaining(e-> { + Iterator elements = e.elements(); + elements.forEachRemaining(f -> + messages.add(f.toString()) + ); + }); + } else { + System.out.println("Error: " + response.getStatusCodeValue()); + } + + String expected = "DemoService.log invoked!!!"; + assertTrue(messages.stream().anyMatch(e -> e.contains(expected))); + } +}