diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java index 20861bae9..ccbfb43d3 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java @@ -343,15 +343,13 @@ public class ModeledConnection extends ModeledChildDirectoryObject implements User { if (attributes.containsKey(ACCESS_WINDOW_START_ATTRIBUTE_NAME)) { try { getModel().setAccessWindowStart(parseTime(attributes.get(ACCESS_WINDOW_START_ATTRIBUTE_NAME))); } catch (ParseException e) { - logger.warn("Not setting start time of user access window: {}", e.getMessage()); - logger.debug("Unable to parse time attribute.", e); + logger.warn("Not setting start time of user access window: {}", e.getMessage(), e); } } @@ -451,8 +450,7 @@ public class ModeledUser extends ModeledPermissions implements User { if (attributes.containsKey(ACCESS_WINDOW_END_ATTRIBUTE_NAME)) { try { getModel().setAccessWindowEnd(parseTime(attributes.get(ACCESS_WINDOW_END_ATTRIBUTE_NAME))); } catch (ParseException e) { - logger.warn("Not setting end time of user access window: {}", e.getMessage()); - logger.debug("Unable to parse time attribute.", e); + logger.warn("Not setting end time of user access window: {}", e.getMessage(), e); } } @@ -460,8 +458,7 @@ public class ModeledUser extends ModeledPermissions implements User { if (attributes.containsKey(VALID_FROM_ATTRIBUTE_NAME)) { try { getModel().setValidFrom(parseDate(attributes.get(VALID_FROM_ATTRIBUTE_NAME))); } catch (ParseException e) { - logger.warn("Not setting user validity start date: {}", e.getMessage()); - logger.debug("Unable to parse date attribute.", e); + logger.warn("Not setting user validity start date: {}", e.getMessage(), e); } } @@ -469,8 +466,7 @@ public class ModeledUser extends ModeledPermissions implements User { if (attributes.containsKey(VALID_UNTIL_ATTRIBUTE_NAME)) { try { getModel().setValidUntil(parseDate(attributes.get(VALID_UNTIL_ATTRIBUTE_NAME))); } catch (ParseException e) { - logger.warn("Not setting user validity end date: {}", e.getMessage()); - logger.debug("Unable to parse date attribute.", e); + logger.warn("Not setting user validity end date: {}", e.getMessage(), e); } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java index 6064ddaf2..57bf2ddbf 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledUserContext.java @@ -325,13 +325,9 @@ public class ModeledUserContext extends RestrictedObject } catch (GuacamoleException e) { - - logger.warn( - "Unable to determine if access window enforcement is" - + " enabled for active sessions; enforcing by default: {}" - , e.getMessage()); - logger.debug("Unable to determine access window enforcement policy.", e); - + logger.warn("Unable to determine if access window enforcement is" + + " enabled for active sessions; enforcing by default: {}", + e.getMessage(), e); } // A user context is valid if the associated user's account is valid diff --git a/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/RequestValidationService.java b/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/RequestValidationService.java index 12cd1c6ab..f7a591407 100644 --- a/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/RequestValidationService.java +++ b/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/RequestValidationService.java @@ -78,14 +78,15 @@ public class RequestValidationService { // Deny all requests if restrictions cannot be parsed catch (GuacamoleException e) { - logger.warn("Authentication request from \"{}\" is DENIED due to parse error: {}", credentials.getRemoteAddress(), e.getMessage()); - logger.debug("Error parsing authentication request restrictions from guacamole.properties.", e); + logger.warn("Authentication request from \"{}\" is DENIED due to " + + "parse error: {}", credentials.getRemoteAddress(), e.getMessage(), e); return false; } // All requests are allowed if no restrictions are defined if (trustedNetworks.isEmpty()) { - logger.debug("Authentication request from \"{}\" is ALLOWED (no restrictions).", credentials.getRemoteAddress()); + logger.debug("Authentication request from \"{}\" is ALLOWED (no " + + "restrictions).", credentials.getRemoteAddress()); return true; } @@ -94,14 +95,16 @@ public class RequestValidationService { // Request is allowed if any subnet matches if (new IPAddressString(network).contains(new IPAddressString(credentials.getRemoteAddress()))) { - logger.debug("Authentication request from \"{}\" is ALLOWED (matched subnet).", credentials.getRemoteAddress()); + logger.debug("Authentication request from \"{}\" is ALLOWED " + + "(matched subnet).", credentials.getRemoteAddress()); return true; } } // Otherwise request is denied - no subnets matched - logger.debug("Authentication request from \"{}\" is DENIED (did not match subnet).", credentials.getRemoteAddress()); + logger.debug("Authentication request from \"{}\" is DENIED (did not " + + "match subnet).", credentials.getRemoteAddress()); return false; } diff --git a/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/user/UserDataService.java b/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/user/UserDataService.java index 33b5beb7d..717abfe07 100644 --- a/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/user/UserDataService.java +++ b/extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/user/UserDataService.java @@ -168,22 +168,19 @@ public class UserDataService { // Fail if base64 data is not valid catch (IllegalArgumentException e) { - logger.warn("Submitted data is not proper base64."); - logger.debug("Invalid base64 data.", e); + logger.warn("Submitted data is not proper base64: {}", e.getMessage(), e); return null; } // Handle lack of standard UTF-8 support (should never happen) catch (UnsupportedEncodingException e) { - logger.error("Unexpected lack of support for UTF-8: {}", e.getMessage()); - logger.debug("Unable to decode base64 data as UTF-8.", e); + logger.error("Unexpected lack of support for UTF-8: {}", e.getMessage(), e); return null; } // Fail if decryption or key retrieval fails for any reason catch (GuacamoleException e) { - logger.error("Decryption of received data failed: {}", e.getMessage()); - logger.debug("Unable to decrypt received data.", e); + logger.error("Decryption of received data failed: {}", e.getMessage(), e); return null; } @@ -205,8 +202,7 @@ public class UserDataService { // Fail UserData creation if JSON is invalid/unreadable catch (IOException e) { - logger.error("Received JSON is invalid: {}", e.getMessage()); - logger.debug("Error parsing UserData JSON.", e); + logger.error("Received JSON is invalid: {}", e.getMessage(), e); return null; } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java index 06f83b5eb..f9f76082e 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java @@ -322,8 +322,7 @@ public class LDAPConnectionService { ldapConnection.close(); logger.error("Binding with the LDAP server at \"{}\" as user " + "\"{}\" failed: {}", config.getLdapHost(), bindUser, - e.getMessage()); - logger.debug("Unable to bind to LDAP server.", e); + e.getMessage(), e); return null; } @@ -369,8 +368,8 @@ public class LDAPConnectionService { } catch (LdapInvalidDnException e) { logger.error("Credentials of existing connection cannot be used. " - + "The username used (\"{}\") is not a valid DN.", username); - logger.debug("Cannot bind using invalid DN.", e); + + "The username used (\"{}\") is not a valid DN.", + username, e); ldapConnection.close(); return null; } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java index 02814ac9c..42d57d19f 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java @@ -265,8 +265,7 @@ public class ObjectQueryService { + "URL \"{}\".", url); } catch (GuacamoleException e) { - logger.warn("Referral to \"{}\" could not be followed: {}", url, e.getMessage()); - logger.debug("Failed to follow LDAP referral.", e); + logger.warn("Referral to \"{}\" could not be followed: {}", url, e.getMessage(), e); } } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java index 15bd0026c..d0e838c75 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java @@ -122,7 +122,7 @@ public class ConfigurationService { } catch (IOException e) { - logger.error("\"{}\" could not be read/parsed: {}", ldapServers, e.getMessage()); + logger.error("\"{}\" could not be read/parsed: {}", ldapServers, e.getMessage(), e); } } else diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java index 50626ca3b..a40824dd4 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java @@ -202,8 +202,7 @@ public class ConnectionService { } catch (LdapInvalidAttributeValueException e) { logger.error("Invalid value for {} attribute: {}", - LDAP_ATTRIBUTE_NAME_ID, e.getMessage()); - logger.debug("LDAP exception while getting CN attribute.", e); + LDAP_ATTRIBUTE_NAME_ID, e.getMessage(), e); return null; } @@ -223,8 +222,7 @@ public class ConnectionService { config.setProtocol(protocol.getString()); } catch (LdapInvalidAttributeValueException e) { - logger.error("Invalid value of the protocol entry: {}", e.getMessage()); - logger.debug("LDAP exception when getting protocol value.", e); + logger.error("Invalid value of the protocol entry: {}", e.getMessage(), e); return null; } @@ -234,8 +232,7 @@ public class ConnectionService { proxyConfig = getProxyConfiguration(entry); } catch (GuacamoleException e) { - logger.error("Failed to retrieve proxy configuration.", e.getMessage()); - logger.debug("Guacamole Exception when retrieving proxy configuration.", e); + logger.error("Failed to retrieve proxy configuration.", e.getMessage(), e); return null; } @@ -250,8 +247,7 @@ public class ConnectionService { parameter = parameterAttribute.getString(); } catch (LdapInvalidAttributeValueException e) { - logger.warn("Parameter value not valid for {}: {}", cnName, e.getMessage()); - logger.debug("LDAP exception when getting parameter value.", e); + logger.warn("Parameter value not valid for {}: {}", cnName, e.getMessage(), e); return null; } parameterAttribute.remove(parameter); @@ -394,8 +390,7 @@ public class ConnectionService { return new GuacamoleProxyConfiguration(proxyHostname, proxyPort, proxyEncryption); } catch (LdapInvalidAttributeValueException e) { - logger.error("Invalid value in proxy configuration: {}", e.getMessage()); - logger.debug("LDAP exception fetching proxy attribute value.", e); + logger.error("Invalid value in proxy configuration: {}", e.getMessage(), e); throw new GuacamoleServerException("Invalid LDAP value in proxy configuration.", e); } } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java index b38fc9198..89049a00b 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java @@ -214,9 +214,7 @@ public class UserGroupService { } catch (LdapInvalidAttributeValueException e) { logger.error("User group missing identifier: {}", - e.getMessage()); - logger.debug("LDAP exception while getting " - + "group identifier.", e); + e.getMessage(), e); } } } @@ -280,8 +278,7 @@ public class UserGroupService { } catch (LdapInvalidAttributeValueException e) { logger.error("User group missing identifier: {}", - e.getMessage()); - logger.debug("LDAP exception while getting group identifier.", e); + e.getMessage(), e); } }); diff --git a/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json index dc812d399..737d9533f 100644 --- a/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-ldap/src/main/resources/guac-manifest.json @@ -10,7 +10,6 @@ ], "translations" : [ - "translations/de.json", "translations/en.json", "translations/zh.json" ] diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/AuthenticationProviderService.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/AuthenticationProviderService.java index 8568ced7e..c82680288 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/AuthenticationProviderService.java @@ -94,16 +94,18 @@ public class AuthenticationProviderService { // Try to get the state attribute - if it's not there, we have a problem RadiusAttribute stateAttr = challengePacket.findAttribute(Attr_State.TYPE); if (stateAttr == null) { - logger.error("Something went wrong, state attribute not present."); - logger.debug("State Attribute turned up null, which shouldn't happen in AccessChallenge."); + logger.error("RADIUS server did not include the required \"{}\" " + + "attribute in its challenge packet - cannot continue.", + Attr_State.NAME); return null; } // We need to get the reply message so we know what to ask the user RadiusAttribute replyAttr = challengePacket.findAttribute(Attr_ReplyMessage.TYPE); if (replyAttr == null) { - logger.error("No reply message received from the server."); - logger.debug("Expecting a Attr_ReplyMessage attribute on this packet, and did not get one."); + logger.error("RADIUS server did not include the required \"{}\" " + + "attribute in its challenge packet - cannot continue.", + Attr_ReplyMessage.NAME); return null; } @@ -162,8 +164,7 @@ public class AuthenticationProviderService { null); } catch (GuacamoleException e) { - logger.error("Cannot configure RADIUS server: {}", e.getMessage()); - logger.debug("Error configuring RADIUS server.", e); + logger.error("Cannot configure RADIUS server: {}", e.getMessage(), e); throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD); } } @@ -184,13 +185,11 @@ public class AuthenticationProviderService { stateBytes); } catch (IllegalArgumentException e) { - logger.warn("Illegal hexadecimal value while parsing RADIUS state string: {}", e.getMessage()); - logger.debug("Encountered exception while attempting to parse the hexidecimal state value.", e); + logger.warn("Illegal hexadecimal value while parsing RADIUS state string: {}", e.getMessage(), e); throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD); } catch (GuacamoleException e) { - logger.error("Cannot configure RADIUS server: {}", e.getMessage()); - logger.debug("Error configuring RADIUS server.", e); + logger.error("Cannot configure RADIUS server: {}", e.getMessage(), e); throw new GuacamoleInvalidCredentialsException("Authentication error.", CredentialsInfo.USERNAME_PASSWORD); } } diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java index 800e451a5..5dad9edd3 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java @@ -245,18 +245,15 @@ public class RadiusConnectionService { } catch (RadiusException e) { - logger.error("Unable to complete authentication.", e.getMessage()); - logger.debug("Authentication with RADIUS failed.", e); + logger.error("Unable to complete authentication: {}", e.getMessage(), e); return null; } catch (NoSuchAlgorithmException e) { - logger.error("No such RADIUS algorithm: {}", e.getMessage()); - logger.debug("Unknown RADIUS algorithm.", e); + logger.error("No such RADIUS algorithm: {}", e.getMessage(), e); return null; } catch (UnknownHostException e) { - logger.error("Could not resolve address: {}", e.getMessage()); - logger.debug("Exception resolving host address.", e); + logger.error("Could not resolve address: {}", e.getMessage(), e); return null; } finally { diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/host/HostRestrictionParser.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/host/HostRestrictionParser.java index d368c807a..3e5a14ec7 100644 --- a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/host/HostRestrictionParser.java +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/host/HostRestrictionParser.java @@ -64,8 +64,7 @@ public class HostRestrictionParser { addressList.add(hostName); } catch (HostNameException e) { - LOGGER.warn("Invalid host name or IP: {}", host); - LOGGER.debug("HostNameException.", e.getMessage()); + LOGGER.warn("Invalid host name or IP: {}", host, e); } } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java index 139ab2021..35a515837 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java @@ -122,12 +122,10 @@ public class TokenValidationService { } // Log any failures to validate/parse the JWT catch (MalformedClaimException e) { - logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage()); - logger.debug("Malformed claim within received JWT.", e); + logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage(), e); } catch (InvalidJwtException e) { - logger.info("Rejected invalid OpenID token: {}", e.getMessage()); - logger.debug("Invalid JWT received.", e); + logger.info("Rejected invalid OpenID token: {}", e.getMessage(), e); } return null; @@ -160,8 +158,7 @@ public class TokenValidationService { return username; } catch (MalformedClaimException e) { - logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage()); - logger.debug("Malformed claim within received JWT.", e); + logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage(), e); } // Warn if username was not present in token, as it likely means @@ -203,8 +200,7 @@ public class TokenValidationService { return Collections.unmodifiableSet(new HashSet<>(oidcGroups)); } catch (MalformedClaimException e) { - logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage()); - logger.debug("Malformed claim within received JWT.", e); + logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage(), e); } } @@ -262,8 +258,7 @@ public class TokenValidationService { return Collections.unmodifiableMap(tokens); } catch (MalformedClaimException e) { - logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage()); - logger.debug("Malformed claim within received JWT.", e); + logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage(), e); } } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/acs/AssertionConsumerServiceResource.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/acs/AssertionConsumerServiceResource.java index 7aadc73f5..492dc64a6 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/acs/AssertionConsumerServiceResource.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/acs/AssertionConsumerServiceResource.java @@ -125,8 +125,7 @@ public class AssertionConsumerServiceResource extends SSOResource { // If invalid, redirect back to main page to re-attempt authentication catch (GuacamoleException e) { - logger.warn("Authentication attempted with an invalid SAML response: {}", e.getMessage()); - logger.debug("Received SAML response failed validation.", e); + logger.warn("Authentication attempted with an invalid SAML response: {}", e.getMessage(), e); return Response.seeOther(guacBase).build(); } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLClientAuthenticationResource.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLClientAuthenticationResource.java index e9a798466..37977876f 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLClientAuthenticationResource.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/src/main/java/org/apache/guacamole/auth/ssl/SSLClientAuthenticationResource.java @@ -351,16 +351,16 @@ public class SSLClientAuthenticationResource extends SSOResource { } catch (GuacamoleClientException e) { - logger.warn("SSL/TLS client authentication attempt rejected: {}", e.getMessage()); - logger.debug("SSL/TLS client authentication failed.", e); + logger.warn("SSL/TLS client authentication attempt rejected: {}", + e.getMessage(), e); } catch (GuacamoleException e) { - logger.error("SSL/TLS client authentication attempt could not be processed: {}", e.getMessage()); - logger.debug("SSL/TLS client authentication failed.", e); + logger.error("SSL/TLS client authentication attempt could not be " + + "processed: {}", e.getMessage(), e); } catch (RuntimeException | Error e) { - logger.error("SSL/TLS client authentication attempt failed internally: {}", e.getMessage()); - logger.debug("Internal failure processing SSL/TLS client authentication attempt.", e); + logger.error("SSL/TLS client authentication attempt failed " + + "internally: {}", e.getMessage(), e); } return sessionManager.generateInvalid(); diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java index 3ebd85b5b..03e482103 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java @@ -128,8 +128,7 @@ public class UserVerificationService { // If key is not valid base32, warn but otherwise pretend the key does // not exist catch (IllegalArgumentException e) { - logger.warn("TOTP key of user \"{}\" is not valid base32.", self.getIdentifier()); - logger.debug("TOTP key is not valid base32.", e); + logger.warn("TOTP key of user \"{}\" is not valid base32.", self.getIdentifier(), e); return null; } @@ -218,9 +217,7 @@ public class UserVerificationService { + "lack permission to update their own account and the " + "TOTP extension was unable to obtain privileged access. " + "TOTP will be disabled for this user.", - self.getIdentifier()); - logger.debug("Permission denied to set TOTP key of user " - + "account.", e); + self.getIdentifier(), e); return false; } catch (GuacamoleUnsupportedException e) { @@ -422,8 +419,7 @@ public class UserVerificationService { } catch (InvalidKeyException e) { - logger.warn("User \"{}\" is associated with an invalid TOTP key.", username); - logger.debug("TOTP key is not valid.", e); + logger.warn("User \"{}\" is associated with an invalid TOTP key.", username, e); } // Provided code is not valid diff --git a/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnectionRecord.java b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnectionRecord.java index f1c53ff87..91f2ce0e6 100644 --- a/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnectionRecord.java +++ b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnectionRecord.java @@ -147,8 +147,8 @@ public class HistoryConnectionRecord extends DelegatingConnectionRecord { } catch (IOException e) { logger.warn("Possible session recording \"{}\" could not be " - + "identified as it cannot be read: {}", file, e.getMessage()); - logger.debug("Possible session recording \"{}\" could not be read.", file, e); + + "identified as it cannot be read: {}", + file, e.getMessage(), e); } finally { @@ -161,9 +161,7 @@ public class HistoryConnectionRecord extends DelegatingConnectionRecord { catch (IOException e) { logger.warn("Unexpected error closing recording file \"{}\": {}", - file, e.getMessage()); - logger.debug("Session recording file \"{}\" could not be closed.", - file, e); + file, e.getMessage(), e); } } @@ -297,8 +295,9 @@ public class HistoryConnectionRecord extends DelegatingConnectionRecord { .array()); } catch (MalformedURLException e) { - logger.warn("Ignoring file \"{}\" as a unique URL and UUID for that file could not be generated: {}", e.getMessage()); - logger.debug("URL for file \"{}\" could not be determined.", file, e); + logger.warn("Ignoring file \"{}\" as a unique URL and UUID for " + + "that file could not be generated: {}", + e.getMessage(), e); return; } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmAttributeService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmAttributeService.java index 8b3c5d938..c1aee2121 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmAttributeService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/conf/KsmAttributeService.java @@ -155,15 +155,12 @@ public class KsmAttributeService implements VaultAttributeService { catch (GuacamoleException e) { - logger.warn( - "Unable to determine if KSM user attributes " - + "should be exposed due to config parsing error: {}.", e.getMessage()); - logger.debug( - "Config parsing error prevented checking user attribute configuration", - e); - - // If the configuration can't be parsed, default to not exposing the attributes + // If the configuration can't be parsed, default to not exposing + // the attributes + logger.warn("Unable to determine if KSM user attributes should be " + + "exposed due to config parsing error: {}.", e.getMessage(), e); return Collections.emptyList(); + } } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java index f811e9421..bd412a720 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java @@ -781,7 +781,7 @@ public class KsmClient { // Show the warning only if there is no fallback function // and this was the last attempt logger.warn("Keeper notation \"{}\" could not be resolved " - + "to a record: {}", notation, e.getMessage()); + + "to a record: {}", notation, e.getMessage(), e); return CompletableFuture.completedFuture(null); } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/user/KsmUser.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/user/KsmUser.java index 9c002eb4d..6c7b6413a 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/user/KsmUser.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/user/KsmUser.java @@ -87,13 +87,10 @@ import com.google.inject.assistedinject.AssistedInject; boolean userKsmConfigEnabled = false; try { userKsmConfigEnabled = configurationService.getAllowUserConfig(); - } catch (GuacamoleException e) { - - logger.warn( - "Disabling user KSM config due to exception: {}" - , e.getMessage()); - logger.debug("Error looking up if user KSM config is enabled.", e); - + } + catch (GuacamoleException e) { + logger.warn("Disabling user KSM config due to exception: {}", + e.getMessage(), e); } // If user-specific KSM configuration is not enabled, do not expose the @@ -112,4 +109,4 @@ import com.google.inject.assistedinject.AssistedInject; return attributes; } - } \ No newline at end of file + } diff --git a/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelServlet.java b/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelServlet.java index 492076933..e6c1ac3eb 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelServlet.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelServlet.java @@ -328,13 +328,12 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { // Catch any thrown guacamole exception and attempt to pass within the // HTTP response, logging each error appropriately. catch (GuacamoleClientException e) { - logger.warn("HTTP tunnel request rejected: {}", e.getMessage()); + logger.warn("HTTP tunnel request rejected: {}", e.getMessage(), e); sendError(response, e.getStatus().getGuacamoleStatusCode(), e.getStatus().getHttpStatusCode(), e.getMessage()); } catch (GuacamoleException e) { - logger.error("HTTP tunnel request failed: {}", e.getMessage()); - logger.debug("Internal error in HTTP tunnel.", e); + logger.error("HTTP tunnel request failed: {}", e.getMessage(), e); sendError(response, e.getStatus().getGuacamoleStatusCode(), e.getStatus().getHttpStatusCode(), "Internal server error."); } diff --git a/guacamole-common/src/main/java/org/apache/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java b/guacamole-common/src/main/java/org/apache/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java index 772ce64b2..901ed8348 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java @@ -149,7 +149,12 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint { // connection has changed, and synchronization need only be performed // in context of the new remote. synchronized (remote) { - remote.sendText(instruction); + try { + remote.sendText(instruction); + } + catch (IllegalStateException e) { + throw new IOException("WebSocket message could not be sent.", e); + } } } @@ -205,8 +210,7 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint { } 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; @@ -265,8 +269,7 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint { // 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()); } @@ -275,15 +278,14 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint { 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()); } } catch (IOException e) { - logger.debug("I/O error prevents further reads.", e); + logger.debug("I/O error prevents further WebSocket communication.", e); closeConnection(session, GuacamoleStatus.SERVER_ERROR); } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/environment/DefaultEnvironment.java b/guacamole-ext/src/main/java/org/apache/guacamole/environment/DefaultEnvironment.java index a3db06c19..272a42ee3 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/environment/DefaultEnvironment.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/environment/DefaultEnvironment.java @@ -158,9 +158,8 @@ class DefaultEnvironment extends DelegatingEnvironment { logger.error("Defaulting to case-sensitive handling of " + "usernames and group names as the desired case " + "sensitivity configuration could not be read: {}", - e.getMessage()); + e.getMessage(), e); - logger.debug("Error reading case sensitivity configuration.", e); return CaseSensitivity.ENABLED; } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/environment/LocalEnvironment.java b/guacamole-ext/src/main/java/org/apache/guacamole/environment/LocalEnvironment.java index bd0f3f152..d323e2bfd 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/environment/LocalEnvironment.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/environment/LocalEnvironment.java @@ -266,8 +266,9 @@ public class LocalEnvironment implements Environment { } catch (IOException e) { - logger.error("Unable to read connection parameter information from \"{}\": {}", file.getAbsolutePath(), e.getMessage()); - logger.debug("Error reading protocol JSON.", e); + logger.error("Unable to read connection parameter " + + "information from \"{}\": {}", + file.getAbsolutePath(), e.getMessage(), e); } } @@ -290,8 +291,9 @@ public class LocalEnvironment implements Environment { protocols.put(protocol, readProtocol(stream)); } catch (IOException e) { - logger.error("Unable to read pre-defined connection parameter information for protocol \"{}\": {}", protocol, e.getMessage()); - logger.debug("Error reading pre-defined protocol JSON.", e); + logger.error("Unable to read pre-defined connection " + + "parameter information for protocol \"{}\": {}", + protocol, e.getMessage(), e); } } diff --git a/guacamole/.ratignore b/guacamole/.ratignore index 548fcb76c..6ced18f45 100644 --- a/guacamole/.ratignore +++ b/guacamole/.ratignore @@ -1 +1,2 @@ src/main/frontend/dist/**/* +src/main/resources/META-INF/services/* diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleApplication.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleApplication.java index 7f3e4603c..bdaf9213a 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleApplication.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleApplication.java @@ -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); diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContainerInitializer.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContainerInitializer.java new file mode 100644 index 000000000..1eea13c7c --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContainerInitializer.java @@ -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. + *

+ * 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> 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()); + } + + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java index d8e3f3d58..99db0b9fd 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java @@ -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); + } } } diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java index 5bf0371b3..50ee2a13d 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java @@ -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); } } diff --git a/guacamole/src/main/java/org/apache/guacamole/SystemFileEnvironmentGuacamoleProperties.java b/guacamole/src/main/java/org/apache/guacamole/SystemFileEnvironmentGuacamoleProperties.java index 8b0825292..440051c65 100644 --- a/guacamole/src/main/java/org/apache/guacamole/SystemFileEnvironmentGuacamoleProperties.java +++ b/guacamole/src/main/java/org/apache/guacamole/SystemFileEnvironmentGuacamoleProperties.java @@ -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); } } diff --git a/guacamole/src/main/java/org/apache/guacamole/auth/file/FileAuthenticationProvider.java b/guacamole/src/main/java/org/apache/guacamole/auth/file/FileAuthenticationProvider.java index 3099b3b92..0e24d8534 100644 --- a/guacamole/src/main/java/org/apache/guacamole/auth/file/FileAuthenticationProvider.java +++ b/guacamole/src/main/java/org/apache/guacamole/auth/file/FileAuthenticationProvider.java @@ -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; } diff --git a/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java b/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java index 16bee6ae8..ec7a484dc 100644 --- a/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java @@ -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()); } } diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java b/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java index b89b2ada6..d922f851e 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/AuthenticationProviderFacade.java @@ -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 authProviderClass, Set 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 diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/DirectoryClassLoader.java b/guacamole/src/main/java/org/apache/guacamole/extension/DirectoryClassLoader.java index 83ad9be73..71508c47d 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/DirectoryClassLoader.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/DirectoryClassLoader.java @@ -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"; + } + } diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java b/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java index be67a384b..355dc50e4 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java @@ -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); } diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionClassLoader.java b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionClassLoader.java index 6473aa230..4f79678e2 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionClassLoader.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionClassLoader.java @@ -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 temporaryFiles, - ClassLoader parent) throws GuacamoleException { + public ExtensionClassLoader(ExtensionManifest manifest, File extension, + List 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(); + } + } diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java index c4744e2d9..21adbe81e 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java @@ -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.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); } } diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/LanguageResourceService.java b/guacamole/src/main/java/org/apache/guacamole/extension/LanguageResourceService.java index 3cb2acbb9..53e329426 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/LanguageResourceService.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/LanguageResourceService.java @@ -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); } } diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/LoggerContextProvider.java b/guacamole/src/main/java/org/apache/guacamole/extension/LoggerContextProvider.java new file mode 100644 index 000000000..3cf37f4bf --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/extension/LoggerContextProvider.java @@ -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(); + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ProviderFactory.java b/guacamole/src/main/java/org/apache/guacamole/extension/ProviderFactory.java index 01fda5719..9eeab14f9 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/ProviderFactory.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ProviderFactory.java @@ -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; diff --git a/guacamole/src/main/java/org/apache/guacamole/log/ContextAwareLogger.java b/guacamole/src/main/java/org/apache/guacamole/log/ContextAwareLogger.java new file mode 100644 index 000000000..68ab7c973 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/log/ContextAwareLogger.java @@ -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(); + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/log/ContextAwareLoggerFactory.java b/guacamole/src/main/java/org/apache/guacamole/log/ContextAwareLoggerFactory.java new file mode 100644 index 000000000..e751e71eb --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/log/ContextAwareLoggerFactory.java @@ -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. + *

