package rocks.inspectit.agent.java; import java.io.File; import java.lang.instrument.Instrumentation; import java.util.Collection; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import rocks.inspectit.agent.java.analyzer.IByteCodeAnalyzer; import rocks.inspectit.agent.java.config.IConfigurationStorage; import rocks.inspectit.agent.java.config.StorageException; import rocks.inspectit.agent.java.hooking.IHookDispatcher; import rocks.inspectit.agent.java.instrumentation.IInstrumentationAware; import rocks.inspectit.agent.java.logback.LogInitializer; import rocks.inspectit.agent.java.sdk.opentracing.internal.TracerLoggerProvider; import rocks.inspectit.agent.java.sdk.opentracing.internal.impl.TracerLoggerWrapper; import rocks.inspectit.agent.java.spring.SpringConfiguration; import rocks.inspectit.agent.java.tracing.core.Slf4jTracerLoggerProvider; import rocks.inspectit.shared.all.instrumentation.config.impl.RetransformationStrategy; import rocks.inspectit.shared.all.pattern.IMatchPattern; import rocks.inspectit.shared.all.util.UnderlyingSystemInfo; import rocks.inspectit.shared.all.util.UnderlyingSystemInfo.JvmProvider; import rocks.inspectit.shared.all.version.VersionService; /** * The {@link SpringAgent} is used by the javaagent to analyze the passed bytecode if its needed to * be instrumented. The {@link #getInstance()} method returns the singleton instance of this class. * <p> * The {@link #inspectByteCode(byte[], String, ClassLoader)} is the method which should be called by * the javaagent. The method returns null if nothing has to be changed or something happened * unexpectedly. * <p> * This class is named <b>Spring</b>Agent as its using the Spring to handle the different components * in the Agent. * * @author Patrice Bouillet * */ public class SpringAgent implements IAgent { /** * The logger of this class. Initialized manually. */ private static final Logger LOG = LoggerFactory.getLogger(SpringAgent.class); /** * inspectIT jar file. */ private static File inspectitJarFile; /** * The hook dispatcher used by the instrumented methods. */ IHookDispatcher hookDispatcher; /** * The configuration storage. */ IConfigurationStorage configurationStorage; /** * The byte code analyzer. */ IByteCodeAnalyzer byteCodeAnalyzer; /** * The thread transform helper. */ IThreadTransformHelper threadTransformHelper; /** * Set to <code>true</code> if something happened and we need to disable further * instrumentation. */ volatile boolean disableInstrumentation = false; /** * Created bean factory. */ private BeanFactory beanFactory; /** * Ignore classes patterns. */ Collection<IMatchPattern> ignoreClassesPatterns; /** * The used {@link Instrumentation}. */ private final Instrumentation instrumentation; /** * Specifies whether retransformation is used. */ private Boolean usingRetransformation = null; /** * Constructor initializing this agent. * * @param inspectitJarFile * The inspectIT jar file needed for proper logging * @param instrumentation * The {@link Instrumentation} to use */ public SpringAgent(File inspectitJarFile, Instrumentation instrumentation) { this.instrumentation = instrumentation; setInspectITJarFile(inspectitJarFile); // init logging LogInitializer.initLogging(); // init spring this.initSpring(); } /** * Sets {@link #inspectitJarFile} in synch mode. * * @param inspectitJarFile * The inspectIT jar file. */ private synchronized void setInspectITJarFile(File inspectitJarFile) { SpringAgent.inspectitJarFile = inspectitJarFile; LOG.info("Location of inspectit-agent.jar set to: " + inspectitJarFile.getAbsolutePath()); } /** * Initializes the spring. */ private void initSpring() { if (LOG.isInfoEnabled()) { LOG.info("Initializing Spring on inspectIT Agent..."); } // first add shutdown hook so we are informed when shutdown is initialized to stop // instrumenting classes on the shutdown Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { disableInstrumentation = true; }; }); // IMPORTANT: set the logger provider before starting the spring // this was we ensure we use correct provider before any SDK classes are loaded TracerLoggerProvider provider = new Slf4jTracerLoggerProvider(); TracerLoggerWrapper.setProvider(provider); // set inspectIT class loader to be the context class loader // so that bean factory can use correct class loader for finding the classes ClassLoader inspectITClassLoader = this.getClass().getClassLoader(); ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(inspectITClassLoader); // load spring context in try block, catch exception and set init error to true try { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(SpringConfiguration.class); ctx.refresh(); beanFactory = ctx; if (beanFactory instanceof ConfigurableApplicationContext) { ((ConfigurableApplicationContext) beanFactory).registerShutdownHook(); } if (LOG.isInfoEnabled()) { LOG.info("Spring successfully initialized"); } // log version if (LOG.isInfoEnabled()) { VersionService versionService = beanFactory.getBean(VersionService.class); LOG.info("Using agent version " + versionService.getVersionAsString() + "."); } // load all necessary beans right away hookDispatcher = beanFactory.getBean(IHookDispatcher.class); configurationStorage = beanFactory.getBean(IConfigurationStorage.class); byteCodeAnalyzer = beanFactory.getBean(IByteCodeAnalyzer.class); threadTransformHelper = beanFactory.getBean(IThreadTransformHelper.class); // load ignore patterns only once ignoreClassesPatterns = configurationStorage.getIgnoreClassesPatterns(); // injecting instrumentation in instrumentation aware beans Map<String, IInstrumentationAware> instrumentationAwareBeans = ctx.getBeansOfType(IInstrumentationAware.class); for (IInstrumentationAware instrumentationAwareBean : instrumentationAwareBeans.values()) { instrumentationAwareBean.setInstrumentation(instrumentation); } } catch (Throwable throwable) { // NOPMD disableInstrumentation = true; LOG.error("inspectIT agent initialization failed. Agent will not be active.", throwable); } // switch back to the original context class loader Thread.currentThread().setContextClassLoader(contextClassLoader); } /** * {@inheritDoc} */ @Override public byte[] inspectByteCode(byte[] byteCode, String className, ClassLoader classLoader) { // if an error in the init method was caught, we'll do nothing here. // This prevents further errors. if (disableInstrumentation) { return byteCode; } boolean threadTransformDisabled = threadTransformHelper.isThreadTransformDisabled(); if (threadTransformDisabled) { // if transform is currently disabled for thread trying to transform the class do // nothing return byteCode; } // check if it should be ignored if (shouldClassBeIgnored(className)) { return byteCode; } try { // set transform disabled from this point for this thread threadTransformHelper.setThreadTransformDisabled(true); byte[] instrumentedByteCode = byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); return instrumentedByteCode; } catch (Throwable throwable) { // NOPMD LOG.error("Something unexpected happened while trying to analyze or instrument the bytecode with the class name: " + className, throwable); return byteCode; } finally { // reset the state of transform disabled if we set it originally threadTransformHelper.setThreadTransformDisabled(false); } } /** * {@inheritDoc} */ @Override public IHookDispatcher getHookDispatcher() { return hookDispatcher; } /** * Returns absolute file to the inspectit jar if the {@link #inspectitJarFile} is set, otherwise * returns <code>null</code>. * * @return Returns absolute file to the inspectit jar if the {@link #inspectitJarFile} is set, * otherwise returns <code>null</code>. */ public static File getInspectitJarFile() { if (null != inspectitJarFile) { return inspectitJarFile.getAbsoluteFile(); } return null; } /** * {@inheritDoc} */ @Override public boolean shouldClassBeIgnored(String className) { // if we are in disable instrumentation mode ignore all if (disableInstrumentation) { return true; } // ignore all classes which fit to the patterns in the configuration for (IMatchPattern matchPattern : ignoreClassesPatterns) { if (matchPattern.match(className)) { return true; } } return false; } /** * {@inheritDoc} */ @Override public boolean isUsingRetransformation() { if (disableInstrumentation) { return false; } if (usingRetransformation == null) { RetransformationStrategy retransformationStrategy; try { retransformationStrategy = configurationStorage.getRetransformStrategy(); } catch (StorageException e) { if (LOG.isWarnEnabled()) { LOG.warn("Class retransformation strategy could be read. Using the default retransformation stretegy: " + RetransformationStrategy.DISABLE_ON_IBM_JVM); } retransformationStrategy = RetransformationStrategy.DISABLE_ON_IBM_JVM; } usingRetransformation = (retransformationStrategy == RetransformationStrategy.ALWAYS) || ((retransformationStrategy == RetransformationStrategy.DISABLE_ON_IBM_JVM) && (UnderlyingSystemInfo.JVM_PROVIDER != JvmProvider.IBM)); } return usingRetransformation.booleanValue(); } /** * {@inheritDoc} */ @Override public boolean isInstrumentationDisabled() { return disableInstrumentation; } }