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)));
+ }
+}