diff --git a/azure-application-insights-spring-boot-starter/README.md b/azure-application-insights-spring-boot-starter/README.md new file mode 100644 index 00000000000..72bd7a6b218 --- /dev/null +++ b/azure-application-insights-spring-boot-starter/README.md @@ -0,0 +1,201 @@ + +**Application Insights Spring Boot Starter** + +This Starter provides you the minimal and required configuration to use Application Insights in your Spring Boot application. + +**Requirements** +Spring Boot 1.5+ or 2.0+ + +**Quick Start** + +*1. Add dependency* +Gradle: +```groovy +compile "com.microsoft.azure:azure-application-insights-spring-boot-starter:${version}" +``` + +Maven: +```xml + + com.microsoft.azure + azure-application-insights-spring-boot-starter + ${version} + +``` + +*2. Provide Instrumentation Key* + +Add property +``` +azure.application-insights.instrumentation-key= +``` +into your `application.properties` + +*3. Run your application* + +Start your spring boot application as usual and in few minutes you'll start getting events. + +**Additional Configuration** + +#### Sending custom telemetry +```java +@RestController +public class TelemetryController { + + @Autowired + private TelemetryClient telemetryClient; + + @RequestMapping("/telemetry") + public void telemetry() { + telemetryClient.trackEvent("my event"); + } +} +``` + + +#### Sending logs to the application insight + +Follow the instructions from [Spring Boot logging documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-logging.html) to configure custom logback or log4j2 appender. + +`logback-spring.xml`: +```xml + + + + + +``` + +`log4j2.xml`: +```xml + + + + + + + + + + +``` + +#### Register own telemetry module, processor or initializer by defining it as a bean in the configuration +```java +@SpringBootApplication +public class MyApplication { + + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } + + @Bean + public TelemetryModule myTelemetryModule() { + return new MyTelemetryModule(); + } + + @Bean + public TelemetryInitializer myTelemetryInitializer() { + return new MyTelemetryInitializer(); + } + + @Bean + public TelemetryProcessor myTelemetryProcessor() { + return new MyTelemetryProcessor(); + } + + @Bean + public ContextInitializer myContextInitializer() { + return new MyContextInitializer(); + } +} +``` + + +#### Configure more parameters using `application.properties` +```properties +# Instrumentation key from the Azure Portal. Required. +azure.application-insights.instrumentation-key=00000000-0000-0000-0000-000000000000 + +# Enable/Disable tracking. Default value: true. +azure.application-insights.enabled=true + +# Enable/Disable web modules. Default value: true. +azure.application-insights.web.enabled=true + +# Logging type [console, file]. Default value: console. +azure.application-insights.logger.type=console +# Logging level [all, trace, info, warn, error, off]. Default value: error. +azure.application-insights.logger.level=error + +# Enable/Disable QuickPulse (Live Metrics). Default value: True +azure.application-insights.quick-pulse.enabled=true + +# Enable/Disable developer mode, all telemetry will be sent immediately without batching. Significantly affects performance and should be used only in developer environment. Default value: false. +azure.application-insights.channel.in-process.developer-mode=false +# Endpoint address, Default value: https://dc.services.visualstudio.com/v2/track. +azure.application-insights.channel.in-process.endpoint-address=https://dc.services.visualstudio.com/v2/track +# Maximum count of telemetries that will be batched before sending. Must be between 1 and 1000. Default value: 500. +azure.application-insights.channel.in-process.max-telemetry-buffer-capacity=500 +# Interval to send telemetry. Must be between 1 and 300. Default value: 5 seconds. +azure.application-insights.channel.in-process.flush-interval-in-seconds=5 +# Size of disk space that Application Insights can use to store telemetry in case of network outage. Must be between 1 and 1000. Default value: 10 megabytes. +azure.application-insights.channel.in-process.max-transmission-storage-files-capacity-in-mb=10 +# Enable/Disable throttling on sending telemetry data. Default value: true. +azure.application-insights.channel.in-process.throttling=true + +# Percent of telemetry events that will be sent to Application Insights. Percentage must be close to 100/N where N is an integer. +# E.g. 50 (=100/2), 33.33 (=100/3), 25 (=100/4), 20, 1 (=100/100), 0.1 (=100/1000). Default value: 100 (all telemetry events). +azure.application-insights.telemetry-processor.sampling.percentage=100 +# If set only telemetry of specified types will be included. Default value: all telemetries are included; +azure.application-insights.telemetry-processor.sampling.include= +# If set telemetry of specified type will be excluded. Default value: none telemetries are excluded. +azure.application-insights.telemetry-processor.sampling.exclude= + +# Enable/Disable default telemetry modules. Default value: true. +azure.application-insights.default-modules.ProcessPerformanceCountersModule.enabled=true +azure.application-insights.default-modules.JvmPerformanceCountersModule.enabled=true +azure.application-insights.default-modules.WebRequestTrackingTelemetryModule.enabled=true +azure.application-insights.default-modules.WebSessionTrackingTelemetryModule.enabled=true +azure.application-insights.default-modules.WebUserTrackingTelemetryModule.enabled=true +azure.application-insights.default-modules.WebPerformanceCounterModule.enabled=true +azure.application-insights.default-modules.WebOperationIdTelemetryInitializer.enabled=true +azure.application-insights.default-modules.WebOperationNameTelemetryInitializer.enabled=true +azure.application-insights.default-modules.WebSessionTelemetryInitializer.enabled=true +azure.application-insights.default-modules.WebUserTelemetryInitializer.enabled=true +azure.application-insights.default-modules.WebUserAgentTelemetryInitializer.enabled=true + +#Enable/Disable heartbeat module. Default value : true +azure.application-insights.heart-beat.enabled=true +#Default heartbeat interval is 15 minutes. Minimum heartbeat interval can be 30 seconds. +azure.application-insights.heart-beat.heart-beat-interval=900 +#If set of properties are specified they would be excluded from Heartbeat payload +azure.application-insights.heart-beat.excluded-heart-beat-properties-list= +#If set of HeartBeat providers are specified they would be excluded +azure.application-insights.heart-beat.excluded-heart-beat-provider-list= +``` + +### Completely disable Application Insights using `application.properties` +```properties +azure.application-insights.enabled=false +azure.application-insights.web.enabled=false +``` +Note: Do not configure `azure.application-insights.instrumentation-key` property for optimal performance +and avoiding any Application Insights beans creation by Spring. + + +## Migrating from XML based configuration ## +1. Please remove ApplicationInsights.xml file from the project resources or class path. +2. Add applicationinsights-spring-boot-starter-.jar file to pom.xml or build.gradle (you do not need to specify applicationinsights-core and web jars independently). + The starter takes are of it for you. +3. Please configure springboot Application.properties file with Application Insights Instrumentation key. +4. Compile the project and execute it from your IDE or command line using java -jar applicationjarname +5. To specify AI properties using command line please refer to SpringBoot Documentation. +6. To use [ApplicationInsigts Java agent](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-java-agent) please follow official documentation +4. To get an initialized instance of TelemetryClient please use Spring autowired annotation. This will provide a fully initialized instance of TelemetryClient. + +```Java +@Autowired +TelemetryClient client; +``` \ No newline at end of file diff --git a/azure-application-insights-spring-boot-starter/build.gradle b/azure-application-insights-spring-boot-starter/build.gradle new file mode 100644 index 00000000000..b0ed36614ed --- /dev/null +++ b/azure-application-insights-spring-boot-starter/build.gradle @@ -0,0 +1,67 @@ +import com.microsoft.applicationinsights.build.tasks.PropsFileGen + +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +apply from: "$buildScriptsDir/common-java.gradle" +apply from: "$buildScriptsDir/publishing.gradle" + +archivesBaseName = 'applicationinsights-spring-boot-starter' +version = project.properties['spring.boot.starter.version-number'] + +def starterVersionFileDir = "$project.buildDir/src/generated/main/resources" +task generateVersionProperties(type: PropsFileGen) { + targetFile = new File(starterVersionFileDir, "starter-version.properties") + property "version", project.version +} + +processResources.dependsOn generateVersionProperties + +sourceSets { + main { + resources { + srcDir starterVersionFileDir + } + } +} + +dependencies { + compile (project(':core')) + compile (project(':web')) + provided('org.springframework.boot:spring-boot:1.5.9.RELEASE') + provided('org.springframework.boot:spring-boot-autoconfigure:1.5.9.RELEASE') + provided('org.springframework.boot:spring-boot-starter-web:1.5.9.RELEASE') + provided('org.springframework.boot:spring-boot-configuration-processor:1.5.9.RELEASE') + testCompile('junit:junit:4.12') + testCompile('org.springframework.boot:spring-boot-starter-test:1.5.9.RELEASE') + testCompile('org.assertj:assertj-core:2.6.0') +} + +compileJava.dependsOn(processResources) +// region Publishing properties + +projectPomName = project.msftAppInsightsJavaSdk + " Spring Boot starter" +projectPomDescription = "This is the Spring Boot starter of " + project.msftAppInsightsJavaSdk + +whenPomConfigured = { p -> + def agentArtifactId = project(":agent").jar.baseName + p.dependencies = p.dependencies.findAll { dep -> dep.artifactId != agentArtifactId } +} diff --git a/azure-application-insights-spring-boot-starter/gradle.properties b/azure-application-insights-spring-boot-starter/gradle.properties new file mode 100644 index 00000000000..44fa15e3ccf --- /dev/null +++ b/azure-application-insights-spring-boot-starter/gradle.properties @@ -0,0 +1 @@ +spring.boot.starter.version-number=1.0.0-BETA \ No newline at end of file diff --git a/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsModuleConfiguration.java b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsModuleConfiguration.java new file mode 100644 index 00000000000..9a8d63c6882 --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsModuleConfiguration.java @@ -0,0 +1,187 @@ +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.microsoft.applicationinsights.boot; + +import com.microsoft.applicationinsights.boot.ApplicationInsightsProperties.HeartBeat; +import com.microsoft.applicationinsights.boot.ApplicationInsightsProperties.TelemetryProcessor.Sampling; +import com.microsoft.applicationinsights.boot.HeartBeatProvider.SpringBootHeartBeatProvider; +import com.microsoft.applicationinsights.boot.initializer.SpringBootTelemetryInitializer; +import com.microsoft.applicationinsights.extensibility.TelemetryProcessor; +import com.microsoft.applicationinsights.extensibility.initializer.DeviceInfoContextInitializer; +import com.microsoft.applicationinsights.extensibility.initializer.SdkVersionContextInitializer; +import com.microsoft.applicationinsights.internal.channel.samplingV2.FixedRateSamplingTelemetryProcessor; +import com.microsoft.applicationinsights.internal.heartbeat.HeartBeatModule; +import com.microsoft.applicationinsights.internal.heartbeat.HeartBeatPayloadProviderInterface; +import com.microsoft.applicationinsights.internal.heartbeat.HeartBeatProviderInterface; +import com.microsoft.applicationinsights.internal.heartbeat.HeartbeatDefaultPayload; +import com.microsoft.applicationinsights.internal.perfcounter.JvmPerformanceCountersModule; +import com.microsoft.applicationinsights.internal.perfcounter.ProcessPerformanceCountersModule; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.core.env.Environment; + +/** + *

Core Application Insights Configuration

+ *

+ * This class provides the Core Configuration for ApplicationInsights. This configuration is + * irrespective of WebApplications. {@link Configuration} for non-web applications. + *