+ * 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 + + "(? + * 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> 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 optionEnum = (Class) 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, List>> 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>) 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. + *

+ * 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> 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> 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); + + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/log/GuacamoleLogbackServiceProvider.java b/guacamole/src/main/java/org/apache/guacamole/log/GuacamoleLogbackServiceProvider.java new file mode 100644 index 000000000..4ae3e1a97 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/log/GuacamoleLogbackServiceProvider.java @@ -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; + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/log/LogLevel.java b/guacamole/src/main/java/org/apache/guacamole/log/LogLevel.java index 27bf0fe88..7d3c118d2 100644 --- a/guacamole/src/main/java/org/apache/guacamole/log/LogLevel.java +++ b/guacamole/src/main/java/org/apache/guacamole/log/LogLevel.java @@ -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 { + " \n" + " \n" + " \n" - + " %%d{HH:mm:ss.SSS} [%%thread] %%-5level %%logger{36} - %%msg%%n\n" + + " %s\n" + " \n" + " \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); } /** diff --git a/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java b/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java index a9e4ac3bc..8410b8d83 100644 --- a/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java +++ b/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java @@ -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); } } diff --git a/guacamole/src/main/java/org/apache/guacamole/log/ReconfigurableLoggerFactory.java b/guacamole/src/main/java/org/apache/guacamole/log/ReconfigurableLoggerFactory.java new file mode 100644 index 000000000..89659ebb4 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/log/ReconfigurableLoggerFactory.java @@ -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; + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java index ecb99a473..e84510453 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java @@ -75,7 +75,7 @@ public class RESTExceptionMapper implements ExceptionMapper { 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 { // 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 { // 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) diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java index 5a0e4c5fe..7037bb789 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java @@ -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(); diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/HashTokenSessionMap.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/HashTokenSessionMap.java index cabbb78ee..745362d21 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/HashTokenSessionMap.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/HashTokenSessionMap.java @@ -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; } diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/InterceptedStreamMap.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/InterceptedStreamMap.java index 6ddfdf976..e06ded01b 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/InterceptedStreamMap.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/InterceptedStreamMap.java @@ -69,8 +69,7 @@ public class InterceptedStreamMap { 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 diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/OutputStreamInterceptingFilter.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/OutputStreamInterceptingFilter.java index 99cf1538f..7fab4ff87 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/OutputStreamInterceptingFilter.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/OutputStreamInterceptingFilter.java @@ -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; } diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java index 304e1dd95..11e10c873 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java @@ -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()); } diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java index 6422f5718..7ba42d903 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java @@ -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()); } diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java index 215cc8f05..25e8059ee 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java @@ -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()); } diff --git a/guacamole/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/guacamole/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer new file mode 100644 index 000000000..11801bac0 --- /dev/null +++ b/guacamole/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer @@ -0,0 +1 @@ +org.apache.guacamole.GuacamoleServletContainerInitializer diff --git a/guacamole/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider b/guacamole/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider new file mode 100644 index 000000000..b510f4067 --- /dev/null +++ b/guacamole/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider @@ -0,0 +1 @@ +org.apache.guacamole.log.GuacamoleLogbackServiceProvider diff --git a/guacamole/src/main/webapp/WEB-INF/web.xml b/guacamole/src/main/webapp/WEB-INF/web.xml index ba1f6115e..3b99914c1 100644 --- a/guacamole/src/main/webapp/WEB-INF/web.xml +++ b/guacamole/src/main/webapp/WEB-INF/web.xml @@ -28,6 +28,14 @@ index.html + + + logbackDisableServletContainerInitializer + true + + cacheRevalidationFilter