Skip to content

Commit

Permalink
Add applicationinsights-rp.json (#1559)
Browse files Browse the repository at this point in the history
  • Loading branch information
trask authored Mar 16, 2021
1 parent af13cad commit 1f703d4
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

import java.io.File;
import java.lang.instrument.Instrumentation;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;

Expand All @@ -46,6 +45,7 @@
import com.microsoft.applicationinsights.agent.internal.instrumentation.sdk.TelemetryClientClassFileTransformer;
import com.microsoft.applicationinsights.agent.internal.instrumentation.sdk.WebRequestTrackingFilterClassFileTransformer;
import com.microsoft.applicationinsights.agent.internal.sampling.SamplingPercentage;
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.RpConfiguration;
import com.microsoft.applicationinsights.common.CommonUtils;
import com.microsoft.applicationinsights.customExceptions.FriendlyException;
import com.microsoft.applicationinsights.extensibility.initializer.ResourceAttributesContextInitializer;
Expand Down Expand Up @@ -179,11 +179,9 @@ public void run() {
}
});

if (config.preview.configReloadEnabled) {
Path configPath = MainEntryPoint.getConfigPath();
if (configPath != null) {
JsonConfigPolling.pollJsonConfigEveryMinute(configPath, MainEntryPoint.getLastModifiedTime(), config.sampling.percentage);
}
RpConfiguration rpConfiguration = MainEntryPoint.getRpConfiguration();
if (rpConfiguration != null) {
RpConfigurationPolling.startPolling(rpConfiguration);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,79 +23,68 @@

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.concurrent.Executors;

import com.microsoft.applicationinsights.TelemetryConfiguration;
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration;
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.ConfigurationBuilder;
import com.microsoft.applicationinsights.agent.internal.sampling.DelegatingSampler;
import com.microsoft.applicationinsights.agent.internal.sampling.Samplers;
import com.microsoft.applicationinsights.agent.internal.sampling.SamplingPercentage;
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.RpConfiguration;
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.RpConfigurationBuilder;
import com.microsoft.applicationinsights.internal.util.ThreadPoolUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.concurrent.TimeUnit.SECONDS;

public class JsonConfigPolling implements Runnable {
public class RpConfigurationPolling implements Runnable {

private final Path path;
private volatile long lastModifiedTime;
private volatile double lastReadSamplingPercentage;
private static final Logger logger = LoggerFactory.getLogger(JsonConfigPolling.class);
private volatile RpConfiguration rpConfiguration;
private static final Logger logger = LoggerFactory.getLogger(RpConfigurationPolling.class);

// visible for testing
JsonConfigPolling(Path path, long lastModifiedTime, double lastReadSamplingPercentage) {
this.path = path;
this.lastModifiedTime = lastModifiedTime;
this.lastReadSamplingPercentage = lastReadSamplingPercentage;
RpConfigurationPolling(RpConfiguration rpConfiguration) {
this.rpConfiguration = rpConfiguration;
}

// passing in lastReadSamplingPercentage instead of using the real samplingPercentage, because the real
// samplingPercentage is rounded to nearest 100/N, and we want to know specifically when the underlying config value changes
// which is lastReadSamplingPercentage
public static void pollJsonConfigEveryMinute(Path path, long lastModifiedTime, double lastReadSamplingPercentage) {
Executors.newSingleThreadScheduledExecutor(ThreadPoolUtils.createDaemonThreadFactory(JsonConfigPolling.class))
.scheduleWithFixedDelay(new JsonConfigPolling(path, lastModifiedTime, lastReadSamplingPercentage), 60, 60, SECONDS);
public static void startPolling(RpConfiguration rpConfiguration) {
Executors.newSingleThreadScheduledExecutor(ThreadPoolUtils.createDaemonThreadFactory(RpConfigurationPolling.class))
.scheduleWithFixedDelay(new RpConfigurationPolling(rpConfiguration), 60, 60, SECONDS);
}

@Override
public void run() {
if (path == null) {
logger.warn("JSON config path is null.");
if (rpConfiguration.configPath == null) {
logger.warn("rp configuration path is null");
return;
}

if (!Files.exists(path)) {
logger.warn(path + " doesn't exist.");
if (!Files.exists(rpConfiguration.configPath)) {
logger.warn("rp configuration path doesn't exist: {}", rpConfiguration.configPath);
return;
}

try {
BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
BasicFileAttributes attributes = Files.readAttributes(rpConfiguration.configPath, BasicFileAttributes.class);
FileTime fileTime = attributes.lastModifiedTime();
if (lastModifiedTime != fileTime.toMillis()) {
lastModifiedTime = fileTime.toMillis();
Configuration configuration = ConfigurationBuilder.loadJsonConfigFile(path);
// important to overlay env vars here, so that we don't overwrite the value set by env var
ConfigurationBuilder.overlayEnvVars(configuration);
if (rpConfiguration.lastModifiedTime != fileTime.toMillis()) {
rpConfiguration.lastModifiedTime = fileTime.toMillis();
RpConfiguration newRpConfiguration = RpConfigurationBuilder.loadJsonConfigFile(rpConfiguration.configPath);

if (!configuration.connectionString.equals(TelemetryConfiguration.getActive().getConnectionString())) {
if (!newRpConfiguration.connectionString.equals(TelemetryConfiguration.getActive().getConnectionString())) {
logger.debug("Connection string from the JSON config file is overriding the previously configured connection string.");
TelemetryConfiguration.getActive().setConnectionString(configuration.connectionString);
TelemetryConfiguration.getActive().setConnectionString(newRpConfiguration.connectionString);
AppIdSupplier.startAppIdRetrieval();
}

if (configuration.sampling.percentage != lastReadSamplingPercentage) {
logger.debug("Updating sampling percentage from {} to {}", lastReadSamplingPercentage, configuration.sampling.percentage);
double roundedSamplingPercentage = SamplingPercentage.roundToNearest(configuration.sampling.percentage);
if (newRpConfiguration.sampling.percentage != rpConfiguration.sampling.percentage) {
logger.debug("Updating sampling percentage from {} to {}", rpConfiguration.sampling.percentage, newRpConfiguration.sampling.percentage);
double roundedSamplingPercentage = SamplingPercentage.roundToNearest(newRpConfiguration.sampling.percentage);
DelegatingSampler.getInstance().setDelegate(Samplers.getSampler(roundedSamplingPercentage));
Global.setSamplingPercentage(roundedSamplingPercentage);
lastReadSamplingPercentage = configuration.sampling.percentage;
rpConfiguration.sampling.percentage = newRpConfiguration.sampling.percentage;
}
rpConfiguration = newRpConfiguration;
}
} catch (IOException e) {
logger.error("Error occurred when polling json config file: {}", e.getMessage(), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration;
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.SelfDiagnostics;
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.ConfigurationBuilder;
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.RpConfiguration;
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.RpConfigurationBuilder;
import com.microsoft.applicationinsights.customExceptions.FriendlyException;
import io.opentelemetry.javaagent.tooling.AgentInstaller;
import org.slf4j.Logger;
Expand All @@ -47,23 +49,18 @@
// this class initializes configuration and logging before passing control to opentelemetry-java-instrumentation
public class MainEntryPoint {

private static RpConfiguration rpConfiguration;
private static Configuration configuration;
private static Path configPath;
private static long lastModifiedTime;

private MainEntryPoint() {
}

public static Configuration getConfiguration() {
return configuration;
}

public static Path getConfigPath() {
return configPath;
public static RpConfiguration getRpConfiguration() {
return rpConfiguration;
}

public static long getLastModifiedTime() {
return lastModifiedTime;
public static Configuration getConfiguration() {
return configuration;
}

// TODO turn this into an interceptor
Expand All @@ -75,9 +72,8 @@ public static void start(Instrumentation instrumentation, URL bootstrapURL) {
Path agentPath = new File(bootstrapURL.toURI()).toPath();
DiagnosticsHelper.setAgentJarFile(agentPath);
// configuration is only read this early in order to extract logging configuration
configuration = ConfigurationBuilder.create(agentPath);
configPath = configuration.configPath;
lastModifiedTime = configuration.lastModifiedTime;
rpConfiguration = RpConfigurationBuilder.create(agentPath);
configuration = ConfigurationBuilder.create(agentPath, rpConfiguration);
startupLogger = configureLogging(configuration.selfDiagnostics, agentPath);
ConfigurationBuilder.logConfigurationWarnMessages();
MDC.put(DiagnosticsHelper.MDC_PROP_OPERATION, "Startup");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

package com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand All @@ -30,7 +29,6 @@
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;


import com.microsoft.applicationinsights.customExceptions.FriendlyException;

import static java.util.concurrent.TimeUnit.MINUTES;
Expand Down Expand Up @@ -166,8 +164,6 @@ public static class PreviewConfiguration {
// so safer to only allow single interval for now
public int metricIntervalSeconds = 60;
public LiveMetrics liveMetrics = new LiveMetrics();
// config reload only supports connection string and sampling percentage
public boolean configReloadEnabled;
}

public static class LiveMetrics {
Expand Down Expand Up @@ -463,11 +459,4 @@ public static class ProcessorActionJson {
private static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}

// transient so that Moshi will ignore when binding from json
public transient Path configPath;

// transient so that Moshi will ignore when binding from json
public transient long lastModifiedTime;

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;

import com.microsoft.applicationinsights.agent.bootstrap.diagnostics.DiagnosticsHelper;
import com.microsoft.applicationinsights.agent.internal.wasbootstrap.configuration.Configuration.JmxMetric;
import com.microsoft.applicationinsights.customExceptions.FriendlyException;
import com.squareup.moshi.JsonAdapter;
Expand Down Expand Up @@ -75,9 +73,8 @@ public class ConfigurationBuilder {
// cannot use logger before loading configuration, so need to store warning messages locally until logger is initialized
private static final List<ConfigurationWarnMessage> configurationWarnMessages = new CopyOnWriteArrayList<>();

public static Configuration create(Path agentJarPath) throws IOException {
public static Configuration create(Path agentJarPath, RpConfiguration rpConfiguration) throws IOException {
Configuration config = loadConfigurationFile(agentJarPath);
overlayEnvVars(config);
if (config.instrumentation.micrometer.reportingIntervalSeconds != 60) {
configurationWarnMessages.add(new ConfigurationWarnMessage(
"micrometer \"reportingIntervalSeconds\" setting leaked out previously" +
Expand All @@ -86,6 +83,12 @@ public static Configuration create(Path agentJarPath) throws IOException {
" (and note that metricIntervalSeconds applies to all auto-collected metrics," +
" not only micrometer)"));
}
overlayEnvVars(config);
// rp configuration should always be last (so it takes precedence)
// currently applicationinsights-rp.json is only used by Azure Spring Cloud
if (rpConfiguration != null) {
overlayRpConfiguration(config, rpConfiguration);
}
return config;
}

Expand Down Expand Up @@ -157,8 +160,8 @@ private static void loadInstrumentationEnabledEnvVars(Configuration config) {
}

private static Configuration loadConfigurationFile(Path agentJarPath) throws IOException {
String configurationContent = System.getenv(APPLICATIONINSIGHTS_CONFIGURATION_CONTENT);
if (configurationContent != null && !configurationContent.isEmpty()) {
String configurationContent = getEnvVar(APPLICATIONINSIGHTS_CONFIGURATION_CONTENT);
if (configurationContent != null) {
return getConfigurationFromEnvVar(configurationContent, true);
}

Expand Down Expand Up @@ -198,8 +201,8 @@ public static void overlayEnvVars(Configuration config) throws IOException {
config.connectionString = overlayWithEnvVar(APPLICATIONINSIGHTS_CONNECTION_STRING, config.connectionString);
if (config.connectionString == null) {
// this is for backwards compatibility only
String instrumentationKey = System.getenv(APPINSIGHTS_INSTRUMENTATIONKEY);
if (instrumentationKey != null && !instrumentationKey.isEmpty()) {
String instrumentationKey = getEnvVar(APPINSIGHTS_INSTRUMENTATIONKEY);
if (instrumentationKey != null) {
// TODO log an info message recommending APPLICATIONINSIGHTS_CONNECTION_STRING
config.connectionString = "InstrumentationKey=" + instrumentationKey;
}
Expand Down Expand Up @@ -233,6 +236,16 @@ public static void overlayEnvVars(Configuration config) throws IOException {
loadInstrumentationEnabledEnvVars(config);
}

private static void overlayRpConfiguration(Configuration config, RpConfiguration rpConfiguration) {
String connectionString = rpConfiguration.connectionString;
if (!isTrimEmpty(connectionString)) {
config.connectionString = connectionString;
}
if (rpConfiguration.sampling != null) {
config.sampling.percentage = rpConfiguration.sampling.percentage;
}
}

private static String getConfigPath() {
String value = getEnvVar(APPLICATIONINSIGHTS_CONFIGURATION_FILE);
if (value != null) {
Expand Down Expand Up @@ -420,20 +433,12 @@ public static Configuration loadJsonConfigFile(Path configPath) throws IOExcepti
if (!Files.exists(configPath)) {
throw new IllegalStateException("config file does not exist: " + configPath);
}

BasicFileAttributes attributes = Files.readAttributes(configPath, BasicFileAttributes.class);
// important to read last modified before reading the file, to prevent possible race condition
// where file is updated after reading it but before reading last modified, and then since
// last modified doesn't change after that, the new updated file will not be read afterwards
long lastModifiedTime = attributes.lastModifiedTime().toMillis();
Configuration configuration = getConfigurationFromConfigFile(configPath, true);
if (configuration.instrumentationSettings != null) {
throw new IllegalStateException("It looks like you are using an old applicationinsights.json file" +
" which still has \"instrumentationSettings\", please see the docs for the new format:" +
" https://docs.microsoft.com/en-us/azure/azure-monitor/app/java-standalone-config");
}
configuration.configPath = configPath;
configuration.lastModifiedTime = lastModifiedTime;
return configuration;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.agent.internal.wasbootstrap.configuration;

import java.nio.file.Path;

// currently only Azure Spring Cloud uses this
public class RpConfiguration {

// transient so that Moshi will ignore when binding from json
public transient Path configPath;

// transient so that Moshi will ignore when binding from json
public transient long lastModifiedTime;

public String connectionString;
// intentionally null, so that we can tell if rp is providing or not
public Sampling sampling = new Sampling();

public static class Sampling {

public double percentage = 100;
}
}
Loading

0 comments on commit 1f703d4

Please sign in to comment.