/** * Executes (and returns the result) of the given callable, but surrounds that execution with a try/catch that * should allow SLF4J logging replay to occur in cases where the log messages would otherwise get swallowed. One * example where this is necessary is when launching an application while using a java agent (e.g. the NewRelic * agent) - if the app crashes before enough time has passed, some or all of the log messages sent to SLF4J would * not get a chance to be output, swallowing potentially crucial info about what went wrong. By catching any * exception and waiting long enough before letting the exception propagate, we give the agent enough time to let * SLF4J replay any log messages that were stored. The app will still crash exactly as it normally would, but the * application log should be filled with all log messages sent to SLF4J before it crashes. * * <p>See http://www.slf4j.org/codes.html#replay for more info. * * <p>This method will use the {@link #DEFAULT_CRASH_DELAY_MILLIS} as the crash delay value. If you want to specify * a different value you can call {@link #executeCallableWithLoggingReplayProtection(Callable, long)} instead. */ public static <T> T executeCallableWithLoggingReplayProtection(Callable<T> callable) throws Exception { return executeCallableWithLoggingReplayProtection(callable, DEFAULT_CRASH_DELAY_MILLIS); }
/** * Allow the appId and Environment to be overridden when loading TypesafeConfig files. * This will support resolution of different TypesafeConfig files without globally modifying the * appId or environment. This would allow region specific property files * while still maintaining the same appId or environment variables. * * @return {@code Pair<String,String>} with the {@code Left} the appId of the TypesafeConfig to load * and the {@code Right} the Environment. */ protected Pair<String,String> getAppIdAndEnvironmentPair(){ return MainClassUtils.getAppIdAndEnvironmentFromSystemProperties(); }
outputExceptionalShutdownMessage( "An exception occurred during startup (see exception stack trace following this message). " + "Attempting to force SLF4J to finish initializing to make sure all the log messages " outputExceptionalShutdownMessage( "Done with initialization call. Waiting " + delayInMillisIfExceptionOccurs + " milliseconds to give it time to finish " ); Thread.sleep(delayInMillisIfExceptionOccurs); outputExceptionalShutdownMessage("Done waiting. The app will crash now.", null, logger); outputExceptionalShutdownMessage( "SLF4J exploded while trying to force initialization with the following error:", lfEx, logger );
/** * Initializes the Archaius system and configures the Netty leak detection level (if necessary). * DO NOT CALL THIS DIRECTLY. Use {@link #launchServer(String[])} when you're ready to start the server. */ protected void infrastructureInit() { MainClassUtils.setupJbossLoggingToUseSlf4j(); try { Pair<String, String> appIdAndEnvironmentPair = MainClassUtils.getAppIdAndEnvironmentFromSystemProperties(); ConfigurationManager.loadCascadedPropertiesFromResources(appIdAndEnvironmentPair.getLeft()); } catch (IOException e) { throw new RuntimeException("Error loading Archaius properties", e); } AbstractConfiguration appConfig = ConfigurationManager.getConfigInstance(); Function<String, Boolean> hasPropertyFunction = (propKey) -> appConfig.getProperty(propKey) != null; Function<String, String> propertyExtractionFunction = (propKey) -> { // Properties in Archaius might be a Collection or an Object. Object propValObj = appConfig.getProperty(propKey); return (propValObj instanceof Collection) ? ((Collection<?>) propValObj).stream().map(String::valueOf).collect(Collectors.joining(",")) : String.valueOf(propValObj); }; Set<String> propKeys = new LinkedHashSet<>(); appConfig.getKeys().forEachRemaining(propKeys::add); MainClassUtils.logApplicationPropertiesIfDebugActionsEnabled( hasPropertyFunction, propertyExtractionFunction, propKeys, false ); MainClassUtils.setupNettyLeakDetectionLevel(hasPropertyFunction, propertyExtractionFunction); }
/** * Initializes the Typesafe Config system and configures the Netty leak detection level (if necessary). * DO NOT CALL THIS DIRECTLY. Use {@link #launchServer(String[])} when you're ready to start the server. */ protected void infrastructureInit() { MainClassUtils.setupJbossLoggingToUseSlf4j(); Pair<String, String> appIdAndEnvironmentPair = getAppIdAndEnvironmentPair(); Config appConfig = TypesafeConfigUtil .loadConfigForAppIdAndEnvironment(appIdAndEnvironmentPair.getLeft(), appIdAndEnvironmentPair.getRight()); MainClassUtils.logApplicationPropertiesIfDebugActionsEnabled(appConfig::hasPath, (path) -> appConfig.getAnyRef(path).toString(), appConfig.entrySet().stream() .map(Map.Entry::getKey) .collect(Collectors.toList()), false); MainClassUtils.setupNettyLeakDetectionLevel(appConfig::hasPath, appConfig::getString); setAppConfig(appConfig); }
@Test public void setupJbossLoggingToUseSlf4j_sets_relevant_system_property_to_slf4j() { // given assertThat(System.getProperty(JBOSS_LOGGING_PROVIDER_SYSTEM_PROP_KEY)).isNull(); // when MainClassUtils.setupJbossLoggingToUseSlf4j(); // then assertThat(System.getProperty(JBOSS_LOGGING_PROVIDER_SYSTEM_PROP_KEY)).isEqualTo("slf4j"); }
MainClassUtils.logApplicationPropertiesIfDebugActionsEnabled(hasPropertyFunction, propertyExtractionFunction, appPropNames, forceLogging);
MainClassUtils.setupNettyLeakDetectionLevel(hasPropertyFunction, propertyExtractionFunction);
@Test public void code_coverage_hoops() { // jump! new MainClassUtils(); }
/** * Call this when you're ready to launch/start the server. The {@code args} argument should be the same as what's * passed into the application's {@code public static void main} method entrypoint. */ @SuppressWarnings("UnusedParameters") public void launchServer(String[] args) throws Exception { MainClassUtils.executeCallableWithLoggingReplayProtection(() -> { infrastructureInit(); startServer(); return null; }); }
Pair<String, String> results = null; try { results = MainClassUtils.getAppIdAndEnvironmentFromSystemProperties();
/** * Call this when you're ready to launch/start the server. The {@code args} argument should be the same as what's * passed into the application's {@code public static void main} method entrypoint. */ @SuppressWarnings("UnusedParameters") public void launchServer(String[] args) throws Exception { MainClassUtils.executeCallableWithLoggingReplayProtection(() -> { infrastructureInit(); startServer(); return null; }); }
@Test public void executeCallableWithLoggingReplayProtection_does_nothing_if_callable_does_not_throw_exception() throws Exception { // given String expectedResult = UUID.randomUUID().toString(); Callable<String> callable = () -> expectedResult; // when String result = MainClassUtils.executeCallableWithLoggingReplayProtection(callable); // then assertThat(result).isEqualTo(expectedResult); }
@Test public void executeCallableWithLoggingReplayProtection_delays_by_specified_amount_if_callable_throws_exception() { // given System.setProperty(DELAY_CRASH_ON_STARTUP_SYSTEM_PROP_KEY, "true"); RuntimeException ex = new RuntimeException("kaboom"); Callable<String> callable = () -> { throw ex; }; long delay = 200; // when long beforeMillis = System.currentTimeMillis(); Throwable caughtEx = catchThrowable(() -> MainClassUtils.executeCallableWithLoggingReplayProtection(callable, delay)); long afterMillis = System.currentTimeMillis(); // then assertThat(caughtEx).isSameAs(ex); assertThat(afterMillis - beforeMillis).isGreaterThanOrEqualTo(delay); }
@DataProvider(value = { "true", "false" }) @Test public void executeCallableWithLoggingReplayProtection_does_not_delay_on_exception_if_system_property_is_set( boolean useNull ) { // given if (useNull) System.clearProperty(DELAY_CRASH_ON_STARTUP_SYSTEM_PROP_KEY); else System.setProperty(DELAY_CRASH_ON_STARTUP_SYSTEM_PROP_KEY, "false"); RuntimeException ex = new RuntimeException("kaboom"); Callable<String> callable = () -> { throw ex; }; long delay = 200; // when long beforeMillis = System.currentTimeMillis(); Throwable caughtEx = catchThrowable(() -> MainClassUtils.executeCallableWithLoggingReplayProtection(callable, delay)); long afterMillis = System.currentTimeMillis(); // then assertThat(caughtEx).isSameAs(ex); assertThat(afterMillis - beforeMillis).isLessThan(delay); } }