This is an automated email from the ASF dual-hosted git repository.

shirshanka pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-gobblin.git


The following commit(s) were added to refs/heads/master by this push:
     new 3fbfd58  [GOBBLIN-1273] Improving handling and logging of config file 
load failures
3fbfd58 is described below

commit 3fbfd584f32e3eae5e16b8c8cd1878bef528465c
Author: Shirshanka Das <[email protected]>
AuthorDate: Thu Oct 8 10:50:31 2020 -0700

    [GOBBLIN-1273] Improving handling and logging of config file load failures
    
    Closes #3113 from shirshanka/confdebug
---
 .../org/apache/gobblin/util/PullFileLoader.java    | 97 ++++++++++++++--------
 .../apache/gobblin/util/PullFileLoaderTest.java    | 14 ++++
 .../resources/pullFileLoaderTest/dir2/badjob.conf  | 18 ++++
 3 files changed, 93 insertions(+), 36 deletions(-)

diff --git 
a/gobblin-utility/src/main/java/org/apache/gobblin/util/PullFileLoader.java 
b/gobblin-utility/src/main/java/org/apache/gobblin/util/PullFileLoader.java
index 6458683..4ed4698 100644
--- a/gobblin-utility/src/main/java/org/apache/gobblin/util/PullFileLoader.java
+++ b/gobblin-utility/src/main/java/org/apache/gobblin/util/PullFileLoader.java
@@ -26,13 +26,14 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
 import java.util.Properties;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.apache.commons.configuration.ConfigurationConverter;
 import org.apache.commons.configuration.ConfigurationException;
 import org.apache.commons.configuration.PropertiesConfiguration;
-import org.apache.commons.lang.exception.ExceptionUtils;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
@@ -47,6 +48,7 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.typesafe.config.Config;
+import com.typesafe.config.ConfigException;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigParseOptions;
 import com.typesafe.config.ConfigSyntax;