+ * + * @author Arthur Gavlyukovskiy, Dhaval Doshi + */ +@Configuration +@EnableConfigurationProperties(ApplicationInsightsProperties.class) +@ConditionalOnProperty(value = "azure.application-insights.enabled", havingValue = "true", matchIfMissing = true) +public class ApplicationInsightsModuleConfiguration { + + /** + * Instance for the container of ApplicationInsights Properties + */ + private ApplicationInsightsProperties applicationInsightsProperties; + + @Autowired + public ApplicationInsightsModuleConfiguration(ApplicationInsightsProperties properties) { + this.applicationInsightsProperties = properties; + } + + /** + * Bean for SdkVersionContextInitializer + * @return instance of {@link SdkVersionContextInitializer} + */ + @Bean + public SdkVersionContextInitializer sdkVersionContextInitializer() { + return new SdkVersionContextInitializer(); + } + + /** + * Bean for DeviceInfoContextInitializer + * @return instance of {@link DeviceInfoContextInitializer} + */ + @Bean + public DeviceInfoContextInitializer deviceInfoContextInitializer() { + return new DeviceInfoContextInitializer(); + } + + /** + * Bean for SpringBootTelemetryInitializer + * @return instance of {@link SpringBootTelemetryInitializer} + */ + @Bean + public SpringBootTelemetryInitializer springBootTelemetryInitializer() { + return new SpringBootTelemetryInitializer(); + } + + /** + * Bean for ProcessPerformanceCounterModule + * @return instance of {@link ProcessPerformanceCountersModule} + */ + //FIXME: This should be conditional on operating System. However, current architecture of ProcessBuiltInPerformanceCountersFactory + //FIXME: does not separate this concerns therefore cannot condition as of now. + @Bean + @DependsOn("performanceCounterContainer") + @ConditionalOnProperty(value = "azure.application-insights.default-modules.ProcessPerformanceCountersModule.enabled", havingValue = "true", matchIfMissing = true) + public ProcessPerformanceCountersModule processPerformanceCountersModule() { + try { + return new ProcessPerformanceCountersModule(); + } + catch (Exception e) { + throw new IllegalStateException("Could not initialize Windows performance counters module, " + + "please set property 'azure.application-insights.default-modules.ProcessPerformanceCountersModule.enabled=false' to avoid this error message.", e); + } + } + + /** + * Bean for JvmPerformanceCounterModule + * @return instance of {@link JvmPerformanceCountersModule} + */ + @Bean + @DependsOn("performanceCounterContainer") + @ConditionalOnProperty(value = "azure.application-insights.default.modules.JvmPerformanceCountersModule.enabled", havingValue = "true", matchIfMissing = true) + public JvmPerformanceCountersModule jvmPerformanceCountersModule() { + try { + return new JvmPerformanceCountersModule(); + } + catch (Exception e) { + throw new IllegalStateException("Could not initialize Jvm Performance Counters module " + + "please set the property 'azure.application-insights.default.modules.JvmPerformanceCountersModule.enabled=false' to " + + "avoid this error message", e); + } + } + + @Bean + @ConditionalOnMissingBean + public HeartBeatPayloadProviderInterface heartBeatProviderInterface(Environment environment) { + return new SpringBootHeartBeatProvider(environment); + } + + /** + * Bean for HeartBeatModule. This also sets the properties for HeartBeatModule + * @param heartBeatPayloadProviderInterface + * @return initialized instance with user specified properties of {@link HeartBeatModule} + */ + @Bean + @ConditionalOnProperty(value = "azure.application-insights.heart-beat.enabled", havingValue = "true", matchIfMissing = true) + public HeartBeatModule heartBeatModule(HeartBeatPayloadProviderInterface heartBeatPayloadProviderInterface) { + try { + HeartBeatModule heartBeatModule = new HeartBeatModule(); + HeartbeatDefaultPayload.addDefaultPayLoadProvider(heartBeatPayloadProviderInterface); + HeartBeat heartBeat = applicationInsightsProperties.getHeartBeat(); + heartBeatModule.setHeartBeatInterval(heartBeat.getHeartBeatInterval()); + if (heartBeat.getExcludedHeartBeatPropertiesList().size() > 0) { + heartBeatModule.setExcludedHeartBeatProperties(heartBeat.getExcludedHeartBeatPropertiesList()); + } + if (heartBeat.getExcludedHeartBeatProviderList().size() > 0) { + heartBeatModule.setExcludedHeartBeatPropertiesProvider(heartBeat.getExcludedHeartBeatProviderList()); + } + return heartBeatModule; + } + catch (Exception e) { + throw new IllegalStateException("could not configure Heartbeat, please set 'azure.application-insights.heart-beat.enabled'" + + " to false ", e); + } + } + + /** + * Bean for FixedRateSamplingTelemetryProcessor. This bean helps in configuring the fixed rate sampling. + * @return instance of {@link FixedRateSamplingTelemetryProcessor} + */ + @Bean + @ConditionalOnProperty(value = "azure.application-insights.telemetry-processor.sampling.enabled", havingValue = "true") + public TelemetryProcessor fixedRateSamplingTelemetryProcessor() { + Sampling sampling = applicationInsightsProperties.getTelemetryProcessor().getSampling(); + FixedRateSamplingTelemetryProcessor processor = new FixedRateSamplingTelemetryProcessor(); + processor.setSamplingPercentage(String.valueOf(sampling.getPercentage())); + for (String include : sampling.getInclude()) { + processor.addToIncludedType(include); + } + for (String exclude : sampling.getExclude()) { + processor.addToExcludedType(exclude); + } + return processor; + } + +} diff --git a/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsProperties.java b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsProperties.java new file mode 100644 index 00000000000..2c8ce00f790 --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsProperties.java @@ -0,0 +1,598 @@ +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.microsoft.applicationinsights.boot; + +import com.microsoft.applicationinsights.channel.concrete.inprocess.InProcessTelemetryChannel; +import com.microsoft.applicationinsights.internal.channel.common.TransmissionFileSystemOutput; +import com.microsoft.applicationinsights.internal.channel.common.TransmissionNetworkOutput; +import com.microsoft.applicationinsights.internal.channel.samplingV2.FixedRateSamplingTelemetryProcessor; +import com.microsoft.applicationinsights.internal.heartbeat.HeartBeatProvider; +import com.microsoft.applicationinsights.internal.jmx.JmxAttributeData; +import com.microsoft.applicationinsights.internal.logger.InternalLogger; +import com.microsoft.applicationinsights.internal.logger.InternalLogger.LoggerOutputType; +import com.microsoft.applicationinsights.internal.logger.InternalLogger.LoggingLevel; +import com.microsoft.applicationinsights.internal.perfcounter.JmxMetricPerformanceCounter; +import com.microsoft.applicationinsights.internal.perfcounter.PerformanceCounterContainer; +import com.microsoft.applicationinsights.web.extensibility.initializers.WebOperationIdTelemetryInitializer; +import com.microsoft.applicationinsights.web.extensibility.initializers.WebOperationNameTelemetryInitializer; +import com.microsoft.applicationinsights.web.extensibility.initializers.WebSessionTelemetryInitializer; +import com.microsoft.applicationinsights.web.extensibility.initializers.WebUserAgentTelemetryInitializer; +import com.microsoft.applicationinsights.web.extensibility.initializers.WebUserTelemetryInitializer; +import com.microsoft.applicationinsights.web.extensibility.modules.WebRequestTrackingTelemetryModule; +import com.microsoft.applicationinsights.web.extensibility.modules.WebSessionTrackingTelemetryModule; +import com.microsoft.applicationinsights.web.extensibility.modules.WebUserTrackingTelemetryModule; +import com.microsoft.applicationinsights.web.internal.perfcounter.WebPerformanceCounterModule; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

This class is the container for Configuration Properties of Application Insights

+ * + * {@link ConfigurationProperties} for configuring application insights. + * + * @author Arthur Gavlyukovskiy, Dhaval Doshi + */ +@ConfigurationProperties("azure.application-insights") +public class ApplicationInsightsProperties { + + /** Enables application insights auto-configuration. */ + private boolean enabled = true; + /** Instrumentation key from Azure Portal. */ + private String instrumentationKey; + /** Telemetry transmission channel configuration. */ + private Channel channel = new Channel(); + /** Built in telemetry processors configuration. */ + private TelemetryProcessor telemetryProcessor = new TelemetryProcessor(); + /** Web plugins settings. */ + private Web web = new Web(); + /** Quick Pulse settings. */ + private QuickPulse quickPulse = new QuickPulse(); + /** Logger properties. */ + private Logger logger = new Logger(); + + /** Performance Counter Container Properties */ + private PerformanceCounter performanceCounter = new PerformanceCounter(); + + /** Jmx Counter container */ + private Jmx jmx = new Jmx(); + + /** Heartbeat Properties container */ + private HeartBeat heartBeat = new HeartBeat(); + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getInstrumentationKey() { + return instrumentationKey; + } + + public void setInstrumentationKey(String instrumentationKey) { + this.instrumentationKey = instrumentationKey; + } + + public Channel getChannel() { + return channel; + } + + public void setChannel(Channel channel) { + this.channel = channel; + } + + public TelemetryProcessor getTelemetryProcessor() { + return telemetryProcessor; + } + + public void setTelemetryProcessor(TelemetryProcessor telemetryProcessor) { + this.telemetryProcessor = telemetryProcessor; + } + + public Web getWeb() { + return web; + } + + public void setWeb(Web web) { + this.web = web; + } + + public QuickPulse getQuickPulse() { + return quickPulse; + } + + public void setQuickPulse(QuickPulse quickPulse) { + this.quickPulse = quickPulse; + } + + public Logger getLogger() { + return logger; + } + + public void setLogger(Logger logger) { + this.logger = logger; + } + + public PerformanceCounter getPerformanceCounter() { + return performanceCounter; + } + + public void setPerformanceCounter(PerformanceCounter performanceCounter) { + this.performanceCounter = performanceCounter; + } + + public Jmx getJmx() { + return jmx; + } + + public void setJmx(Jmx jmx) { + this.jmx = jmx; + } + + public HeartBeat getHeartBeat() { + return heartBeat; + } + + public void setHeartBeat(HeartBeat heartBeat) { + this.heartBeat = heartBeat; + } + + static class Channel { + /** Configuration of {@link InProcessTelemetryChannel}. */ + private InProcess inProcess = new InProcess(); + + public InProcess getInProcess() { + return inProcess; + } + + public void setInProcess(InProcess inProcess) { + this.inProcess = inProcess; + } + + static class InProcess { + /** + * Enables developer mode, all telemetry will be sent immediately without batching. + * Significantly affects performance and should be used only in developer environment. + */ + private boolean developerMode = false; + /** Endpoint address. */ + private String endpointAddress = TransmissionNetworkOutput.DEFAULT_SERVER_URI; + /** + * Maximum count of telemetries that will be batched before sending. Must be between 1 and + * 1000. + */ + private int maxTelemetryBufferCapacity = + InProcessTelemetryChannel.DEFAULT_MAX_TELEMETRY_BUFFER_CAPACITY; + /** Interval to send telemetry. Must be between 1 and 300. */ + private int flushIntervalInSeconds = + InProcessTelemetryChannel.DEFAULT_FLUSH_BUFFER_TIMEOUT_IN_SECONDS; + /** + * Size of disk space that Application Insights can use to store telemetry in case of network + * outage. Must be between 1 and 1000. + */ + private int maxTransmissionStorageFilesCapacityInMb = + TransmissionFileSystemOutput.DEFAULT_CAPACITY_MEGABYTES; + /** Enables throttling on sending telemetry data. */ + private boolean throttling = true; + + /** Sets the size of maximum instant retries without delay */ + private int maxInstantRetry = InProcessTelemetryChannel.DEFAULT_MAX_INSTANT_RETRY; + + public boolean isDeveloperMode() { + return developerMode; + } + + public void setDeveloperMode(boolean developerMode) { + this.developerMode = developerMode; + } + + public int getMaxInstantRetry() { + return maxInstantRetry; + } + + public void setMaxInstantRetry(int maxInstantRetry) { + this.maxInstantRetry = maxInstantRetry; + } + + public String getEndpointAddress() { + return endpointAddress; + } + + public void setEndpointAddress(String endpointAddress) { + this.endpointAddress = endpointAddress; + } + + public int getMaxTelemetryBufferCapacity() { + return maxTelemetryBufferCapacity; + } + + public void setMaxTelemetryBufferCapacity(int maxTelemetryBufferCapacity) { + this.maxTelemetryBufferCapacity = maxTelemetryBufferCapacity; + } + + public int getFlushIntervalInSeconds() { + return flushIntervalInSeconds; + } + + public void setFlushIntervalInSeconds(int flushIntervalInSeconds) { + this.flushIntervalInSeconds = flushIntervalInSeconds; + } + + public int getMaxTransmissionStorageFilesCapacityInMb() { + return maxTransmissionStorageFilesCapacityInMb; + } + + public void setMaxTransmissionStorageFilesCapacityInMb( + int maxTransmissionStorageFilesCapacityInMb) { + this.maxTransmissionStorageFilesCapacityInMb = maxTransmissionStorageFilesCapacityInMb; + } + + public boolean isThrottling() { + return throttling; + } + + public void setThrottling(boolean throttling) { + this.throttling = throttling; + } + } + } + + static class TelemetryProcessor { + + /** Configuration of {@link FixedRateSamplingTelemetryProcessor}. */ + private Sampling sampling = new Sampling(); + + public Sampling getSampling() { + return sampling; + } + + public void setSampling(Sampling sampling) { + this.sampling = sampling; + } + + static class Sampling { + + private boolean enabled = false; + + /** + * Percent of telemetry events that will be sent to Application Insights. Percentage must be + * close to 100/N where N is an integer. E.g. 50 (=100/2), 33.33 (=100/3), 25 (=100/4), 20, 1 + * (=100/100), 0.1 (=100/1000). + */ + private double percentage = FixedRateSamplingTelemetryProcessor.DEFAULT_SAMPLING_PERCENTAGE; + /** If set only telemetry of specified types will be included. */ + private List include = new ArrayList<>(); + /** If set telemetry of specified type will be excluded. */ + private List exclude = new ArrayList<>(); + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public double getPercentage() { + return percentage; + } + + public void setPercentage(double percentage) { + this.percentage = percentage; + } + + public List getInclude() { + return include; + } + + public void setInclude(List include) { + this.include = include; + } + + public List getExclude() { + return exclude; + } + + public void setExclude(List exclude) { + this.exclude = exclude; + } + } + } + + static class Web { + /** + * Enables Web telemetry modules. + * + *

Implicitly affects modules: - {@link WebRequestTrackingTelemetryModule} - {@link + * WebSessionTrackingTelemetryModule} - {@link WebUserTrackingTelemetryModule} - {@link + * WebPerformanceCounterModule} - {@link WebOperationIdTelemetryInitializer} - {@link + * WebOperationNameTelemetryInitializer} - {@link WebSessionTelemetryInitializer} - {@link + * WebUserTelemetryInitializer} - {@link WebUserAgentTelemetryInitializer} + * + *

False means that all those modules will be disabled regardless of the enabled property of + * concrete module. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class QuickPulse { + /** Enables Quick Pulse integration. */ + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + static class Logger { + /** Type of application insights logger. */ + private LoggerOutputType type = LoggerOutputType.CONSOLE; + /** Minimal level of application insights logger. */ + private LoggingLevel level = LoggingLevel.OFF; + + public LoggerOutputType getType() { + return type; + } + + public void setType(LoggerOutputType type) { + this.type = type; + } + + public LoggingLevel getLevel() { + return level; + } + + public void setLevel(LoggingLevel level) { + this.level = level; + } + } + + static class PerformanceCounter { + + /** Default collection frequency of performance counters */ + private long collectionFrequencyInSeconds = + PerformanceCounterContainer.DEFAULT_COLLECTION_FREQUENCY_IN_SEC; + + public long getCollectionFrequencyInSeconds() { + return collectionFrequencyInSeconds; + } + + public void setCollectionFrequencyInSeconds(long collectionFrequencyInSeconds) { + this.collectionFrequencyInSeconds = collectionFrequencyInSeconds; + } + } + + static class Jmx { + + /** List of JMX counters */ + List jmxCounters = new ArrayList<>(); + + public List getJmxCounters() { + return jmxCounters; + } + + public void setJmxCounters(List jmxCounters) { + this.jmxCounters = jmxCounters; + } + } + + static class HeartBeat { + + /** + * Switch to enable / disable heartbeat + */ + boolean enabled = false; + + /** + * The heartbeat interval in seconds. + */ + long heartBeatInterval = HeartBeatProvider.DEFAULT_HEARTBEAT_INTERVAL; + + /** + * List of excluded heartbeat properties + */ + List excludedHeartBeatProviderList = new ArrayList<>(); + + /** + * List of excluded heartbeat providers + */ + List excludedHeartBeatPropertiesList = new ArrayList<>(); + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public long getHeartBeatInterval() { + return heartBeatInterval; + } + + public void setHeartBeatInterval(long heartBeatInterval) { + this.heartBeatInterval = heartBeatInterval; + } + + public List getExcludedHeartBeatProviderList() { + return excludedHeartBeatProviderList; + } + + public void setExcludedHeartBeatProviderList(List excludedHeartBeatProviderList) { + this.excludedHeartBeatProviderList = excludedHeartBeatProviderList; + } + + public List getExcludedHeartBeatPropertiesList() { + return excludedHeartBeatPropertiesList; + } + + public void setExcludedHeartBeatPropertiesList(List excludedHeartBeatPropertiesList) { + this.excludedHeartBeatPropertiesList = excludedHeartBeatPropertiesList; + } + } + + /** + * This method is used to process and load list of JmxCounters provided in the configuration. + * @param jmxCounterList + */ + void processAndLoadJmxCounters(List jmxCounterList) { + + try { + Map> data = new HashMap<>(); + for (String jmxCounter : jmxCounterList) { + CompositeJmxData compositeJmxData = convertToCompositeJmxData(jmxCounter); + if (compositeJmxData == null) { + InternalLogger.INSTANCE.warn("unable to add Jmx counter %s", jmxCounter); + } else { + List collection = data.get(compositeJmxData.getObjectName()); + if (collection == null) { + collection = new ArrayList<>(); + data.put(compositeJmxData.getObjectName(), collection); + } + collection.add(new JmxAttributeData(compositeJmxData.getDisplayName(), + compositeJmxData.getAttributeName(), compositeJmxData.getType())); + } + } + + //Register each entry in performance counter container + for (Map.Entry> entry : data.entrySet()) { + try { + if (PerformanceCounterContainer.INSTANCE.register(new JmxMetricPerformanceCounter( + entry.getKey(), entry.getKey(), entry.getValue() + ))) { + InternalLogger.INSTANCE.trace("Registered Jmx performance counter %s", + entry.getKey()); + } + else { + InternalLogger.INSTANCE.trace("Failed to register Jmx performance" + + " counter %s", entry.getKey()); + } + } + catch (Exception e) { + InternalLogger.INSTANCE.warn("Failed to register Jmx performance counter," + + " of object name %s Stack trace is %s", entry.getKey(), ExceptionUtils.getStackTrace(e)); + } + } + } + catch (Exception e) { + InternalLogger.INSTANCE.warn("Unable to add Jmx performance counter. Exception is" + + " %s", ExceptionUtils.getStackTrace(e)); + } + } + + /** + * This Internal class is used to represent the Jmx Object Structure + */ + private class CompositeJmxData { + String displayName; + String objectName; + String attributeName; + String type; + + String getDisplayName() { + return displayName; + } + + void setDisplayName(String displayName) { + this.displayName = displayName; + } + + String getObjectName() { + return objectName; + } + + void setObjectName(String objectName) { + this.objectName = objectName; + } + + String getAttributeName() { + return attributeName; + } + + void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + + String getType() { + return type; + } + + void setType(String type) { + this.type = type; + if (this.type != null) { + this.type = this.type.toUpperCase(); + } + } + } + + /** + * This converts jmxCounter String to {@link CompositeJmxData} object + * @param jmxCounter + * @return CompositeJmxData object + */ + private CompositeJmxData convertToCompositeJmxData(String jmxCounter) { + if (jmxCounter != null && jmxCounter.length() > 0) { + String[] attributes = jmxCounter.split("/"); + if (attributes.length < 3) { + InternalLogger.INSTANCE.warn("Missing either objectName or attributeName or" + + " display name. Jmx counter %s will not be added" , jmxCounter); + return null; + } + CompositeJmxData data = new CompositeJmxData(); + for (int i = 0; i < attributes.length; ++i) { + if (i > 3) break; + if (i == 0) { + data.setObjectName(attributes[0]); + } + else if (i == 1) { + data.setAttributeName(attributes[1]); + } + else if (i == 2) { + data.setDisplayName(attributes[2]); + } + else { + data.setType(attributes[3]); + } + } + return data; + } + return null; + } +} diff --git a/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsTelemetryAutoConfiguration.java b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsTelemetryAutoConfiguration.java new file mode 100644 index 00000000000..800e4caaa25 --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsTelemetryAutoConfiguration.java @@ -0,0 +1,194 @@ +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.microsoft.applicationinsights.boot; + +import static org.slf4j.LoggerFactory.getLogger; + +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.applicationinsights.TelemetryConfiguration; +import com.microsoft.applicationinsights.boot.ApplicationInsightsProperties.Channel.InProcess; +import com.microsoft.applicationinsights.boot.ApplicationInsightsProperties.TelemetryProcessor.Sampling; +import com.microsoft.applicationinsights.channel.TelemetryChannel; +import com.microsoft.applicationinsights.channel.concrete.inprocess.InProcessTelemetryChannel; +import com.microsoft.applicationinsights.extensibility.ContextInitializer; +import com.microsoft.applicationinsights.extensibility.TelemetryInitializer; +import com.microsoft.applicationinsights.extensibility.TelemetryModule; +import com.microsoft.applicationinsights.extensibility.TelemetryProcessor; +import com.microsoft.applicationinsights.internal.channel.samplingV2.FixedRateSamplingTelemetryProcessor; +import com.microsoft.applicationinsights.internal.logger.InternalLogger; +import com.microsoft.applicationinsights.internal.perfcounter.PerformanceCounterContainer; +import com.microsoft.applicationinsights.internal.quickpulse.QuickPulse; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Import; + +/** + *

The central class for configuring and creating initialized {@link TelemetryConfiguration}

+ * + * @author Arthur Gavlyukovskiy, Dhaval Doshi + */ +@Configuration +@ConditionalOnProperty(value = "azure.application-insights.instrumentation-key") +@EnableConfigurationProperties(ApplicationInsightsProperties.class) +@ConditionalOnClass(TelemetryConfiguration.class) +@Import({ + ApplicationInsightsModuleConfiguration.class, + ApplicationInsightsWebModuleConfiguration.class +}) +public class ApplicationInsightsTelemetryAutoConfiguration { + + private static final Logger log = getLogger(ApplicationInsightsTelemetryAutoConfiguration.class); + + private ApplicationInsightsProperties applicationInsightsProperties; + + private Collection contextInitializers; + + private Collection telemetryInitializers; + + private Collection telemetryModules; + + private Collection telemetryProcessors; + + @Autowired + public ApplicationInsightsTelemetryAutoConfiguration( + ApplicationInsightsProperties applicationInsightsProperties) { + this.applicationInsightsProperties = applicationInsightsProperties; + } + + @Autowired(required = false) + public void setContextInitializers( + Collection contextInitializers) { + this.contextInitializers = contextInitializers; + } + + @Autowired(required = false) + public void setTelemetryInitializers( + Collection telemetryInitializers) { + this.telemetryInitializers = telemetryInitializers; + } + + @Autowired(required = false) + public void setTelemetryModules( + Collection telemetryModules) { + this.telemetryModules = telemetryModules; + } + + @Autowired(required = false) + public void setTelemetryProcessors( + Collection telemetryProcessors) { + this.telemetryProcessors = telemetryProcessors; + } + + @Bean + @DependsOn("internalLogger") + public TelemetryConfiguration telemetryConfiguration(TelemetryChannel telemetryChannel) { + TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.getActiveWithoutInitializingConfig(); + telemetryConfiguration.setTrackingIsDisabled(!applicationInsightsProperties.isEnabled()); + telemetryConfiguration.setInstrumentationKey(applicationInsightsProperties.getInstrumentationKey()); + if (contextInitializers != null) { + telemetryConfiguration.getContextInitializers().addAll(contextInitializers); + } + if (telemetryInitializers != null) { + telemetryConfiguration.getTelemetryInitializers().addAll(telemetryInitializers); + } + if (telemetryModules != null) { + telemetryConfiguration.getTelemetryModules().addAll(telemetryModules); + } + if (telemetryProcessors != null) { + telemetryConfiguration.getTelemetryProcessors().addAll(telemetryProcessors); + } + telemetryConfiguration.setChannel(telemetryChannel); + initializeComponents(telemetryConfiguration); + return telemetryConfiguration; + } + + // TODO: copy-paste from TelemetryConfigurationFactory, move to TelemetryConfiguration? + private void initializeComponents(TelemetryConfiguration configuration) { + List telemetryModules = configuration.getTelemetryModules(); + + for (TelemetryModule module : telemetryModules) { + try { + module.initialize(configuration); + } + catch (Exception e) { + log.error("Failed to initialized telemetry module " + module.getClass().getSimpleName(), e); + } + } + } + + @Bean + public TelemetryClient telemetryClient(TelemetryConfiguration configuration) { + return new TelemetryClient(configuration); + } + + + + @Bean + @ConditionalOnMissingBean + public TelemetryChannel telemetryChannel() { + InProcess inProcess = applicationInsightsProperties.getChannel().getInProcess(); + return new InProcessTelemetryChannel(inProcess.getEndpointAddress(), + String.valueOf(inProcess.getMaxTransmissionStorageFilesCapacityInMb()), inProcess.isDeveloperMode(), + inProcess.getMaxTelemetryBufferCapacity(), inProcess.getFlushIntervalInSeconds(), inProcess.isThrottling(), + inProcess.getMaxInstantRetry()); + } + + @Bean + @ConditionalOnProperty(value = "azure.application-insights.quick-pulse.enabled", havingValue = "true", matchIfMissing = true) + @DependsOn("telemetryConfiguration") + public QuickPulse quickPulse() { + QuickPulse.INSTANCE.initialize(); + return QuickPulse.INSTANCE; + } + + @Bean + public InternalLogger internalLogger() { + Map loggerParameters = new HashMap<>(); + ApplicationInsightsProperties.Logger logger = applicationInsightsProperties.getLogger(); + loggerParameters.put("Level", logger.getLevel().name()); + InternalLogger.INSTANCE.initialize(logger.getType().name(), loggerParameters); + return InternalLogger.INSTANCE; + } + + @Bean + public PerformanceCounterContainer performanceCounterContainer() { + ApplicationInsightsProperties.PerformanceCounter performanceCounter = applicationInsightsProperties.getPerformanceCounter(); + PerformanceCounterContainer.INSTANCE.setCollectionFrequencyInSec(performanceCounter.getCollectionFrequencyInSeconds()); + + ApplicationInsightsProperties.Jmx jmx = applicationInsightsProperties.getJmx(); + if (jmx.getJmxCounters() !=null && jmx.getJmxCounters().size() > 0) { + applicationInsightsProperties.processAndLoadJmxCounters(jmx.getJmxCounters()); + } + return PerformanceCounterContainer.INSTANCE; + } +} diff --git a/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsWebModuleConfiguration.java b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsWebModuleConfiguration.java new file mode 100644 index 00000000000..42e77421152 --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsWebModuleConfiguration.java @@ -0,0 +1,142 @@ +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.microsoft.applicationinsights.boot; + +import com.microsoft.applicationinsights.web.extensibility.initializers.WebOperationIdTelemetryInitializer; +import com.microsoft.applicationinsights.web.extensibility.initializers.WebOperationNameTelemetryInitializer; +import com.microsoft.applicationinsights.web.extensibility.initializers.WebSessionTelemetryInitializer; +import com.microsoft.applicationinsights.web.extensibility.initializers.WebUserAgentTelemetryInitializer; +import com.microsoft.applicationinsights.web.extensibility.initializers.WebUserTelemetryInitializer; +import com.microsoft.applicationinsights.web.extensibility.modules.WebRequestTrackingTelemetryModule; +import com.microsoft.applicationinsights.web.extensibility.modules.WebSessionTrackingTelemetryModule; +import com.microsoft.applicationinsights.web.extensibility.modules.WebUserTrackingTelemetryModule; +import com.microsoft.applicationinsights.web.internal.perfcounter.WebPerformanceCounterModule; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; + +/** + *

Web Configuration for Application Insights

+ * + *

+ * This class provides the configuration for applications of type web. The modules in this class + * will only be configured if the Spring Framework identifies them as web Application. + *

+ * + * {@link Configuration} for web applications. + * + * @author Arthur Gavlyukovskiy + */ +@Configuration +@ConditionalOnProperty(value = "azure.application-insights.web.enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnWebApplication +public class ApplicationInsightsWebModuleConfiguration { + + /** + * Bean for WebRequestTrackingTelemetryModule + * @return instance of {@link WebRequestTrackingTelemetryModule} + */ + @Bean + @ConditionalOnProperty(value = "azure.application-insights.default-modules.WebRequestTrackingTelemetryModule.enabled", havingValue = "true", matchIfMissing = true) + public WebRequestTrackingTelemetryModule webRequestTrackingTelemetryModule() { + return new WebRequestTrackingTelemetryModule(); + } + + /** + * Bean for WebSessionTrackingTelemetryModule + * @return instance of {@link WebSessionTrackingTelemetryModule} + */ + @Bean + @ConditionalOnProperty(value = "azure.application-insights.default-modules.WebSessionTrackingTelemetryModule.enabled", havingValue = "true", matchIfMissing = true) + public WebSessionTrackingTelemetryModule webSessionTrackingTelemetryModule() { + return new WebSessionTrackingTelemetryModule(); + } + + /** + * Bean for WebUserTrackingTelemetryModule + * @return instance of {@link WebUserTrackingTelemetryModule} + */ + @Bean + @ConditionalOnProperty(value = "azure.application-insights.default-modules.WebUserTrackingTelemetryModule.enabled", havingValue = "true", matchIfMissing = true) + public WebUserTrackingTelemetryModule webUserTrackingTelemetryModule() { + return new WebUserTrackingTelemetryModule(); + } + + /** + * Bean for WebPerformanceCounterModule + * @return instance of {@link WebPerformanceCounterModule} + */ + @Bean + @DependsOn("performanceCounterContainer") + @ConditionalOnProperty(value = "azure.application-insights.default-modules.WebPerformanceCounterModule.enabled", havingValue = "true", matchIfMissing = true) + public WebPerformanceCounterModule webPerformanceCounterModule() { + return new WebPerformanceCounterModule(); + } + + /** + * Bean for WebOperationIdTelemetryInitializer + * @return instance of {@link WebOperationIdTelemetryInitializer} + */ + @Bean + @ConditionalOnProperty(value = "azure.application-insights.default-modules.WebOperationIdTelemetryInitializer.enabled", havingValue = "true", matchIfMissing = true) + public WebOperationIdTelemetryInitializer webOperationIdTelemetryInitializer() { + return new WebOperationIdTelemetryInitializer(); + } + + @Bean + @ConditionalOnProperty(value = "azure.application-insights.default-modules.WebOperationNameTelemetryInitializer.enabled", havingValue = "true", matchIfMissing = true) + public WebOperationNameTelemetryInitializer webOperationNameTelemetryInitializer() { + return new WebOperationNameTelemetryInitializer(); + } + + /** + * Bean for WebSessionTelemetryInitializer + * @return instance of {@link WebSessionTelemetryInitializer} + */ + @Bean + @ConditionalOnProperty(value = "azure.application-insights.default-modules.WebSessionTelemetryInitializer.enabled", havingValue = "true", matchIfMissing = true) + public WebSessionTelemetryInitializer webSessionTelemetryInitializer() { + return new WebSessionTelemetryInitializer(); + } + + /** + * Bean for WebUserTelemetryInitializer + * @return instance of {@link WebUserTelemetryInitializer} + */ + @Bean + @ConditionalOnProperty(value = "azure.application-insights.default-modules.WebUserTelemetryInitializer.enabled", havingValue = "true", matchIfMissing = true) + public WebUserTelemetryInitializer webUserTelemetryInitializer() { + return new WebUserTelemetryInitializer(); + } + + /** + * Bean for WebUserAgentTelemetryInitializer + * @return instance of {@link WebUserAgentTelemetryInitializer} + */ + @Bean + @ConditionalOnProperty(value = "azure.application-insights.default-modules.WebUserAgentTelemetryInitializer.enabled", havingValue = "true", matchIfMissing = true) + public WebUserAgentTelemetryInitializer webUserAgentTelemetryInitializer() { + return new WebUserAgentTelemetryInitializer(); + } +} diff --git a/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsWebMvcAutoConfiguration.java b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsWebMvcAutoConfiguration.java new file mode 100644 index 00000000000..81ecb292fca --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/ApplicationInsightsWebMvcAutoConfiguration.java @@ -0,0 +1,75 @@ +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.microsoft.applicationinsights.boot; + +import com.microsoft.applicationinsights.TelemetryConfiguration; +import com.microsoft.applicationinsights.web.internal.WebRequestTrackingFilter; +import com.microsoft.applicationinsights.web.spring.internal.InterceptorRegistry; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; + +/** + *

Configuration for Auto-collection of HTTP requests.

+ * + *

+ * This class is responsible for configuring {@link WebRequestTrackingFilter} for auto collection + * of incoming HTTP requests + *

+ * + * @author Arthur Gavlyukovskiy + */ + +@Configuration +@Import(InterceptorRegistry.class) +@ConditionalOnBean(TelemetryConfiguration.class) +@ConditionalOnWebApplication +@ConditionalOnProperty(value = "azure.application-insights.web.enabled", havingValue = "true", matchIfMissing = true) +@AutoConfigureAfter(ApplicationInsightsTelemetryAutoConfiguration.class) +public class ApplicationInsightsWebMvcAutoConfiguration { + + @Bean + public FilterRegistrationBean webRequestTrackingFilterRegistrationBean(WebRequestTrackingFilter webRequestTrackingFilter) { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(webRequestTrackingFilter); + registration.addUrlPatterns("/*"); + registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 10); + return registration; + } + + @Bean + @ConditionalOnMissingBean + @DependsOn("telemetryConfiguration") + public WebRequestTrackingFilter webRequestTrackingFilter(@Value("${spring.application.name:application}") String applicationName) { + return new WebRequestTrackingFilter(applicationName); + } +} + diff --git a/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/HeartBeatProvider/SpringBootHeartBeatProvider.java b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/HeartBeatProvider/SpringBootHeartBeatProvider.java new file mode 100644 index 00000000000..feeb194440a --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/HeartBeatProvider/SpringBootHeartBeatProvider.java @@ -0,0 +1,142 @@ +package com.microsoft.applicationinsights.boot.HeartBeatProvider; + +import com.microsoft.applicationinsights.internal.heartbeat.HeartBeatPayloadProviderInterface; +import com.microsoft.applicationinsights.internal.heartbeat.HeartBeatProviderInterface; +import com.microsoft.applicationinsights.internal.heartbeat.MiscUtils; +import com.microsoft.applicationinsights.internal.logger.InternalLogger; +import com.microsoft.applicationinsights.internal.util.PropertyHelper; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.Callable; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.boot.SpringBootVersion; +import org.springframework.core.SpringVersion; +import org.springframework.core.env.Environment; + +/** + *

SpringBoot Heartbeat Property Provider

+ *

+ * This class is a concrete implementation of {@link HeartBeatPayloadProviderInterface} + * It enables setting SpringBoot Metadata to heartbeat payload. + *

+ * + * @author Dhaval Doshi + */ +public class SpringBootHeartBeatProvider implements HeartBeatPayloadProviderInterface { + + /** + * Collection holding default properties for this default provider. + */ + private final Set defaultFields; + + /** + * Name of this provider. + */ + private final String name = "SpringBootProvider"; + + private final Environment environment; + + private final String SPRING_BOOT_VERSION = "ai.spring-boot.version"; + + private final String SPRING_VERSION = "ai.spring.version"; + + private final String SPRING_BOOT_STARTER_VERSION = "ai.spring.boot.starter.version"; + + + + public SpringBootHeartBeatProvider(Environment environment) { + defaultFields = new HashSet<>(); + this.environment = environment; + initializeDefaultFields(defaultFields); + } + + @Override + + public String getName() { + return this.name; + } + + @Override + public boolean isKeyword(String keyword) { + return defaultFields.contains(keyword); + } + + @Override + public Callable setDefaultPayload(final List disableFields, + final HeartBeatProviderInterface provider) { + return new Callable() { + + Set enabledProperties = MiscUtils.except(disableFields, defaultFields); + @Override + public Boolean call() { + boolean hasSetValues = false; + for (String fieldName : enabledProperties) { + try { + switch (fieldName) { + case SPRING_BOOT_VERSION: + provider.addHeartBeatProperty(fieldName, getSpringBootVersion(), true); + hasSetValues = true; + break; + case SPRING_VERSION: + provider.addHeartBeatProperty(fieldName, getSpringVersion(), true); + hasSetValues = true; + break; + case SPRING_BOOT_STARTER_VERSION: + provider.addHeartBeatProperty(fieldName, getSpringBootStarterVersionNumber(), true); + default: + //We won't accept unknown properties in default providers. + InternalLogger.INSTANCE.trace("Encountered unknown default property"); + break; + } + } + catch (Exception e) { + InternalLogger.INSTANCE.warn("Failed to obtain heartbeat property, stack trace" + + "is: %s", ExceptionUtils.getStackTrace(e)); + } + } + return hasSetValues; + } + }; + } + + /** + * This method initializes the collection with Default Properties of this provider. + * @param defaultFields collection to hold default properties. + */ + private void initializeDefaultFields(Set defaultFields) { + defaultFields.add(SPRING_BOOT_VERSION); + defaultFields.add(SPRING_VERSION); + defaultFields.add(SPRING_BOOT_STARTER_VERSION); + } + + /** + * Gets the version of SpringBoot + * @return returns springboot version string + */ + private String getSpringBootVersion() { + return SpringBootVersion.getVersion(); + } + + /** + * Gets the Spring Framework version + * @return the SpringFrameWork version String + */ + private String getSpringVersion() { + return SpringVersion.getVersion(); + } + + /** + * Gets the AI SpringBoot starter version number + * @return the AI SpringBoot starter version number + */ + private String getSpringBootStarterVersionNumber() { + Properties starterVersionProperties = PropertyHelper.getStarterVersionProperties(); + if (starterVersionProperties != null) { + return starterVersionProperties.getProperty("version"); + } + return "undefined"; + } + +} diff --git a/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/initializer/SpringBootTelemetryInitializer.java b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/initializer/SpringBootTelemetryInitializer.java new file mode 100644 index 00000000000..930d91b27e2 --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/main/java/com/microsoft/applicationinsights/boot/initializer/SpringBootTelemetryInitializer.java @@ -0,0 +1,31 @@ +package com.microsoft.applicationinsights.boot.initializer; + +import com.microsoft.applicationinsights.extensibility.TelemetryInitializer; +import com.microsoft.applicationinsights.extensibility.context.DeviceContext; +import com.microsoft.applicationinsights.telemetry.Telemetry; +import org.springframework.beans.factory.annotation.Value; + +/** + *

TelemetryInitializer to set the CloudRoleName Instance

+ * + *

+ * This Telemetry Initializer is used to auto-configure cloud_RoleName field + * to get a logical component on AppMap. + *

+ * + * @author Dhaval Doshi + */ +public class SpringBootTelemetryInitializer implements TelemetryInitializer { + + /** The Logical Name of SpringBoot Application*/ + @Value("${spring.application.name:application}") + String appName; + + @Override + public void initialize(Telemetry telemetry) { + + DeviceContext device = telemetry.getContext().getDevice(); + device.setRoleName(appName); + } + +} diff --git a/azure-application-insights-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/azure-application-insights-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000000..923d5a1e734 --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,70 @@ +{ + "properties": [ + { + "name": "azure.application-insights.default-modules.ProcessPerformanceCountersModule.enabled", + "type": "java.lang.Boolean", + "description": "Enable ProcessPerformanceCountersModule.", + "defaultValue": "true" + }, + { + "name": "azure.application-insights.default-modules.WebRequestTrackingTelemetryModule.enabled", + "type": "java.lang.Boolean", + "description": "Enable WebRequestTrackingTelemetryModule.", + "defaultValue": "true" + }, + { + "name": "azure.application-insights.default-modules.WebSessionTrackingTelemetryModule.enabled", + "type": "java.lang.Boolean", + "description": "Enable WebSessionTrackingTelemetryModule.", + "defaultValue": "true" + }, + { + "name": "azure.application-insights.default-modules.WebUserTrackingTelemetryModule.enabled", + "type": "java.lang.Boolean", + "description": "Enable WebUserTrackingTelemetryModule.", + "defaultValue": "true" + }, + { + "name": "azure.application-insights.default-modules.WebPerformanceCounterModule.enabled", + "type": "java.lang.Boolean", + "description": "Enable WebPerformanceCounterModule.", + "defaultValue": "true" + }, + { + "name": "azure.application-insights.default-modules.WebOperationIdTelemetryInitializer.enabled", + "type": "java.lang.Boolean", + "description": "Enable WebOperationIdTelemetryInitializer.", + "defaultValue": "true" + }, + { + "name": "azure.application-insights.default-modules.WebOperationNameTelemetryInitializer.enabled", + "type": "java.lang.Boolean", + "description": "Enable WebOperationNameTelemetryInitializer.", + "defaultValue": "true" + }, + { + "name": "azure.application-insights.default-modules.WebSessionTelemetryInitializer.enabled", + "type": "java.lang.Boolean", + "description": "Enable WebSessionTelemetryInitializer.", + "defaultValue": "true" + }, + { + "name": "azure.application-insights.default-modules.WebUserTelemetryInitializer.enabled", + "type": "java.lang.Boolean", + "description": "Enable WebUserTelemetryInitializer.", + "defaultValue": "true" + }, + { + "name": "azure.application-insights.default-modules.WebUserAgentTelemetryInitializer.enabled", + "type": "java.lang.Boolean", + "description": "Enable WebUserAgentTelemetryInitializer.", + "defaultValue": "true" + }, + { + "name": "azure.application-insights.default.modules.JvmPerformanceCountersModule.enabled", + "type":"java.lang.Boolean", + "description": "Enable JvmPerformanceCountersModule.", + "defaultValue": true + } + ] +} \ No newline at end of file diff --git a/azure-application-insights-spring-boot-starter/src/main/resources/META-INF/spring.factories b/azure-application-insights-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..bfbc19b901b --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,22 @@ +# ApplicationInsights-Java +# Copyright (c) Microsoft Corporation +# All rights reserved. +# +# MIT License +# Permission is hereby granted, free of charge, to any person obtaining a copy of this +# software and associated documentation files (the ""Software""), to deal in the Software +# without restriction, including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# The above copyright notice and this permission notice shall be included in all copies or +# substantial portions of the Software. +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +# FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.microsoft.applicationinsights.boot.ApplicationInsightsTelemetryAutoConfiguration,\ +com.microsoft.applicationinsights.boot.ApplicationInsightsWebMvcAutoConfiguration \ No newline at end of file diff --git a/azure-application-insights-spring-boot-starter/src/test/java/com/microsoft/applicationinsights/boot/ApplicationInsightsStarterCoreParityTests.java b/azure-application-insights-spring-boot-starter/src/test/java/com/microsoft/applicationinsights/boot/ApplicationInsightsStarterCoreParityTests.java new file mode 100644 index 00000000000..cb74ddb5c0b --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/test/java/com/microsoft/applicationinsights/boot/ApplicationInsightsStarterCoreParityTests.java @@ -0,0 +1,75 @@ +package com.microsoft.applicationinsights.boot; + +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.applicationinsights.TelemetryConfiguration; +import com.microsoft.applicationinsights.internal.logger.InternalLogger; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@SpringBootTest( + properties = { + "spring.application.name: test-application", + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000" + }, + classes = { + PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class, + ApplicationInsightsWebMvcAutoConfiguration.class + } +) +@RunWith(SpringRunner.class) +public class ApplicationInsightsStarterCoreParityTests { + + //Instance from Spring Bean Factory + @Autowired + TelemetryClient telemetryClient; + + @Test + public void shouldHaveIdenticalConfiguration() throws Exception{ + Field field = telemetryClient.getClass().getDeclaredField("configuration"); + field.setAccessible(true); + TelemetryConfiguration config1 = (TelemetryConfiguration)field.get(telemetryClient); + + //needed for clearing down the active instance and get the new config. + tearDown(); + + //Instance created from XML config. + TelemetryClient t2 = new TelemetryClient(); + + Field field2 = t2.getClass().getDeclaredField("configuration"); + field2.setAccessible(true); + TelemetryConfiguration config2 = (TelemetryConfiguration)field2.get(t2); + + Assert.assertNotEquals(config1, config2); + //There is one additional TelemetryInitializer in case of SpringBoot(For Cloud_RoleName) + Assert.assertEquals(config1.getTelemetryInitializers().size(), config2.getTelemetryInitializers().size() + 1); + Assert.assertEquals(config1.getTelemetryModules().size(), config2.getTelemetryModules().size()); + Assert.assertEquals(config1.getContextInitializers().size(), config2.getContextInitializers().size()); + Assert.assertEquals(config1.getTelemetryProcessors().size(), config2.getTelemetryProcessors().size()); + Assert.assertEquals(config1.getInstrumentationKey(), config2.getInstrumentationKey()); + Assert.assertEquals(config1.isTrackingDisabled(), config2.isTrackingDisabled()); + } + + @AfterClass + public static void tearDown() throws Exception { + Method method = TelemetryConfiguration.class.getDeclaredMethod("setActiveAsNull"); + method.setAccessible(true); + method.invoke(null); + + //InternalLogger needs to be shutdown + Field field = InternalLogger.class.getDeclaredField("initialized"); + field.setAccessible(true); + field.set(InternalLogger.INSTANCE, false); + System.out.println("Exiting core parity tests"); + } + + +} diff --git a/azure-application-insights-spring-boot-starter/src/test/java/com/microsoft/applicationinsights/boot/ApplicationInsightsTelemetryAutoConfigurationTests.java b/azure-application-insights-spring-boot-starter/src/test/java/com/microsoft/applicationinsights/boot/ApplicationInsightsTelemetryAutoConfigurationTests.java new file mode 100644 index 00000000000..316e9bfc8bb --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/test/java/com/microsoft/applicationinsights/boot/ApplicationInsightsTelemetryAutoConfigurationTests.java @@ -0,0 +1,330 @@ +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.microsoft.applicationinsights.boot; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.applicationinsights.TelemetryConfiguration; +import com.microsoft.applicationinsights.channel.TelemetryChannel; +import com.microsoft.applicationinsights.channel.concrete.inprocess.InProcessTelemetryChannel; +import com.microsoft.applicationinsights.extensibility.ContextInitializer; +import com.microsoft.applicationinsights.extensibility.TelemetryInitializer; +import com.microsoft.applicationinsights.extensibility.TelemetryModule; +import com.microsoft.applicationinsights.extensibility.TelemetryProcessor; +import com.microsoft.applicationinsights.internal.channel.samplingV2.FixedRateSamplingTelemetryProcessor; +import com.microsoft.applicationinsights.internal.heartbeat.HeartBeatModule; +import com.microsoft.applicationinsights.internal.logger.InternalLogger; +import com.microsoft.applicationinsights.internal.perfcounter.JvmPerformanceCountersModule; +import com.microsoft.applicationinsights.internal.perfcounter.PerformanceCounter; +import com.microsoft.applicationinsights.internal.perfcounter.PerformanceCounterContainer; +import com.microsoft.applicationinsights.telemetry.EventTelemetry; +import com.microsoft.applicationinsights.telemetry.RequestTelemetry; +import com.microsoft.applicationinsights.telemetry.Telemetry; +import com.microsoft.applicationinsights.telemetry.TelemetryContext; +import com.microsoft.applicationinsights.web.extensibility.modules.WebUserTrackingTelemetryModule; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; + +/** + * @author Arthur Gavlyukovskiy, Dhaval Doshi + */ +public final class ApplicationInsightsTelemetryAutoConfigurationTests { + + private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After + public void restore() { + context.close(); + } + + @Test + public void shouldSetInstrumentationKeyWhenContextLoads() { + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + TelemetryClient telemetryClient = context.getBean(TelemetryClient.class); + TelemetryConfiguration telemetryConfiguration = context.getBean(TelemetryConfiguration.class); + + assertThat(telemetryConfiguration).isSameAs(TelemetryConfiguration.getActive()); + assertThat(telemetryConfiguration.getInstrumentationKey()).isEqualTo("00000000-0000-0000-0000-000000000000"); + assertThat(telemetryClient.getContext().getInstrumentationKey()).isEqualTo("00000000-0000-0000-0000-000000000000"); + } + + @Test + public void shouldSetInstrumentationKeyFromRelaxedCase() { + EnvironmentTestUtils.addEnvironment(context, + "AZURE.APPLICATION_INSIGHTS.INSTRUMENTATION_KEY: 00000000-0000-0000-0000-000000000000"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + TelemetryClient telemetryClient = context.getBean(TelemetryClient.class); + TelemetryConfiguration telemetryConfiguration = context.getBean(TelemetryConfiguration.class); + + assertThat(telemetryConfiguration).isSameAs(TelemetryConfiguration.getActive()); + assertThat(telemetryConfiguration.getInstrumentationKey()).isEqualTo("00000000-0000-0000-0000-000000000000"); + assertThat(telemetryClient.getContext().getInstrumentationKey()).isEqualTo("00000000-0000-0000-0000-000000000000"); + } + + @Test + @Ignore + public void shouldReloadInstrumentationKeyOnTelemetryClient() { + TelemetryClient myClient = new TelemetryClient(); + + EventTelemetry eventTelemetry1 = new EventTelemetry("test1"); + myClient.trackEvent(eventTelemetry1); + assertThat(eventTelemetry1.getTimestamp()).isNull(); + + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + EventTelemetry eventTelemetry2 = new EventTelemetry("test2"); + myClient.trackEvent(eventTelemetry2); + assertThat(eventTelemetry2.getTimestamp()).describedAs("Expecting telemetry event to be sent").isNotNull(); + } + + @Test + public void shouldNotFailIfInstrumentationKeyIsNotSet() { + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + assertThat(context.getBeansOfType(TelemetryClient.class)).isEmpty(); + assertThat(context.getBeansOfType(TelemetryConfiguration.class)).isEmpty(); + } + + @Test + public void shouldBeAbleToDisableInstrumentationByProperty() { + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.enabled: false", + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + TelemetryConfiguration telemetryConfiguration = context.getBean(TelemetryConfiguration.class); + assertThat(telemetryConfiguration.isTrackingDisabled()).isTrue(); + } + + @Test + public void shouldBeAbleToConfigureTelemetryChannel() { + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000", + "azure.application-insights.channel.in-process.developer-mode=false", + "azure.application-insights.channel.in-process.flush-interval-in-seconds=123", + "azure.application-insights.channel.in-process.max-telemetry-buffer-capacity=10"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + TelemetryConfiguration telemetryConfiguration = context.getBean(TelemetryConfiguration.class); + TelemetryChannel channel = telemetryConfiguration.getChannel(); + + assertThat(channel).isInstanceOf(InProcessTelemetryChannel.class); + assertThat(channel.isDeveloperMode()).isFalse(); + assertThat(channel).extracting("telemetryBuffer").extracting("transmitBufferTimeoutInSeconds").contains(123); + assertThat(channel).extracting("telemetryBuffer").extracting("maxTelemetriesInBatch").contains(10); + } + + @Test + public void shouldBeAbleToConfigureSamplingTelemetryProcessor() { + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000", + "azure.application-insights.telemetry-processor.sampling.percentage=50", + "azure.application-insights.telemetry-processor.sampling.include=Request", + "azure.application-insights.telemetry-processor.sampling.enabled=true"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + TelemetryConfiguration telemetryConfiguration = context.getBean(TelemetryConfiguration.class); + FixedRateSamplingTelemetryProcessor fixedRateSamplingTelemetryProcessor = context.getBean(FixedRateSamplingTelemetryProcessor.class); + + assertThat(telemetryConfiguration.getTelemetryProcessors()).extracting("class").contains(FixedRateSamplingTelemetryProcessor.class); + assertThat(fixedRateSamplingTelemetryProcessor).extracting("samplingPercentage").contains(50.); + assertThat(fixedRateSamplingTelemetryProcessor.getIncludedTypes()).contains(RequestTelemetry.class); + assertThat(fixedRateSamplingTelemetryProcessor.getExcludedTypes()).isEmpty(); + } + + @Test + public void shouldBeAbleToDisableAllWebModules() { + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000", + "azure.application-insights.web.enabled=false"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + assertThat(context.getBeansOfType(WebUserTrackingTelemetryModule.class)).isEmpty(); + } + + @Test + public void internalLoggerShouldBeInitializedBeforeTelemetryConfiguration() { + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000", + "azure.application-insights.logger.level=INFO" + ); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + InternalLogger logger = context.getBean(InternalLogger.class); + assertThat(logger.isInfoEnabled()).isEqualTo(true); + } + + @Test + public void shouldBeAbleToDisableDefaultModules() { + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000", + "azure.application-insights.default-modules.WebUserTrackingTelemetryModule.enabled=false"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + assertThat(context.getBeansOfType(WebUserTrackingTelemetryModule.class)).isEmpty(); + } + + @Test + public void shouldBeAbleToAddCustomModules() { + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class, + CustomModuleConfiguration.class); + context.refresh(); + + TelemetryConfiguration telemetryConfiguration = context.getBean(TelemetryConfiguration.class); + + ContextInitializer myContextInitializer = context.getBean("myContextInitializer", ContextInitializer.class); + TelemetryInitializer myTelemetryInitializer = context.getBean("myTelemetryInitializer", TelemetryInitializer.class); + TelemetryModule myTelemetryModule = context.getBean("myTelemetryModule", TelemetryModule.class); + TelemetryProcessor myTelemetryProcessor = context.getBean("myTelemetryProcessor", TelemetryProcessor.class); + + assertThat(telemetryConfiguration.getContextInitializers()).contains(myContextInitializer); + assertThat(telemetryConfiguration.getTelemetryInitializers()).contains(myTelemetryInitializer); + assertThat(telemetryConfiguration.getTelemetryModules()).contains(myTelemetryModule); + assertThat(telemetryConfiguration.getTelemetryProcessors()).contains(myTelemetryProcessor); + } + + @Test + public void shouldBeAbleToConfigureJmxPerformanceCounters() throws Exception{ + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000", + "azure.application-insights.jmx.jmx-counters:" + + "java.lang:type=ClassLoading/LoadedClassCount/Current Loaded Class Count"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + PerformanceCounterContainer counterContainer = context.getBean(PerformanceCounterContainer.class); + Field field = counterContainer.getClass().getDeclaredField("performanceCounters"); + field.setAccessible(true); + Map map = (Map)field.get(counterContainer); + assertThat(map.containsKey("java.lang:type=ClassLoading")).isNotNull(); + } + + @Test + public void shouldBeAbleToConfigureJvmPerformanceCounters() { + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000", + "azure.application-insights.default.modules.JvmPerformanceCountersModule.enabled=true"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + assertThat(context.getBeansOfType(JvmPerformanceCountersModule.class)).isNotEmpty(); + } + + @Test + public void heartBeatModuleShouldBeEnabledByDefault() { + EnvironmentTestUtils.addEnvironment(context, + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000", + "azure.application-insights.heart-beat.enabled=true"); + context.register(PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class); + context.refresh(); + + assertThat(context.getBeansOfType(HeartBeatModule.class)).isNotEmpty(); + } + + private static class CustomModuleConfiguration { + + @Bean + public ContextInitializer myContextInitializer() { + return new ContextInitializer() { + @Override + public void initialize(TelemetryContext context) { + } + }; + } + + @Bean + public TelemetryInitializer myTelemetryInitializer() { + return new TelemetryInitializer() { + @Override + public void initialize(Telemetry telemetry) { + } + }; + } + + @Bean + public TelemetryModule myTelemetryModule() { + return new TelemetryModule() { + @Override + public void initialize(TelemetryConfiguration configuration) { + } + }; + } + + @Bean + public TelemetryProcessor myTelemetryProcessor() { + return new TelemetryProcessor() { + @Override + public boolean process(Telemetry telemetry) { + return true; + } + }; + } + } + + @AfterClass + public static void tearDown() throws Exception { + Method method = TelemetryConfiguration.class.getDeclaredMethod("setActiveAsNull"); + method.setAccessible(true); + method.invoke(null); + } +} \ No newline at end of file diff --git a/azure-application-insights-spring-boot-starter/src/test/java/com/microsoft/applicationinsights/boot/ApplicationInsightsWebMvcAutoConfigurationTests.java b/azure-application-insights-spring-boot-starter/src/test/java/com/microsoft/applicationinsights/boot/ApplicationInsightsWebMvcAutoConfigurationTests.java new file mode 100644 index 00000000000..51e30786fd5 --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/test/java/com/microsoft/applicationinsights/boot/ApplicationInsightsWebMvcAutoConfigurationTests.java @@ -0,0 +1,79 @@ +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.microsoft.applicationinsights.boot; + +import com.microsoft.applicationinsights.internal.quickpulse.QuickPulse; +import com.microsoft.applicationinsights.web.internal.WebRequestTrackingFilter; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; +import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Arthur Gavlyukovskiy + */ +@SpringBootTest( + properties = { + "spring.test.mockmvc: true", + "spring.application.name: test-application", + "azure.application-insights.instrumentation-key: 00000000-0000-0000-0000-000000000000" + }, + classes = { + EmbeddedServletContainerAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + WebMvcAutoConfiguration.class, + MockMvcAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, + ApplicationInsightsTelemetryAutoConfiguration.class, + ApplicationInsightsWebMvcAutoConfiguration.class + }, + webEnvironment = WebEnvironment.RANDOM_PORT +) +@RunWith(SpringRunner.class) +public class ApplicationInsightsWebMvcAutoConfigurationTests { + + @Autowired + private ApplicationContext context; + + @Test + public void shouldRegisterWebRequestTrackingFilter() { + WebRequestTrackingFilter webRequestTrackingFilter = context.getBean(WebRequestTrackingFilter.class); + + assertThat(webRequestTrackingFilter).extracting("appName").contains("test-application"); + assertThat(QuickPulse.INSTANCE).extracting("initialized").contains(true); + } + +} \ No newline at end of file diff --git a/azure-application-insights-spring-boot-starter/src/test/resources/ApplicationInsights.xml b/azure-application-insights-spring-boot-starter/src/test/resources/ApplicationInsights.xml new file mode 100644 index 00000000000..5c85a0210c7 --- /dev/null +++ b/azure-application-insights-spring-boot-starter/src/test/resources/ApplicationInsights.xml @@ -0,0 +1,33 @@ + + + + + + + 00000000-0000-0000-0000-000000000000 + + + true + JavaSDKLog + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/main/java/com/microsoft/applicationinsights/TelemetryConfiguration.java b/core/src/main/java/com/microsoft/applicationinsights/TelemetryConfiguration.java index 5c505726357..3625e9bb023 100644 --- a/core/src/main/java/com/microsoft/applicationinsights/TelemetryConfiguration.java +++ b/core/src/main/java/com/microsoft/applicationinsights/TelemetryConfiguration.java @@ -74,6 +74,24 @@ public static TelemetryConfiguration getActive() { return active; } + /** + * This method provides the new instance of TelmetryConfiguration without loading the configuration + * from configuration file. This will just give a plain bare bone instance. Typically used when + * performing configuration programatically by creating beans, using @Beans tags. This is a common + * scenario in SpringBoot. + * @return {@link com.microsoft.applicationinsights.TelemetryConfiguration} + */ + public static TelemetryConfiguration getActiveWithoutInitializingConfig() { + if (active == null) { + synchronized (s_lock) { + if (active == null) { + active = new TelemetryConfiguration(); + } + } + } + return active; + } + /** * Creates a new instance loaded from the ApplicationInsights.xml file. * If the configuration file does not exist, the new configuration instance is initialized with minimum defaults diff --git a/core/src/main/java/com/microsoft/applicationinsights/channel/concrete/inprocess/InProcessTelemetryChannel.java b/core/src/main/java/com/microsoft/applicationinsights/channel/concrete/inprocess/InProcessTelemetryChannel.java index 4fcd89af4cc..aa375c3ea02 100644 --- a/core/src/main/java/com/microsoft/applicationinsights/channel/concrete/inprocess/InProcessTelemetryChannel.java +++ b/core/src/main/java/com/microsoft/applicationinsights/channel/concrete/inprocess/InProcessTelemetryChannel.java @@ -21,15 +21,11 @@ package com.microsoft.applicationinsights.channel.concrete.inprocess; -import java.io.IOException; -import java.io.StringWriter; -import java.net.URI; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -import com.microsoft.applicationinsights.internal.channel.TelemetriesTransmitter; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.microsoft.applicationinsights.channel.TelemetryChannel; import com.microsoft.applicationinsights.channel.TelemetrySampler; +import com.microsoft.applicationinsights.internal.channel.TelemetriesTransmitter; import com.microsoft.applicationinsights.internal.channel.TransmitterFactory; import com.microsoft.applicationinsights.internal.channel.common.TelemetryBuffer; import com.microsoft.applicationinsights.internal.logger.InternalLogger; @@ -38,48 +34,47 @@ import com.microsoft.applicationinsights.internal.util.Sanitizer; import com.microsoft.applicationinsights.telemetry.JsonTelemetryDataSerializer; import com.microsoft.applicationinsights.telemetry.Telemetry; -import com.microsoft.applicationinsights.channel.TelemetryChannel; - -import com.google.common.base.Strings; -import com.google.common.base.Preconditions; +import java.io.IOException; +import java.io.StringWriter; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.lang3.exception.ExceptionUtils; /** - * An implementation of - * {@link com.microsoft.applicationinsights.channel.TelemetryChannel} - *

- * The channel holds two main entities: - *

- * A buffer for incoming - * {@link com.microsoft.applicationinsights.telemetry.Telemetry} instances A - * transmitter - *

- * The buffer is stores incoming telemetry instances. Every new buffer starts a - * timer. When the timer expires, or when the buffer is 'full' (whichever - * happens first), the transmitter will pick up that buffer and will handle its - * sending to the server. For example, a transmitter will be responsible for - * compressing, sending and activate a policy in case of failures. - *

- * The model here is: - *

- * Use application threads to populate the buffer Use channel's threads to send - * buffers to the server - *

- * Created by gupele on 12/17/2014. + * An implementation of {@link com.microsoft.applicationinsights.channel.TelemetryChannel} + * + *

The channel holds two main entities: + * + *

A buffer for incoming {@link com.microsoft.applicationinsights.telemetry.Telemetry} instances + * A transmitter + * + *

The buffer is stores incoming telemetry instances. Every new buffer starts a timer. When the + * timer expires, or when the buffer is 'full' (whichever happens first), the transmitter will pick + * up that buffer and will handle its sending to the server. For example, a transmitter will be + * responsible for compressing, sending and activate a policy in case of failures. + * + *

The model here is: + * + *

Use application threads to populate the buffer Use channel's threads to send buffers to the + * server + * + *

Created by gupele on 12/17/2014. */ public final class InProcessTelemetryChannel implements TelemetryChannel { - - private final static String INSTANT_RETRY_NAME = "MaxInstantRetry"; - private final static int DEFAULT_MAX_INSTANT_RETRY = 3; - private final static int DEFAULT_MAX_TELEMETRY_BUFFER_CAPACITY = 500; - private final static int MIN_MAX_TELEMETRY_BUFFER_CAPACITY = 1; - private final static int MAX_MAX_TELEMETRY_BUFFER_CAPACITY = 1000; - private final static String MAX_MAX_TELEMETRY_BUFFER_CAPACITY_NAME = "MaxTelemetryBufferCapacity"; - - private final static int DEFAULT_FLUSH_BUFFER_TIMEOUT_IN_SECONDS = 5; - private final static int MIN_FLUSH_BUFFER_TIMEOUT_IN_SECONDS = 1; - private final static int MAX_FLUSH_BUFFER_TIMEOUT_IN_SECONDS = 300; - private final static String FLUSH_BUFFER_TIMEOUT_IN_SECONDS_NAME = "FlushIntervalInSeconds"; + + private static final String INSTANT_RETRY_NAME = "MaxInstantRetry"; + public static final int DEFAULT_MAX_INSTANT_RETRY = 3; + public static final int DEFAULT_MAX_TELEMETRY_BUFFER_CAPACITY = 500; + private static final int MIN_MAX_TELEMETRY_BUFFER_CAPACITY = 1; + private static final int MAX_MAX_TELEMETRY_BUFFER_CAPACITY = 1000; + private static final String MAX_MAX_TELEMETRY_BUFFER_CAPACITY_NAME = "MaxTelemetryBufferCapacity"; + + public static final int DEFAULT_FLUSH_BUFFER_TIMEOUT_IN_SECONDS = 5; + private static final int MIN_FLUSH_BUFFER_TIMEOUT_IN_SECONDS = 1; + private static final int MAX_FLUSH_BUFFER_TIMEOUT_IN_SECONDS = 300; + private static final String FLUSH_BUFFER_TIMEOUT_IN_SECONDS_NAME = "FlushIntervalInSeconds"; private final static String DEVELOPER_MODE_SYSTEM_PROPRETY_NAME = "APPLICATION_INSIGHTS_DEVELOPER_MODE"; @@ -114,31 +109,52 @@ public InProcessTelemetryChannel() { createDefaultSendIntervalInSecondsEnforcer(null), true); } - /** - * Ctor - * - * @param endpointAddress - * Must be empty string or a valid uri, else an exception will be - * thrown - * @param developerMode - * True will behave in a 'non-production' mode to ease the debugging - * @param maxTelemetryBufferCapacity - * Max number of Telemetries we keep in the buffer, when reached we - * will send the buffer Note, value should be between - * TRANSMIT_BUFFER_MIN_TIMEOUT_IN_MILLIS and - * TRANSMIT_BUFFER_MAX_TIMEOUT_IN_MILLIS inclusive - * @param sendIntervalInMillis - * The maximum number of milliseconds to wait before we send the - * buffer Note, value should be between - * MIN_MAX_TELEMETRY_BUFFER_CAPACITY and - * MAX_MAX_TELEMETRY_BUFFER_CAPACITY inclusive - */ - public InProcessTelemetryChannel(String endpointAddress, boolean developerMode, int maxTelemetryBufferCapacity, - int sendIntervalInMillis) { - initialize(endpointAddress, null, developerMode, - createDefaultMaxTelemetryBufferCapacityEnforcer(maxTelemetryBufferCapacity), - createDefaultSendIntervalInSecondsEnforcer(sendIntervalInMillis), true); - } + /** + * Ctor + * + * @param endpointAddress Must be empty string or a valid uri, else an exception will be thrown + * @param developerMode True will behave in a 'non-production' mode to ease the debugging + * @param maxTelemetryBufferCapacity Max number of Telemetries we keep in the buffer, when reached + * we will send the buffer Note, value should be between TRANSMIT_BUFFER_MIN_TIMEOUT_IN_MILLIS + * and TRANSMIT_BUFFER_MAX_TIMEOUT_IN_MILLIS inclusive + * @param sendIntervalInMillis The maximum number of milliseconds to wait before we send the + * buffer Note, value should be between MIN_MAX_TELEMETRY_BUFFER_CAPACITY and + * MAX_MAX_TELEMETRY_BUFFER_CAPACITY inclusive + */ + public InProcessTelemetryChannel( + String endpointAddress, + boolean developerMode, + int maxTelemetryBufferCapacity, + int sendIntervalInMillis) { + + // Builder pattern implementation + this( + endpointAddress, + null, + developerMode, + maxTelemetryBufferCapacity, + sendIntervalInMillis, + true, + DEFAULT_MAX_INSTANT_RETRY); + } + + public InProcessTelemetryChannel( + String endpointAddress, + String maxTransmissionStorageCapacity, + boolean developerMode, + int maxTelemetryBufferCapacity, + int sendIntervalInMillis, + boolean throttling, + int maxInstantRetries) { + initialize( + endpointAddress, + maxTransmissionStorageCapacity, + developerMode, + createDefaultMaxTelemetryBufferCapacityEnforcer(maxTelemetryBufferCapacity), + createDefaultSendIntervalInSecondsEnforcer(sendIntervalInMillis), + throttling, + maxInstantRetries); + } /** * This Ctor will query the 'namesAndValues' map for data to initialize itself @@ -152,7 +168,7 @@ public InProcessTelemetryChannel(Map namesAndValues) { boolean developerMode = false; String endpointAddress = null; int maxInstantRetries = DEFAULT_MAX_INSTANT_RETRY; - + LimitsEnforcer maxTelemetryBufferCapacityEnforcer = createDefaultMaxTelemetryBufferCapacityEnforcer(null); LimitsEnforcer sendIntervalInSecondsEnforcer = createDefaultSendIntervalInSecondsEnforcer(null); @@ -164,17 +180,17 @@ public InProcessTelemetryChannel(Map namesAndValues) { try { String instantRetryValue = namesAndValues.get(INSTANT_RETRY_NAME); if (instantRetryValue != null){ - maxInstantRetries = Integer.parseInt(instantRetryValue); + maxInstantRetries = Integer.parseInt(instantRetryValue); } - + } catch (NumberFormatException e) { InternalLogger.INSTANCE.error("Unable to parse configuration setting %s to integer value.%nStack Trace:%n%s", INSTANT_RETRY_NAME, ExceptionUtils.getStackTrace(e)); } - - if (!developerMode) { - developerMode = Boolean.valueOf(System.getProperty(DEVELOPER_MODE_SYSTEM_PROPRETY_NAME)); - } - endpointAddress = namesAndValues.get(ENDPOINT_ADDRESS_NAME); + + if (!developerMode) { + developerMode = Boolean.valueOf(System.getProperty(DEVELOPER_MODE_SYSTEM_PROPRETY_NAME)); + } + endpointAddress = namesAndValues.get(ENDPOINT_ADDRESS_NAME); maxTelemetryBufferCapacityEnforcer .normalizeStringValue(namesAndValues.get(MAX_MAX_TELEMETRY_BUFFER_CAPACITY_NAME)); @@ -182,10 +198,17 @@ public InProcessTelemetryChannel(Map namesAndValues) { .normalizeStringValue(namesAndValues.get(FLUSH_BUFFER_TIMEOUT_IN_SECONDS_NAME)); } - String maxTransmissionStorageCapacity = namesAndValues.get(MAX_TRANSMISSION_STORAGE_CAPACITY_NAME); - initialize(endpointAddress, maxTransmissionStorageCapacity, developerMode, maxTelemetryBufferCapacityEnforcer, - sendIntervalInSecondsEnforcer, throttling, maxInstantRetries); - } + String maxTransmissionStorageCapacity = + namesAndValues.get(MAX_TRANSMISSION_STORAGE_CAPACITY_NAME); + initialize( + endpointAddress, + maxTransmissionStorageCapacity, + developerMode, + maxTelemetryBufferCapacityEnforcer, + sendIntervalInSecondsEnforcer, + throttling, + maxInstantRetries); + } /** * Gets value indicating whether this channel is in developer mode. diff --git a/core/src/main/java/com/microsoft/applicationinsights/internal/channel/common/TransmissionFileSystemOutput.java b/core/src/main/java/com/microsoft/applicationinsights/internal/channel/common/TransmissionFileSystemOutput.java index 3d4723bab1d..64166a0d08c 100644 --- a/core/src/main/java/com/microsoft/applicationinsights/internal/channel/common/TransmissionFileSystemOutput.java +++ b/core/src/main/java/com/microsoft/applicationinsights/internal/channel/common/TransmissionFileSystemOutput.java @@ -77,7 +77,7 @@ public final class TransmissionFileSystemOutput implements TransmissionOutput { private final static int MAX_RETRY_FOR_DELETE = 2; private final static int DELETE_TIMEOUT_ON_FAILURE_IN_MILLS = 100; - private final static int DEFAULT_CAPACITY_MEGABYTES = 10; + public final static int DEFAULT_CAPACITY_MEGABYTES = 10; private final static int MAX_CAPACITY_MEGABYTES = 1000; private final static int MIN_CAPACITY_MEGABYTES = 1; private static final String MAX_TRANSMISSION_STORAGE_CAPACITY_NAME = "Channel.MaxTransmissionStorageCapacityInMB"; diff --git a/core/src/main/java/com/microsoft/applicationinsights/internal/channel/common/TransmissionNetworkOutput.java b/core/src/main/java/com/microsoft/applicationinsights/internal/channel/common/TransmissionNetworkOutput.java index 311382a5e65..2a80e5a8934 100644 --- a/core/src/main/java/com/microsoft/applicationinsights/internal/channel/common/TransmissionNetworkOutput.java +++ b/core/src/main/java/com/microsoft/applicationinsights/internal/channel/common/TransmissionNetworkOutput.java @@ -50,12 +50,12 @@ * * Created by gupele on 12/18/2014. */ -public final class TransmissionNetworkOutput implements TransmissionOutput { +public final class TransmissionNetworkOutput implements TransmissionOutput { private final static String CONTENT_TYPE_HEADER = "Content-Type"; private final static String CONTENT_ENCODING_HEADER = "Content-Encoding"; private final static String RESPONSE_THROTTLING_HEADER = "Retry-After"; - private final static String DEFAULT_SERVER_URI = "https://dc.services.visualstudio.com/v2/track"; + public final static String DEFAULT_SERVER_URI = "https://dc.services.visualstudio.com/v2/track"; // For future use: re-send a failed transmission back to the dispatcher private TransmissionDispatcher transmissionDispatcher; @@ -73,7 +73,7 @@ public final class TransmissionNetworkOutput implements TransmissionOutput { * Creates an instance of the network transmission class. *

* Will use the DEFAULT_SERVER_URI for the endpoint. - * + * * @param transmissionPolicyManager * The transmission policy used to mark this sender active or * blocked. @@ -85,7 +85,7 @@ public static TransmissionNetworkOutput create(TransmissionPolicyManager transmi /** * Creates an instance of the network transmission class. - * + * * @param endpoint * The HTTP endpoint to send our telemetry too. * @param transmissionPolicyManager @@ -103,7 +103,7 @@ public static TransmissionNetworkOutput create(String endpoint, * Private Ctor to initialize class. *

* Also creates the httpClient using the ApacheSender instance - * + * * @param serverUri * The HTTP endpoint to send our telemetry too. * @param transmissionPolicyManager @@ -125,7 +125,7 @@ private TransmissionNetworkOutput(String serverUri, TransmissionPolicyManager tr /** * Used to inject the dispatcher used for this output so it can be injected to * the retry logic. - * + * * @param transmissionDispatcher * The dispatcher to be injected. */ @@ -135,7 +135,7 @@ public void setTransmissionDispatcher(TransmissionDispatcher transmissionDispatc /** * Stops all threads from sending data. - * + * * @param timeout * The timeout to wait, which is not relevant here. * @param timeUnit @@ -157,7 +157,7 @@ public synchronized void stop(long timeout, TimeUnit timeUnit) { * The thread that calls that method might be suspended if there is a throttling * issues, in any case the thread that enters this method is responsive for * 'stop' request that might be issued by the application. - * + * * @param transmission * The data to send * @return True when done. @@ -259,7 +259,7 @@ public boolean send(Transmission transmission) { /** * Generates the HTTP POST to send to the endpoint. - * + * * @param transmission * The transmission to send. * @return The completed {@link HttpPost} object diff --git a/core/src/main/java/com/microsoft/applicationinsights/internal/channel/samplingV2/FixedRateSamplingTelemetryProcessor.java b/core/src/main/java/com/microsoft/applicationinsights/internal/channel/samplingV2/FixedRateSamplingTelemetryProcessor.java index a71b2c3d944..48bd2e87dd2 100644 --- a/core/src/main/java/com/microsoft/applicationinsights/internal/channel/samplingV2/FixedRateSamplingTelemetryProcessor.java +++ b/core/src/main/java/com/microsoft/applicationinsights/internal/channel/samplingV2/FixedRateSamplingTelemetryProcessor.java @@ -3,8 +3,15 @@ import com.microsoft.applicationinsights.extensibility.TelemetryProcessor; import com.microsoft.applicationinsights.internal.annotation.BuiltInProcessor; import com.microsoft.applicationinsights.internal.logger.InternalLogger; +import com.microsoft.applicationinsights.telemetry.EventTelemetry; +import com.microsoft.applicationinsights.telemetry.ExceptionTelemetry; +import com.microsoft.applicationinsights.telemetry.PageViewTelemetry; +import com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry; +import com.microsoft.applicationinsights.telemetry.RequestTelemetry; import com.microsoft.applicationinsights.telemetry.SupportSampling; import com.microsoft.applicationinsights.telemetry.Telemetry; +import com.microsoft.applicationinsights.telemetry.TraceTelemetry; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import java.util.HashMap; @@ -12,8 +19,6 @@ import java.util.Map; import java.util.Set; -import org.apache.commons.lang3.StringUtils; - /** * This processor is used to Perform Sampling on User specified sampling rate *

@@ -41,14 +46,24 @@ @BuiltInProcessor("FixedRateSamplingTelemetryProcessor") public final class FixedRateSamplingTelemetryProcessor implements TelemetryProcessor { - private final String dependencyTelemetryName = "Dependency"; + public static final double DEFAULT_SAMPLING_PERCENTAGE = 100.0; + private static Map allowedTypes = new HashMap<>(); + + private static final String dependencyTelemetryName = "Dependency"; private static final String eventTelemetryName = "Event"; private static final String exceptionTelemetryName = "Exception"; private static final String pageViewTelemetryName = "PageView"; private static final String requestTelemetryName = "Request"; private static final String traceTelemetryName = "Trace"; - private static Map allowedTypes; + static { + allowedTypes.put(dependencyTelemetryName, RemoteDependencyTelemetry.class); + allowedTypes.put(eventTelemetryName, EventTelemetry.class); + allowedTypes.put(exceptionTelemetryName, ExceptionTelemetry.class); + allowedTypes.put(pageViewTelemetryName, PageViewTelemetry.class); + allowedTypes.put(requestTelemetryName, RequestTelemetry.class); + allowedTypes.put(traceTelemetryName, TraceTelemetry.class); + } private Set excludedTypes; @@ -65,21 +80,10 @@ public final class FixedRateSamplingTelemetryProcessor implements TelemetryProce * to default settings */ public FixedRateSamplingTelemetryProcessor() { - this.samplingPercentage = 100.00; - this.includedTypes = new HashSet(); - this.excludedTypes = new HashSet(); - try { - this.allowedTypes = new HashMap() {{ - put(dependencyTelemetryName, com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry.class); - put(eventTelemetryName, com.microsoft.applicationinsights.telemetry.EventTelemetry.class); - put(exceptionTelemetryName, com.microsoft.applicationinsights.telemetry.ExceptionTelemetry.class); - put(pageViewTelemetryName, com.microsoft.applicationinsights.telemetry.PageViewTelemetry.class); - put(requestTelemetryName, com.microsoft.applicationinsights.telemetry.RequestTelemetry.class); - put(traceTelemetryName, com.microsoft.applicationinsights.telemetry.TraceTelemetry.class); - }}; - } catch (Exception e) { - InternalLogger.INSTANCE.trace("Unable to locate telemetry classes. stack trace is %s", ExceptionUtils.getStackTrace(e)); - } + + this.samplingPercentage = DEFAULT_SAMPLING_PERCENTAGE; + this.includedTypes = new HashSet<>(); + this.excludedTypes = new HashSet<>(); } /** @@ -108,10 +112,10 @@ private void setIncludedOrExcludedTypes(String value, Set typeSet) { if (!StringUtils.isEmpty(value) && allowedTypes.containsKey(value)) { typeSet.add(allowedTypes.get(value)); } else { - InternalLogger.INSTANCE.error("Item is either not allowed to sample or is empty"); + InternalLogger.INSTANCE.error("Item %s is either not allowed to sample or is empty", value); } } else { - InternalLogger.INSTANCE.error("Empty types cannot be considered"); + InternalLogger.INSTANCE.error("Telemetry type %s is empty", value); } } diff --git a/core/src/main/java/com/microsoft/applicationinsights/internal/heartbeat/MiscUtils.java b/core/src/main/java/com/microsoft/applicationinsights/internal/heartbeat/MiscUtils.java index de913e63ca1..e7eaf6e5317 100644 --- a/core/src/main/java/com/microsoft/applicationinsights/internal/heartbeat/MiscUtils.java +++ b/core/src/main/java/com/microsoft/applicationinsights/internal/heartbeat/MiscUtils.java @@ -9,7 +9,7 @@ * * @author Dhaval Doshi */ -class MiscUtils { +public class MiscUtils { /** * Returns a list which contains result of List - Set @@ -17,7 +17,7 @@ class MiscUtils { * @param set * @return */ - static Set except(List list2, Set set) { + public static Set except(List list2, Set set) { try { if (set == null) { throw new IllegalArgumentException("Input is null"); diff --git a/core/src/main/java/com/microsoft/applicationinsights/internal/perfcounter/PerformanceCounterContainer.java b/core/src/main/java/com/microsoft/applicationinsights/internal/perfcounter/PerformanceCounterContainer.java index de11b112e6d..6fa1f9e5ce8 100644 --- a/core/src/main/java/com/microsoft/applicationinsights/internal/perfcounter/PerformanceCounterContainer.java +++ b/core/src/main/java/com/microsoft/applicationinsights/internal/perfcounter/PerformanceCounterContainer.java @@ -63,7 +63,7 @@ public enum PerformanceCounterContainer implements Stoppable { private final static long START_DEFAULT_MIN_DELAY_IN_MILLIS = 20000; // By default the container will collect performance data every 1 minute. - private final static long DEFAULT_COLLECTION_FREQUENCY_IN_SEC = 60; + public final static long DEFAULT_COLLECTION_FREQUENCY_IN_SEC = 60; private final static long MIN_COLLECTION_FREQUENCY_IN_SEC = 1; private final ConcurrentMap performanceCounters = new ConcurrentHashMap(); diff --git a/core/src/main/java/com/microsoft/applicationinsights/internal/util/PropertyHelper.java b/core/src/main/java/com/microsoft/applicationinsights/internal/util/PropertyHelper.java index 3d0dbc09031..62a49065178 100644 --- a/core/src/main/java/com/microsoft/applicationinsights/internal/util/PropertyHelper.java +++ b/core/src/main/java/com/microsoft/applicationinsights/internal/util/PropertyHelper.java @@ -33,6 +33,7 @@ */ public final class PropertyHelper { public final static String SDK_VERSION_FILE_NAME = "sdk-version.properties"; + final static String STARTER_VERSION_FILE_NAME = "starter-version.properties"; /** * Reads the properties from a properties file. @@ -74,6 +75,21 @@ public static Properties getSdkVersionProperties() { return null; } + /** + * A method that loads the properties file that contains the AI SpringBootStarter version number + * @return The properties or null if not found. + */ + public static Properties getStarterVersionProperties() { + try { + return getProperties(STARTER_VERSION_FILE_NAME); + } + catch (IOException e) { + InternalLogger.INSTANCE.trace("Could not find starter version file: %s," + + "stack trace is: ", SDK_VERSION_FILE_NAME, ExceptionUtils.getStackTrace(e)); + } + return null; + } + private PropertyHelper() { } } diff --git a/core/src/test/java/com/microsoft/applicationinsights/internal/channel/inprocess/InProcessTelemetryChannelTest.java b/core/src/test/java/com/microsoft/applicationinsights/internal/channel/inprocess/InProcessTelemetryChannelTest.java index eed12e3e5e2..9d4e489bfd4 100644 --- a/core/src/test/java/com/microsoft/applicationinsights/internal/channel/inprocess/InProcessTelemetryChannelTest.java +++ b/core/src/test/java/com/microsoft/applicationinsights/internal/channel/inprocess/InProcessTelemetryChannelTest.java @@ -22,14 +22,15 @@ package com.microsoft.applicationinsights.internal.channel.inprocess; import com.microsoft.applicationinsights.channel.concrete.inprocess.InProcessTelemetryChannel; +import org.junit.Assert; import org.junit.Test; import java.util.HashMap; public class InProcessTelemetryChannelTest { - private final static String NON_VALID_URL = "http:sd{@~fsd.s.d.f;fffff"; - private final static String INSTANT_RETRY_NAME = "MaxInstantRetry"; + private final static String NON_VALID_URL = "http:sd{@~fsd.s.d.f;fffff"; + private final static String INSTANT_RETRY_NAME = "MaxInstantRetry"; private final static int DEFAULT_MAX_INSTANT_RETRY = 3; @Test(expected = IllegalArgumentException.class) @@ -44,18 +45,24 @@ public void testStringIntegerMaxInstanceRetry() { map.put(INSTANT_RETRY_NAME, "AABB"); new InProcessTelemetryChannel(map); } - + @Test() public void testValidIntegerMaxInstanceRetry() { HashMap map = new HashMap(); map.put(INSTANT_RETRY_NAME, "4"); new InProcessTelemetryChannel(map); } - + @Test() public void testInvalidIntegerMaxInstanceRetry() { HashMap map = new HashMap(); map.put(INSTANT_RETRY_NAME, "-1"); new InProcessTelemetryChannel(map); } + + @Test + public void testInProcessTelemetryChannelWithDefaultSpringBootParameters() { + new InProcessTelemetryChannel("https://dc.services.visualstudio.com/v2/track", "10", + false, 500, 5, true, DEFAULT_MAX_INSTANT_RETRY); + } } \ No newline at end of file diff --git a/core/src/test/java/com/microsoft/applicationinsights/internal/config/TelemetryConfigurationFactoryTest.java b/core/src/test/java/com/microsoft/applicationinsights/internal/config/TelemetryConfigurationFactoryTest.java index 9781f8516f1..05a4af20ef1 100644 --- a/core/src/test/java/com/microsoft/applicationinsights/internal/config/TelemetryConfigurationFactoryTest.java +++ b/core/src/test/java/com/microsoft/applicationinsights/internal/config/TelemetryConfigurationFactoryTest.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.*; import com.google.common.base.Predicates; @@ -398,6 +399,17 @@ public void testDefaultChannelWithBadData() { assertEquals(mockConfiguration.getChannel().isDeveloperMode(), false); } + @Test + public void testEmptyConfiguration() { + TelemetryConfiguration emptyConfig = TelemetryConfiguration.getActiveWithoutInitializingConfig(); + Assert.assertEquals(null, emptyConfig.getInstrumentationKey()); + Assert.assertEquals(null, emptyConfig.getChannel()); + Assert.assertEquals(0, emptyConfig.getTelemetryModules().size()); + Assert.assertEquals(false, emptyConfig.isTrackingDisabled()); + Assert.assertEquals(0, emptyConfig.getContextInitializers().size()); + Assert.assertEquals(0, emptyConfig.getTelemetryProcessors().size()); + } + private MockTelemetryModule generateTelemetryModules(boolean addParameter) { AppInsightsConfigurationBuilder mockParser = createMockParser(true, true, false); ApplicationInsightsXmlConfiguration appConf = mockParser.build(null); @@ -567,4 +579,11 @@ private void ikeyTest(String configurationIkey, String expectedIkey) { assertEquals(mockConfiguration.getInstrumentationKey(), expectedIkey); assertTrue(mockConfiguration.getChannel() instanceof InProcessTelemetryChannel); } + + @After + public void tearDown() throws Exception { + Method method = TelemetryConfiguration.class.getDeclaredMethod("setActiveAsNull"); + method.setAccessible(true); + method.invoke(null); + } } diff --git a/settings.gradle b/settings.gradle index 65a1e3e9969..3645ec1c991 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,6 +26,7 @@ include 'logging:log4j1_2' include 'logging:log4j2' include 'logging:logback' include 'web' +include 'azure-application-insights-spring-boot-starter' include 'distributions' include 'samples' include 'test:performance' @@ -59,3 +60,6 @@ if (System.env.'COLLECTD_HOME') { include 'collectd' } +include ':test:smoke:testApps:SpringBootTest' + + diff --git a/test/smoke/build.gradle b/test/smoke/build.gradle index d565b81acd4..7c4f4a0cec6 100644 --- a/test/smoke/build.gradle +++ b/test/smoke/build.gradle @@ -22,6 +22,7 @@ subprojects { aiLog4j1_2Jar = project(':logging:log4j1_2') aiLo4j2Jar = project(':logging:log4j2') aiLogbackJar = project(':logging:logback') + springBootStarterJar = project(':azure-application-insights-spring-boot-starter') } } diff --git a/test/smoke/framework/testCore/src/main/java/com/microsoft/applicationinsights/smoketest/AiSmokeTest.java b/test/smoke/framework/testCore/src/main/java/com/microsoft/applicationinsights/smoketest/AiSmokeTest.java index 1e43b88f172..e75ae5fe2c3 100644 --- a/test/smoke/framework/testCore/src/main/java/com/microsoft/applicationinsights/smoketest/AiSmokeTest.java +++ b/test/smoke/framework/testCore/src/main/java/com/microsoft/applicationinsights/smoketest/AiSmokeTest.java @@ -310,6 +310,7 @@ protected void callTargetUriAndWaitForTelemetry() throws Exception { } System.out.println("Calling "+targetUri+" ..."); String url = getBaseUrl()+targetUri; + System.out.println("calling " + url); final String content; switch(httpMethod) { case "GET": diff --git a/test/smoke/testApps/SpringBootTest/build.gradle b/test/smoke/testApps/SpringBootTest/build.gradle new file mode 100644 index 00000000000..6ad8a668e7f --- /dev/null +++ b/test/smoke/testApps/SpringBootTest/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'war' + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.9.RELEASE' + } +} + +apply plugin: 'org.springframework.boot' + +compileJava.sourceCompatibility = 1.7 +compileJava.targetCompatibility = 1.7 +compileSmokeTestJava.sourceCompatibility = 1.8 +compileSmokeTestJava.targetCompatibility = 1.8 + +dependencies { + compile springBootStarterJar + compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.9.RELEASE' + compile group: 'org.springframework.boot', name: 'spring-boot-starter-tomcat', version: '1.5.9.RELEASE' + testCompile group: 'junit', name: 'junit', version: '4.12' +} + +configurations { + smokeTestCompile.exclude group:'org.springframework.boot' + smokeTestRuntime.exclude group:'org.springframework.boot' +} + +ext.testAppArtifactDir = war.destinationDir +ext.testAppArtifactFilename = war.archiveName \ No newline at end of file diff --git a/test/smoke/testApps/SpringBootTest/src/main/java/com/SpringBootApp.java b/test/smoke/testApps/SpringBootTest/src/main/java/com/SpringBootApp.java new file mode 100644 index 00000000000..2e1dd842b30 --- /dev/null +++ b/test/smoke/testApps/SpringBootTest/src/main/java/com/SpringBootApp.java @@ -0,0 +1,18 @@ +package com; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.support.SpringBootServletInitializer; + +@SpringBootApplication +public class SpringBootApp extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) { + return applicationBuilder.sources(SpringBootApp.class); + } + public static void main(String[] args) { + SpringApplication.run(SpringBootApp.class, args); + } +} diff --git a/test/smoke/testApps/SpringBootTest/src/main/java/com/springbootstartertest/controller/TestController.java b/test/smoke/testApps/SpringBootTest/src/main/java/com/springbootstartertest/controller/TestController.java new file mode 100644 index 00000000000..db0efc0555e --- /dev/null +++ b/test/smoke/testApps/SpringBootTest/src/main/java/com/springbootstartertest/controller/TestController.java @@ -0,0 +1,39 @@ +package com.springbootstartertest.controller; + +import com.microsoft.applicationinsights.TelemetryClient; +import java.util.HashMap; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + + @Autowired + TelemetryClient client; + + @GetMapping("/") + public String rootPage() { + return "OK"; + } + + @GetMapping("/basic/trackEvent") + public String trackEventSpringBoot() { + Map properties = new HashMap() { + { + put("key", "value"); + } + }; + Map metrics = new HashMap() { + { + put("key", 1d); + } + }; + + //Event + client.trackEvent("EventDataTest"); + client.trackEvent("EventDataPropertyTest", properties, metrics); + return "hello"; + } +} diff --git a/test/smoke/testApps/SpringBootTest/src/main/resources/application.properties b/test/smoke/testApps/SpringBootTest/src/main/resources/application.properties new file mode 100644 index 00000000000..741e0983b10 --- /dev/null +++ b/test/smoke/testApps/SpringBootTest/src/main/resources/application.properties @@ -0,0 +1,5 @@ +spring.application.name=SpringBootTest +azure.application-insights.channel.in-process.endpoint-address=http://fakeingestion:60606/v2/track +azure.application-insights.instrumentation-key=00000000-0000-0000-0000-cba987654321 +azure.application-insights.logger.level=TRACE +azure.application-insights.default-modules.ProcessPerformanceCountersModule.enabled=false diff --git a/test/smoke/testApps/SpringBootTest/src/smokeTest/java/com/springbootstartertest/smoketest/SpringbootSmokeTest.java b/test/smoke/testApps/SpringBootTest/src/smokeTest/java/com/springbootstartertest/smoketest/SpringbootSmokeTest.java new file mode 100644 index 00000000000..872cedfaf28 --- /dev/null +++ b/test/smoke/testApps/SpringBootTest/src/smokeTest/java/com/springbootstartertest/smoketest/SpringbootSmokeTest.java @@ -0,0 +1,37 @@ +package com.springbootstartertest.smoketest; + +import static org.junit.Assert.assertEquals; + +import com.microsoft.applicationinsights.internal.schemav2.EventData; +import com.microsoft.applicationinsights.smoketest.AiSmokeTest; +import com.microsoft.applicationinsights.smoketest.TargetUri; +import org.junit.Test; + +public class SpringbootSmokeTest extends AiSmokeTest{ + + @Test + @TargetUri("/basic/trackEvent") + public void trackEvent() throws Exception { + assertEquals(1, mockedIngestion.getCountForType("RequestData")); + assertEquals(2, mockedIngestion.getCountForType("EventData")); + int totalItems = mockedIngestion.getItemCount(); + int expectedItems = 3; + assertEquals(String.format("There were %d extra telemetry items received.", expectedItems - totalItems), + expectedItems, totalItems); + + // TODO get event data envelope and verify value + EventData d = getTelemetryDataForType(0, "EventData"); + final String name = "EventDataTest"; + assertEquals(name, d.getName()); + + EventData d2 = getTelemetryDataForType(1, "EventData"); + + final String expectedName = "EventDataPropertyTest"; + final String expectedProperties = "value"; + final Double expectedMetric = 1d; + + assertEquals(expectedName, d2.getName()); + assertEquals(expectedProperties, d2.getProperties().get("key")); + assertEquals(expectedMetric, d2.getMeasurements().get("key")); + } +} diff --git a/test/smoke/testApps/SpringBootTest/src/smokeTest/resources/appServers.txt b/test/smoke/testApps/SpringBootTest/src/smokeTest/resources/appServers.txt new file mode 100644 index 00000000000..ec0fa258a85 --- /dev/null +++ b/test/smoke/testApps/SpringBootTest/src/smokeTest/resources/appServers.txt @@ -0,0 +1 @@ +tomcat8 \ No newline at end of file