Merge patch branch changes to main.
This commit is contained in:
commit
a6719b1093
61 files changed
+1320
-288
No files matched your search
@@ -1 +1,2 @@
|
||||
src/main/frontend/dist/**/*
|
||||
src/main/resources/META-INF/services/*
|
||||
@@ -26,6 +26,7 @@ import javax.ws.rs.ApplicationPath;
|
||||
import org.glassfish.hk2.api.ServiceLocator;
|
||||
import org.glassfish.jersey.jackson.JacksonFeature;
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.server.ServerProperties;
|
||||
import org.jvnet.hk2.guice.bridge.api.GuiceBridge;
|
||||
import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge;
|
||||
import org.slf4j.bridge.SLF4JBridgeHandler;
|
||||
@@ -68,6 +69,10 @@ public class GuacamoleApplication extends ResourceConfig {
|
||||
// Automatically scan for REST resources
|
||||
packages("org.apache.guacamole.rest");
|
||||
|
||||
// Explicitly disable WADL generation (we don't use it and Jersey warns
|
||||
// of the fact that it won't generate a WADL otherwise)
|
||||
property(ServerProperties.WADL_FEATURE_DISABLE, true);
|
||||
|
||||
// Use Jackson for JSON
|
||||
register(JacksonFeature.class);
|
||||
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole;
|
||||
|
||||
import ch.qos.logback.core.joran.spi.JoranException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Set;
|
||||
import javax.servlet.ServletContainerInitializer;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import org.apache.guacamole.log.GuacamoleLogbackServiceProvider;
|
||||
import org.apache.guacamole.log.LogLevel;
|
||||
import org.apache.guacamole.log.ReconfigurableLoggerFactory;
|
||||
import org.slf4j.ILoggerFactory;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.helpers.Reporter;
|
||||
|
||||
/**
|
||||
* ServletContainerInitializer which forces the SLF4J service provider to be
|
||||
* Guacamole's built-in logging system. This is necessary as Logback also
|
||||
* includes an SLF4J service provider, and there is no guarantee which provider
|
||||
* will be chosen by SLF4J in the case that multiple are present.
|
||||
* <p>
|
||||
* SLF4J uses a "slf4j.provider" system property to allow a specific provider
|
||||
* to be chosen, which this initializer sets automatically.
|
||||
*/
|
||||
public class GuacamoleServletContainerInitializer implements ServletContainerInitializer {
|
||||
|
||||
/**
|
||||
* The log level that should be used when we have not yet loaded any
|
||||
* configuration dictating otherwise.
|
||||
*/
|
||||
private static final LogLevel EARLY_LOG_LEVEL = LogLevel.INFO;
|
||||
|
||||
@Override
|
||||
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
|
||||
|
||||
// Ideally, we want to use Guacamole's built-in logging facility, but
|
||||
// we shouldn't override the administrator if they have their own SLF4J
|
||||
// provider that needs to be used instead (ie: for some log aggregation
|
||||
// service)
|
||||
if (System.getProperty(LoggerFactory.PROVIDER_PROPERTY_KEY) == null) {
|
||||
|
||||
System.setProperty(LoggerFactory.PROVIDER_PROPERTY_KEY,
|
||||
GuacamoleLogbackServiceProvider.class.getCanonicalName());
|
||||
|
||||
// Avoid excessive verbosity from SLF4J unless the administrator
|
||||
// has explicitly chosen SLF4J's verbosity level (the SLF4J provider
|
||||
// class will otherwise be logged at startup, which is an internal
|
||||
// detail and not typically needed)
|
||||
if (System.getProperty(Reporter.SLF4J_INTERNAL_VERBOSITY_KEY) == null)
|
||||
System.setProperty(Reporter.SLF4J_INTERNAL_VERBOSITY_KEY, "WARN");
|
||||
|
||||
}
|
||||
|
||||
// Attempt early initialization of Logback (before we begin parsing
|
||||
// Guacamole's configuration), such that even startup messages follow
|
||||
// a consistent format
|
||||
try (InputStream logbackConfiguration = EARLY_LOG_LEVEL.getLogbackConfiguration()) {
|
||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
||||
if (loggerFactory instanceof ReconfigurableLoggerFactory)
|
||||
((ReconfigurableLoggerFactory) loggerFactory).reconfigure(logbackConfiguration);
|
||||
}
|
||||
catch (JoranException | IOException e) {
|
||||
System.err.println("Early initialization of Logback failed - "
|
||||
+ "logging format may not match until the web application "
|
||||
+ "finishes starting up: " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -166,8 +166,7 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
|
||||
logger.info("Read configuration parameters from \"{}\".", guacProperties);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Unable to read guacamole.properties: {}", e.getMessage());
|
||||
logger.debug("Error reading guacamole.properties.", e);
|
||||
logger.error("Unable to read guacamole.properties: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// For any values not defined in GUACAMOLE_HOME/guacamole.properties,
|
||||
@@ -181,8 +180,7 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
|
||||
}
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Unable to configure support for environment properties: {}", e.getMessage());
|
||||
logger.debug("Error reading \"{}\" property from guacamole.properties.", ENABLE_ENVIRONMENT_PROPERTIES.getName(), e);
|
||||
logger.error("Unable to configure support for environment properties: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// For any values not defined in GUACAMOLE_HOME/guacamole.properties
|
||||
@@ -198,8 +196,7 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
|
||||
}
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Unable to configure support for file environment properties: {}", e.getMessage());
|
||||
logger.debug("Error reading \"{}\" property from guacamole.properties.", ENABLE_FILE_ENVIRONMENT_PROPERTIES.getName(), e);
|
||||
logger.error("Unable to configure support for file environment properties: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// Now that at least the main guacamole.properties source of
|
||||
@@ -225,8 +222,7 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
|
||||
});
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("An extension listening for application startup failed: {}", e.getMessage());
|
||||
logger.debug("Extension failed internally while handling the application startup event.", e);
|
||||
logger.error("An extension listening for application startup failed: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -301,14 +297,15 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
|
||||
}
|
||||
|
||||
// Inform any listeners that application shutdown has completed
|
||||
try {
|
||||
listenerService.handleEvent(new ApplicationShutdownEvent() {
|
||||
// The application shutdown event currently has no content
|
||||
});
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("An extension listening for application shutdown failed: {}", e.getMessage());
|
||||
logger.debug("Extension failed internally while handling the application shutdown event.", e);
|
||||
if (listenerService != null) {
|
||||
try {
|
||||
listenerService.handleEvent(new ApplicationShutdownEvent() {
|
||||
// The application shutdown event currently has no content
|
||||
});
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("An extension listening for application shutdown failed: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -320,8 +320,7 @@ public class GuacamoleSession {
|
||||
});
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("An extension listening for session invalidation failed: {}", e.getMessage());
|
||||
logger.debug("Extension failed internally while handling the session invalidation event.", e);
|
||||
logger.error("An extension listening for session invalidation failed: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -51,8 +51,8 @@ public class SystemFileEnvironmentGuacamoleProperties implements GuacamoleProper
|
||||
return Files.asCharSource(new File(filename), StandardCharsets.UTF_8).read();
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.error("Property \"{}\" could not be read from file \"{}\": {}", name, filename, e.getMessage());
|
||||
logger.debug("Error reading property value from file.", e);
|
||||
logger.error("Property \"{}\" could not be read from file \"{}\": {}", name,
|
||||
filename, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-8
@@ -106,13 +106,13 @@ public class FileAuthenticationProvider extends SimpleAuthenticationProvider {
|
||||
parser = SAXParserFactory.newInstance().newSAXParser();
|
||||
}
|
||||
catch (ParserConfigurationException e) {
|
||||
logger.error("Unable to create XML parser for reading \"{}\": {}", USER_MAPPING_FILENAME, e.getMessage());
|
||||
logger.debug("An instance of SAXParser could not be created.", e);
|
||||
logger.error("Unable to create XML parser for reading \"{}\": {}",
|
||||
USER_MAPPING_FILENAME, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
catch (SAXException e) {
|
||||
logger.error("Unable to create XML parser for reading \"{}\": {}", USER_MAPPING_FILENAME, e.getMessage());
|
||||
logger.debug("An instance of SAXParser could not be created.", e);
|
||||
logger.error("Unable to create XML parser for reading \"{}\": {}",
|
||||
USER_MAPPING_FILENAME, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -138,15 +138,13 @@ public class FileAuthenticationProvider extends SimpleAuthenticationProvider {
|
||||
|
||||
// If the file is unreadable, return no mapping
|
||||
catch (IOException e) {
|
||||
logger.warn("Unable to read user mapping file \"{}\": {}", userMappingFile, e.getMessage());
|
||||
logger.debug("Error reading user mapping file.", e);
|
||||
logger.warn("Unable to read user mapping file \"{}\": {}", userMappingFile, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the file cannot be parsed, return no mapping
|
||||
catch (SAXException e) {
|
||||
logger.warn("User mapping file \"{}\" is not valid: {}", userMappingFile, e.getMessage());
|
||||
logger.debug("Error parsing user mapping file.", e);
|
||||
logger.warn("User mapping file \"{}\" is not valid: {}", userMappingFile, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -183,30 +183,33 @@ public class EventLoggingListener implements Listener {
|
||||
if (creds.isEmpty())
|
||||
logger.debug("Empty authentication attempt (login screen "
|
||||
+ "initialization) from {} failed: {}",
|
||||
new RemoteAddress(creds), new Failure(event));
|
||||
new RemoteAddress(creds), new Failure(event), event.getFailure());
|
||||
else if (username == null || username.isEmpty())
|
||||
logger.debug("Anonymous authentication attempt from {} failed: {}",
|
||||
new RemoteAddress(creds), new Failure(event));
|
||||
new RemoteAddress(creds), new Failure(event), event.getFailure());
|
||||
else if (event.getFailure() instanceof GuacamoleInsufficientCredentialsException) {
|
||||
if (authProvider != null)
|
||||
logger.debug("Authentication attempt from {} for user \"{}\" "
|
||||
+ "requires additional credentials to continue: {} "
|
||||
+ "(requested by \"{}\")", new RemoteAddress(creds),
|
||||
username, new Failure(event), authProvider.getIdentifier());
|
||||
username, new Failure(event), authProvider.getIdentifier(),
|
||||
event.getFailure());
|
||||
else
|
||||
logger.debug("Authentication attempt from {} for user \"{}\" "
|
||||
+ "requires additional credentials to continue: {}",
|
||||
new RemoteAddress(creds), username, new Failure(event));
|
||||
new RemoteAddress(creds), username, new Failure(event),
|
||||
event.getFailure());
|
||||
}
|
||||
else {
|
||||
if (authProvider != null)
|
||||
logger.warn("Authentication attempt from {} for user \"{}\" "
|
||||
+ "failed: {} (rejected by \"{}\")", new RemoteAddress(creds),
|
||||
username, new Failure(event), authProvider.getIdentifier());
|
||||
username, new Failure(event), authProvider.getIdentifier(),
|
||||
event.getFailure());
|
||||
else
|
||||
logger.warn("Authentication attempt from {} for user \"{}\" "
|
||||
+ "failed: {}", new RemoteAddress(creds), username,
|
||||
new Failure(event));
|
||||
new Failure(event), event.getFailure());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-7
@@ -40,7 +40,7 @@ public class AuthenticationProviderFacade implements AuthenticationProvider {
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private Logger logger = LoggerFactory.getLogger(AuthenticationProviderFacade.class);
|
||||
private final Logger logger = LoggerFactory.getLogger(AuthenticationProviderFacade.class);
|
||||
|
||||
/**
|
||||
* The underlying authentication provider, or null if the authentication
|
||||
@@ -86,9 +86,11 @@ public class AuthenticationProviderFacade implements AuthenticationProvider {
|
||||
public AuthenticationProviderFacade(
|
||||
Class<? extends AuthenticationProvider> authProviderClass,
|
||||
Set<String> tolerateFailures) {
|
||||
|
||||
this.tolerateFailures = tolerateFailures;
|
||||
this.authProvider = ProviderFactory.newInstance("authentication provider",
|
||||
authProviderClass);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -155,15 +157,11 @@ public class AuthenticationProviderFacade implements AuthenticationProvider {
|
||||
* authentication provider being skipped.
|
||||
*/
|
||||
private void warnAuthProviderSkipped(Throwable e) {
|
||||
|
||||
logger.warn("The \"{}\" authentication provider has been skipped due "
|
||||
+ "to an internal error. If this is unexpected or you are the "
|
||||
+ "developer of this authentication provider, you may wish to "
|
||||
+ "enable debug-level logging: {}",
|
||||
getIdentifier(), e.getMessage());
|
||||
|
||||
logger.debug("Authentication provider skipped due to an internal failure.", e);
|
||||
|
||||
getIdentifier(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,7 +297,7 @@ public class AuthenticationProviderFacade implements AuthenticationProvider {
|
||||
|
||||
// Delegate to underlying auth provider
|
||||
return authProvider.updateUserContext(context, authenticatedUser, credentials);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.apache.guacamole.GuacamoleException;
|
||||
* A ClassLoader implementation which finds classes within .jar files within a
|
||||
* given directory.
|
||||
*/
|
||||
public class DirectoryClassLoader extends URLClassLoader {
|
||||
public class DirectoryClassLoader extends URLClassLoader implements LoggerContextProvider {
|
||||
|
||||
/**
|
||||
* Returns all .jar files within the given directory as an array of URLs.
|
||||
@@ -105,4 +105,9 @@ public class DirectoryClassLoader extends URLClassLoader {
|
||||
super(getJarURLs(dir), DirectoryClassLoader.class.getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLoggerContext() {
|
||||
return "lib";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -397,7 +397,7 @@ public class Extension {
|
||||
}
|
||||
|
||||
// Create isolated classloader for this extension
|
||||
classLoader = new ExtensionClassLoader(file, temporaryFiles, parent);
|
||||
classLoader = new ExtensionClassLoader(manifest, file, temporaryFiles, parent);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ import org.slf4j.LoggerFactory;
|
||||
* the given .jar. If classes are defined in both the parent and the extension
|
||||
* .jar, the versions defined within the extension .jar are used.
|
||||
*/
|
||||
public class ExtensionClassLoader extends URLClassLoader {
|
||||
public class ExtensionClassLoader extends URLClassLoader implements LoggerContextProvider {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
@@ -71,6 +71,11 @@ public class ExtensionClassLoader extends URLClassLoader {
|
||||
*/
|
||||
private final ClassLoader parent;
|
||||
|
||||
/**
|
||||
* The parsed manifest file of the extension that this class loader is for.
|
||||
*/
|
||||
private final ExtensionManifest manifest;
|
||||
|
||||
/**
|
||||
* Returns the URL that refers to the given file. If the given file refers
|
||||
* to a directory, an exception is thrown.
|
||||
@@ -251,6 +256,9 @@ public class ExtensionClassLoader extends URLClassLoader {
|
||||
* multiple times will not affect previously-returned instances of
|
||||
* ExtensionClassLoader.
|
||||
*
|
||||
* @param manifest
|
||||
* The parsed manifest of the extension.
|
||||
*
|
||||
* @param extension
|
||||
* The extension .jar file from which classes should be loaded.
|
||||
*
|
||||
@@ -267,10 +275,11 @@ public class ExtensionClassLoader extends URLClassLoader {
|
||||
* If the given file is not actually a file, or the contents of the
|
||||
* file cannot be read.
|
||||
*/
|
||||
public ExtensionClassLoader(File extension, List<File> temporaryFiles,
|
||||
ClassLoader parent) throws GuacamoleException {
|
||||
public ExtensionClassLoader(ExtensionManifest manifest, File extension,
|
||||
List<File> temporaryFiles, ClassLoader parent) throws GuacamoleException {
|
||||
super(getExtensionURLs(extension, temporaryFiles), null);
|
||||
this.parent = parent;
|
||||
this.manifest = manifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -288,4 +297,9 @@ public class ExtensionClassLoader extends URLClassLoader {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLoggerContext() {
|
||||
return "ext:" + manifest.getNamespace();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -434,8 +434,9 @@ public class ExtensionModule extends ServletModule {
|
||||
|
||||
// Use empty set by default if property cannot be parsed
|
||||
catch (GuacamoleException e) {
|
||||
logger.warn("The list of authentication providers specified via the \"{}\" property could not be parsed: {}", SKIP_IF_UNAVAILABLE.getName(), e.getMessage());
|
||||
logger.debug("Unable to parse \"{}\" property.", SKIP_IF_UNAVAILABLE.getName(), e);
|
||||
logger.warn("The list of authentication providers specified via "
|
||||
+ "the \"{}\" property could not be parsed: {}",
|
||||
SKIP_IF_UNAVAILABLE.getName(), e.getMessage(), e);
|
||||
return Collections.<String>emptySet();
|
||||
}
|
||||
|
||||
@@ -457,8 +458,9 @@ public class ExtensionModule extends ServletModule {
|
||||
|
||||
// Sort by filename if the desired order cannot be read
|
||||
catch (GuacamoleException e) {
|
||||
logger.warn("The list of extensions specified via the \"{}\" property could not be parsed: {}", EXTENSION_PRIORITY.getName(), e.getMessage());
|
||||
logger.debug("Unable to parse \"{}\" property.", EXTENSION_PRIORITY.getName(), e);
|
||||
logger.warn("The list of extensions specified via the \"{}\" "
|
||||
+ "property could not be parsed: {}",
|
||||
EXTENSION_PRIORITY.getName(), e.getMessage(), e);
|
||||
return ExtensionOrderProperty.DEFAULT_COMPARATOR;
|
||||
}
|
||||
|
||||
@@ -522,8 +524,8 @@ public class ExtensionModule extends ServletModule {
|
||||
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Extension \"{}\" could not be loaded: {}", extensionFile.getName(), e.getMessage());
|
||||
logger.debug("Unable to load extension.", e);
|
||||
logger.error("Extension \"{}\" could not be loaded: {}",
|
||||
extensionFile.getName(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -125,8 +125,7 @@ public class LanguageResourceService {
|
||||
// Warn of failure to parse
|
||||
catch (GuacamoleException e) {
|
||||
parsedAllowedLanguages = null;
|
||||
logger.error("Unable to parse list of allowed languages: {}", e.getMessage());
|
||||
logger.debug("Error parsing list of allowed languages.", e);
|
||||
logger.error("Unable to parse list of allowed languages: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
this.allowedLanguages = parsedAllowedLanguages;
|
||||
@@ -308,8 +307,7 @@ public class LanguageResourceService {
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.error("Unable to merge language resource \"{}\": {}", key, e.getMessage());
|
||||
logger.debug("Error merging language resource.", e);
|
||||
logger.error("Unable to merge language resource \"{}\": {}", key, e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -437,8 +435,7 @@ public class LanguageResourceService {
|
||||
|
||||
// Continue with next language if unable to read
|
||||
catch (IOException e) {
|
||||
logger.warn("Unable to read language resource \"{}\".", languageKey);
|
||||
logger.debug("Error reading language resource.", e);
|
||||
logger.warn("Unable to read language resource \"{}\".", languageKey, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.extension;
|
||||
|
||||
/**
|
||||
* Provider of general, human-readable text that describes the context of a
|
||||
* logger. This is currently used to allow our various custom class loaders to
|
||||
* provide contextual information, which our custom logger factory can pull from
|
||||
* the call stack when loggers are created.
|
||||
*/
|
||||
public interface LoggerContextProvider {
|
||||
|
||||
/**
|
||||
* Returns human-readable text describing the context of events logged by
|
||||
* a logger. The text should describe the general context of the source of
|
||||
* the entry.
|
||||
*
|
||||
* @return
|
||||
* Human-readable text describing the context of events logged by any
|
||||
* logger created/maintained by the class implementing this interface.
|
||||
*/
|
||||
String getLoggerContext();
|
||||
|
||||
}
|
||||
@@ -61,35 +61,29 @@ class ProviderFactory {
|
||||
catch (NoSuchMethodException e) {
|
||||
logger.error("The {} extension in use is not properly defined. "
|
||||
+ "Please contact the developers of the extension or, if you "
|
||||
+ "are the developer, turn on debug-level logging.", typeName);
|
||||
logger.debug("{} is missing a default constructor.",
|
||||
providerClass.getName(), e);
|
||||
+ "are the developer, turn on debug-level logging.",
|
||||
typeName, e);
|
||||
}
|
||||
catch (SecurityException e) {
|
||||
logger.error("The Java security manager is preventing extensions "
|
||||
+ "from being loaded. Please check the configuration of Java or your "
|
||||
+ "servlet container.");
|
||||
logger.debug("Creation of {} disallowed by security manager.",
|
||||
providerClass.getName(), e);
|
||||
+ "servlet container.", e);
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
logger.error("The {} extension in use is not properly defined. "
|
||||
+ "Please contact the developers of the extension or, if you "
|
||||
+ "are the developer, turn on debug-level logging.", typeName);
|
||||
logger.debug("{} cannot be instantiated.", providerClass.getName(), e);
|
||||
+ "are the developer, turn on debug-level logging.",
|
||||
typeName, e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
logger.error("The {} extension in use is not properly defined. "
|
||||
+ "Please contact the developers of the extension or, if you "
|
||||
+ "are the developer, turn on debug-level logging.");
|
||||
logger.debug("Default constructor of {} is not public.", typeName, e);
|
||||
+ "are the developer, turn on debug-level logging.", e);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
logger.error("The {} extension in use is not properly defined. "
|
||||
+ "Please contact the developers of the extension or, if you "
|
||||
+ "are the developer, turn on debug-level logging.", typeName);
|
||||
logger.debug("Default constructor of {} cannot accept zero arguments.",
|
||||
providerClass.getName(), e);
|
||||
+ "are the developer, turn on debug-level logging.", typeName, e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
// Obtain causing error - create relatively-informative stub error if cause is unknown
|
||||
@@ -97,8 +91,7 @@ class ProviderFactory {
|
||||
if (cause == null)
|
||||
cause = new GuacamoleException("Error encountered during initialization.");
|
||||
|
||||
logger.error("{} extension failed to start: {}", typeName, cause.getMessage());
|
||||
logger.debug("{} instantiation failed.", providerClass.getName(), e);
|
||||
logger.error("{} extension failed to start: {}", typeName, cause.getMessage(), cause);
|
||||
}
|
||||
|
||||
return instance;
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.log;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
import org.slf4j.event.Level;
|
||||
import org.slf4j.spi.LoggingEventBuilder;
|
||||
|
||||
/**
|
||||
* {@link Logger} that automatically includes arbitrary context. The additional
|
||||
* context is included as a {@link Marker}, and all logging functions are
|
||||
* overridden to include at least this Marker. If a logging function is given a
|
||||
* Marker by the caller, it is wrapped as a reference of Marker created by the
|
||||
* ContextAwareLogger's Marker.
|
||||
*/
|
||||
public class ContextAwareLogger implements Logger {
|
||||
|
||||
/**
|
||||
* The wrapped Logger.
|
||||
*/
|
||||
private final Logger logger;
|
||||
|
||||
/**
|
||||
* The arbitrary context that should be included as a Marker in all log
|
||||
* messages.
|
||||
*/
|
||||
private final String context;
|
||||
|
||||
/**
|
||||
* Creates a new ContextAwareLogger that wraps given Logger, automatically
|
||||
* including the provided context as a Marker for each logged message.
|
||||
*
|
||||
* @param context
|
||||
* The arbitrary context to include as a Marker with each
|
||||
* logged message.
|
||||
*
|
||||
* @param logger
|
||||
* The Logger to wrap.
|
||||
*/
|
||||
public ContextAwareLogger(String context, Logger logger) {
|
||||
this.logger = logger;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Marker containing the arbitrary context provided when this
|
||||
* ContextAwareLogger was created. If Markers are provided to this function,
|
||||
* they are included as references within the returned Marker.
|
||||
*
|
||||
* @param refs
|
||||
* The Markers to include in the returned Marker as references, if any.
|
||||
*
|
||||
* @return
|
||||
* A Marker containing the context provided when this
|
||||
* ContextAwareLogger was created, as well as any provided Marker
|
||||
* references.
|
||||
*/
|
||||
private Marker getContextMarker(Marker... refs) {
|
||||
Marker marker = MarkerFactory.getMarker(context);
|
||||
Arrays.stream(refs).forEach(marker::add);
|
||||
return marker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return logger.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingEventBuilder makeLoggingEventBuilder(Level level) {
|
||||
return logger.makeLoggingEventBuilder(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingEventBuilder atLevel(Level level) {
|
||||
return logger.atLevel(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForLevel(Level level) {
|
||||
return logger.isEnabledForLevel(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTraceEnabled() {
|
||||
return logger.isTraceEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String msg) {
|
||||
logger.trace(getContextMarker(), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String format, Object arg) {
|
||||
logger.trace(getContextMarker(), format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String format, Object arg1, Object arg2) {
|
||||
logger.trace(getContextMarker(), format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String format, Object... arguments) {
|
||||
logger.trace(getContextMarker(), format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String msg, Throwable t) {
|
||||
logger.trace(getContextMarker(), msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTraceEnabled(Marker marker) {
|
||||
return logger.isTraceEnabled(marker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingEventBuilder atTrace() {
|
||||
return logger.atTrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(Marker marker, String msg) {
|
||||
logger.trace(getContextMarker(marker), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(Marker marker, String format, Object arg) {
|
||||
logger.trace(getContextMarker(marker), format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(Marker marker, String format, Object arg1, Object arg2) {
|
||||
logger.trace(getContextMarker(marker), format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(Marker marker, String format, Object... argArray) {
|
||||
logger.trace(getContextMarker(marker), format, argArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(Marker marker, String msg, Throwable t) {
|
||||
logger.trace(getContextMarker(marker), msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebugEnabled() {
|
||||
return logger.isDebugEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String msg) {
|
||||
logger.debug(getContextMarker(), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String format, Object arg) {
|
||||
logger.debug(getContextMarker(), format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String format, Object arg1, Object arg2) {
|
||||
logger.debug(getContextMarker(), format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String format, Object... arguments) {
|
||||
logger.debug(getContextMarker(), format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String msg, Throwable t) {
|
||||
logger.debug(getContextMarker(), msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebugEnabled(Marker marker) {
|
||||
return logger.isDebugEnabled(marker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Marker marker, String msg) {
|
||||
logger.debug(getContextMarker(marker), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Marker marker, String format, Object arg) {
|
||||
logger.debug(getContextMarker(marker), format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Marker marker, String format, Object arg1, Object arg2) {
|
||||
logger.debug(getContextMarker(marker), format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Marker marker, String format, Object... arguments) {
|
||||
logger.debug(getContextMarker(marker), format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Marker marker, String msg, Throwable t) {
|
||||
logger.debug(getContextMarker(marker), msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingEventBuilder atDebug() {
|
||||
return logger.atDebug();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInfoEnabled() {
|
||||
return logger.isInfoEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String msg) {
|
||||
logger.info(getContextMarker(), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String format, Object arg) {
|
||||
logger.info(getContextMarker(), format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String format, Object arg1, Object arg2) {
|
||||
logger.info(getContextMarker(), format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String format, Object... arguments) {
|
||||
logger.info(getContextMarker(), format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String msg, Throwable t) {
|
||||
logger.info(getContextMarker(), msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInfoEnabled(Marker marker) {
|
||||
return logger.isInfoEnabled(marker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(Marker marker, String msg) {
|
||||
logger.info(getContextMarker(marker), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(Marker marker, String format, Object arg) {
|
||||
logger.info(getContextMarker(marker), format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(Marker marker, String format, Object arg1, Object arg2) {
|
||||
logger.info(getContextMarker(marker), format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(Marker marker, String format, Object... arguments) {
|
||||
logger.info(getContextMarker(marker), format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(Marker marker, String msg, Throwable t) {
|
||||
logger.info(getContextMarker(marker), msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingEventBuilder atInfo() {
|
||||
return logger.atInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWarnEnabled() {
|
||||
return logger.isWarnEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg) {
|
||||
logger.warn(getContextMarker(), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String format, Object arg) {
|
||||
logger.warn(getContextMarker(), format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String format, Object... arguments) {
|
||||
logger.warn(getContextMarker(), format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String format, Object arg1, Object arg2) {
|
||||
logger.warn(getContextMarker(), format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Throwable t) {
|
||||
logger.warn(getContextMarker(), msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWarnEnabled(Marker marker) {
|
||||
return logger.isWarnEnabled(marker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(Marker marker, String msg) {
|
||||
logger.warn(getContextMarker(marker), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(Marker marker, String format, Object arg) {
|
||||
logger.warn(getContextMarker(marker), format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(Marker marker, String format, Object arg1, Object arg2) {
|
||||
logger.warn(getContextMarker(marker), format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(Marker marker, String format, Object... arguments) {
|
||||
logger.warn(getContextMarker(marker), format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(Marker marker, String msg, Throwable t) {
|
||||
logger.warn(getContextMarker(marker), msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingEventBuilder atWarn() {
|
||||
return logger.atWarn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isErrorEnabled() {
|
||||
return logger.isErrorEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg) {
|
||||
logger.error(getContextMarker(), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String format, Object arg) {
|
||||
logger.error(getContextMarker(), format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String format, Object arg1, Object arg2) {
|
||||
logger.error(getContextMarker(), format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String format, Object... arguments) {
|
||||
logger.error(getContextMarker(), format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg, Throwable t) {
|
||||
logger.error(getContextMarker(), msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isErrorEnabled(Marker marker) {
|
||||
return logger.isErrorEnabled(marker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(Marker marker, String msg) {
|
||||
logger.error(getContextMarker(marker), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(Marker marker, String format, Object arg) {
|
||||
logger.error(getContextMarker(marker), format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(Marker marker, String format, Object arg1, Object arg2) {
|
||||
logger.error(getContextMarker(marker), format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(Marker marker, String format, Object... arguments) {
|
||||
logger.error(getContextMarker(marker), format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(Marker marker, String msg, Throwable t) {
|
||||
logger.error(getContextMarker(marker), msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingEventBuilder atError() {
|
||||
return logger.atError();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.log;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
import ch.qos.logback.classic.util.ContextInitializer;
|
||||
import ch.qos.logback.core.joran.spi.JoranException;
|
||||
import ch.qos.logback.core.util.StatusPrinter;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.guacamole.extension.LoggerContextProvider;
|
||||
import org.slf4j.ILoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.MDC;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.spi.MDCAdapter;
|
||||
|
||||
/**
|
||||
* {@link ILoggerFactory} implementation that automatically includes additional
|
||||
* {@link Marker} context based on the origin of each log message. The origin
|
||||
* of each message is determined by investigating the caller classes, with
|
||||
* the actual context provided by the associated classloader if it implements
|
||||
* {@link LoggerContextProvider}.
|
||||
*/
|
||||
public class ContextAwareLoggerFactory implements ReconfigurableLoggerFactory {
|
||||
|
||||
/**
|
||||
* The name of the LoggerContext maintained by this logger factory.
|
||||
*/
|
||||
private static final String LOGGER_CONTEXT_NAME = "guacamole";
|
||||
|
||||
/**
|
||||
* The default text that should be displayed if no other context can be
|
||||
* derived from any caller.
|
||||
*/
|
||||
private static final String DEFAULT_CONTEXT = "app";
|
||||
|
||||
/**
|
||||
* Regular expression that matches one or more newlines, regardless of style
|
||||
* (Windows, Linux, etc.), preserving only the first such newline. This
|
||||
* expression is intended to reliably match newlines that may be interpreted
|
||||
* by the shell/viewer, regardless of the platform producing the log
|
||||
* message.
|
||||
* <p>
|
||||
* This specific regular expression is intended for use with content that is
|
||||
* being included inline (concatenated with an existing line of output).
|
||||
*/
|
||||
private static final String INLINE_NEWLINE_REGEX =
|
||||
|
||||
// Locate the right edge of the first newline sequence, whether
|
||||
// that is CR, LF, CRLF, or LFCR
|
||||
"(?<="
|
||||
|
||||
// Do not match newline sequences that are not the first
|
||||
// newline in the sequence
|
||||
+ "(?<![\\n\\r])"
|
||||
|
||||
// Match either CR, LF, CRLF, or LFCR, ensuring that CRLF
|
||||
// does not get matched as just CR (same for LFCR vs. LF)
|
||||
+ "("
|
||||
+ "\\n(?!\\r)" // LF (with no following CR)
|
||||
+ "|\\r(?!\\n)" // CR (with no following LF)
|
||||
+ "|\\n\\r" // LFCR
|
||||
+ "|\\r\\n" // CRLF
|
||||
+ ")"
|
||||
|
||||
+ ")"
|
||||
|
||||
// Ignore (and strip) and remaining newline characters
|
||||
+ "[\\n\\r]*";
|
||||
|
||||
/**
|
||||
* Identical in purpose and behavior to {@link #INLINE_NEWLINE_REGEX}, but
|
||||
* intended for output that stands on its own as a block of text. This
|
||||
* variation preserves formatting of a block by matching the beginning of
|
||||
* all lines (regardless of whether preceded by a newline) and not matching
|
||||
* the final newline.
|
||||
*/
|
||||
private static final String BLOCK_NEWLINE_REGEX = "(^|" + INLINE_NEWLINE_REGEX + ")(?=[^\\r\\n])";
|
||||
|
||||
/**
|
||||
* Logback encoder pattern fragment that produces a local timestamp (with
|
||||
* timezone information) in roughly ISO 8601 format.
|
||||
*/
|
||||
private static final String LOG_PATTERN_FRAGMENT_TIMESTAMP = "%d{yyyy-MM-dd HH:mm:ss.SSS xxx}";
|
||||
|
||||
/**
|
||||
* The text that should be prepended to log messages when they span multiple
|
||||
* lines.
|
||||
*/
|
||||
private static final String CONTINUATION_PREFIX = "+ ";
|
||||
|
||||
/**
|
||||
* Logback encoder pattern fragment that produces the logged message. This
|
||||
* automatically prepends any newlines within the message with a "+ " prefix
|
||||
* to clearly represent when a message has been split across multiple lines.
|
||||
*/
|
||||
private static final String LOG_PATTERN_FRAGMENT_MESSAGE_BODY = "%replace(%msg){'" + INLINE_NEWLINE_REGEX + "','" + CONTINUATION_PREFIX + "'}";
|
||||
|
||||
/**
|
||||
* Logback encoder pattern that should be used for messages when the highest
|
||||
* level of verbosity is not needed.
|
||||
*/
|
||||
private static final String LOG_PATTERN_DEFAULT = LOG_PATTERN_FRAGMENT_TIMESTAMP
|
||||
+ " guacamole [%marker] %level: "
|
||||
+ LOG_PATTERN_FRAGMENT_MESSAGE_BODY + "%n"
|
||||
+ "%nopex";
|
||||
|
||||
/**
|
||||
* Logback encoder pattern that should be used for messages with the highest
|
||||
* level of verbosity.
|
||||
*/
|
||||
private static final String LOG_PATTERN_VERBOSE = LOG_PATTERN_FRAGMENT_TIMESTAMP
|
||||
+ " guacamole [%marker, thread:%thread] %level: %logger{36} - "
|
||||
+ LOG_PATTERN_FRAGMENT_MESSAGE_BODY + "%n"
|
||||
+ "%replace(%xThrowable){'" + BLOCK_NEWLINE_REGEX + "','" + CONTINUATION_PREFIX + "'}";
|
||||
|
||||
/**
|
||||
* The top-level Logback object, the LoggerContext, which is also Logback's
|
||||
* LoggerFactory implementation.
|
||||
*/
|
||||
private final LoggerContext context;
|
||||
|
||||
/**
|
||||
* Creates a new ContextAwareLoggerFactory that uses the given MDCAdapter
|
||||
* for the {@link MDC} implementation.
|
||||
*
|
||||
* @param mdcAdapter
|
||||
* The MDCAdapter that should be used to provide the MDC
|
||||
* implementation.
|
||||
*/
|
||||
public ContextAwareLoggerFactory(MDCAdapter mdcAdapter) {
|
||||
|
||||
context = new LoggerContext();
|
||||
context.setName(LOGGER_CONTEXT_NAME);
|
||||
context.setMDCAdapter(mdcAdapter);
|
||||
|
||||
// Perform basic init (prior to loading any Guacamole-specific
|
||||
// configuration, which is loaded by the LogModule)
|
||||
try {
|
||||
new ContextInitializer(context).autoConfig();
|
||||
}
|
||||
catch (JoranException | RuntimeException | Error e) {
|
||||
System.err.println("Logging system could not be initialized: " + e.getMessage());
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
context.start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the {@link StackWalker} class provided by Java9+ to retrieve all
|
||||
* classes currently on the call stack for the current function, in order of
|
||||
* decreasing depth. The current function (the deepest function in terms of
|
||||
* call depth) is the first item in the returned list.
|
||||
* <p>
|
||||
* NOTE: This method is only supported by Java 9 and late and will throw a
|
||||
* ClassNotFoundException otherwise..
|
||||
*
|
||||
* @return
|
||||
* A List of all classes currently on the call stack, in order of
|
||||
* decreasing depth.
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
* If support for StackWalker is not present (Java 8).
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Class<?>> getCallersFromStackWalker()
|
||||
throws UnsupportedOperationException {
|
||||
|
||||
//
|
||||
// NOTE: The following is the reflection equivalent of:
|
||||
//
|
||||
// return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk((stack) ->
|
||||
// stack.map((frame) -> frame.getDeclaringClass()).collect(Collectors.toList())
|
||||
// );
|
||||
//
|
||||
|
||||
try {
|
||||
|
||||
// Retrieve StackWalker definition and related classes
|
||||
Class<?> stackWalkerClass = Class.forName("java.lang.StackWalker");
|
||||
Class<?> stackFrameClass = Class.forName("java.lang.StackWalker$StackFrame");
|
||||
Class<Enum> optionEnum = (Class<Enum>) Class.forName("java.lang.StackWalker$Option");
|
||||
|
||||
// Retrieve references to functions we will be needing
|
||||
Method stackWalkerGetInstanceMethod = stackWalkerClass.getMethod("getInstance", optionEnum);
|
||||
Method walkMethod = stackWalkerClass.getMethod("walk", Function.class);
|
||||
Method getDeclaringClassMethod = stackFrameClass.getMethod("getDeclaringClass");
|
||||
|
||||
// Equivalent to:
|
||||
// StackWalker stackWalker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
|
||||
Object stackWalker = stackWalkerGetInstanceMethod.invoke(null,
|
||||
Enum.valueOf(optionEnum, "RETAIN_CLASS_REFERENCE"));
|
||||
|
||||
// Equivalent to:
|
||||
// (stack) -> stack.map((frame) -> frame.getDeclaringClass()).collect(Collectors.toList())
|
||||
Function<Stream<?>, List<Class<?>>> walker =
|
||||
(stack) -> stack.map((frame) -> {
|
||||
try {
|
||||
return (Class<?>) getDeclaringClassMethod.invoke(frame);
|
||||
}
|
||||
catch (ReflectiveOperationException e) {
|
||||
throw new UnsupportedOperationException("getDeclaringClass() "
|
||||
+ "could not be invoked for stack frame ", e);
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
// Equivalent to: stackWalker.walk(...)
|
||||
return (List<Class<?>>) walkMethod.invoke(stackWalker, walker);
|
||||
|
||||
}
|
||||
catch (ReflectiveOperationException e) {
|
||||
throw new UnsupportedOperationException("StackWalker is not "
|
||||
+ "supported by this version of Java", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the deprecated {@link SecurityManager} to retrieve all classes
|
||||
* currently on the call stack for the current function, in order of
|
||||
* decreasing depth. The current function (the deepest function in terms of
|
||||
* call depth) is the first item in the returned list.
|
||||
* <p>
|
||||
* NOTE: This is the only method that is supported by Java 8 that includes
|
||||
* direct Class references.
|
||||
*
|
||||
* @return
|
||||
* A List of all classes currently on the call stack, in order of
|
||||
* decreasing depth.
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
* If support for SecurityManager is not present (expected for future
|
||||
* Java versions).
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
private List<Class<?>> getCallersFromSecurityManager() {
|
||||
try {
|
||||
|
||||
// Atempt to locate SecurityManager class and grab its
|
||||
// getClassContext() method (which returns a stack trace)
|
||||
Class<?> securityManagerClass = Class.forName("java.lang.SecurityManager");
|
||||
Object securityManager = securityManagerClass.getDeclaredConstructor().newInstance();
|
||||
Method getClassContextMethod = securityManagerClass.getDeclaredMethod("getClassContext");
|
||||
|
||||
// getClassContext() is protected
|
||||
getClassContextMethod.setAccessible(true);
|
||||
|
||||
return Arrays.asList((Class<?>[]) getClassContextMethod.invoke(securityManager));
|
||||
|
||||
}
|
||||
catch (ReflectiveOperationException e) {
|
||||
throw new UnsupportedOperationException("SecurityManager is not "
|
||||
+ "supported by this version of Java", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all classes currently on the call stack for the current function,
|
||||
* in order of decreasing depth. The current function (the deepest function
|
||||
* in terms of call depth) is the first item in the returned list.
|
||||
*
|
||||
* @return
|
||||
* A List of all classes currently on the call stack, in order of
|
||||
* decreasing depth.
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
* If no supported mechanism exists for retrieving the call stack.
|
||||
*/
|
||||
private List<Class<?>> getCallers() throws UnsupportedOperationException {
|
||||
try {
|
||||
return getCallersFromStackWalker();
|
||||
}
|
||||
catch (UnsupportedOperationException e) {
|
||||
return getCallersFromSecurityManager();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the context of the message currently being logged based on the
|
||||
* content of the call stack.
|
||||
*
|
||||
* @return
|
||||
* The context of the message currently being logged.
|
||||
*/
|
||||
private String getCallerLogContext() {
|
||||
|
||||
// Derive context from the most recent caller that explicitly provides
|
||||
// their own logging context, if any
|
||||
for (Class<?> caller : getCallers()) {
|
||||
|
||||
ClassLoader classloader = caller.getClassLoader();
|
||||
if (classloader instanceof LoggerContextProvider)
|
||||
return ((LoggerContextProvider) classloader).getLoggerContext();
|
||||
|
||||
}
|
||||
|
||||
// Use default context if no callers provide their own
|
||||
return DEFAULT_CONTEXT;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger(String name) {
|
||||
return new ContextAwareLogger(getCallerLogContext(), context.getLogger(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure(InputStream logbackConfiguration) throws JoranException {
|
||||
|
||||
context.reset();
|
||||
|
||||
// Include convenience properties for building off Guacamole's
|
||||
// built-in configuration with a custom logback.xml
|
||||
context.putProperty("guac_timestamp_pattern", LOG_PATTERN_FRAGMENT_TIMESTAMP);
|
||||
context.putProperty("guac_message_pattern", LOG_PATTERN_FRAGMENT_MESSAGE_BODY);
|
||||
context.putProperty("guac_pattern", LOG_PATTERN_DEFAULT);
|
||||
context.putProperty("guac_verbose_pattern", LOG_PATTERN_VERBOSE);
|
||||
|
||||
// Initialize logback
|
||||
JoranConfigurator configurator = new JoranConfigurator();
|
||||
configurator.setContext(context);
|
||||
configurator.doConfigure(logbackConfiguration);
|
||||
|
||||
// Dump any errors that occur during logback init
|
||||
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.log;
|
||||
|
||||
import ch.qos.logback.classic.util.LogbackMDCAdapter;
|
||||
import org.slf4j.IMarkerFactory;
|
||||
import org.slf4j.helpers.BasicMarkerFactory;
|
||||
import org.slf4j.spi.MDCAdapter;
|
||||
import org.slf4j.spi.SLF4JServiceProvider;
|
||||
|
||||
/**
|
||||
* SLF4JServiceProvider that overrides the default behavior of Logback's
|
||||
* service provider, forcing the use of a custom, context-aware logger factory.
|
||||
*/
|
||||
public class GuacamoleLogbackServiceProvider implements SLF4JServiceProvider {
|
||||
|
||||
/**
|
||||
* The highest version of SLF4J that this service provider can support.
|
||||
* This is intentionally not actually a released version, but a reasonable
|
||||
* upper bound. This value is identical to the value used by Logback.
|
||||
*/
|
||||
private static final String SLF4J_API_MAX_VERSION = "2.0.99";
|
||||
|
||||
/**
|
||||
* The singleton instance of the LoggerFactory maintained by this service
|
||||
* provider. The custom implementation provided here uses the call stack
|
||||
* and our various custom class loaders to determine the context of the
|
||||
* loggers that will be created.
|
||||
*/
|
||||
private ContextAwareLoggerFactory loggerFactory;
|
||||
|
||||
/**
|
||||
* The singleton instance of the MDCAdapter implementation that should be
|
||||
* used by Logback and SLF4J.
|
||||
*/
|
||||
private final MDCAdapter mdcAdapter = new LogbackMDCAdapter();
|
||||
|
||||
/**
|
||||
* The singleton MarkerFactory instance maintained by this service
|
||||
* provider.
|
||||
*/
|
||||
private final IMarkerFactory markerFactory = new BasicMarkerFactory();
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
loggerFactory = new ContextAwareLoggerFactory(mdcAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MDCAdapter getMDCAdapter() {
|
||||
return mdcAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContextAwareLoggerFactory getLoggerFactory() {
|
||||
return loggerFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMarkerFactory getMarkerFactory() {
|
||||
return markerFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestedApiVersion() {
|
||||
return SLF4J_API_MAX_VERSION;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -56,14 +56,14 @@ public enum LogLevel {
|
||||
* messages, while verbose, will not affect performance.
|
||||
*/
|
||||
@PropertyValue("debug")
|
||||
DEBUG("debug"),
|
||||
DEBUG("debug", "${guac_verbose_pattern}"),
|
||||
|
||||
/**
|
||||
* Informational messages that may be useful for debugging, but which are
|
||||
* so low-level that they may affect performance.
|
||||
*/
|
||||
@PropertyValue("trace")
|
||||
TRACE("trace");
|
||||
TRACE("trace", "${guac_verbose_pattern}");
|
||||
|
||||
/**
|
||||
* Format string whose sole format argument is a String containing the
|
||||
@@ -77,7 +77,7 @@ public enum LogLevel {
|
||||
+ " <!-- Default appender -->\n"
|
||||
+ " <appender name=\"GUAC-DEFAULT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n"
|
||||
+ " <encoder>\n"
|
||||
+ " <pattern>%%d{HH:mm:ss.SSS} [%%thread] %%-5level %%logger{36} - %%msg%%n</pattern>\n"
|
||||
+ " <pattern>%s</pattern>\n"
|
||||
+ " </encoder>\n"
|
||||
+ " </appender>\n"
|
||||
+ "\n"
|
||||
@@ -102,9 +102,10 @@ public enum LogLevel {
|
||||
private final byte[] logbackConfig;
|
||||
|
||||
/**
|
||||
* Creates a new LogLevel with the given names. The pair of names provided
|
||||
* correspond to the name used within Guacamole's configuration and the
|
||||
* name used within Logback's configuration.
|
||||
* Creates a new LogLevel with the given names and pattern. The pair of
|
||||
* names provided correspond to the name used within Guacamole's
|
||||
* configuration and the name used within Logback's configuration, while
|
||||
* the pattern determines how Logback will convert log messages to text.
|
||||
*
|
||||
* @param canonicalName
|
||||
* The name that should be used for this log level when configuring
|
||||
@@ -113,10 +114,14 @@ public enum LogLevel {
|
||||
* @param logbackLogLevel
|
||||
* The name that would be provided to Logback to log at this level if
|
||||
* manually configuring Logback using "logback.xml".
|
||||
*
|
||||
* @param pattern
|
||||
* The Logback encoder pattern that should be use for messages logged
|
||||
* at this level.
|
||||
*/
|
||||
private LogLevel(String canonicalName, String logbackLogLevel) {
|
||||
private LogLevel(String canonicalName, String logbackLogLevel, String pattern) {
|
||||
this.canonicalName = canonicalName;
|
||||
this.logbackConfig = String.format(LOGBACK_XML_TEMPLATE, logbackLogLevel).getBytes(StandardCharsets.UTF_8);
|
||||
this.logbackConfig = String.format(LOGBACK_XML_TEMPLATE, pattern, logbackLogLevel).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +136,27 @@ public enum LogLevel {
|
||||
* "logback.xml" configuration file.
|
||||
*/
|
||||
private LogLevel(String logLevel) {
|
||||
this(logLevel, logLevel);
|
||||
this(logLevel, logLevel, "${guac_pattern}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new LogLevel with the given name and pattern. The provided name
|
||||
* corresponds to both the name used within Guacamole's configuration and
|
||||
* the name used within Logback's configuration, while the pattern
|
||||
* determines how Logback will convert log messages to text.
|
||||
*
|
||||
* @param logLevel
|
||||
* The name that should be used for this log level when configuring
|
||||
* Guacamole to log at this level using the "log-level" property AND
|
||||
* when manually configuring Logback to log at this level using a
|
||||
* "logback.xml" configuration file.
|
||||
*
|
||||
* @param pattern
|
||||
* The Logback encoder pattern that should be use for messages logged
|
||||
* at this level.
|
||||
*/
|
||||
private LogLevel(String logLevel, String pattern) {
|
||||
this(logLevel, logLevel, pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,10 +19,7 @@
|
||||
|
||||
package org.apache.guacamole.log;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
import ch.qos.logback.core.joran.spi.JoranException;
|
||||
import ch.qos.logback.core.util.StatusPrinter;
|
||||
import com.google.inject.AbstractModule;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -32,6 +29,7 @@ import java.io.InputStream;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.properties.EnumGuacamoleProperty;
|
||||
import org.slf4j.ILoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -97,7 +95,7 @@ public class LogModule extends AbstractModule {
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
logger.info("Logback configuration could not be read "
|
||||
+ "from \"{}\": {}", logbackFile, e.getMessage());
|
||||
+ "from \"{}\": {}", logbackFile, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +108,7 @@ public class LogModule extends AbstractModule {
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
level = LogLevel.INFO;
|
||||
logger.error("Falling back to \"{}\" log level: {}", level.getCanonicalName(), e.getMessage());
|
||||
logger.error("Falling back to \"{}\" log level: {}", level.getCanonicalName(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
return level.getLogbackConfiguration();
|
||||
@@ -122,25 +120,26 @@ public class LogModule extends AbstractModule {
|
||||
|
||||
try (InputStream logbackConfiguration = getLogbackConfiguration()) {
|
||||
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
context.reset();
|
||||
// Warn if the system is somehow preventing Guacamole from
|
||||
// using its own logging
|
||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
||||
if (!(loggerFactory instanceof ReconfigurableLoggerFactory)) {
|
||||
logger.warn("Guacamole depends on its own SLF4J service "
|
||||
+ "provider to configure logging, but your system has "
|
||||
+ "forced a different service provider to take "
|
||||
+ "priority. Guacamole's own logging configuration "
|
||||
+ "may not have any effect, and logging context may "
|
||||
+ "be missing.");
|
||||
}
|
||||
|
||||
// Initialize logback
|
||||
JoranConfigurator configurator = new JoranConfigurator();
|
||||
configurator.setContext(context);
|
||||
configurator.doConfigure(logbackConfiguration);
|
||||
|
||||
// Dump any errors that occur during logback init
|
||||
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
|
||||
((ReconfigurableLoggerFactory) loggerFactory).reconfigure(logbackConfiguration);
|
||||
|
||||
}
|
||||
catch (JoranException e) {
|
||||
logger.error("Initialization of logback failed: {}", e.getMessage());
|
||||
logger.debug("Unable to load logback configuration.", e);
|
||||
logger.error("Initialization of logback failed: {}", e.getMessage(), e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.warn("Logback configuration file could not be cleanly closed: {}", e.getMessage());
|
||||
logger.debug("Failed to close logback configuration file.", e);
|
||||
logger.warn("Logback configuration file could not be cleanly closed: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.log;
|
||||
|
||||
import ch.qos.logback.core.joran.spi.JoranException;
|
||||
import java.io.InputStream;
|
||||
import org.slf4j.ILoggerFactory;
|
||||
|
||||
/**
|
||||
* An {@link ILoggerFactory} that can be reconfigured as needed.
|
||||
*/
|
||||
public interface ReconfigurableLoggerFactory extends ILoggerFactory {
|
||||
|
||||
/**
|
||||
* Reconfigures this {@link ILoggerFactory} using the given InputStream for
|
||||
* a Logback XML configuration file. It is the responsibility of the caller
|
||||
* to close the provided InputStream.
|
||||
*
|
||||
* @param logbackConfiguration
|
||||
* An InputStream that provides the desired Logback configuration in
|
||||
* XML format.
|
||||
*
|
||||
* @throws JoranException
|
||||
* If the configuration is invalid, or an error occurs that prevents
|
||||
* the configuration from being read/applied.
|
||||
*/
|
||||
void reconfigure(InputStream logbackConfiguration) throws JoranException;
|
||||
|
||||
}
|
||||
@@ -75,7 +75,7 @@ public class RESTExceptionMapper implements ExceptionMapper<Throwable> {
|
||||
if (t instanceof GuacamoleUnauthorizedException) {
|
||||
String token = authenticationService.getAuthenticationToken(request);
|
||||
if (authenticationService.destroyGuacamoleSession(token))
|
||||
logger.debug("Implicitly invalidated session for token \"{}\"", token);
|
||||
logger.debug("Implicitly invalidated session for token \"{}\"", token, t);
|
||||
}
|
||||
|
||||
// Translate GuacamoleException subclasses to HTTP error codes
|
||||
@@ -84,11 +84,9 @@ public class RESTExceptionMapper implements ExceptionMapper<Throwable> {
|
||||
// Always log the human-readable details of GuacacamoleExceptions
|
||||
// for the benefit of the administrator
|
||||
if (t instanceof GuacamoleClientException)
|
||||
logger.debug("Client request rejected: {}", t.getMessage());
|
||||
else {
|
||||
logger.error("Request could not be processed: {}", t.getMessage());
|
||||
logger.debug("Processing of request aborted by extension.", t);
|
||||
}
|
||||
logger.debug("Client request rejected: {}", t.getMessage(), t);
|
||||
else
|
||||
logger.error("Request could not be processed: {}", t.getMessage(), t);
|
||||
|
||||
return Response
|
||||
.status(((GuacamoleException) t).getHttpStatusCode())
|
||||
@@ -100,13 +98,11 @@ public class RESTExceptionMapper implements ExceptionMapper<Throwable> {
|
||||
// Wrap unchecked exceptions
|
||||
String message = t.getMessage();
|
||||
if (message != null)
|
||||
logger.error("Unexpected internal error: {}", message);
|
||||
logger.error("Unexpected internal error: {}", message, t);
|
||||
else
|
||||
logger.error("An internal error occurred, but did not contain "
|
||||
+ "an error message. Enable debug-level logging for "
|
||||
+ "details.");
|
||||
|
||||
logger.debug("Unexpected error in REST endpoint.", t);
|
||||
+ "details.", t);
|
||||
|
||||
return Response
|
||||
.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
|
||||
@@ -433,12 +433,12 @@ public class AuthenticationService {
|
||||
// This line SHOULD be unreachable unless a bug causes
|
||||
// rethrowCause() to not actually rethrow the underlying failure
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null) {
|
||||
logger.warn("An underlying internal error was not correctly rethrown by rethrowCause(): {}", cause.getMessage());
|
||||
logger.debug("Internal error not rethrown by rethrowCause().", cause);
|
||||
}
|
||||
if (cause != null)
|
||||
logger.warn("An underlying internal error was not correctly "
|
||||
+ "rethrown by rethrowCause(): {}", cause.getMessage(), cause);
|
||||
else
|
||||
logger.warn("An underlying internal error was not correctly rethrown by rethrowCause().");
|
||||
logger.warn("An underlying internal error was not correctly "
|
||||
+ "rethrown by rethrowCause().");
|
||||
|
||||
throw e.getCauseAsGuacamoleException();
|
||||
|
||||
|
||||
@@ -81,8 +81,7 @@ public class HashTokenSessionMap implements TokenSessionMap {
|
||||
sessionTimeoutValue = environment.getProperty(API_SESSION_TIMEOUT, 60);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Unable to read guacamole.properties: {}", e.getMessage());
|
||||
logger.debug("Error while reading session timeout value.", e);
|
||||
logger.error("Unable to read guacamole.properties: {}", e.getMessage(), e);
|
||||
sessionTimeoutValue = 60;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,8 +69,7 @@ public class InterceptedStreamMap<T extends Closeable> {
|
||||
stream.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.warn("Unable to close intercepted stream: {}", e.getMessage());
|
||||
logger.debug("I/O error prevented closure of intercepted stream.", e);
|
||||
logger.warn("Unable to close intercepted stream: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// Notify waiting threads that the stream has ended
|
||||
|
||||
+1
-2
@@ -130,8 +130,7 @@ public class OutputStreamInterceptingFilter
|
||||
blob = BaseEncoding.base64().decode(data);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
logger.warn("Received base64 data for intercepted stream was invalid.");
|
||||
logger.debug("Decoding base64 data for intercepted stream failed.", e);
|
||||
logger.warn("Received base64 data for intercepted stream was invalid.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+3
-6
@@ -240,8 +240,7 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
tunnel = doConnect(tunnelRequest);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage(), e);
|
||||
closeConnection(connection, e.getStatus().getGuacamoleStatusCode(),
|
||||
e.getWebSocketCode());
|
||||
return;
|
||||
@@ -295,8 +294,7 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
// to pass within the WebSocket connection, logging
|
||||
// each error appropriately.
|
||||
catch (GuacamoleClientException e) {
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage(), e);
|
||||
closeConnection(connection, e.getStatus().getGuacamoleStatusCode(),
|
||||
e.getWebSocketCode());
|
||||
}
|
||||
@@ -305,8 +303,7 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
closeConnection(connection, GuacamoleStatus.SUCCESS);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||
logger.debug("Internal error during connection to guacd.", e);
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage(), e);
|
||||
closeConnection(connection, e.getStatus().getGuacamoleStatusCode(),
|
||||
e.getWebSocketCode());
|
||||
}
|
||||
|
||||
+3
-6
@@ -196,8 +196,7 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage(), e);
|
||||
closeConnection(session, e.getStatus().getGuacamoleStatusCode(), e.getWebSocketCode());
|
||||
return;
|
||||
}
|
||||
@@ -245,8 +244,7 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
// to pass within the WebSocket connection, logging
|
||||
// each error appropriately.
|
||||
catch (GuacamoleClientException e) {
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage(), e);
|
||||
closeConnection(session, e.getStatus().getGuacamoleStatusCode(),
|
||||
e.getWebSocketCode());
|
||||
}
|
||||
@@ -255,8 +253,7 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
closeConnection(session, GuacamoleStatus.SUCCESS);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||
logger.debug("Internal error during connection to guacd.", e);
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage(), e);
|
||||
closeConnection(session, e.getStatus().getGuacamoleStatusCode(),
|
||||
e.getWebSocketCode());
|
||||
}
|
||||
|
||||
+3
-6
@@ -268,8 +268,7 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
tunnel = doConnect(tunnelRequest);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage(), e);
|
||||
closeConnection(outbound, e.getStatus().getGuacamoleStatusCode(),
|
||||
e.getWebSocketCode());
|
||||
return;
|
||||
@@ -323,8 +322,7 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
// to pass within the WebSocket connection, logging
|
||||
// each error appropriately.
|
||||
catch (GuacamoleClientException e) {
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage(), e);
|
||||
closeConnection(outbound, e.getStatus().getGuacamoleStatusCode(),
|
||||
e.getWebSocketCode());
|
||||
}
|
||||
@@ -333,8 +331,7 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
closeConnection(outbound, GuacamoleStatus.SUCCESS);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||
logger.debug("Internal error during connection to guacd.", e);
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage(), e);
|
||||
closeConnection(outbound, e.getStatus().getGuacamoleStatusCode(),
|
||||
e.getWebSocketCode());
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.apache.guacamole.GuacamoleServletContainerInitializer
|
||||
@@ -0,0 +1 @@
|
||||
org.apache.guacamole.log.GuacamoleLogbackServiceProvider
|
||||
@@ -28,6 +28,14 @@
|
||||
<welcome-file>index.html</welcome-file>
|
||||
</welcome-file-list>
|
||||
|
||||
<!-- Ensure that our own ServletContainerInitializer is used to initialize
|
||||
logging, and that Logback does not potentially initialize SLF4J first (which
|
||||
would prevent our custom logging from taking effect) -->
|
||||
<context-param>
|
||||
<param-name>logbackDisableServletContainerInitializer</param-name>
|
||||
<param-value>true</param-value>
|
||||
</context-param>
|
||||
|
||||
<!-- Request that the browser always revalidate its cached copy of index.html -->
|
||||
<filter>
|
||||
<filter-name>cacheRevalidationFilter</filter-name>
|
||||
|
||||
Reference in new issue
Block a user