Skip to content

Commit fbffa16

Browse files
authored
Better configuration debug logging (#2463)
* Better configuration debug logging * Fix * Feedback * Feedback * Feedback
1 parent 261e0ed commit fbffa16

File tree

1 file changed

+116
-105
lines changed

1 file changed

+116
-105
lines changed

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/configuration/ConfigurationBuilder.java

Lines changed: 116 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,18 @@
2424
import com.azure.monitor.opentelemetry.exporter.implementation.utils.HostName;
2525
import com.azure.monitor.opentelemetry.exporter.implementation.utils.Strings;
2626
import com.fasterxml.jackson.core.JsonParseException;
27+
import com.fasterxml.jackson.core.JsonProcessingException;
2728
import com.fasterxml.jackson.core.type.TypeReference;
2829
import com.fasterxml.jackson.databind.DeserializationFeature;
2930
import com.fasterxml.jackson.databind.JsonMappingException;
31+
import com.fasterxml.jackson.databind.JsonNode;
3032
import com.fasterxml.jackson.databind.ObjectMapper;
3133
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
3234
import com.microsoft.applicationinsights.agent.bootstrap.diagnostics.DiagnosticsHelper;
3335
import com.microsoft.applicationinsights.agent.internal.common.FriendlyException;
3436
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration.JmxMetric;
3537
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration.SamplingOverride;
3638
import java.io.IOException;
37-
import java.io.InputStream;
3839
import java.net.URL;
3940
import java.net.URLDecoder;
4041
import java.nio.charset.StandardCharsets;
@@ -120,6 +121,8 @@ public class ConfigurationBuilder {
120121
// cannot use logger before loading configuration, so need to store warning messages locally until
121122
// logger is initialized
122123
private static final ConfigurationLogger configurationLogger = new ConfigurationLogger();
124+
public static final String CONFIGURATION_OPTIONS_LINK =
125+
"https://go.microsoft.com/fwlink/?linkid=2153358";
123126

124127
// using deprecated fields to give warning message to user if they are still using them
125128
@SuppressWarnings("deprecation")
@@ -257,7 +260,7 @@ private static void overlayAadEnvVars(Configuration config) {
257260
try {
258261
keyValueMap = Strings.splitToMap(aadAuthString);
259262
} catch (IllegalArgumentException e) {
260-
throw new IllegalStateException(
263+
throw new ConfigurationException(
261264
"Unable to parse APPLICATIONINSIGHTS_AUTHENTICATION_STRING environment variable: "
262265
+ aadAuthString,
263266
e);
@@ -372,17 +375,17 @@ private static void overlayInstrumentationEnabledEnvVars(Configuration config) {
372375
config.instrumentation.springScheduling.enabled);
373376
}
374377

375-
private static Configuration loadConfigurationFile(Path agentJarPath) throws IOException {
378+
private static Configuration loadConfigurationFile(Path agentJarPath) {
376379
String configurationContent = getEnvVar(APPLICATIONINSIGHTS_CONFIGURATION_CONTENT);
377380
if (configurationContent != null) {
378381
return getConfigurationFromEnvVar(configurationContent);
379382
}
380383

381384
String runtimeAttachedConfigurationContent =
382-
System.getProperty(APPLICATIONINSIGHTS_RUNTIME_ATTACHED_CONFIGURATION_CONTENT);
385+
getSystemProperty(APPLICATIONINSIGHTS_RUNTIME_ATTACHED_CONFIGURATION_CONTENT);
383386
if (runtimeAttachedConfigurationContent != null) {
384387
return getConfiguration(
385-
runtimeAttachedConfigurationContent, true, ConfigurationOrigin.RUNTIME_ATTACHED);
388+
runtimeAttachedConfigurationContent, true, JsonOrigin.RUNTIME_ATTACHED);
386389
}
387390

388391
String configPathStr = getConfigPath();
@@ -392,7 +395,7 @@ private static Configuration loadConfigurationFile(Path agentJarPath) throws IOE
392395
return loadJsonConfigFile(configPath);
393396
} else {
394397
// fail fast any time configuration is invalid
395-
throw new IllegalStateException(
398+
throw new ConfigurationException(
396399
"could not find requested configuration file: " + configPathStr);
397400
}
398401
}
@@ -411,7 +414,7 @@ private static Configuration loadConfigurationFile(Path agentJarPath) throws IOE
411414
}
412415

413416
if (Files.exists(agentJarPath.resolveSibling("ApplicationInsights.json"))) {
414-
throw new IllegalStateException(
417+
throw new ConfigurationException(
415418
"found ApplicationInsights.json, but it should be lowercase: applicationinsights.json");
416419
}
417420

@@ -561,7 +564,7 @@ private static Configuration.Proxy overlayProxyFromEnv(Configuration.Proxy proxy
561564
} catch (IOException e) {
562565
throw new FriendlyException(
563566
"Error parsing environment variable APPLICATIONINSIGHTS_PROXY",
564-
"Learn more about configuration options here: https://go.microsoft.com/fwlink/?linkid=2153358",
567+
"Learn more about configuration options here: " + CONFIGURATION_OPTIONS_LINK,
565568
e);
566569
}
567570

@@ -588,23 +591,23 @@ static void overlayRpConfiguration(Configuration config, RpConfiguration rpConfi
588591
}
589592

590593
private static String getConfigPath() {
591-
String value = getEnvVar(APPLICATIONINSIGHTS_CONFIGURATION_FILE);
592-
if (value != null) {
593-
return value;
594+
String configPath = getEnvVar(APPLICATIONINSIGHTS_CONFIGURATION_FILE);
595+
if (configPath != null) {
596+
return configPath;
594597
}
595598
// intentionally not checking system properties for other system properties
596599
// with the intention to keep configuration paths minimal to help with supportability
597-
return Strings.trimAndEmptyToNull(System.getProperty("applicationinsights.configuration.file"));
600+
return getSystemProperty("applicationinsights.configuration.file");
598601
}
599602

600603
private static String getWebsiteSiteNameEnvVar() {
601-
String value = getEnvVar(WEBSITE_SITE_NAME);
604+
String websiteSiteName = getEnvVar(WEBSITE_SITE_NAME);
602605
// TODO we can update this check after the new functions model is deployed.
603-
if (value != null && "java".equals(System.getenv("FUNCTIONS_WORKER_RUNTIME"))) {
606+
if (websiteSiteName != null && "java".equals(getEnvVar("FUNCTIONS_WORKER_RUNTIME"))) {
604607
// special case for Azure Functions
605-
return value.toLowerCase(Locale.ENGLISH);
608+
return websiteSiteName.toLowerCase(Locale.ENGLISH);
606609
}
607-
return value;
610+
return websiteSiteName;
608611
}
609612

610613
public static String overlayWithSysPropEnvVar(
@@ -620,7 +623,6 @@ public static String overlayWithSysPropEnvVar(
620623
public static String overlayWithEnvVar(String name, String defaultValue) {
621624
String value = getEnvVar(name);
622625
if (value != null) {
623-
configurationLogger.debug("using environment variable: {}", name);
624626
return value;
625627
}
626628
return defaultValue;
@@ -629,7 +631,7 @@ public static String overlayWithEnvVar(String name, String defaultValue) {
629631
static float overlayWithEnvVar(String name, float defaultValue) {
630632
String value = getEnvVar(name);
631633
if (value != null) {
632-
configurationLogger.debug("using environment variable: {}", name);
634+
configurationLogger.debug("applying environment variable: {}={}", name, value);
633635
// intentionally allowing NumberFormatException to bubble up as invalid configuration and
634636
// prevent agent from starting
635637
return Float.parseFloat(value);
@@ -640,20 +642,28 @@ static float overlayWithEnvVar(String name, float defaultValue) {
640642
static boolean overlayWithEnvVar(String name, boolean defaultValue) {
641643
String value = getEnvVar(name);
642644
if (value != null) {
643-
configurationLogger.debug("using environment variable: {}", name);
645+
configurationLogger.debug("applying environment variable: {}={}", name, value);
644646
return Boolean.parseBoolean(value);
645647
}
646648
return defaultValue;
647649
}
648650

649651
// never returns empty string (empty string is normalized to null)
650652
protected static String getSystemProperty(String name) {
651-
return Strings.trimAndEmptyToNull(System.getProperty(name));
653+
String value = Strings.trimAndEmptyToNull(System.getProperty(name));
654+
if (value != null) {
655+
configurationLogger.debug("read system property: {}={}", name, value);
656+
}
657+
return value;
652658
}
653659

654660
// never returns empty string (empty string is normalized to null)
655661
protected static String getEnvVar(String name) {
656-
return Strings.trimAndEmptyToNull(System.getenv(name));
662+
String value = Strings.trimAndEmptyToNull(System.getenv(name));
663+
if (value != null) {
664+
configurationLogger.debug("read environment variable: {}={}", name, value);
665+
}
666+
return value;
657667
}
658668

659669
private static boolean isTrimEmpty(@Nullable String value) {
@@ -671,48 +681,32 @@ public static class ConfigurationException extends RuntimeException {
671681
}
672682
}
673683

674-
static Configuration getConfigurationFromConfigFile(Path configPath, boolean strict)
675-
throws IOException {
676-
ObjectMapper mapper = new ObjectMapper();
677-
try (InputStream in = Files.newInputStream(configPath)) {
678-
if (!strict) {
679-
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
680-
}
681-
return mapper.readValue(in, Configuration.class);
682-
} catch (UnrecognizedPropertyException ex) {
683-
if (strict) {
684-
Configuration configuration = getConfigurationFromConfigFile(configPath, false);
685-
configurationLogger.warn(
686-
getJsonEncodingExceptionMessageForFile(configPath, ex.getMessage()));
687-
return configuration;
688-
} else {
689-
throw new FriendlyException(
690-
getJsonEncodingExceptionMessageForFile(configPath, ex.getMessage()),
691-
"Learn more about configuration options here: https://go.microsoft.com/fwlink/?linkid=2153358");
692-
}
693-
} catch (JsonMappingException | JsonParseException ex) {
694-
throw new FriendlyException(
695-
"Error parsing configuration from file: "
696-
+ configPath.toAbsolutePath()
697-
+ System.lineSeparator()
698-
+ System.lineSeparator()
699-
+ ex.getMessage(),
700-
"Learn more about configuration options here: https://go.microsoft.com/fwlink/?linkid=2153358",
701-
ex);
702-
} catch (Exception e) {
703-
throw new ConfigurationException(
704-
"Error parsing configuration from file: " + configPath.toAbsolutePath(), e);
684+
private static class JsonOrigin {
685+
686+
private static final JsonOrigin ENV_VAR =
687+
new JsonOrigin("env var " + APPLICATIONINSIGHTS_CONFIGURATION_CONTENT);
688+
private static final JsonOrigin RUNTIME_ATTACHED =
689+
new JsonOrigin("JSON file coming from runtime attachment");
690+
691+
private final String description;
692+
693+
private static JsonOrigin fromPath(Path configPath) {
694+
return new JsonOrigin("file " + configPath.toAbsolutePath());
695+
}
696+
697+
private JsonOrigin(String description) {
698+
this.description = description;
705699
}
706-
}
707700

708-
enum ConfigurationOrigin {
709-
ENV_VAR,
710-
RUNTIME_ATTACHED
701+
@Override
702+
public String toString() {
703+
return description;
704+
}
711705
}
712706

713-
static Configuration getConfigurationFromEnvVar(String content) {
707+
static Configuration getConfigurationFromEnvVar(String json) {
714708

715-
Configuration configuration = getConfiguration(content, true, ConfigurationOrigin.ENV_VAR);
709+
Configuration configuration = getConfiguration(json, true, JsonOrigin.ENV_VAR);
716710

717711
if (configuration.connectionString != null) {
718712
throw new ConfigurationException(
@@ -725,55 +719,79 @@ static Configuration getConfigurationFromEnvVar(String content) {
725719
return configuration;
726720
}
727721

722+
private static Configuration loadJsonConfigFile(Path configPath) {
723+
if (!Files.exists(configPath)) {
724+
throw new ConfigurationException("config file does not exist: " + configPath);
725+
}
726+
Configuration configuration = getConfigurationFromConfigFile(configPath, true);
727+
if (configuration.instrumentationSettings != null) {
728+
throw new ConfigurationException(
729+
"It looks like you are using an old applicationinsights.json file"
730+
+ " which still has \"instrumentationSettings\", please see the docs for the new format:"
731+
+ " https://docs.microsoft.com/en-us/azure/azure-monitor/app/java-standalone-config");
732+
}
733+
return configuration;
734+
}
735+
736+
// visible for testing
737+
static Configuration getConfigurationFromConfigFile(Path configPath, boolean strict) {
738+
byte[] bytes;
739+
try {
740+
bytes = Files.readAllBytes(configPath);
741+
} catch (IOException e) {
742+
throw new ConfigurationException(
743+
"Error reading configuration file: " + configPath.toAbsolutePath(), e);
744+
}
745+
String json = new String(bytes, StandardCharsets.UTF_8);
746+
return getConfiguration(json, strict, JsonOrigin.fromPath(configPath));
747+
}
748+
728749
private static Configuration getConfiguration(
729-
String content, boolean strict, ConfigurationOrigin configurationOrigin) {
730-
Configuration configuration;
750+
String json, boolean strict, JsonOrigin jsonOrigin) {
751+
configurationLogger.debug("configuration: {}", json);
731752
ObjectMapper mapper = new ObjectMapper();
732753
if (!strict) {
733754
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
734755
}
756+
JsonNode jsonNode;
735757
try {
736-
configuration = mapper.readValue(content, Configuration.class);
737-
} catch (UnrecognizedPropertyException ex) {
758+
jsonNode = mapper.readTree(json);
759+
} catch (JsonProcessingException e) {
760+
throw new FriendlyException(
761+
"The configuration "
762+
+ jsonOrigin
763+
+ " contains malformed JSON."
764+
+ System.lineSeparator()
765+
+ System.lineSeparator()
766+
+ e.getMessage(),
767+
"Learn more about configuration options here: " + CONFIGURATION_OPTIONS_LINK,
768+
e);
769+
}
770+
try {
771+
return mapper.treeToValue(jsonNode, Configuration.class);
772+
} catch (UnrecognizedPropertyException e) {
738773
if (strict) {
739-
// Try extracting the configuration without failOnUnknown
740-
configuration = getConfiguration(content, false, configurationOrigin);
741-
// cannot use logger before loading configuration, so need to store warning messages locally
742-
// until logger is initialized
743-
configurationLogger.warn(
744-
getJsonEncodingExceptionMessage(ex.getMessage(), configurationOrigin));
774+
Configuration configuration = getConfiguration(json, false, jsonOrigin);
775+
configurationLogger.warn(getJsonEncodingExceptionMessage(e.getMessage(), jsonOrigin), e);
776+
return configuration;
745777
} else {
746778
throw new FriendlyException(
747-
getJsonEncodingExceptionMessage(ex.getMessage(), configurationOrigin),
748-
"Learn more about configuration options here: https://go.microsoft.com/fwlink/?linkid=2153358");
779+
getJsonEncodingExceptionMessage(e.getMessage(), jsonOrigin),
780+
"Learn more about configuration options here: " + CONFIGURATION_OPTIONS_LINK);
749781
}
750-
} catch (JsonMappingException | JsonParseException ex) {
782+
} catch (JsonParseException | JsonMappingException e) {
751783
throw new FriendlyException(
752-
getJsonEncodingExceptionMessage(ex.getMessage(), configurationOrigin),
753-
"Learn more about configuration options here: https://go.microsoft.com/fwlink/?linkid=2153358");
784+
"Error parsing configuration from "
785+
+ jsonOrigin
786+
+ "."
787+
+ System.lineSeparator()
788+
+ System.lineSeparator()
789+
+ e.getMessage(),
790+
"Learn more about configuration options here: " + CONFIGURATION_OPTIONS_LINK,
791+
e);
754792
} catch (Exception e) {
755-
if (ConfigurationOrigin.ENV_VAR.equals(configurationOrigin)) {
756-
throw new ConfigurationException(
757-
"Error parsing configuration from env var: "
758-
+ APPLICATIONINSIGHTS_CONFIGURATION_CONTENT,
759-
e);
760-
}
761-
throw new ConfigurationException("Error parsing configuration with runtime attachment", e);
762-
}
763-
return configuration;
764-
}
765-
766-
static String getJsonEncodingExceptionMessageForFile(Path configPath, String message) {
767-
return getJsonEncodingExceptionMessage(message, "file " + configPath.toAbsolutePath());
768-
}
769-
770-
static String getJsonEncodingExceptionMessage(
771-
String message, ConfigurationOrigin configurationOrigin) {
772-
if (ConfigurationOrigin.ENV_VAR.equals(configurationOrigin)) {
773-
return getJsonEncodingExceptionMessage(
774-
message, "env var " + APPLICATIONINSIGHTS_CONFIGURATION_CONTENT);
793+
throw new ConfigurationException("Error parsing configuration from " + jsonOrigin, e);
775794
}
776-
return getJsonEncodingExceptionMessage(message, "JSON file coming from runtime attachment");
777795
}
778796

779797
static String getJsonEncodingExceptionMessage(@Nullable String message, String location) {
@@ -783,18 +801,11 @@ static String getJsonEncodingExceptionMessage(@Nullable String message, String l
783801
return "The configuration " + location + " contains malformed JSON\n";
784802
}
785803

786-
public static Configuration loadJsonConfigFile(Path configPath) throws IOException {
787-
if (!Files.exists(configPath)) {
788-
throw new IllegalStateException("config file does not exist: " + configPath);
789-
}
790-
Configuration configuration = getConfigurationFromConfigFile(configPath, true);
791-
if (configuration.instrumentationSettings != null) {
792-
throw new IllegalStateException(
793-
"It looks like you are using an old applicationinsights.json file"
794-
+ " which still has \"instrumentationSettings\", please see the docs for the new format:"
795-
+ " https://docs.microsoft.com/en-us/azure/azure-monitor/app/java-standalone-config");
804+
static String getJsonEncodingExceptionMessage(String message, JsonOrigin jsonOrigin) {
805+
if (message != null && !message.isEmpty()) {
806+
return message;
796807
}
797-
return configuration;
808+
return "The configuration " + jsonOrigin + " contains malformed JSON\n";
798809
}
799810

800811
// this is for external callers, where logging is ok

0 commit comments

Comments
 (0)