diff --git a/.gitignore b/.gitignore
index e003ee6..66c5b0c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -356,3 +356,11 @@ EventHubSamplingOverrides/output.txt
EventHubSamplingOverrides/target/*
target/
+
+
+application-local.yml
+application-local.yaml
+application-local.properties
+/applicationinsights.log
+it-test-local.properties
+/docker/**/data/
diff --git a/insightCustomDimensions/Docker/applicationinsights-agent-3.7.4.jar b/insightCustomDimensions/Docker/applicationinsights-agent-3.7.4.jar
new file mode 100644
index 0000000..51cfeb3
Binary files /dev/null and b/insightCustomDimensions/Docker/applicationinsights-agent-3.7.4.jar differ
diff --git a/insightCustomDimensions/Docker/applicationinsights.json b/insightCustomDimensions/Docker/applicationinsights.json
new file mode 100644
index 0000000..29f5de9
--- /dev/null
+++ b/insightCustomDimensions/Docker/applicationinsights.json
@@ -0,0 +1,32 @@
+{
+ "selfDiagnostics": {
+ "destination": "console",
+ "level": "DEBUG"
+ },
+ "instrumentation": {
+ "logging": {
+ "enabled": true,
+ "level": "DEBUG"
+ },
+ "jdbc": {
+ "enabled": true,
+ "masking": {
+ "enabled": false
+ }
+ },
+ "springScheduling": {
+ "enabled": true
+ }
+ },
+ "customDimensions": {
+ },
+ "sampling": {
+ "percentage": 100
+ },
+ "preview": {
+ "captureLogbackMarker": false,
+ "captureLogbackCodeAttributes": false,
+ "captureLoggingLevelAsCustomDimension": true
+ }
+
+}
\ No newline at end of file
diff --git a/insightCustomDimensions/Docker/applicationinsights_to_file.json b/insightCustomDimensions/Docker/applicationinsights_to_file.json
new file mode 100644
index 0000000..45bdb33
--- /dev/null
+++ b/insightCustomDimensions/Docker/applicationinsights_to_file.json
@@ -0,0 +1,37 @@
+{
+ "selfDiagnostics": {
+ "destination": "file+console",
+ "level": "DEBUG",
+ "file": {
+ "path": "/tmp/applicationinsights.log",
+ "maxSizeMb": 5,
+ "maxHistory": 1
+ }
+ },
+ "instrumentation": {
+ "logging": {
+ "enabled": true,
+ "level": "DEBUG"
+ },
+ "jdbc": {
+ "enabled": true,
+ "masking": {
+ "enabled": false
+ }
+ },
+ "springScheduling": {
+ "enabled": true
+ }
+ },
+ "customDimensions": {
+ },
+ "sampling": {
+ "percentage": 100
+ },
+ "preview": {
+ "captureLogbackMarker": false,
+ "captureLogbackCodeAttributes": false,
+ "captureLoggingLevelAsCustomDimension": true
+ }
+
+}
\ No newline at end of file
diff --git a/insightCustomDimensions/Dockerfile b/insightCustomDimensions/Dockerfile
new file mode 100644
index 0000000..580e217
--- /dev/null
+++ b/insightCustomDimensions/Dockerfile
@@ -0,0 +1,41 @@
+# ---- Builder Stage ----
+FROM maven:3.9.6-eclipse-temurin-17 AS builder
+
+WORKDIR /app
+
+# Copy pom.xml and preload dependencies
+COPY pom.xml .
+RUN mvn dependency:go-offline -B
+
+# Copy sources and build
+COPY src ./src
+RUN mvn clean package -DskipTests
+
+# Extract layers
+RUN java -Djarmode=layertools -jar /app/target/*.jar extract
+
+
+# the second stage of our build will copy the extracted layers
+FROM eclipse-temurin:17-jre-alpine
+
+WORKDIR /application
+
+COPY --from=builder /app/dependencies/ ./dependencies/
+COPY --from=builder /app/snapshot-dependencies/ ./snapshot-dependencies/
+COPY --from=builder /app/spring-boot-loader/ ./spring-boot-loader/
+COPY --from=builder /app/application/ ./application/
+
+# Copy Application Insights agent + config
+COPY ./Docker/applicationinsights-agent-3.7.4.jar /application/insight/
+COPY ./Docker/applicationinsights.json /application/insight/
+
+# Set AI config
+ENV APPLICATIONINSIGHTS_CONFIGURATION_FILE=/application/insight/applicationinsights.json
+# Create a group and user
+RUN addgroup -S appgroup && adduser -S appuser -G appgroup
+# Tell docker that all future commands should run as the appuser user
+USER appuser
+
+EXPOSE 8080
+# Run Spring Boot via JarLauncher with AI agent
+ENTRYPOINT ["java", "-javaagent:/application/insight/applicationinsights-agent-3.7.4.jar","org.springframework.boot.loader.launch.JarLauncher"]
diff --git a/insightCustomDimensions/README.md b/insightCustomDimensions/README.md
new file mode 100644
index 0000000..ab1f5a1
--- /dev/null
+++ b/insightCustomDimensions/README.md
@@ -0,0 +1,66 @@
+* A single controller that receives HTTP requests
+* A request filter similar to the one you have in your code using the @Order tag.
+* Inside the filter, create your custom span and apply the filter as you currently do in your code.
+* Collect a dummy custom property that is expected to be added to your request.
+* Enable App Insights Java Agent 3.7.2
+* Use the same applicationinsights.json file that you have on your current Application.
+* Even if this dummy code does not reproduce the issue, it will help our engineering team to
+ understand this scenario.
+
+To Reproduce
+Submitting a PR with an example reproducing the issue
+in [this repository](https://github.com/microsoft/ApplicationInsights-Java-Repros) would make it
+easier for the Application Insight maintainers to help you. Before doing this, you have to fork this
+repository.
+
+## Sample Test App
+
+* 5 Filter added
+ * `SpanInitializationFilter`: init the span
+ * `OidLoggingFilter`: read the OID from JWT Baerer if exists
+ * `CountryLoggingFilter`: read the dbName from URL like /api/v1/{dbName}/company
+ * `HttpQueryLoggingFilter`: read the Query-Parameter
+ * `RawBodyLoggingFilter`: read the body of POST and PUT
+* Endpoints:
+ * GET /api/v1/:dbName/company (i.e. :dbName=germany)
+ * Response:
+ ```
+ {
+ "id": 1758202622283,
+ "name": "germany-Name",
+ "street": "germany-Street",
+ "postalCode": "germany-PostalCode",
+ "city": "germany-City"
+ },
+ {
+ "id": 1758202622283,
+ "name": "germany-Name",
+ "street": "germany-Street",
+ "postalCode": "germany-PostalCode",
+ "city": "germany-City"
+ }
+ ]
+ ```
+ * GET /api/v1/:dbName/company/:id (i.e :dbName=germany, id=123)
+ * Response:
+ ```
+ {
+ "id": 111,
+ "name": "germany-Name",
+ "street": "germany-Street",
+ "postalCode": "germany-PostalCode",
+ "city": "germany-City"
+ }
+ ```
+
+ * POST/PUT /api/v1/:dbName/company (i.e :dbName=germany)
+ * Request/Response (sample)
+ ```
+ {
+ "id": 111,
+ "name": "germany-Name",
+ "street": "germany-Street",
+ "postalCode": "germany-PostalCode",
+ "city": "germany-City"
+ }
+ ```
\ No newline at end of file
diff --git a/insightCustomDimensions/pom.xml b/insightCustomDimensions/pom.xml
new file mode 100644
index 0000000..4eca459
--- /dev/null
+++ b/insightCustomDimensions/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.4.5
+
+
+
+ com.peri.psd
+ testapp
+ 0.0.1-SNAPSHOT
+ testapp
+ Test App
+
+
+
+
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ io.micrometer
+ micrometer-tracing-bridge-otel
+
+
+
+ com.nimbusds
+ nimbus-jose-jwt
+ 10.4.2
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/PsdTestApplication.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/PsdTestApplication.java
new file mode 100644
index 0000000..2814f64
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/PsdTestApplication.java
@@ -0,0 +1,14 @@
+package com.peri.psd.testapp;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+@SpringBootApplication(scanBasePackages = {"com.peri.psd.testapp"})
+@EnableConfigurationProperties
+public class PsdTestApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(PsdTestApplication.class, args);
+ }
+
+}
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/api/PsdTestController.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/api/PsdTestController.java
new file mode 100644
index 0000000..b1aad4d
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/api/PsdTestController.java
@@ -0,0 +1,73 @@
+package com.peri.psd.testapp.playground.api;
+
+import com.peri.psd.testapp.playground.dto.CompanyMasterDto;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("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/api/v1")
+@Slf4j
+@RequiredArgsConstructor
+public class PsdTestController {
+
+
+ @GetMapping("/{dbName}/company/{id}")
+ public ResponseEntity getCompanyById(
+ @PathVariable String dbName,
+ @PathVariable Long id) {
+
+ log.debug("GET: /{}/company/{}", dbName, id);
+
+ return ResponseEntity.ok(CompanyMasterDto.builder()
+ .id(id)
+ .name(dbName + "-Name")
+ .city(dbName + "-City")
+ .street(dbName + "-Street")
+ .postalCode(dbName + "-PostalCode")
+ .build());
+ }
+
+ @GetMapping("/{dbName}/company")
+ public ResponseEntity> getAllCompanies(@PathVariable String dbName) {
+
+ log.debug("GET: /{}/company", dbName);
+ return ResponseEntity.ok(List.of(CompanyMasterDto.builder()
+ .id(System.currentTimeMillis())
+ .name(dbName + "-Name")
+ .city(dbName + "-City")
+ .street(dbName + "-Street")
+ .postalCode(dbName + "-PostalCode")
+ .build(),
+ CompanyMasterDto.builder()
+ .id(System.currentTimeMillis())
+ .name(dbName + "-Name")
+ .city(dbName + "-City")
+ .street(dbName + "-Street")
+ .postalCode(dbName + "-PostalCode")
+ .build()));
+ }
+
+ @PostMapping("/{dbName}/company")
+ public ResponseEntity createCompany(@PathVariable String dbName,
+ @RequestBody CompanyMasterDto companyMasterDto) {
+
+ log.debug("POST: /{}/company - {}", dbName, companyMasterDto);
+ return ResponseEntity.status(HttpStatus.CREATED)
+ .body(companyMasterDto);
+ }
+
+ @PutMapping("/{dbName}/company")
+ public ResponseEntity updateCompany(@PathVariable String dbName,
+ @RequestBody CompanyMasterDto companyMasterDto) {
+
+ log.debug("PUT: /{}/company - {}", dbName, companyMasterDto);
+ return ResponseEntity.status(HttpStatus.ACCEPTED)
+ .body(companyMasterDto);
+ }
+
+}
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/dto/CompanyMasterDto.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/dto/CompanyMasterDto.java
new file mode 100644
index 0000000..78af265
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/dto/CompanyMasterDto.java
@@ -0,0 +1,19 @@
+package com.peri.psd.testapp.playground.dto;
+
+import lombok.*;
+
+@Builder
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString
+public class CompanyMasterDto {
+
+ private Long id;
+ private String name;
+ private String street;
+ private String postalCode;
+ private String city;
+
+}
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/AbstractLoggingFilter.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/AbstractLoggingFilter.java
new file mode 100644
index 0000000..f95b357
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/AbstractLoggingFilter.java
@@ -0,0 +1,54 @@
+package com.peri.psd.testapp.playground.telemetry.filter;
+
+import io.opentelemetry.api.trace.Span;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.util.List;
+
+@Slf4j
+public abstract class AbstractLoggingFilter extends OncePerRequestFilter {
+
+
+ final static String KEY_CALLER = "caller";
+ final static String KEY_COUNTRY = "country";
+ final static String KEY_REQUESTQUERY = "requestQuery";
+ final static String KEY_REQUESTBODYRAW = "requestBodyRaw";
+
+ // List of URL patterns to be exclude
+ private static final List EXCLUDED_PATHS = List.of(
+ "/actuator"
+ );
+
+ @Override
+ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
+ String path = request.getRequestURI();
+
+ return "/".equals(path) || EXCLUDED_PATHS.stream()
+ .anyMatch(path::startsWith);
+ }
+
+ public void sendToSpan(String key, String value) {
+
+ Span currentSpan = Span.current();
+ String spanKind = MDC.get("MDC.span.kind");
+
+ if (currentSpan != null && currentSpan.getSpanContext()
+ .isValid() && "SERVER".equals(spanKind)) {
+
+ log.debug("Span: Kind={} - Attribute: {}={}", spanKind, key, value);
+
+ MDC.put("MDC." + key, value);
+
+ currentSpan.setAttribute(key, value);
+ } else {
+ log.debug("REQUEST Attribute: {} = {}", key, value);
+ }
+
+ }
+
+
+}
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/CountryLoggingFilter.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/CountryLoggingFilter.java
new file mode 100644
index 0000000..cd12e6b
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/CountryLoggingFilter.java
@@ -0,0 +1,71 @@
+package com.peri.psd.testapp.playground.telemetry.filter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Component
+@ConditionalOnProperty(name = "psd.azure.insight.filter.country-filter", havingValue = "true", matchIfMissing = true)
+@RequiredArgsConstructor
+@Slf4j
+@Order(SecurityProperties.DEFAULT_FILTER_ORDER + 120)
+public class CountryLoggingFilter extends AbstractLoggingFilter {
+
+ // https://petrepopescu.tech/2023/07/how-to-log-http-request-and-response-in-spring-boot/
+ // https://www.linkedin.com/pulse/rest-api-monitoring-azure-application-insights-srinivas-anumala
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+ RepeatableContentCachingRequestWrapper requestWrapper = new RepeatableContentCachingRequestWrapper(
+ request);
+
+ logRequest(requestWrapper);
+
+ filterChain.doFilter(request, response);
+
+ }
+
+ private void logRequest(
+ RepeatableContentCachingRequestWrapper requestWrapper) throws IOException {
+ extractCurrentDbFromUri(requestWrapper.getRequestURI())
+ .ifPresent(currentDb ->
+ sendToSpan(KEY_COUNTRY, currentDb)
+ );
+ }
+
+ /**
+ * @param uri i.e /api/v3/germany/foo or /germany/api/v3/foo
+ * @return germany
+ */
+ public static Optional extractCurrentDbFromUri(String uri) {
+ if (uri == null || uri.trim()
+ .isEmpty()) {
+ return Optional.empty();
+ }
+
+ Pattern pattern = Pattern.compile("^/([^/]+)/api/v\\d+/.+|^/api/v\\d+/([^/]+)/.+",
+ Pattern.CASE_INSENSITIVE);
+
+ Matcher matcher = pattern.matcher(uri);
+ if (matcher.find()) {
+ // Group 1: db before /api/vXX, Group 2: db after /api/vXX
+ return Optional.ofNullable(
+ matcher.group(1) != null ? matcher.group(1) : matcher.group(2)
+ );
+ }
+ return Optional.empty();
+ }
+
+}
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/HttpQueryLoggingFilter.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/HttpQueryLoggingFilter.java
new file mode 100644
index 0000000..a0860bd
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/HttpQueryLoggingFilter.java
@@ -0,0 +1,54 @@
+package com.peri.psd.testapp.playground.telemetry.filter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.stream.Stream;
+
+@Component
+@ConditionalOnProperty(name = "psd.azure.insight.filter.query-filter", havingValue = "true", matchIfMissing = true)
+@RequiredArgsConstructor
+@Slf4j
+@Order(SecurityProperties.DEFAULT_FILTER_ORDER + 130)
+public class HttpQueryLoggingFilter extends AbstractLoggingFilter {
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+ RepeatableContentCachingRequestWrapper requestWrapper = new RepeatableContentCachingRequestWrapper(
+ request);
+
+ logQueryParameter(requestWrapper);
+
+ filterChain.doFilter(request, response);
+
+ }
+
+ private void logQueryParameter(
+ RepeatableContentCachingRequestWrapper requestWrapper) throws IOException {
+
+ String queryAsString = requestWrapper.getQueryString();
+
+ if (shouldLogQuery(requestWrapper)) {
+ sendToSpan(KEY_REQUESTQUERY, queryAsString);
+ }
+ }
+
+ private boolean shouldLogQuery(RepeatableContentCachingRequestWrapper requestWrapper) {
+ String method = requestWrapper.getMethod();
+ boolean b = Stream.of("GET", "POST", "PUT", "PATCH", "DELETE")
+ .anyMatch(m -> m.equalsIgnoreCase(method));
+ log.trace("SHOULD LOG = {} - {}:{}", b, method, requestWrapper.getServletPath());
+ return b;
+ }
+
+}
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/OidLoggingFilter.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/OidLoggingFilter.java
new file mode 100644
index 0000000..9ca71fb
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/OidLoggingFilter.java
@@ -0,0 +1,62 @@
+package com.peri.psd.testapp.playground.telemetry.filter;
+
+import com.nimbusds.jwt.SignedJWT;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.text.ParseException;
+
+@Component
+@ConditionalOnProperty(name = "psd.azure.insight.filter.oid-filter", havingValue = "true", matchIfMissing = true)
+@RequiredArgsConstructor
+@Slf4j
+@Order(SecurityProperties.DEFAULT_FILTER_ORDER + 110)
+public class OidLoggingFilter extends AbstractLoggingFilter {
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+ RepeatableContentCachingRequestWrapper requestWrapper = new RepeatableContentCachingRequestWrapper(
+ request);
+
+ logRequest(requestWrapper);
+
+ filterChain.doFilter(request, response);
+
+ }
+
+ private void logRequest(
+ RepeatableContentCachingRequestWrapper requestWrapper) throws IOException {
+
+ String authHeader = requestWrapper.getHeader("Authorization");
+
+ if (authHeader != null && authHeader.startsWith("Bearer ")) {
+ String jwt = authHeader.substring(7); // Token without "Bearer "
+
+ try {
+ SignedJWT parsed = SignedJWT.parse(jwt);
+ String oid = parsed.getJWTClaimsSet()
+ .getClaim("oid")
+ .toString();
+ sendToSpan(KEY_CALLER, oid);
+
+ } catch (ParseException e) {
+ log.error("JWT parse error", e);
+ }
+
+ } else {
+ log.warn("Request has no SecurityContext and no Bearer Token");
+ }
+
+ }
+
+}
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/RawBodyLoggingFilter.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/RawBodyLoggingFilter.java
new file mode 100644
index 0000000..1ac9ea4
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/RawBodyLoggingFilter.java
@@ -0,0 +1,77 @@
+package com.peri.psd.testapp.playground.telemetry.filter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.web.util.ContentCachingResponseWrapper;
+
+import java.io.IOException;
+import java.util.stream.Stream;
+
+@Component
+@ConditionalOnProperty(name = "psd.azure.insight.filter.body-filter", havingValue = "true", matchIfMissing = true)
+@RequiredArgsConstructor
+@Slf4j
+@Order(SecurityProperties.DEFAULT_FILTER_ORDER + 140)
+public class RawBodyLoggingFilter extends AbstractLoggingFilter {
+
+ private final int MAX_BODY_SIZE = 5000;
+
+ // https://petrepopescu.tech/2023/07/how-to-log-http-request-and-response-in-spring-boot/
+ // https://www.linkedin.com/pulse/rest-api-monitoring-azure-application-insights-srinivas-anumala
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+ RepeatableContentCachingRequestWrapper requestWrapper = new RepeatableContentCachingRequestWrapper(
+ request);
+ ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
+
+ logRequest(requestWrapper);
+
+ filterChain.doFilter(requestWrapper, responseWrapper);
+ logResponse(responseWrapper);
+
+ }
+
+ private void logRequest(
+ RepeatableContentCachingRequestWrapper requestWrapper) throws IOException {
+
+ // this must be read always
+ String body = requestWrapper.readInputAndDuplicate();
+ if (body.length() > MAX_BODY_SIZE) {
+ log.trace("trim request from {} to {}", body.length(),
+ MAX_BODY_SIZE);
+ body = body.substring(0, MAX_BODY_SIZE);
+ }
+
+ if (shouldLogRequest(requestWrapper)) {
+ sendToSpan(KEY_REQUESTBODYRAW, body);
+ }
+ }
+
+ private void logResponse(ContentCachingResponseWrapper responseWrapper) throws IOException {
+ if (shouldLogResponsePayload(responseWrapper)) {
+ log.debug("Response {}", new String(responseWrapper.getContentAsByteArray()));
+ }
+ responseWrapper.copyBodyToResponse();
+ }
+
+ private boolean shouldLogRequest(RepeatableContentCachingRequestWrapper requestWrapper) {
+ String method = requestWrapper.getMethod();
+ boolean b = Stream.of("POST", "PUT", "PATCH")
+ .anyMatch(m -> m.equalsIgnoreCase(method));
+ log.trace("SHOULD LOG = {} - {}:{}", b, method, requestWrapper.getServletPath());
+ return b;
+ }
+
+ private boolean shouldLogResponsePayload(ContentCachingResponseWrapper responseWrapper) {
+ return false;
+ }
+}
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/RepeatableContentCachingRequestWrapper.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/RepeatableContentCachingRequestWrapper.java
new file mode 100644
index 0000000..4dc48c0
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/RepeatableContentCachingRequestWrapper.java
@@ -0,0 +1,29 @@
+package com.peri.psd.testapp.playground.telemetry.filter;
+
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.web.util.ContentCachingRequestWrapper;
+
+import java.io.IOException;
+
+public class RepeatableContentCachingRequestWrapper extends ContentCachingRequestWrapper {
+ private SimpleServletInputStream inputStream;
+
+ public RepeatableContentCachingRequestWrapper(HttpServletRequest request) {
+ super(request);
+ }
+
+ @Override
+ public ServletInputStream getInputStream() {
+ return this.inputStream;
+ }
+
+ public String readInputAndDuplicate() throws IOException {
+ if (inputStream == null) {
+ byte[] body = super.getInputStream()
+ .readAllBytes();
+ this.inputStream = new SimpleServletInputStream(body);
+ }
+ return new String(super.getContentAsByteArray());
+ }
+}
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/SimpleServletInputStream.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/SimpleServletInputStream.java
new file mode 100644
index 0000000..6d84862
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/SimpleServletInputStream.java
@@ -0,0 +1,36 @@
+package com.peri.psd.testapp.playground.telemetry.filter;
+
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class SimpleServletInputStream extends ServletInputStream {
+ private InputStream delegate;
+
+ public SimpleServletInputStream(byte[] data) {
+ this.delegate = new ByteArrayInputStream(data);
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setReadListener(ReadListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int read() throws IOException {
+ return this.delegate.read();
+ }
+}
\ No newline at end of file
diff --git a/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/SpanInitializationFilter.java b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/SpanInitializationFilter.java
new file mode 100644
index 0000000..f09e097
--- /dev/null
+++ b/insightCustomDimensions/src/main/java/com/peri/psd/testapp/playground/telemetry/filter/SpanInitializationFilter.java
@@ -0,0 +1,54 @@
+package com.peri.psd.testapp.playground.telemetry.filter;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Scope;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+@Component
+@Order(SecurityProperties.DEFAULT_FILTER_ORDER + 100)// Ensure it runs after the Security filters
+@RequiredArgsConstructor
+@Slf4j
+public class SpanInitializationFilter extends AbstractLoggingFilter {
+
+ private final Tracer tracer;
+
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain)
+ throws ServletException, IOException {
+
+ // Create and activate a span for this request
+ Span span = tracer.spanBuilder(request.getMethod() + " " + request.getRequestURI())
+ .setSpanKind(SpanKind.SERVER)
+ .startSpan();
+ try (Scope scope = span.makeCurrent()) {
+ SpanContext spanContext = span.getSpanContext();
+ MDC.put("MDC.span.kind", SpanKind.SERVER.name());
+ span.setAttribute("span.kind", SpanKind.SERVER.name());
+ log.debug("current SPAN {}", spanContext);
+
+ filterChain.doFilter(request, response);
+ } finally {
+ log.debug("END current SPAN: {}", span.getSpanContext());
+ MDC.clear();
+ span.end();
+ }
+ }
+
+}
diff --git a/insightCustomDimensions/src/main/resources/application.yaml b/insightCustomDimensions/src/main/resources/application.yaml
new file mode 100644
index 0000000..ed73296
--- /dev/null
+++ b/insightCustomDimensions/src/main/resources/application.yaml
@@ -0,0 +1,7 @@
+spring:
+ application:
+ name: psd-testapp
+
+logging:
+ level:
+ com.peri: DEBUG
\ No newline at end of file