2424import com .azure .monitor .opentelemetry .exporter .implementation .utils .HostName ;
2525import com .azure .monitor .opentelemetry .exporter .implementation .utils .Strings ;
2626import com .fasterxml .jackson .core .JsonParseException ;
27+ import com .fasterxml .jackson .core .JsonProcessingException ;
2728import com .fasterxml .jackson .core .type .TypeReference ;
2829import com .fasterxml .jackson .databind .DeserializationFeature ;
2930import com .fasterxml .jackson .databind .JsonMappingException ;
31+ import com .fasterxml .jackson .databind .JsonNode ;
3032import com .fasterxml .jackson .databind .ObjectMapper ;
3133import com .fasterxml .jackson .databind .exc .UnrecognizedPropertyException ;
3234import com .microsoft .applicationinsights .agent .bootstrap .diagnostics .DiagnosticsHelper ;
3335import com .microsoft .applicationinsights .agent .internal .common .FriendlyException ;
3436import com .microsoft .applicationinsights .agent .internal .configuration .Configuration .JmxMetric ;
3537import com .microsoft .applicationinsights .agent .internal .configuration .Configuration .SamplingOverride ;
3638import java .io .IOException ;
37- import java .io .InputStream ;
3839import java .net .URL ;
3940import java .net .URLDecoder ;
4041import 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