package org.stagemonitor.tracing; import com.uber.jaeger.context.TraceContext; import com.uber.jaeger.context.TracingUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.stagemonitor.configuration.ConfigurationOption; import org.stagemonitor.configuration.ConfigurationRegistry; import org.stagemonitor.configuration.converter.DoubleValueConverter; import org.stagemonitor.configuration.converter.StringValueConverter; import org.stagemonitor.core.CorePlugin; import org.stagemonitor.core.Stagemonitor; import org.stagemonitor.core.StagemonitorPlugin; import org.stagemonitor.core.elasticsearch.ElasticsearchClient; import org.stagemonitor.core.grafana.GrafanaClient; import org.stagemonitor.core.metrics.metrics2.Metric2Registry; import org.stagemonitor.core.util.JsonUtils; import org.stagemonitor.tracing.anonymization.AnonymizingSpanEventListener; import org.stagemonitor.tracing.mdc.MDCSpanEventListener; import org.stagemonitor.tracing.metrics.ExternalRequestMetricsSpanEventListener; import org.stagemonitor.tracing.metrics.ServerRequestMetricsSpanEventListener; import org.stagemonitor.tracing.profiler.CallTreeSpanEventListener; import org.stagemonitor.tracing.reporter.ReadbackSpan; import org.stagemonitor.tracing.reporter.ReadbackSpanEventListener; import org.stagemonitor.tracing.reporter.ReportingSpanEventListener; import org.stagemonitor.tracing.reporter.SpanReporter; import org.stagemonitor.tracing.sampling.PostExecutionSpanInterceptor; import org.stagemonitor.tracing.sampling.PreExecutionSpanInterceptor; import org.stagemonitor.tracing.sampling.SamplePriorityDeterminingSpanEventListener; import org.stagemonitor.tracing.wrapper.SpanEventListenerFactory; import org.stagemonitor.tracing.wrapper.SpanWrappingTracer; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.ServiceLoader; import java.util.regex.Pattern; import io.opentracing.NoopTracerFactory; import io.opentracing.Span; import io.opentracing.Tracer; import io.opentracing.util.GlobalTracer; public class TracingPlugin extends StagemonitorPlugin { private static final Logger logger = LoggerFactory.getLogger(TracingPlugin.class); private static final String TRACING_PLUGIN = "Tracing Plugin"; /* What/how to monitor */ private final ConfigurationOption<Boolean> collectCpuTime = ConfigurationOption.booleanOption() .key("stagemonitor.requestmonitor.cpuTime") .dynamic(true) .label("Collect CPU time") .description("Whether or not a timer for the cpu time of executions should be created. " + "This is useful if you want to know which use cases are responsible for the most CPU usage. " + "Be aware that setting this to true almost doubles the amount of timers created.") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(false); private final ConfigurationOption<Boolean> collectDbTimePerRequest = ConfigurationOption.booleanOption() .key("stagemonitor.requestmonitor.collectExternalRequestTimePerRequest") .dynamic(true) .label("Collect external request time per request group") .description("Whether or not the execution time of external should be collected per request group\n" + "If set to true, a timer will be created for each request to record the total db time per request.") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(false); private final ConfigurationOption<BusinessTransactionNamingStrategy> businessTransactionNamingStrategy = ConfigurationOption.enumOption(BusinessTransactionNamingStrategy.class) .key("stagemonitor.businessTransaction.namingStrategy") .dynamic(false) .label("Business Transaction naming strategy") .description("Defines how to name a business transaction that was detected by a method call. " + "For example a Spring-MVC controller method or a method that is annotated with @" + MonitorRequests.class.getSimpleName() + ". " + BusinessTransactionNamingStrategy.METHOD_NAME_SPLIT_CAMEL_CASE + ": Say Hello " + BusinessTransactionNamingStrategy.CLASS_NAME_DOT_METHOD_NAME + ": HelloController.sayHello " + BusinessTransactionNamingStrategy.CLASS_NAME_HASH_METHOD_NAME + ": HelloController#sayHello ") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(BusinessTransactionNamingStrategy.METHOD_NAME_SPLIT_CAMEL_CASE); private final ConfigurationOption<Boolean> monitorScheduledTasks = ConfigurationOption.booleanOption() .key("stagemonitor.requestmonitor.monitorScheduledTasks") .dynamic(false) .label("Monitor scheduled tasks") .description("Set to true trace EJB (@Schedule) and Spring (@Scheduled) scheduled tasks.") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(false); private final ConfigurationOption<Collection<Pattern>> confidentialParameters = ConfigurationOption.regexListOption() .key("stagemonitor.requestmonitor.params.confidential.regex") .dynamic(true) .label("Confidential parameters (regex)") .description("A list of request parameter name patterns that should not be collected.\n" + "In the context of a HTTP request, a request parameter is either a query string or a application/x-www-form-urlencoded request " + "body (POST form content). In the context of a method invocation monitored with @MonitorRequests," + "this refers to the parameter name of the monitored method. Note that you have to compile your classes" + "with 'vars' debug information.") .tags("security-relevant") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(Arrays.asList( Pattern.compile("(?i).*pass.*"), Pattern.compile("(?i).*credit.*"), Pattern.compile("(?i).*pwd.*"), Pattern.compile("(?i)pw"))); /* Profiler */ private final ConfigurationOption<Boolean> profilerActive = ConfigurationOption.booleanOption() .key("stagemonitor.profiler.active") .dynamic(false) .label("Activate Profiler") .description("Whether or not the call tree profiler should be active.") .tags("profiler") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(true); private final ConfigurationOption<Long> minExecutionTimeNanos = ConfigurationOption.longOption() .key("stagemonitor.profiler.minExecutionTimeNanos") .dynamic(false) .label("Min execution time (nanos)") .description("Don't show methods that executed faster than this value in the call tree (1 ms = 1,000,000 ns).") .tags("profiler") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(100000L); private final ConfigurationOption<Double> minExecutionTimePercent = ConfigurationOption.doubleOption() .key("stagemonitor.profiler.minExecutionTimePercent") .dynamic(true) .label("Min execution time (%)") .description("Don't show methods that executed faster than this value in the call tree (0.5 or 0,5 means 0.5%).") .tags("profiler") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(0.5); private final ConfigurationOption<Boolean> profilerObjectPooling = ConfigurationOption.booleanOption() .key("stagemonitor.profiler.objectPooling") .dynamic(false) .label("Activate Profiler Object Pooling") .description("Activates the experimental object pooling feature for the profiler. When enabled, instances of " + "CallStackElement are not garbage collected but put into an object pool when not needed anymore. " + "When we need a new instance of CallStackElement, it is not created with `new CallStackElement()` " + "but taken from the pool instead. This aims to reduce heap usage and garbage collections caused by " + "stagemonitor.") .tags("profiler", "experimental") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(false); private final ConfigurationOption<Double> profilerRateLimitPerMinute = ConfigurationOption.doubleOption() .key("stagemonitor.profiler.sampling.rateLimitPerMinute") .aliasKeys("stagemonitor.requestmonitor.onlyCollectNCallTreesPerMinute") .dynamic(true) .label("Only report N call trees per minute") .description("Limits the rate at which call trees are collected. " + "Set to a value below 1 to deactivate call tree recording and to 1000000 or higher to always collect.") .tags("profiler", "sampling") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(1000000d); /* Privacy */ private final ConfigurationOption<Boolean> anonymizeIPs = ConfigurationOption.booleanOption() .key("stagemonitor.anonymizeIPs") .dynamic(true) .label("Anonymize IP Addresses") .description("For IPv4 addresses, the last octet is set to zero. " + "If the address is a IPv6 address, the last 80 bits (10 bytes) are set to zero. " + "This is just like Google Analytics handles IP anonymization.") .tags("privacy") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(true); private final ConfigurationOption<Boolean> pseudonymizeUserName = ConfigurationOption.booleanOption() .key("stagemonitor.pseudonymize.username") .dynamic(true) .label("Pseudonymize Usernames") .description("Stagemonitor collects the user names which may be a privacy issue. " + "If set to true, the user name will be pseudonymized (SHA1 hashed).") .tags("privacy") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(false); private final ConfigurationOption<Collection<String>> discloseUsers = ConfigurationOption.stringsOption() .key("stagemonitor.disclose.users") .dynamic(true) .label("Disclose users") .description("When you pseudonymize user names and detect that a specific user seems malicious, " + "you can disclose their real user name to make further investigations. Also, the IP won't be " + "anonymized anymore for these users. " + "If pseudonymizing user names is active you can specify a list of user name pseudonyms to disclose. " + "If not, just use the plain user names to disclose their IP address.") .tags("privacy") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(Collections.<String>emptySet()); /* Reporting */ private final ConfigurationOption<Boolean> logSpans = ConfigurationOption.booleanOption() .key("stagemonitor.requestmonitor.reporting.log") .dynamic(true) .label("Log spans") .description("Whether or not spans should be logged.") .tags("reporting") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(false); private final ConfigurationOption<Boolean> reportSpansAsync = ConfigurationOption.booleanOption() .key("stagemonitor.requestmonitor.report.async") .dynamic(true) .label("Report Async") .description("Set to true to report collected spans asynchronously. It's recommended to always set this to " + "true. Otherwise the performance of your requests will suffer as spans are reported in band.") .configurationCategory(TRACING_PLUGIN) .tags("reporting") .buildWithDefault(true); /* Exceptions */ private final ConfigurationOption<Collection<String>> unnestExceptions = ConfigurationOption.stringsOption() .key("stagemonitor.requestmonitor.unnestExeptions") .dynamic(true) .label("Unnest Exceptions") .description("Some Exceptions are so called 'nested exceptions' which wrap the actual cause of the exception. " + "A prominent example is Spring's NestedServletException. " + "In those cases it makes sense to unnest the exception to see the actual cause in the request analysis dashboard.") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(Collections.singleton("org.springframework.web.util.NestedServletException")); private final ConfigurationOption<Collection<String>> ignoreExceptions = ConfigurationOption.stringsOption() .key("stagemonitor.requestmonitor.ignoreExeptions") .dynamic(true) .label("Ignore Exceptions") .description("The class names of exception to ignore. These exceptions won't show up in the span " + "and won't cause the error flag of the span to be set to true.") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(Collections.<String>emptyList()); /* Sampling */ private final ConfigurationOption<Collection<String>> onlyReportSpansWithName = ConfigurationOption.stringsOption() .key("stagemonitor.requestmonitor.sampling.onlyReportSpansWithName") .aliasKeys("stagemonitor.requestmonitor.onlyReportRequestsWithNameToElasticsearch") .dynamic(true) .label("Only report these operation names") .description("Limits the reporting of spans to operations with a certain name.") .tags("sampling") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(Collections.<String>emptySet()); private final ConfigurationOption<Double> rateLimitServerSpansPerMinute = ConfigurationOption.doubleOption() .key("stagemonitor.requestmonitor.sampling.server.rateLimitPerMinute") .aliasKeys("stagemonitor.requestmonitor.onlyReportNRequestsPerMinuteToElasticsearch") .dynamic(true) .label("Rate limit for server spans per minute") .description("Limits the rate at which spans are collected reported. " + "Set to a value below 1 to deactivate reporting and to 1000000 or higher to always report.") .tags("sampling") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(1000000d); private final ConfigurationOption<Double> rateLimitClientSpansPerMinute = ConfigurationOption.doubleOption() .key("stagemonitor.requestmonitor.sampling.client.rateLimitPerMinute.generic") .aliasKeys("stagemonitor.requestmonitor.external.onlyReportNExternalRequestsPerMinute") .dynamic(true) .label("Rate limit for external requests (client spans) per minute") .description("Limits the rate at which external spans are collected and reported. " + "Set to a value below 1 to deactivate reporting and to 1000000 or higher to always report. " + "This setting is active for all operation types which are not listed in " + "'stagemonitor.requestmonitor.sampling.client.rateLimitPerMinute.perType'") .tags("external-requests", "sampling") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(1000000d); private final ConfigurationOption<Map<String, Double>> rateLimitClientSpansPerTypePerMinute = ConfigurationOption.mapOption(StringValueConverter.INSTANCE, DoubleValueConverter.INSTANCE) .key("stagemonitor.requestmonitor.sampling.client.rateLimitPerMinute.perType") .dynamic(true) .label("Rate limit for external requests (client spans) per minute per operation type") .description("Limits the rate at which specific external spans like 'jdbc' queries are collected and reported. " + "Set to a value below 1 to deactivate reporting and to 1000000 or higher to always report. " + "If your application makes excessive use of for example jdbc queries, you might want to deactivate " + "or rate limit the collection of spans. Example: `jdbc: 0, http: 1000000`") .tags("external-requests", "sampling") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(Collections.<String, Double>emptyMap()); private final ConfigurationOption<Double> excludeCallTreeFromReportWhenFasterThanXPercentOfRequests = ConfigurationOption.doubleOption() .key("stagemonitor.requestmonitor.sampling.excludeCallTreeFromReportWhenFasterThanXPercentOfRequests") .aliasKeys("stagemonitor.requestmonitor.elasticsearch.excludeCallTreeFromElasticsearchReportWhenFasterThanXPercentOfRequests") .dynamic(true) .label("Exclude the Call Tree from reports on x% of the fastest requests") .description("Exclude the Call Tree from report when the request was faster faster than x " + "percent of requests with the same request name. This helps to reduce the network and disk overhead " + "as uninteresting Call Trees (those which are comparatively fast) are excluded. " + "Example: set to 1 to always exclude the Call Tree and to 0 to always include it. " + "With a setting of 0.85, the Call Tree will only be reported for the slowest 25% of the requests.") .tags("sampling") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(0d); private final ConfigurationOption<Double> excludeExternalRequestsWhenFasterThanXPercent = ConfigurationOption.doubleOption() .key("stagemonitor.requestmonitor.external.excludeExternalRequestsWhenFasterThanXPercent") .dynamic(true) .label("Exclude external requests from reporting on x% of the fastest external requests") .description("Exclude the external request from reporting when the request was faster faster than x " + "percent of external requests with the same initiator (executedBy). This helps to reduce the network and disk overhead " + "as uninteresting external requests (those which are comparatively fast) are excluded." + "Example: set to 1 to always exclude the external request and to 0 to always include it. " + "With a setting of 0.85, the external request will only be reported for the slowest 25% of the requests.") .tags("external-requests", "sampling") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(0d); private final ConfigurationOption<Double> excludeExternalRequestsFasterThan = ConfigurationOption.doubleOption() .key("stagemonitor.requestmonitor.external.excludeExternalRequestsFasterThan") .dynamic(true) .label("Exclude external requests from reporting when faster than x ms") .description("Exclude the external request from reporting when the request was faster faster than x ms.") .tags("external-requests", "sampling") .configurationCategory(TRACING_PLUGIN) .buildWithDefault(0d); private static RequestMonitor requestMonitor; private SpanWrappingTracer spanWrappingTracer; private SamplePriorityDeterminingSpanEventListener samplePriorityDeterminingSpanInterceptor; private ReportingSpanEventListener reportingSpanEventListener; private CorePlugin corePlugin; /** * @return the {@link Span} of the current request or a noop {@link Span} (never <code>null</code>) */ public static Span getCurrentSpan() { final TraceContext traceContext = TracingUtils.getTraceContext(); if (!traceContext.isEmpty()) { return traceContext.getCurrentSpan(); } else { return NoopTracerFactory.create().buildSpan(null).start(); } } /** * This is an internal method, use {@link GlobalTracer#get()} */ public Tracer getTracer() { if (spanWrappingTracer != null && corePlugin.isStagemonitorActive()) { return spanWrappingTracer; } else { return NoopTracerFactory.create(); } } @Override public void initializePlugin(final StagemonitorPlugin.InitArguments initArguments) { JsonUtils.getMapper().registerModule(new ReadbackSpan.SpanJsonModule()); corePlugin = initArguments.getPlugin(CorePlugin.class); final ElasticsearchClient elasticsearchClient = corePlugin.getElasticsearchClient(); final GrafanaClient grafanaClient = corePlugin.getGrafanaClient(); if (corePlugin.isReportToGraphite()) { elasticsearchClient.sendGrafana1DashboardAsync("grafana/Grafana1GraphiteRequestDashboard.json"); } if (corePlugin.isReportToElasticsearch()) { elasticsearchClient.sendClassPathRessourceBulkAsync("kibana/Request-Metrics.bulk"); elasticsearchClient.sendClassPathRessourceBulkAsync("kibana/External-Request-Metrics.bulk"); grafanaClient.sendGrafanaDashboardAsync("grafana/ElasticsearchRequestDashboard.json"); grafanaClient.sendGrafanaDashboardAsync("grafana/ElasticsearchExternalRequestsDashboard.json"); } final Metric2Registry metricRegistry = initArguments.getMetricRegistry(); final Tracer tracer = getTracerImpl(initArguments); reportingSpanEventListener = new ReportingSpanEventListener(initArguments.getConfiguration()); for (SpanReporter spanReporter : ServiceLoader.load(SpanReporter.class, RequestMonitor.class.getClassLoader())) { addReporter(spanReporter); } samplePriorityDeterminingSpanInterceptor = new SamplePriorityDeterminingSpanEventListener(initArguments.getConfiguration()); final ServiceLoader<SpanEventListenerFactory> factories = ServiceLoader.load(SpanEventListenerFactory.class, TracingPlugin.class.getClassLoader()); this.spanWrappingTracer = createSpanWrappingTracer(tracer, initArguments.getConfiguration(), metricRegistry, factories, samplePriorityDeterminingSpanInterceptor, reportingSpanEventListener); try { GlobalTracer.register(spanWrappingTracer); } catch (IllegalStateException e) { logger.debug("If this exception occurs outside of stagemonitor's unit tests it indicates a programming " + "error. Make sure you don't call Stagemonitor.reset()", e); } } private Tracer getTracerImpl(InitArguments initArguments) { final Iterator<TracerFactory> tracerFactoryIterator = ServiceLoader.load(TracerFactory.class, RequestMonitor.class.getClassLoader()).iterator(); if (tracerFactoryIterator.hasNext()) { final Tracer tracer = tracerFactoryIterator.next().getTracer(initArguments); assertIsSingleImplementation(initArguments, tracerFactoryIterator, tracer); return tracer; } else { logger.info("No OpenTracing implementation found. Falling back to NoopTracer. " + "This is fine if you just want to use stagemonitor for development, for example with the in-browser-widget. " + "If you want to report your traces to Elasticsearch, add a dependency to stagemonitor-tracing-elasticsearch. " + "If you want to report to Zipkin, add stagemonitor-tracing-zipkin."); return NoopTracerFactory.create(); } } private void assertIsSingleImplementation(InitArguments initArguments, Iterator<TracerFactory> tracerFactoryIterator, Tracer tracer) { if (tracerFactoryIterator.hasNext()) { final Tracer tracer2 = tracerFactoryIterator.next().getTracer(initArguments); throw new IllegalStateException(MessageFormat.format("Multiple tracing implementations found: {0}, {1}. " + "Make sure you only have one stagemonitor-tracing-* jar in your class path.", tracer.getClass().getName(), tracer2.getClass().getName())); } } public static SpanWrappingTracer createSpanWrappingTracer(final Tracer delegate, ConfigurationRegistry configuration, final Metric2Registry metricRegistry, final Iterable<SpanEventListenerFactory> spanInterceptorFactories, final SamplePriorityDeterminingSpanEventListener samplePriorityDeterminingSpanInterceptor, final ReportingSpanEventListener reportingSpanEventListener) { final TracingPlugin tracingPlugin = configuration.getConfig(TracingPlugin.class); final SpanWrappingTracer spanWrappingTracer = new SpanWrappingTracer(delegate); spanWrappingTracer.addSpanInterceptor(new SpanContextInformation.SpanContextSpanEventListener()); spanWrappingTracer.addSpanInterceptor(samplePriorityDeterminingSpanInterceptor); spanWrappingTracer.addSpanInterceptor(new AnonymizingSpanEventListener.MySpanEventListenerFactory(tracingPlugin)); spanWrappingTracer.addSpanInterceptor(new MDCSpanEventListener(configuration.getConfig(CorePlugin.class), tracingPlugin)); for (SpanEventListenerFactory spanEventListenerFactory : spanInterceptorFactories) { spanWrappingTracer.addSpanInterceptor(spanEventListenerFactory); } spanWrappingTracer.addSpanInterceptor(new ExternalRequestMetricsSpanEventListener.Factory(metricRegistry)); spanWrappingTracer.addSpanInterceptor(new ServerRequestMetricsSpanEventListener.Factory(metricRegistry, tracingPlugin)); spanWrappingTracer.addSpanInterceptor(new CallTreeSpanEventListener(tracingPlugin)); spanWrappingTracer.addSpanInterceptor(new ReadbackSpanEventListener.Factory(reportingSpanEventListener, tracingPlugin)); spanWrappingTracer.addSpanInterceptor(reportingSpanEventListener); spanWrappingTracer.addSpanInterceptor(new SpanContextInformation.SpanFinalizer()); return spanWrappingTracer; } @Override public void registerWidgetMetricTabPlugins(WidgetMetricTabPluginsRegistry widgetMetricTabPluginsRegistry) { widgetMetricTabPluginsRegistry.addWidgetMetricTabPlugin("/stagemonitor/static/tabs/metrics/request-metrics"); } public RequestMonitor getRequestMonitor() { if (requestMonitor == null) { requestMonitor = new RequestMonitor(Stagemonitor.getConfiguration(), Stagemonitor.getMetric2Registry()); } return requestMonitor; } public boolean isCollectCpuTime() { return collectCpuTime.getValue(); } public long getMinExecutionTimeNanos() { return minExecutionTimeNanos.getValue(); } public double getProfilerRateLimitPerMinute() { return profilerRateLimitPerMinute.getValue(); } public ConfigurationOption<Double> getProfilerRateLimitPerMinuteOption() { return profilerRateLimitPerMinute; } public boolean isLogSpans() { return logSpans.getValue(); } public boolean isCollectDbTimePerRequest() { return collectDbTimePerRequest.getValue(); } public boolean isProfilerActive() { return profilerActive.getValue(); } public BusinessTransactionNamingStrategy getBusinessTransactionNamingStrategy() { return businessTransactionNamingStrategy.getValue(); } @Override public void onShutDown() { if (reportingSpanEventListener != null) { reportingSpanEventListener.close(); } } public double getMinExecutionTimePercent() { return minExecutionTimePercent.getValue(); } public boolean isAnonymizeIPs() { return anonymizeIPs.getValue(); } public boolean isPseudonymizeUserNames() { return pseudonymizeUserName.getValue(); } public Collection<String> getDiscloseUsers() { return discloseUsers.getValue(); } public Collection<String> getOnlyReportSpansWithName() { return onlyReportSpansWithName.getValue(); } public double getRateLimitServerSpansPerMinute() { return rateLimitServerSpansPerMinute.getValue(); } public ConfigurationOption<Double> getRateLimitServerSpansPerMinuteOption() { return rateLimitServerSpansPerMinute; } public double getExcludeCallTreeFromReportWhenFasterThanXPercentOfRequests() { return excludeCallTreeFromReportWhenFasterThanXPercentOfRequests.getValue(); } public Collection<String> getUnnestExceptions() { return unnestExceptions.getValue(); } public boolean isProfilerObjectPoolingActive() { return profilerObjectPooling.getValue(); } public Collection<Pattern> getConfidentialParameters() { return confidentialParameters.getValue(); } public static Map<String, String> getSafeParameterMap(Map<String, String> parameterMap, Collection<Pattern> confidentialParams) { Map<String, String> params = new LinkedHashMap<String, String>(); for (Map.Entry<String, String> entry : parameterMap.entrySet()) { final boolean paramExcluded = isParamExcluded(entry.getKey(), confidentialParams); if (paramExcluded) { params.put(entry.getKey(), "XXXX"); } else { final String value = String.valueOf(entry.getValue()); params.put(entry.getKey(), value.substring(0, Math.min(255, value.length()))); } } return params; } private static boolean isParamExcluded(String queryParameter, Collection<Pattern> confidentialParams) { for (Pattern excludedParam : confidentialParams) { if (excludedParam.matcher(queryParameter).matches()) { return true; } } return false; } public Collection<String> getIgnoreExceptions() { return ignoreExceptions.getValue(); } public double getRateLimitClientSpansPerMinute() { return rateLimitClientSpansPerMinute.getValue(); } public ConfigurationOption<Double> getRateLimitClientSpansPerMinuteOption() { return rateLimitClientSpansPerMinute; } public Map<String, Double> getRateLimitClientSpansPerTypePerMinute() { return rateLimitClientSpansPerTypePerMinute.getValue(); } public ConfigurationOption<Map<String, Double>> getRateLimitClientSpansPerTypePerMinuteOption() { return rateLimitClientSpansPerTypePerMinute; } public double getExcludeExternalRequestsWhenFasterThanXPercent() { return excludeExternalRequestsWhenFasterThanXPercent.getValue(); } public double getExcludeExternalRequestsFasterThan() { return excludeExternalRequestsFasterThan.getValue(); } public boolean isReportAsync() { return reportSpansAsync.getValue(); } public void addSpanInterceptor(SpanEventListenerFactory spanEventListenerFactory) { spanWrappingTracer.addSpanInterceptor(spanEventListenerFactory); } /** * Add an {@link PreExecutionSpanInterceptor} to the interceptor list * * @param interceptor the interceptor that should be executed before measurement starts */ public void registerPreInterceptor(PreExecutionSpanInterceptor interceptor) { samplePriorityDeterminingSpanInterceptor.addPreInterceptor(interceptor); } /** * Add an {@link PostExecutionSpanInterceptor} to the interceptor list * * @param interceptor the interceptor that should be executed before each report */ public void registerPostInterceptor(PostExecutionSpanInterceptor interceptor) { samplePriorityDeterminingSpanInterceptor.addPostInterceptor(interceptor); } public boolean isMonitorScheduledTasks() { return monitorScheduledTasks.getValue(); } public void addReporter(SpanReporter spanReporter) { reportingSpanEventListener.addReporter(spanReporter); } }