| 
 | 1 | +/*  | 
 | 2 | + * Copyright The OpenTelemetry Authors  | 
 | 3 | + * SPDX-License-Identifier: Apache-2.0  | 
 | 4 | + */  | 
 | 5 | + | 
 | 6 | +package io.opentelemetry.contrib.stacktrace;  | 
 | 7 | + | 
 | 8 | +import com.google.auto.service.AutoService;  | 
 | 9 | +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;  | 
 | 10 | +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;  | 
 | 11 | +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;  | 
 | 12 | +import io.opentelemetry.sdk.trace.ReadableSpan;  | 
 | 13 | +import java.lang.reflect.Constructor;  | 
 | 14 | +import java.lang.reflect.InvocationTargetException;  | 
 | 15 | +import java.time.Duration;  | 
 | 16 | +import java.util.function.Predicate;  | 
 | 17 | +import java.util.logging.Level;  | 
 | 18 | +import java.util.logging.Logger;  | 
 | 19 | +import javax.annotation.Nullable;  | 
 | 20 | + | 
 | 21 | +@AutoService(AutoConfigurationCustomizerProvider.class)  | 
 | 22 | +public class StackTraceAutoConfig implements AutoConfigurationCustomizerProvider {  | 
 | 23 | + | 
 | 24 | +  private static final Logger log = Logger.getLogger(StackTraceAutoConfig.class.getName());  | 
 | 25 | + | 
 | 26 | +  private static final String CONFIG_MIN_DURATION =  | 
 | 27 | +      "otel.java.experimental.span-stacktrace.min.duration";  | 
 | 28 | +  private static final Duration CONFIG_MIN_DURATION_DEFAULT = Duration.ofMillis(5);  | 
 | 29 | + | 
 | 30 | +  private static final String CONFIG_FILTER = "otel.java.experimental.span-stacktrace.filter";  | 
 | 31 | + | 
 | 32 | +  @Override  | 
 | 33 | +  public void customize(AutoConfigurationCustomizer config) {  | 
 | 34 | +    config.addTracerProviderCustomizer(  | 
 | 35 | +        (providerBuilder, properties) -> {  | 
 | 36 | +          long minDuration = getMinDuration(properties);  | 
 | 37 | +          Predicate<ReadableSpan> filter = getFilterPredicate(properties);  | 
 | 38 | +          providerBuilder.addSpanProcessor(new StackTraceSpanProcessor(minDuration, filter));  | 
 | 39 | +          return providerBuilder;  | 
 | 40 | +        });  | 
 | 41 | +  }  | 
 | 42 | + | 
 | 43 | +  // package-private for testing  | 
 | 44 | +  static long getMinDuration(ConfigProperties properties) {  | 
 | 45 | +    long minDuration =  | 
 | 46 | +        properties.getDuration(CONFIG_MIN_DURATION, CONFIG_MIN_DURATION_DEFAULT).toNanos();  | 
 | 47 | +    if (minDuration < 0) {  | 
 | 48 | +      log.fine("Stack traces capture is disabled");  | 
 | 49 | +    } else {  | 
 | 50 | +      log.log(  | 
 | 51 | +          Level.FINE,  | 
 | 52 | +          "Stack traces will be added to spans with a minimum duration of {0} nanos",  | 
 | 53 | +          minDuration);  | 
 | 54 | +    }  | 
 | 55 | +    return minDuration;  | 
 | 56 | +  }  | 
 | 57 | + | 
 | 58 | +  // package private for testing  | 
 | 59 | +  static Predicate<ReadableSpan> getFilterPredicate(ConfigProperties properties) {  | 
 | 60 | +    String filterClass = properties.getString(CONFIG_FILTER);  | 
 | 61 | +    Predicate<ReadableSpan> filter = null;  | 
 | 62 | +    if (filterClass != null) {  | 
 | 63 | +      Class<?> filterType = getFilterType(filterClass);  | 
 | 64 | +      if (filterType != null) {  | 
 | 65 | +        filter = getFilterInstance(filterType);  | 
 | 66 | +      }  | 
 | 67 | +    }  | 
 | 68 | + | 
 | 69 | +    if (filter == null) {  | 
 | 70 | +      // if value is set, lack of filtering is likely an error and must be reported  | 
 | 71 | +      Level disabledLogLevel = filterClass != null ? Level.SEVERE : Level.FINE;  | 
 | 72 | +      log.log(disabledLogLevel, "Span stacktrace filtering disabled");  | 
 | 73 | +      return span -> true;  | 
 | 74 | +    } else {  | 
 | 75 | +      log.fine("Span stacktrace filtering enabled with: " + filterClass);  | 
 | 76 | +      return filter;  | 
 | 77 | +    }  | 
 | 78 | +  }  | 
 | 79 | + | 
 | 80 | +  @Nullable  | 
 | 81 | +  private static Class<?> getFilterType(String filterClass) {  | 
 | 82 | +    try {  | 
 | 83 | +      Class<?> filterType = Class.forName(filterClass);  | 
 | 84 | +      if (!Predicate.class.isAssignableFrom(filterType)) {  | 
 | 85 | +        log.severe("Filter must be a subclass of java.util.function.Predicate");  | 
 | 86 | +        return null;  | 
 | 87 | +      }  | 
 | 88 | +      return filterType;  | 
 | 89 | +    } catch (ClassNotFoundException e) {  | 
 | 90 | +      log.severe("Unable to load filter class: " + filterClass);  | 
 | 91 | +      return null;  | 
 | 92 | +    }  | 
 | 93 | +  }  | 
 | 94 | + | 
 | 95 | +  @Nullable  | 
 | 96 | +  @SuppressWarnings("unchecked")  | 
 | 97 | +  private static Predicate<ReadableSpan> getFilterInstance(Class<?> filterType) {  | 
 | 98 | +    try {  | 
 | 99 | +      Constructor<?> constructor = filterType.getConstructor();  | 
 | 100 | +      return (Predicate<ReadableSpan>) constructor.newInstance();  | 
 | 101 | +    } catch (NoSuchMethodException  | 
 | 102 | +        | InstantiationException  | 
 | 103 | +        | IllegalAccessException  | 
 | 104 | +        | InvocationTargetException e) {  | 
 | 105 | +      log.severe("Unable to create filter instance with no-arg constructor: " + filterType);  | 
 | 106 | +      return null;  | 
 | 107 | +    }  | 
 | 108 | +  }  | 
 | 109 | +}  | 
0 commit comments