@@ -117,8 +119,7 @@ public class PullFileLoader {
   public PullFileLoader(Path rootDirectory, FileSystem fs, Collection<String> 
javaPropsPullFileExtensions,
       Collection<String> hoconPullFileExtensions) {
 
-    Set<String> commonExtensions = 
Sets.intersection(Sets.newHashSet(javaPropsPullFileExtensions),
-        Sets.newHashSet(hoconPullFileExtensions));
+    Set<String> commonExtensions = 
Sets.intersection(Sets.newHashSet(javaPropsPullFileExtensions), 
Sets.newHashSet(hoconPullFileExtensions));
     Preconditions.checkArgument(commonExtensions.isEmpty(),
         "Java props and HOCON pull file extensions intersect: " + 
Arrays.toString(commonExtensions.toArray()));
 
@@ -138,10 +139,10 @@ public class PullFileLoader {
    * @return The loaded {@link Config}.
    * @throws IOException
    */
-  public Config loadPullFile(Path path, Config sysProps, boolean 
loadGlobalProperties, boolean resolve) throws IOException {
+  public Config loadPullFile(Path path, Config sysProps, boolean 
loadGlobalProperties, boolean resolve)
+      throws IOException {
     Config fallback = loadGlobalProperties ? loadAncestorGlobalConfigs(path, 
sysProps) : sysProps;
     Config loadedConfig;
-
     if (this.javaPropsPullFileFilter.accept(path)) {
       loadedConfig = loadJavaPropsWithFallback(path, fallback);
     } else if (this.hoconPullFileFilter.accept(path)) {
@@ -153,7 +154,8 @@ public class PullFileLoader {
     return resolve ? loadedConfig.resolve() : loadedConfig;
   }
 
-  public Config loadPullFile(Path path, Config sysProps, boolean 
loadGlobalProperties) throws IOException {
+  public Config loadPullFile(Path path, Config sysProps, boolean 
loadGlobalProperties)
+      throws IOException {
     return loadPullFile(path, sysProps, loadGlobalProperties, true);
   }
 
@@ -163,7 +165,9 @@ public class PullFileLoader {
    * @param sysProps A {@link Config} used as fallback.
    * @param loadGlobalProperties if true, will also load at most one 
*.properties file per directory from the
    *          {@link #rootDirectory} to the pull file {@link Path} for each 
pull file.
-   * @return The loaded {@link Config}s.
+   * @return The loaded {@link Config}s. Files that fail to parse successfully 
will be logged,
+   *         but will not result in a Config object
+   *
    */
   public List<Config> loadPullFilesRecursively(Path path, Config sysProps, 
boolean loadGlobalProperties) {
     return Lists.transform(this.fetchJobFilesRecursively(path), new 
Function<Path, Config>() {
@@ -175,14 +179,14 @@ public class PullFileLoader {
         }
 
         try {
-          return PullFileLoader.this.loadPullFile(jobFile,
-              sysProps, loadGlobalProperties);
-        } catch (IOException e) {
-          log.error("Cannot load job from {} due to {}", jobFile, 
ExceptionUtils.getFullStackTrace(e));
+          return PullFileLoader.this.loadPullFile(jobFile, sysProps, 
loadGlobalProperties);
+        } catch (IOException ie) {
+          log.error("", ie);
           return null;
         }
       }
-    });
+    }).stream().filter(Objects::nonNull).collect(Collectors.toList());
+    // only return valid parsed configs
   }
 
   public List<Path> fetchJobFilesRecursively(Path path) {
@@ -231,12 +235,13 @@ public class PullFileLoader {
    * Higher directories will serve as fallback for lower directories, and 
sysProps will serve as fallback for all of them.
    * @throws IOException
    */
-  private Config loadAncestorGlobalConfigs(Path path, Config sysProps) throws 
IOException {
+  private Config loadAncestorGlobalConfigs(Path path, Config sysProps)
+      throws IOException {
     Config config = sysProps;
 
     if (!PathUtils.isAncestor(this.rootDirectory, path)) {
-      log.warn(String.format("Loaded path %s is not a descendant of root path 
%s. Cannot load global properties.",
-          path, this.rootDirectory));
+      log.warn(String.format("Loaded path %s is not a descendant of root path 
%s. Cannot load global properties.", path,
+          this.rootDirectory));
     } else {
 
       List<Path> ancestorPaths = Lists.newArrayList();
@@ -258,7 +263,8 @@ public class PullFileLoader {
    * @return The {@link Config} in path with sysProps as fallback.
    * @throws IOException
    */
-  private Config findAndLoadGlobalConfigInDirectory(Path path, Config 
fallback) throws IOException {
+  private Config findAndLoadGlobalConfigInDirectory(Path path, Config fallback)
+      throws IOException {
     FileStatus[] files = this.fs.listStatus(path, GLOBAL_PATH_FILTER);
     if (files == null) {
       log.warn("Could not list files at path " + path);
@@ -284,43 +290,62 @@ public class PullFileLoader {
    * @return The {@link Config} in path with fallback as fallback.
    * @throws IOException
    */
-  private Config loadJavaPropsWithFallback(Path propertiesPath, Config 
fallback) throws IOException {
+  private Config loadJavaPropsWithFallback(Path propertiesPath, Config 
fallback)
+      throws IOException {
 
     PropertiesConfiguration propertiesConfiguration = new 
PropertiesConfiguration();
-    try (InputStreamReader inputStreamReader = new 
InputStreamReader(this.fs.open(propertiesPath),
-        Charsets.UTF_8)) {
-      
propertiesConfiguration.setDelimiterParsingDisabled(ConfigUtils.getBoolean(fallback,
-                 PROPERTY_DELIMITER_PARSING_ENABLED_KEY, 
DEFAULT_PROPERTY_DELIMITER_PARSING_ENABLED_KEY));
+    try (InputStreamReader inputStreamReader = new 
InputStreamReader(this.fs.open(propertiesPath), Charsets.UTF_8)) {
+      propertiesConfiguration.setDelimiterParsingDisabled(ConfigUtils
+          .getBoolean(fallback, PROPERTY_DELIMITER_PARSING_ENABLED_KEY, 
DEFAULT_PROPERTY_DELIMITER_PARSING_ENABLED_KEY));
       propertiesConfiguration.load(inputStreamReader);
 
       Config configFromProps =
           
ConfigUtils.propertiesToConfig(ConfigurationConverter.getProperties(propertiesConfiguration));
 
       return 
ConfigFactory.parseMap(ImmutableMap.of(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY,
-          
PathUtils.getPathWithoutSchemeAndAuthority(propertiesPath).toString()))
-          .withFallback(configFromProps)
+          
PathUtils.getPathWithoutSchemeAndAuthority(propertiesPath).toString())).withFallback(configFromProps)
           .withFallback(fallback);
     } catch (ConfigurationException ce) {
+      log.error("Failed to load Java properties from file at {} due to {}", 
propertiesPath, ce.getLocalizedMessage());
       throw new IOException(ce);
     }
   }
 
-  private Config loadHoconConfigAtPath(Path path) throws IOException {
-    try (InputStream is = fs.open(path);
-        Reader reader = new InputStreamReader(is, Charsets.UTF_8)) {
-        return 
ConfigFactory.parseMap(ImmutableMap.of(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY,
-            PathUtils.getPathWithoutSchemeAndAuthority(path).toString()))
-            .withFallback(ConfigFactory.parseReader(reader, 
ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF)));
+  private Config loadHoconConfigAtPath(Path path)
+      throws IOException {
+    try (InputStream is = fs.open(path); Reader reader = new 
InputStreamReader(is, Charsets.UTF_8)) {
+      return ConfigFactory.parseMap(ImmutableMap
+          .of(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY, 
PathUtils.getPathWithoutSchemeAndAuthority(path).toString()))
+          .withFallback(ConfigFactory.parseReader(reader, 
ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF)));
+    } catch (ConfigException configException) {
+      throw wrapConfigException(path, configException);
     }
   }
 
-  private Config loadHoconConfigWithFallback(Path path, Config fallback) 
throws IOException {
-    try (InputStream is = fs.open(path);
-         Reader reader = new InputStreamReader(is, Charsets.UTF_8)) {
-      return 
ConfigFactory.parseMap(ImmutableMap.of(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY,
-          PathUtils.getPathWithoutSchemeAndAuthority(path).toString()))
-          .withFallback(ConfigFactory.parseReader(reader, 
ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF)))
-          .withFallback(fallback);
+  /**
+   * Wrap a {@link ConfigException} (which extends {@link RuntimeException} 
with an IOException,
+   * with a helpful message if possible
+   * @param path
+   * @param configException
+   * @return an {@link IOException} wrapping the passed in ConfigException.
+   */
+  private IOException wrapConfigException(Path path, ConfigException 
configException) {
+    if (configException.origin() != null) {
+      return new IOException("Failed to parse config file " + path.toString()
+          + " at lineNo:" + configException.origin().lineNumber(), 
configException);
+    } else {
+      return new IOException("Failed to parse config file " + path.toString(), 
configException);
+    }
+  }
+
+  private Config loadHoconConfigWithFallback(Path path, Config fallback)
+      throws IOException {
+    try (InputStream is = fs.open(path); Reader reader = new 
InputStreamReader(is, Charsets.UTF_8)) {
+      return ConfigFactory.parseMap(ImmutableMap
+          .of(ConfigurationKeys.JOB_CONFIG_FILE_PATH_KEY, 
PathUtils.getPathWithoutSchemeAndAuthority(path).toString()))
+          .withFallback(ConfigFactory.parseReader(reader, 
ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF))).withFallback(fallback);
+    } catch (ConfigException configException) {
+      throw wrapConfigException(path, configException);
     }
   }
 
diff --git 
a/gobblin-utility/src/test/java/org/apache/gobblin/util/PullFileLoaderTest.java 
b/gobblin-utility/src/test/java/org/apache/gobblin/util/PullFileLoaderTest.java
index 1c8bff8..094c536 100644
--- 
a/gobblin-utility/src/test/java/org/apache/gobblin/util/PullFileLoaderTest.java
+++ 
b/gobblin-utility/src/test/java/org/apache/gobblin/util/PullFileLoaderTest.java
@@ -118,6 +118,8 @@ public class PullFileLoaderTest {
     sysProps.put("key1", "sysProps1");
     Collection<Config> configs =
         loader.loadPullFilesRecursively(this.basePath, 
ConfigUtils.propertiesToConfig(sysProps), false);
+    // Only 4 files should generate configs (ajob.pull, bjob.pull, 
dir1/job.pull, dir1/job.conf)
+    Assert.assertEquals(configs.size(), 4);
 
     path = new Path(this.basePath, "ajob.pull");
     pullFile = pullFileFromPath(configs, path);
@@ -274,4 +276,16 @@ public class PullFileLoaderTest {
     throw new IOException("Not found.");
   }
 
+
+  @Test
+  public void testExceptionWrapping() throws Exception {
+    Path path = new Path(this.basePath, "dir2/badjob.conf");
+    try {
+      Config pullFile = loader.loadPullFile(path, ConfigFactory.empty(), true);
+      Assert.fail("Should throw exception");
+    } catch (IOException ie) {
+      String message = ie.getMessage();
+      Assert.assertEquals(message, "Failed to parse config file " + 
path.toString() + " at lineNo:18");
+    }
+  }
 }
diff --git 
a/gobblin-utility/src/test/resources/pullFileLoaderTest/dir2/badjob.conf 
b/gobblin-utility/src/test/resources/pullFileLoaderTest/dir2/badjob.conf
new file mode 100644
index 0000000..d762c3e
--- /dev/null
+++ b/gobblin-utility/src/test/resources/pullFileLoaderTest/dir2/badjob.conf
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+key1=foo:122 // unquoted :

Reply via email to