-
Notifications
You must be signed in to change notification settings - Fork 215
feat(debug): global debug mode (correlation ids, SQL detail, JFR profiling) (#6378) #6380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
fb2bbae
feat(debug): global debug mode - correlation ids, SQL detail, JFR pro…
laugiov bd0a5af
fix(debug): gate tracing behind the production barrier and drop unuse…
laugiov 9a368a3
fix(debug): address review feedback and document SQL log usage and li…
laugiov 88f64f9
fix(debug): actually gate tracing behind debug mode so there is zero …
laugiov 1ba3000
fix(debug): suppress the log correlation slot when debug mode is off …
laugiov 908ad0d
docs(debug): address review feedback — define MDC, persistence note
laugiov e62e2e2
Merge remote-tracking branch 'origin/main' into feat/6378-global-debu…
laugiov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
openaev-api/src/main/java/io/openaev/debug/DataSourceProxyBeanPostProcessor.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package io.openaev.debug; | ||
|
|
||
| import javax.sql.DataSource; | ||
| import net.ttddyy.dsproxy.support.ProxyDataSource; | ||
| import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.beans.factory.config.BeanPostProcessor; | ||
|
|
||
| /** | ||
| * Wraps the auto-configured {@link DataSource} in a datasource-proxy for SQL logging. Created only | ||
| * when debug mode is on; when off there is no proxy on the query path at all. | ||
| */ | ||
| public class DataSourceProxyBeanPostProcessor implements BeanPostProcessor { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(DataSourceProxyBeanPostProcessor.class); | ||
|
|
||
| private final MaskingSqlLoggingListener listener; | ||
|
|
||
| public DataSourceProxyBeanPostProcessor(MaskingSqlLoggingListener listener) { | ||
| this.listener = listener; | ||
| } | ||
|
|
||
| @Override | ||
| public Object postProcessAfterInitialization(Object bean, String beanName) { | ||
| if (bean instanceof DataSource dataSource && !(bean instanceof ProxyDataSource)) { | ||
| log.warn("Debug mode: wrapping datasource bean '{}' with SQL statement logging", beanName); | ||
| return ProxyDataSourceBuilder.create(dataSource) | ||
| .name("openaev-debug") | ||
| .listener(listener) | ||
| .build(); | ||
| } | ||
| return bean; | ||
| } | ||
| } |
43 changes: 43 additions & 0 deletions
43
openaev-api/src/main/java/io/openaev/debug/DebugActivationGuard.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package io.openaev.debug; | ||
|
|
||
| import jakarta.annotation.PostConstruct; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
| import org.springframework.core.env.Environment; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| /** | ||
| * Logs the production-barrier outcome. Exists whenever {@code enabled=true} (so a refusal is | ||
| * visible), independently of {@link DebugEnabledCondition} which gates the actual debug beans. | ||
| */ | ||
| @Component | ||
| @ConditionalOnProperty(prefix = "openaev.debug", name = "enabled", havingValue = "true") | ||
| public class DebugActivationGuard { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(DebugActivationGuard.class); | ||
|
|
||
| private final Environment environment; | ||
|
|
||
| public DebugActivationGuard(Environment environment) { | ||
| this.environment = environment; | ||
| } | ||
|
|
||
| @PostConstruct | ||
| public void check() { | ||
| boolean production = DebugEnabledCondition.isProduction(environment); | ||
| boolean override = | ||
| environment.getProperty("openaev.debug.allow-in-production", Boolean.class, false); | ||
|
|
||
| if (production && !override) { | ||
| log.error( | ||
| "Debug mode was requested (openaev.debug.enabled=true) but REFUSED: production environment " | ||
| + "detected (no dev/test/ci profile active). Set openaev.debug.allow-in-production=true " | ||
| + "to override deliberately."); | ||
| } else if (production) { | ||
| log.warn( | ||
| "Debug mode is ACTIVE IN PRODUCTION via openaev.debug.allow-in-production=true. This is a " | ||
| + "deliberate override; do not leave it on."); | ||
| } | ||
| } | ||
| } |
106 changes: 106 additions & 0 deletions
106
openaev-api/src/main/java/io/openaev/debug/DebugConfiguration.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| package io.openaev.debug; | ||
|
|
||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.beans.factory.config.BeanDefinition; | ||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Conditional; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.context.annotation.Role; | ||
|
|
||
| /** | ||
| * Wires the debug-mode beans, gated by {@link DebugEnabledCondition}. When off (or refused in | ||
| * production) the whole configuration backs off: no proxy, no extra per-request cost. | ||
| */ | ||
| @Configuration(proxyBeanMethods = false) | ||
| @Conditional(DebugEnabledCondition.class) | ||
| public class DebugConfiguration { | ||
|
|
||
| @Bean | ||
| public DebugRuntimeState debugRuntimeState() { | ||
| return new DebugRuntimeState(); | ||
| } | ||
|
|
||
| @Bean | ||
| @Role(BeanDefinition.ROLE_INFRASTRUCTURE) | ||
| public SensitiveDataMasker sensitiveDataMasker(DebugProperties properties) { | ||
| return new SensitiveDataMasker(properties.getMasking()); | ||
| } | ||
|
|
||
| @Bean | ||
| @Role(BeanDefinition.ROLE_INFRASTRUCTURE) | ||
| @ConditionalOnProperty( | ||
| prefix = "openaev.debug.sql", | ||
| name = "enabled", | ||
| havingValue = "true", | ||
| matchIfMissing = true) | ||
| public MaskingSqlLoggingListener maskingSqlLoggingListener( | ||
| SensitiveDataMasker masker, DebugRuntimeState runtimeState, DebugProperties properties) { | ||
| return new MaskingSqlLoggingListener(masker, runtimeState, properties.getSql()); | ||
| } | ||
|
|
||
| /** {@code static} so the post-processor is created early enough to wrap the datasource. */ | ||
| @Bean | ||
| @ConditionalOnProperty( | ||
| prefix = "openaev.debug.sql", | ||
| name = "enabled", | ||
| havingValue = "true", | ||
| matchIfMissing = true) | ||
| public static DataSourceProxyBeanPostProcessor dataSourceProxyBeanPostProcessor( | ||
| MaskingSqlLoggingListener listener) { | ||
| return new DataSourceProxyBeanPostProcessor(listener); | ||
| } | ||
|
|
||
| /** ORM summary / N+1 detection; rides on the SQL proxy, gated by {@code sql.enabled}. */ | ||
| @Bean | ||
| @ConditionalOnProperty( | ||
| prefix = "openaev.debug.sql", | ||
| name = "enabled", | ||
| havingValue = "true", | ||
| matchIfMissing = true) | ||
| public OrmInsightFilter ormInsightFilter( | ||
| SensitiveDataMasker masker, DebugRuntimeState runtimeState) { | ||
| return new OrmInsightFilter(masker, runtimeState); | ||
| } | ||
|
|
||
| @Bean | ||
| public JfrRecordingManager jfrRecordingManager(DebugProperties properties) { | ||
| return new JfrRecordingManager(properties.getOutputDir(), properties.getJfr()); | ||
| } | ||
|
|
||
| /** Routes the verbose SQL log to a rotated file (off the console). Gated like SQL logging. */ | ||
| @Bean | ||
| @ConditionalOnProperty( | ||
| prefix = "openaev.debug.sql", | ||
| name = "enabled", | ||
| havingValue = "true", | ||
| matchIfMissing = true) | ||
| public DebugSqlLogFileConfigurer debugSqlLogFileConfigurer(DebugProperties properties) { | ||
| return new DebugSqlLogFileConfigurer(properties.getOutputDir()); | ||
| } | ||
|
|
||
| @Bean | ||
| public DebugTenantSource debugTenantSource() { | ||
| return new DebugTenantSource(); | ||
| } | ||
|
|
||
| @Bean | ||
| public DebugTenantMdcInterceptor debugTenantMdcInterceptor(DebugTenantSource tenantSource) { | ||
| return new DebugTenantMdcInterceptor(tenantSource); | ||
| } | ||
|
|
||
| @Bean | ||
| public DebugWebMvcConfigurer debugWebMvcConfigurer( | ||
| DebugTenantMdcInterceptor tenantMdcInterceptor) { | ||
| return new DebugWebMvcConfigurer(tenantMdcInterceptor); | ||
| } | ||
|
|
||
| @Bean | ||
| public DebugModeManager debugModeManager( | ||
| DebugProperties properties, | ||
| JfrRecordingManager jfrRecordingManager, | ||
| DebugRuntimeState runtimeState, | ||
| @Value("${pyroscope.agent.enabled:false}") boolean pyroscopeEnabled) { | ||
| return new DebugModeManager(properties, jfrRecordingManager, runtimeState, pyroscopeEnabled); | ||
| } | ||
| } |
40 changes: 40 additions & 0 deletions
40
openaev-api/src/main/java/io/openaev/debug/DebugEnabledCondition.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package io.openaev.debug; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.Set; | ||
| import org.springframework.context.annotation.Condition; | ||
| import org.springframework.context.annotation.ConditionContext; | ||
| import org.springframework.core.env.Environment; | ||
| import org.springframework.core.type.AnnotatedTypeMetadata; | ||
|
|
||
| /** | ||
| * Production barrier: debug mode runs only when {@code enabled=true} AND (a non-production profile | ||
| * is active OR {@code allow-in-production=true}). Production = no {@code dev}/{@code test}/{@code | ||
| * ci} profile (there is no explicit {@code production} profile). | ||
| */ | ||
| public class DebugEnabledCondition implements Condition { | ||
|
|
||
| static final Set<String> NON_PRODUCTION_PROFILES = Set.of("dev", "test", "ci"); | ||
|
|
||
| @Override | ||
| public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { | ||
| return isDebugActive(context.getEnvironment()); | ||
| } | ||
|
|
||
| /** | ||
| * The production barrier, reusable outside a {@link Condition} (e.g. an environment processor). | ||
| */ | ||
| static boolean isDebugActive(Environment env) { | ||
| if (!env.getProperty("openaev.debug.enabled", Boolean.class, false)) { | ||
| return false; | ||
| } | ||
| if (env.getProperty("openaev.debug.allow-in-production", Boolean.class, false)) { | ||
| return true; | ||
| } | ||
| return !isProduction(env); | ||
| } | ||
|
|
||
| static boolean isProduction(Environment env) { | ||
| return Arrays.stream(env.getActiveProfiles()).noneMatch(NON_PRODUCTION_PROFILES::contains); | ||
| } | ||
| } |
46 changes: 46 additions & 0 deletions
46
openaev-api/src/main/java/io/openaev/debug/DebugLogCorrelationListener.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package io.openaev.debug; | ||
|
|
||
| import java.util.Map; | ||
| import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; | ||
| import org.springframework.context.ApplicationListener; | ||
| import org.springframework.core.Ordered; | ||
| import org.springframework.core.env.ConfigurableEnvironment; | ||
| import org.springframework.core.env.MapPropertySource; | ||
|
|
||
| /** | ||
| * Removes the log correlation slot ({@code [traceId spanId]}) when debug mode is off. Spring Boot | ||
| * adds a default {@code logging.pattern.correlation} as soon as {@code | ||
| * io.micrometer.tracing.Tracer} is on the classpath, so without this the logs carry an empty {@code | ||
| * [ ]} slot even when nothing is traced. When debug mode is active the pattern is left to Spring | ||
| * Boot, so the traceId shows in the logs (the point of debug mode). | ||
| * | ||
| * <p>It runs on {@link ApplicationEnvironmentPreparedEvent}, before the logging system reads the | ||
| * pattern, and ordered ahead of Spring Boot's {@code LoggingApplicationListener}. It is registered | ||
| * on the {@code SpringApplicationBuilder} in {@code App} (an environment post-processor would not | ||
| * do, as its {@code .imports} registration is not honoured and {@code spring.factories} is | ||
| * generated here). | ||
| */ | ||
| public class DebugLogCorrelationListener | ||
| implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered { | ||
|
|
||
| @Override | ||
| public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { | ||
| suppressCorrelationWhenDebugOff(event.getEnvironment()); | ||
| } | ||
|
|
||
| void suppressCorrelationWhenDebugOff(ConfigurableEnvironment env) { | ||
| if (DebugEnabledCondition.isDebugActive(env)) { | ||
| return; // debug active: keep Spring Boot's correlation pattern so the traceId is logged | ||
| } | ||
| env.getPropertySources() | ||
| .addFirst( | ||
| new MapPropertySource( | ||
| "openaev-debug-log-correlation", Map.of("logging.pattern.correlation", ""))); | ||
| } | ||
|
|
||
| @Override | ||
| public int getOrder() { | ||
| // Before LoggingApplicationListener (HIGHEST_PRECEDENCE + 20) so the pattern is set in time. | ||
| return Ordered.HIGHEST_PRECEDENCE + 11; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent doc thanks