diff --git a/check-deployed-package/Check.java b/check-deployed-package/Check.java index 4b2229b..48e50c3 100644 --- a/check-deployed-package/Check.java +++ b/check-deployed-package/Check.java @@ -2,28 +2,27 @@ import io.logdash.sdk.Logdash; -import java.util.Map; public class Check { public static void main(String[] args) { System.out.println("=== LogDash Java SDK Demo ==="); - + // Get package version (would need to be handled differently in a real scenario) System.out.println("Using logdash package version: " + getLogdashVersion()); System.out.println(); - + String apiKey = System.getenv("LOGDASH_API_KEY"); String logsSeed = System.getenv().getOrDefault("LOGS_SEED", "default"); String metricsSeed = System.getenv().getOrDefault("METRICS_SEED", "1"); - + System.out.println("Using API Key: " + apiKey); System.out.println("Using Logs Seed: " + logsSeed); System.out.println("Using Metrics Seed: " + metricsSeed); - + try (Logdash logdash = Logdash.create(apiKey)) { var logger = logdash.logger(); var metrics = logdash.metrics(); - + // Log some messages with seed appended logger.info("This is an info log " + logsSeed); logger.error("This is an error log " + logsSeed); @@ -33,7 +32,7 @@ public static void main(String[] args) { logger.silly("This is a silly log " + logsSeed); logger.info("This is an info log " + logsSeed); logger.verbose("This is a verbose log " + logsSeed); - + // Set and mutate metrics with seed int seedValue = Integer.parseInt(metricsSeed); metrics.set("users", seedValue); @@ -47,7 +46,7 @@ public static void main(String[] args) { } } } - + private static String getLogdashVersion() { try { return Check.class.getPackage().getImplementationVersion(); @@ -55,4 +54,4 @@ private static String getLogdashVersion() { return "unknown"; } } -} \ No newline at end of file +} diff --git a/check-deployed-package/pom.xml b/check-deployed-package/pom.xml index 97cbe09..fbcf5cc 100644 --- a/check-deployed-package/pom.xml +++ b/check-deployed-package/pom.xml @@ -1,13 +1,16 @@ 4.0.0 + io.logdash check 0.2.0-SNAPSHOT - 17 - 17 + 17 + ${java.version} + ${java.version} + ${java.version} UTF-8 @@ -38,16 +41,22 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.12.1 - 17 - 17 + ${java.version} + + -parameters + -Xlint:unchecked + -Xlint:deprecation + -Werror + + org.codehaus.mojo exec-maven-plugin - 3.1.0 + 3.1.1 io.logdash.check.Check diff --git a/examples/example-simple-java/pom.xml b/examples/example-simple-java/pom.xml index e3474c8..9a0afe3 100644 --- a/examples/example-simple-java/pom.xml +++ b/examples/example-simple-java/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 com.example @@ -32,6 +32,7 @@ + org.apache.maven.plugins maven-compiler-plugin @@ -40,6 +41,9 @@ ${maven.compiler.release} -parameters + -Xlint:unchecked + -Xlint:deprecation + -Werror diff --git a/examples/example-springboot/Dockerfile b/examples/example-springboot/Dockerfile new file mode 100644 index 0000000..536bdbc --- /dev/null +++ b/examples/example-springboot/Dockerfile @@ -0,0 +1,7 @@ +FROM eclipse-temurin:21-jre-alpine + +WORKDIR /app +COPY target/springboot-example-*.jar app.jar + +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/examples/example-springboot/docker-compose.yml b/examples/example-springboot/docker-compose.yml new file mode 100644 index 0000000..a86a90e --- /dev/null +++ b/examples/example-springboot/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.8' +services: + app: + build: . + ports: + - '8080:8080' + environment: + - LOGDASH_API_KEY=${LOGDASH_API_KEY:-your-api-key} + - SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-dev} + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/actuator/health"] + interval: 30s + timeout: 10s + retries: 3 \ No newline at end of file diff --git a/examples/example-springboot/pom.xml b/examples/example-springboot/pom.xml new file mode 100644 index 0000000..e5fbe15 --- /dev/null +++ b/examples/example-springboot/pom.xml @@ -0,0 +1,155 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.4.6 + + + + io.logdash.example + springboot-example + 0.2.0-SNAPSHOT + jar + + Logdash Spring Boot Example + Spring Boot application demonstrating Logdash Java SDK usage + + + 21 + ${java.version} + ${java.version} + ${java.version} + UTF-8 + + + 0.2.0-SNAPSHOT + + + 3.5.1 + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + + + io.logdash + logdash + ${logdash-sdk.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + false + + + true + interval:60 + + maven-central-snapshots + Maven Central Snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + io.logdash.example.SpringBootExampleApplication + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + + -parameters + -Xlint:unchecked + -Xlint:deprecation + -Werror + + + + + + org.apache.maven.plugins + maven-help-plugin + ${maven-help-plugin.version} + + + + + + + local-dev + + true + + + + + never + + maven-central-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + + + + + force-remote + + + + org.apache.maven.plugins + maven-dependency-plugin + + + purge-local-logdash + + purge-local-repository + + initialize + + + io.logdash:logdash + + true + + + + + + + + + + diff --git a/examples/example-springboot/requests.http b/examples/example-springboot/requests.http new file mode 100644 index 0000000..69eed80 --- /dev/null +++ b/examples/example-springboot/requests.http @@ -0,0 +1,23 @@ +### Variables +@baseUrl = http://localhost:8080 + +### Get all pets +GET {{baseUrl}}/api/pets + +### Get pet by ID +GET {{baseUrl}}/api/pets/1 + +### Create a new pet +POST {{baseUrl}}/api/pets +Content-Type: application/json + +{ + "name": "Fluffy", + "type": "DOG" +} + +### Create a random pet +POST {{baseUrl}}/api/pets/random + +### Delete a pet by ID +DELETE {{baseUrl}}/api/pets/3 diff --git a/examples/example-springboot/src/main/java/io/logdash/example/SpringBootExampleApplication.java b/examples/example-springboot/src/main/java/io/logdash/example/SpringBootExampleApplication.java new file mode 100644 index 0000000..7b570be --- /dev/null +++ b/examples/example-springboot/src/main/java/io/logdash/example/SpringBootExampleApplication.java @@ -0,0 +1,14 @@ +package io.logdash.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@EnableScheduling +public class SpringBootExampleApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootExampleApplication.class, args); + } +} diff --git a/examples/example-springboot/src/main/java/io/logdash/example/config/LogdashConfiguration.java b/examples/example-springboot/src/main/java/io/logdash/example/config/LogdashConfiguration.java new file mode 100644 index 0000000..f77b6fd --- /dev/null +++ b/examples/example-springboot/src/main/java/io/logdash/example/config/LogdashConfiguration.java @@ -0,0 +1,51 @@ +package io.logdash.example.config; + +import io.logdash.sdk.Logdash; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static io.logdash.example.config.LogdashConfiguration.LogdashProperties; + +@Configuration +@EnableConfigurationProperties(LogdashProperties.class) +public class LogdashConfiguration { + + @Bean + public Logdash logdash(LogdashProperties properties) { + return Logdash.builder() + .apiKey(properties.apiKey()) + .baseUrl(properties.baseUrl()) + .enableConsoleOutput(properties.enableConsoleOutput()) + .enableVerboseLogging(properties.enableVerboseLogging()) + .requestTimeoutMs(properties.requestTimeoutMs()) + .maxConcurrentRequests(properties.maxConcurrentRequests()) + .build(); + } + + @ConfigurationProperties(prefix = "logdash") + public record LogdashProperties( + String apiKey, + String baseUrl, + boolean enableConsoleOutput, + boolean enableVerboseLogging, + long requestTimeoutMs, + int maxConcurrentRequests + ) { + public LogdashProperties { + if (apiKey == null || apiKey.isBlank()) { + throw new IllegalArgumentException("Logdash API key is required"); + } + if (baseUrl == null || baseUrl.isBlank()) { + baseUrl = "https://api.logdash.io"; + } + if (requestTimeoutMs <= 0) { + requestTimeoutMs = 10000L; + } + if (maxConcurrentRequests <= 0) { + maxConcurrentRequests = 20; + } + } + } +} diff --git a/examples/example-springboot/src/main/java/io/logdash/example/controller/PetController.java b/examples/example-springboot/src/main/java/io/logdash/example/controller/PetController.java new file mode 100644 index 0000000..adc240f --- /dev/null +++ b/examples/example-springboot/src/main/java/io/logdash/example/controller/PetController.java @@ -0,0 +1,133 @@ +package io.logdash.example.controller; + +import io.logdash.example.domain.Pet; +import io.logdash.example.domain.PetType; +import io.logdash.example.service.PetService; +import io.logdash.sdk.Logdash; +import io.logdash.sdk.log.LogdashLogger; +import io.logdash.sdk.metrics.LogdashMetrics; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/pets") +public class PetController { + + private final PetService petService; + private final LogdashLogger logger; + private final LogdashMetrics metrics; + + public PetController(PetService petService, Logdash logdash) { + this.petService = petService; + this.logger = logdash.logger(); + this.metrics = logdash.metrics(); + } + + @GetMapping + public ResponseEntity> getAllPets() { + var pets = petService.getAllPets(); + + logger.info("Retrieved all pets", Map.of( + "count", pets.size(), + "endpoint", "/api/pets", + "timestamp", LocalDateTime.now() + )); + + metrics.mutate("api.requests.total", 1); + metrics.mutate("api.pets.list.requests", 1); + metrics.set("pets.total.count", pets.size()); + + return ResponseEntity.ok(pets); + } + + @GetMapping("/{id}") + public ResponseEntity getPet(@PathVariable Long id) { + var pet = petService.findById(id); + + if (pet.isPresent()) { + logger.debug("Pet retrieved successfully", Map.of( + "petId", id, + "name", pet.get().name(), + "type", pet.get().type().name() + )); + metrics.mutate("api.pets.get.success", 1); + return ResponseEntity.ok(pet.get()); + } else { + logger.warn("Pet not found", Map.of( + "petId", id + )); + metrics.mutate("api.pets.get.not_found", 1); + return ResponseEntity.notFound().build(); + } + } + + @PostMapping + public ResponseEntity createPet(@RequestBody CreatePetRequest request) { + try { + var pet = petService.addPet(request.name(), request.type()); + + logger.info("Pet created successfully", Map.of( + "petId", pet.id(), + "name", pet.name(), + "type", pet.type().name() + )); + + metrics.mutate("pets.created.total", 1); + metrics.mutate("api.pets.create.success", 1); + metrics.set("pets.total.count", petService.getTotalCount()); + + return ResponseEntity.ok(pet); + + } catch (IllegalArgumentException e) { + logger.error("Pet creation failed", Map.of( + "error", e.getMessage(), + "requestName", request.name(), + "requestType", request.type() + )); + metrics.mutate("api.pets.create.validation_error", 1); + return ResponseEntity.badRequest().build(); + } + } + + @PostMapping("/random") + public ResponseEntity createRandomPet() { + var pet = petService.createRandomPet(); + + logger.info("Random pet created", Map.of( + "petId", pet.id(), + "name", pet.name(), + "type", pet.type().name() + )); + + metrics.mutate("pets.created.random", 1); + metrics.mutate("api.requests.total", 1); + metrics.set("pets.total.count", petService.getTotalCount()); + + return ResponseEntity.ok(pet); + } + + @DeleteMapping("/{id}") + public ResponseEntity deletePet(@PathVariable Long id) { + boolean deleted = petService.deletePet(id); + + if (deleted) { + logger.info("Pet deleted successfully", Map.of( + "petId", id + )); + metrics.mutate("pets.deleted.total", 1); + metrics.set("pets.total.count", petService.getTotalCount()); + return ResponseEntity.noContent().build(); + } else { + logger.warn("Delete failed - pet not found", Map.of("petId", id)); + metrics.mutate("api.pets.delete.not_found", 1); + return ResponseEntity.notFound().build(); + } + } + + public record CreatePetRequest(String name, PetType type) { + } +} diff --git a/examples/example-springboot/src/main/java/io/logdash/example/domain/Pet.java b/examples/example-springboot/src/main/java/io/logdash/example/domain/Pet.java new file mode 100644 index 0000000..004fbd5 --- /dev/null +++ b/examples/example-springboot/src/main/java/io/logdash/example/domain/Pet.java @@ -0,0 +1,16 @@ +package io.logdash.example.domain; + +public record Pet( + Long id, + String name, + PetType type +) { + public Pet { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Pet name is required"); + } + if (type == null) { + throw new IllegalArgumentException("Pet type is required"); + } + } +} diff --git a/examples/example-springboot/src/main/java/io/logdash/example/domain/PetType.java b/examples/example-springboot/src/main/java/io/logdash/example/domain/PetType.java new file mode 100644 index 0000000..20cda4b --- /dev/null +++ b/examples/example-springboot/src/main/java/io/logdash/example/domain/PetType.java @@ -0,0 +1,5 @@ +package io.logdash.example.domain; + +public enum PetType { + DOG, CAT, BIRD, RABBIT, FISH +} diff --git a/examples/example-springboot/src/main/java/io/logdash/example/service/MonitoringService.java b/examples/example-springboot/src/main/java/io/logdash/example/service/MonitoringService.java new file mode 100644 index 0000000..c640ae5 --- /dev/null +++ b/examples/example-springboot/src/main/java/io/logdash/example/service/MonitoringService.java @@ -0,0 +1,88 @@ +package io.logdash.example.service; + +import io.logdash.sdk.Logdash; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.lang.management.ManagementFactory; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +@Service +public class MonitoringService { + + private final Logdash logdash; + private final PetService petService; + private int monitoringCycles = 0; + + public MonitoringService(Logdash logdash, PetService petService) { + this.logdash = logdash; + this.petService = petService; + } + + @Scheduled(fixedRate = 10000) // Every 10 seconds + public void recordSystemMetrics() { + var logger = logdash.logger(); + var metrics = logdash.metrics(); + + monitoringCycles++; + + // JVM metrics + var memoryBean = ManagementFactory.getMemoryMXBean(); + var heapUsage = memoryBean.getHeapMemoryUsage(); + + var heapUsedMB = heapUsage.getUsed() / (1024 * 1024); + + // Simulated application metrics + var activeUsers = ThreadLocalRandom.current().nextInt(50, 300); + var errorRate = ThreadLocalRandom.current().nextDouble(0.1, 8.0); + + logger.verbose("System monitoring cycle", Map.of( + "cycle", monitoringCycles, + "activeUsers", activeUsers + )); + + // Send metrics to Logdash + metrics.set("jvm.memory.heap.used_mb", heapUsedMB); + metrics.set("app.users.active", activeUsers); + + // Business metrics + var petCount = petService.getTotalCount(); + metrics.set("pets.total.count", petCount); + + // Increment counters + metrics.mutate("monitoring.cycles.total", 1); + + // Simulate random events + if (ThreadLocalRandom.current().nextDouble() < 0.3) { // 30% chance + logger.info("Random application event", Map.of( + "activeUsers", activeUsers + )); + metrics.mutate("app.events.user_activity", 1); + } + + if (errorRate > 3.0) { + logger.warn("High error rate detected", Map.of( + "errorRate", errorRate + )); + metrics.mutate("alerts.performance.slow_response", 1); + } + } + + @Scheduled(fixedRate = 30000) // Every 30 seconds + public void simulateBusinessEvents() { + var logger = logdash.logger(); + var metrics = logdash.metrics(); + + // Simulate random pet creation + if (ThreadLocalRandom.current().nextBoolean()) { + var pet = petService.createRandomPet(); + + logger.info(String.format("Background pet creation: petId=%s, name=%s, type=%s", pet.id(), pet.name(), + pet.type().name())); + + metrics.mutate("pets.created.background", 1); + metrics.set("pets.total.count", petService.getTotalCount()); + } + } +} diff --git a/examples/example-springboot/src/main/java/io/logdash/example/service/PetService.java b/examples/example-springboot/src/main/java/io/logdash/example/service/PetService.java new file mode 100644 index 0000000..c346210 --- /dev/null +++ b/examples/example-springboot/src/main/java/io/logdash/example/service/PetService.java @@ -0,0 +1,58 @@ +package io.logdash.example.service; + +import io.logdash.example.domain.Pet; +import io.logdash.example.domain.PetType; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicLong; + +@Service +public class PetService { + + private final Map pets = new ConcurrentHashMap<>(); + private final AtomicLong idGenerator = new AtomicLong(1); + + public PetService() { + addPet("Burek", PetType.DOG); + addPet("Mruczek", PetType.CAT); + addPet("Olek", PetType.BIRD); + } + + public Pet addPet(String name, PetType type) { + var pet = new Pet(idGenerator.getAndIncrement(), name, type); + pets.put(pet.id(), pet); + return pet; + } + + public List getAllPets() { + return new ArrayList<>(pets.values()); + } + + public Optional findById(Long id) { + return Optional.ofNullable(pets.get(id)); + } + + public Pet createRandomPet() { + var names = List.of("Azor", "Bella", "Charlie", "Luna", "Max", "Molly", "Oscar"); + var types = PetType.values(); + + var randomName = names.get(ThreadLocalRandom.current().nextInt(names.size())); + var randomType = types[ThreadLocalRandom.current().nextInt(types.length)]; + + return addPet(randomName, randomType); + } + + public int getTotalCount() { + return pets.size(); + } + + public boolean deletePet(Long id) { + return pets.remove(id) != null; + } +} diff --git a/examples/example-springboot/src/main/resources/application.yml b/examples/example-springboot/src/main/resources/application.yml new file mode 100644 index 0000000..e4339c1 --- /dev/null +++ b/examples/example-springboot/src/main/resources/application.yml @@ -0,0 +1,28 @@ +server: + port: 8080 + +spring: + application: + name: logdash-petclinic-demo + +logdash: + api-key: ${LOGDASH_API_KEY:your-api-key} + base-url: https://api.logdash.io + enable-console-output: true + enable-verbose-logging: false + request-timeout-ms: 10000 + max-concurrent-requests: 20 + +management: + endpoints: + web: + exposure: + include: health,info,metrics + endpoint: + health: + show-details: always + +logging: + level: + io.logdash.example: INFO + root: WARN \ No newline at end of file diff --git a/examples/example-springboot/src/test/java/io/logdash/example/SpringBootExampleApplicationTest.java b/examples/example-springboot/src/test/java/io/logdash/example/SpringBootExampleApplicationTest.java new file mode 100644 index 0000000..a996ba5 --- /dev/null +++ b/examples/example-springboot/src/test/java/io/logdash/example/SpringBootExampleApplicationTest.java @@ -0,0 +1,72 @@ +package io.logdash.example; + +import io.logdash.sdk.Logdash; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(properties = { + "logdash.api-key=test-key", + "logdash.enable-console-output=false", + "logdash.enable-verbose-logging=false" +}) +class SpringBootExampleApplicationTest { + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private Logdash logdash; + + @Test + void should_start_application_context() { + assertThat(logdash).isNotNull(); + assertThat(logdash.logger()).isNotNull(); + assertThat(logdash.metrics()).isNotNull(); + } + + @Test + void should_get_all_pets() { + var response = restTemplate.getForEntity( + "http://localhost:" + port + "/api/pets", + String.class + ); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("Burek", "Mruczek", "Olek"); + } + + @Test + void should_create_random_pet() { + var response = restTemplate.postForEntity( + "http://localhost:" + port + "/api/pets/random", + null, + String.class + ); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotBlank(); + } + + @Test + void should_expose_actuator_health_endpoint() { + var response = restTemplate.getForEntity( + "http://localhost:" + port + "/actuator/health", + String.class + ); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("\"status\":\"UP\""); + } + +} diff --git a/examples/example-springboot/src/test/java/io/logdash/example/controller/PetControllerTest.java b/examples/example-springboot/src/test/java/io/logdash/example/controller/PetControllerTest.java new file mode 100644 index 0000000..09ac17a --- /dev/null +++ b/examples/example-springboot/src/test/java/io/logdash/example/controller/PetControllerTest.java @@ -0,0 +1,167 @@ +package io.logdash.example.controller; + +import io.logdash.example.domain.Pet; +import io.logdash.example.domain.PetType; +import io.logdash.example.service.PetService; +import io.logdash.sdk.log.LogdashLogger; +import io.logdash.sdk.metrics.LogdashMetrics; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(PetController.class) +@TestPropertySource(properties = { + "logdash.api-key=test-key", + "logdash.enable-console-output=false" +}) +class PetControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private PetService petService; + + @MockitoBean + private LogdashLogger logger; + + @MockitoBean + private LogdashMetrics metrics; + + @Test + void should_get_all_pets() throws Exception { + // Given + var pets = List.of( + new Pet(1L, "Burek", PetType.DOG), + new Pet(2L, "Mruczek", PetType.CAT) + ); + given(petService.getAllPets()).willReturn(pets); + + // When & Then + mockMvc.perform(get("/api/pets")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0].name").value("Burek")) + .andExpect(jsonPath("$[1].name").value("Mruczek")); + + // Verify logging and metrics + verify(logger).info(eq("Retrieved all pets"), anyMap()); + verify(metrics).mutate("api.requests.total", 1); + verify(metrics).mutate("api.pets.list.requests", 1); + verify(metrics).set("pets.total.count", 2); + } + + @Test + void should_get_pet_by_id() throws Exception { + // Given + var pet = new Pet(1L, "Burek", PetType.DOG); + given(petService.findById(1L)).willReturn(Optional.of(pet)); + + // When & Then + mockMvc.perform(get("/api/pets/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("Burek")) + .andExpect(jsonPath("$.type").value("DOG")); + + // Verify logging and metrics + verify(logger).debug(eq("Fetching pet with id: {}"), anyMap()); + verify(logger).info(eq("Pet found"), anyMap()); + verify(metrics).mutate("api.pets.found", 1); + } + + @Test + void should_return_not_found_for_non_existent_pet() throws Exception { + // Given + given(petService.findById(999L)).willReturn(Optional.empty()); + + // When & Then + mockMvc.perform(get("/api/pets/999")) + .andExpect(status().isNotFound()); + + // Verify logging and metrics + verify(logger).debug(eq("Fetching pet with id: {}"), anyMap()); + verify(logger).warn(eq("Pet not found"), anyMap()); + verify(metrics).mutate("api.pets.not_found", 1); + } + + @Test + void should_create_random_pet() throws Exception { + // Given + var pet = new Pet(3L, "Lucky", PetType.DOG); + given(petService.createRandomPet()).willReturn(pet); + + // When & Then + mockMvc.perform(post("/api/pets/random")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(3)) + .andExpect(jsonPath("$.name").value("Lucky")) + .andExpect(jsonPath("$.type").value("DOG")); + + // Verify logging and metrics + verify(logger).info(eq("Created random pet"), anyMap()); + verify(metrics).mutate("api.pets.created", 1); + verify(metrics).mutate("api.pets.random.created", 1); + } + + @Test + void should_create_pet_with_valid_request() throws Exception { + // Given + var pet = new Pet(4L, "Bella", PetType.CAT); + given(petService.addPet("Bella", PetType.CAT)).willReturn(pet); + + // When & Then + mockMvc.perform(post("/api/pets") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "name": "Bella", + "type": "CAT" + } + """)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(4)) + .andExpect(jsonPath("$.name").value("Bella")) + .andExpect(jsonPath("$.type").value("CAT")); + + // Verify logging and metrics + verify(logger).info(eq("Created pet"), anyMap()); + verify(metrics).mutate("api.pets.created", 1); + verify(metrics).mutate("api.pets.manual.created", 1); + } + + @Test + void should_handle_delete_pet() throws Exception { + // Given + given(petService.deletePet(1L)).willReturn(true); + + // When & Then + mockMvc.perform(delete("/api/pets/1")) + .andExpect(status().isNoContent()); + } + + @Test + void should_handle_delete_non_existent_pet() throws Exception { + // Given + given(petService.deletePet(999L)).willReturn(false); + + // When & Then + mockMvc.perform(delete("/api/pets/999")) + .andExpect(status().isNotFound()); + } +} diff --git a/examples/example-springboot/src/test/resources/application-test.yml b/examples/example-springboot/src/test/resources/application-test.yml new file mode 100644 index 0000000..4e99e94 --- /dev/null +++ b/examples/example-springboot/src/test/resources/application-test.yml @@ -0,0 +1,17 @@ +spring: + main: + banner-mode: off + output: + ansi: + enabled: never + +logdash: + api-key: test-key + enable-console-output: false + enable-verbose-logging: false + +logging: + level: + io.logdash.example: WARN + org.springframework: WARN + org.mockito: WARN diff --git a/pom.xml b/pom.xml index 8124c77..eba0d35 100644 --- a/pom.xml +++ b/pom.xml @@ -178,7 +178,7 @@ maven-compiler-plugin ${maven-compiler-plugin.version} - ${java.version} + ${maven.compiler.release} -parameters -Xlint:unchecked @@ -416,6 +416,13 @@ true 4 + + src/main/java/**/*.java + src/test/java/**/*.java + examples/**/src/main/java/**/*.java + examples/**/src/test/java/**/*.java + check-deployed-package/**/*.java + **/target/** **/generated/** @@ -425,6 +432,7 @@ **/*.md + examples/**/*.md @@ -446,6 +454,7 @@ pom.xml **/pom.xml + examples/**/pom.xml false diff --git a/scripts/dev-cycle.sh b/scripts/dev-cycle.sh new file mode 100755 index 0000000..4322e7f --- /dev/null +++ b/scripts/dev-cycle.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +echo "🔧 Building local Logdash SDK..." +mvn clean install -DskipTests + +echo "🚀 Testing SpringBoot example with local snapshot..." +cd examples/example-springboot +mvn clean spring-boot:run -Plocal-dev + +echo "✅ Development cycle completed!"