diff --git a/README.md b/README.md index 41a2f4eb..097c7681 100644 --- a/README.md +++ b/README.md @@ -112,15 +112,90 @@ java $JAVA_OPTS \ -jar $JETTY_HOME/start.jar \ "$@" ``` +## Logging +This image is configured to use [Java Util Logging](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html)(JUL) to capture all logging from +the container and its dependencies. Applications that also use the JUL API will inherit the same logging configuration. -The configuration of the jetty container in this image can be viewed by running the image locally: +By default JUL is configured to use a [ConsoleHandler](https://docs.oracle.com/javase/8/docs/api/java/util/logging/ConsoleHandler.html) to send logs to the `stderr` of the container process. When run on as a GCP deployment, all output to `stderr` is captured and is available via the Stackdriver logging console, however more detailed and integrated logs are available if the Stackdriver logging mechanism is used directly (see below). + +To alter logging configuration a new `logging.properties` file must be provided to the image that among other things can: alter log levels generated by Loggers; alter log levels accepted by handlers; add/remove/configure log handlers. + + +### Providing `logging.properties` via the web application +A new logging configuration file can be provided as part of the application (typically at `WEB-INF/logging.properties`) +and the Java System Property `java.util.logging.config.file` updated to reference it. + +When running in a GCP environment, the system property can be set in `app.yaml`: +```yaml +env_variables: + JAVA_USER_OPTS: -Djava.util.logging.config.file=WEB-INF/logging.properties ``` -docker run --rm -it launcher.gcr.io/google/jetty --list-config --list-modules + +If the image is run directly, then a `-e` argument to the `docker run` command can be used to set the system property: + +```bash +docker run \ + -e JAVA_USER_OPTS=-Djava.util.logging.config.file=WEB-INF/logging.properties \ + ... +``` + +### Providing `logging.properties` via a custom image +If this image is being used as the base of a custom image, then the following `Dockerfile` commands can be used to add either replace the existing logging configuration file or to add a new `logging.properties` file. + +The default logging configuration file is located at `/var/lib/jetty/etc/java-util-logging.properties`, which can be replaced in a custom image is built. The default configuration can be replaced with a `Dockerfile` like: + +```Dockerfile +FROM gcr.io/google-appengine/jetty +ADD logging.properties /var/lib/jetty/etc/java-util-logging.properties +... ``` +Alternately an entirely new location for the file can be provided and the environment amended in a `Dockerfile` like: + +```Dockerfile +FROM gcr.io/google-appengine/jetty +ADD logging.properties /etc/logging.properties +ENV JAVA_USER_OPTS -Djava.util.logging.config.file=/etc/logging.properties +... +``` + +### Providing `logging.properties` via docker run +A `logging.properties` file may be added to an existing images using the `docker run` command if the deployment environment allows for the run arguments to be modified. The `-v` option can be used to bind a new `logging.properties` file to the running instance and the `-e` option can be used to set the system property to point to it: +```shell +docker run -it --rm \ +-v /mylocaldir/logging.properties:/etc/logging.properties \ +-e JAVA_USER_OPTS="-Djava.util.logging.config.file=/etc/logging.properties" \ +... +``` + +### Enhanced Stackdriver Logging (BETA!) +When running on the Google Cloud Platform Flex environment, the Java Util Logging can be configured to send logs to Google Stackdriver Logging by providing a `logging.properties` file that configures a [LoggingHandler](http://googlecloudplatform.github.io/google-cloud-java/0.10.0/apidocs/com/google/cloud/logging/LoggingHandler.html) as follows: +``` +.level=INFO +io.grpc.netty.level=INFO +sun.net.level=INFO + +handlers=com.google.cloud.logging.LoggingHandler +com.google.cloud.logging.LoggingHandler.level=FINE +com.google.cloud.logging.LoggingHandler.log=gae_app.log +com.google.cloud.logging.LoggingHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=%3$s: %5$s%6$s + +``` +When deployed on the GCP Flex environment, an image so configured will automatically be configured with: +* a [LabelLoggingEnhancer](https://github.com/GoogleCloudPlatform/google-cloud-java/blob/v0.17.2/google-cloud-logging/src/main/java/com/google/cloud/logging/MonitoredResourceUtil.java#L224) instance, that will add labels from the monitored resource to each log entry. +* a [TraceLoggingEnhancer](https://github.com/GoogleCloudPlatform/google-cloud-java/blob/v0.17.2/google-cloud-logging/src/main/java/com/google/cloud/logging/TraceLoggingEnhancer.java) instance that will add any trace-id set to each log entry. +* the `gcp` module will be enabled that configures jetty so that the [setCurrentTraceId](https://github.com/GoogleCloudPlatform/google-cloud-java/blob/v0.17.2/google-cloud-logging/src/main/java/com/google/cloud/logging/TraceLoggingEnhancer.java#L40) method is called for any thread handling a request. + +When deployed in other environments, logging enhancers can be manually configured by setting a comma separated list of class names as the +`com.google.cloud.logging.LoggingHandler.enhancers` property. + +When using Stackdriver logging, it is recommended that `io.grpc` and `sun.net` logging level is kept at INFO level, as both these packages are used by Stackdriver internals and can result in verbose and/or initialisation problems. + + ## Extending the image The image produced by this project may be automatically used/extended by the Cloud SDK and/or App Engine maven plugin. -Alternately it may be explicitly extended with a custom Dockerfile. +Alternately it may be explicitly extended with a custom Dockerfile. The latest released version of this image is available at `launcher.gcr.io/google/jetty`, alternately you may build and push your own version with the shell commands: diff --git a/jetty9-base/pom.xml b/jetty9-base/pom.xml index 95d13ce9..cdcc3a8b 100644 --- a/jetty9-base/pom.xml +++ b/jetty9-base/pom.xml @@ -36,6 +36,17 @@ jar provided + + com.google.cloud + google-cloud-logging + ${gcloud.api.version} + + + commons-logging + commons-logging + + + @@ -96,7 +107,7 @@ gcp-jars - pre-integration-test + package copy-dependencies @@ -118,7 +129,7 @@ add-jetty-modules - pre-integration-test + package exec @@ -139,7 +150,7 @@ maven-assembly-plugin - pre-integration-test + package single diff --git a/jetty9-base/src/main/assembly/assembly.xml b/jetty9-base/src/main/assembly/assembly.xml index 5acda39d..e2578fe2 100644 --- a/jetty9-base/src/main/assembly/assembly.xml +++ b/jetty9-base/src/main/assembly/assembly.xml @@ -33,12 +33,5 @@ **/META-INF/** - - ${project.build.directory} - jetty-base/lib/gcp - - ${artifactId}-${version}.jar - - diff --git a/jetty9-base/src/main/java/com/google/cloud/runtimes/jetty9/RequestContextScope.java b/jetty9-base/src/main/java/com/google/cloud/runtimes/jetty9/RequestContextScope.java new file mode 100644 index 00000000..cb7708b6 --- /dev/null +++ b/jetty9-base/src/main/java/com/google/cloud/runtimes/jetty9/RequestContextScope.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 Google Inc. + * + * Licensed 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 com.google.cloud.runtimes.jetty9; + +import com.google.cloud.logging.TraceLoggingEnhancer; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandler.Context; +import org.eclipse.jetty.server.handler.ContextHandler.ContextScopeListener; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A Jetty {@link ContextScopeListener} that is called whenever + * a container managed thread enters or exits the scope of a context and/or request. + * Used to maintain {@link ThreadLocal} references to the current request and + * Google traceID, primarily for logging. + * @see TracingLogHandler + */ +public class RequestContextScope implements ContextHandler.ContextScopeListener { + static final Logger logger = Logger.getLogger(RequestContextScope.class.getName()); + + private static final String X_CLOUD_TRACE = "x-cloud-trace-context"; + private static final ThreadLocal contextDepth = new ThreadLocal<>(); + + @Override + public void enterScope(Context context, Request request, Object reason) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("enterScope " + context); + } + if (request != null) { + Integer depth = contextDepth.get(); + if (depth == null || depth.intValue() == 0) { + depth = 1; + String traceId = (String) request.getAttribute(X_CLOUD_TRACE); + if (traceId == null) { + traceId = request.getHeader(X_CLOUD_TRACE); + if (traceId != null) { + int slash = traceId.indexOf('/'); + if (slash >= 0) { + traceId = traceId.substring(0, slash); + } + request.setAttribute(X_CLOUD_TRACE, traceId); + TraceLoggingEnhancer.setCurrentTraceId(traceId); + } + } else { + depth = depth + 1; + } + contextDepth.set(depth); + } + } + } + + @Override + public void exitScope(Context context, Request request) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("exitScope " + context); + } + Integer depth = contextDepth.get(); + if (depth != null) { + if (depth > 1) { + contextDepth.set(depth - 1); + } else { + contextDepth.remove(); + TraceLoggingEnhancer.setCurrentTraceId(null); + } + } + } +} diff --git a/jetty9-base/src/main/jetty-base/config-scripts/jetty.commands b/jetty9-base/src/main/jetty-base/config-scripts/jetty.commands index 6d3d9d1b..c48fa1df 100644 --- a/jetty9-base/src/main/jetty-base/config-scripts/jetty.commands +++ b/jetty9-base/src/main/jetty-base/config-scripts/jetty.commands @@ -1,4 +1,4 @@ --create-startd - ---add-to-start=server,webapp,http,deploy,jsp,jstl,resources +--approve-all-licenses +--add-to-start=server,webapp,http,deploy,jsp,jstl,resources,logging-jul,jcl-slf4j diff --git a/jetty9-base/src/main/jetty-base/etc/gcp-web.xml b/jetty9-base/src/main/jetty-base/etc/gcp-web.xml index 4650f4a3..ad30b12e 100644 --- a/jetty9-base/src/main/jetty-base/etc/gcp-web.xml +++ b/jetty9-base/src/main/jetty-base/etc/gcp-web.xml @@ -1,10 +1,15 @@ - + true + + + + + @@ -16,6 +21,5 @@ /etc/gcp-override-web.xml - + - diff --git a/jetty9-base/src/main/jetty-base/etc/java-util-logging.properties b/jetty9-base/src/main/jetty-base/etc/java-util-logging.properties new file mode 100644 index 00000000..3f60ca3f --- /dev/null +++ b/jetty9-base/src/main/jetty-base/etc/java-util-logging.properties @@ -0,0 +1,14 @@ +.level=INFO + +handlers=java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=%3$s: %5$s%6$s%n + +## To use Stackdriver Logging replace the configuration above with the configuration below: +# handlers=com.google.cloud.logging.LoggingHandler +# com.google.cloud.logging.LoggingHandler.level=FINE +# com.google.cloud.logging.LoggingHandler.log=gae_app.log +# com.google.cloud.logging.LoggingHandler.resourceType=gae_app +# com.google.cloud.logging.LoggingHandler.enhancers=com.google.cloud.logging.GaeFlexLoggingEnhancer +# com.google.cloud.logging.LoggingHandler.formatter=java.util.logging.SimpleFormatter +# java.util.logging.SimpleFormatter.format=%3$s: %5$s%6$s diff --git a/jetty9-base/src/main/jetty-base/modules/gcp.mod b/jetty9-base/src/main/jetty-base/modules/gcp.mod index c5b53247..34da110e 100644 --- a/jetty9-base/src/main/jetty-base/modules/gcp.mod +++ b/jetty9-base/src/main/jetty-base/modules/gcp.mod @@ -44,3 +44,6 @@ jetty.httpConfig.sendServerVersion?=true # Don't send date header jetty.httpConfig.sendDateHeader?=false + +# Hide the gcloud libraries from deployed webapps +jetty.webapp.addServerClasses+=,${jetty.base.uri}/lib/gcp/ diff --git a/jetty9-base/src/test/java/com/google/cloud/runtimes/jetty9/TestServlet.java b/jetty9-base/src/test/java/com/google/cloud/runtimes/jetty9/TestServlet.java new file mode 100644 index 00000000..e6faf3e1 --- /dev/null +++ b/jetty9-base/src/test/java/com/google/cloud/runtimes/jetty9/TestServlet.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 Google Inc. + * + * Licensed 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 com.google.cloud.runtimes.jetty9; + +import java.io.IOException; +import java.util.logging.Logger; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +@SuppressWarnings("serial") +@WebServlet(urlPatterns = {"/", "/test/*"}, name = "TestServlet") +public class TestServlet extends HttpServlet { + static final Logger log = Logger.getLogger(TestServlet.class.getName()); + + @PostConstruct + private void myPostConstructMethod() { + log.info("preconstructed"); + } + + @Override + public void init() throws ServletException { + log.info("init info"); + getServletContext().log("init ServletContext.log"); + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + log.info("doGet info"); + getServletContext().log("doGet ServletContext.log"); + + if (request.getParameter("ex") != null) { + try { + throw (Throwable) Class.forName(request.getParameter("ex")).newInstance(); + } catch (ServletException | IOException ex) { + throw ex; + } catch (Throwable th) { + throw new ServletException(th); + } + } + response.getWriter().println("Log Test"); + } +} diff --git a/jetty9/src/main/docker/50-jetty.bash b/jetty9/src/main/docker/50-jetty.bash index 20ef84b9..ed9b2a64 100644 --- a/jetty9/src/main/docker/50-jetty.bash +++ b/jetty9/src/main/docker/50-jetty.bash @@ -19,15 +19,14 @@ if [ ! -e "$ROOT_DIR" -a -d /app ]; then ln -s /app "$ROOT_DIR" fi -# move command line args to $JETTY_ARGS -export JETTY_ARGS="${@/$1/}" -set - "$1" - -# Check for start.jar -if [ "$(echo $JETTY_ARGS | egrep start.jar | wc -l )" = "0" ]; then - JETTY_ARGS="-Djetty.base=${JETTY_BASE} -jar ${JETTY_HOME}/start.jar $JETTY_ARGS" +# If a webapp root directory exist, use it as the work directory +if [ -d "$ROOT_DIR" ]; then + cd "$ROOT_DIR" fi +# Calculate the Jetty args +export JETTY_ARGS + # Add any Jetty properties to the JETTY_ARGS if [ "$JETTY_PROPERTIES" ]; then JETTY_ARGS="$JETTY_ARGS ${JETTY_PROPERTIES//,/ }" @@ -52,7 +51,18 @@ if [ "$PLATFORM" = "gae" ]; then JETTY_ARGS="$JETTY_ARGS --module=gcp" fi -# add the JETTY_ARGS to the JAVA_OPTS -export JAVA_OPTS="$JAVA_OPTS $JETTY_ARGS" +# If command line is running java, then assume it is jetty and mix in JETTY_ARGS +if [ "$1" = "java" ]; then + # Check for jetty start.jar and prepend if missing + if [ "$(echo $@ | egrep start.jar | wc -l )" = "0" ]; then + shift + set -- java -Djetty.base=${JETTY_BASE} -jar ${JETTY_HOME}/start.jar $@ + fi + # Append JETTY_ARGS + if [ -n "$JETTY_ARGS" ]; then + shift + set -- java $@ $JETTY_ARGS + fi +fi diff --git a/jetty9/src/test/workspace/jetty-setup-app.bash b/jetty9/src/test/workspace/jetty-setup-app.bash index 0df6291b..5324af63 100755 --- a/jetty9/src/test/workspace/jetty-setup-app.bash +++ b/jetty9/src/test/workspace/jetty-setup-app.bash @@ -2,6 +2,7 @@ rm -fr $JETTY_BASE/webapps/root $JETTY_BASE/webapps/root.war mkdir /app +trap "rm -rf /app" EXIT echo original > /app/index.html source /setup-env.d/50-jetty.bash diff --git a/jetty9/src/test/workspace/jetty-setup-default-gcp.bash b/jetty9/src/test/workspace/jetty-setup-default-gcp.bash index 52b31794..d6277802 100755 --- a/jetty9/src/test/workspace/jetty-setup-default-gcp.bash +++ b/jetty9/src/test/workspace/jetty-setup-default-gcp.bash @@ -1,12 +1,12 @@ #!/bin/bash -set - zero one two three +set - java -zero one two three JAVA_OPTS="-java -options" PLATFORM=gae source /setup-env.d/50-jetty.bash -if [ "$(echo ${JETTY_ARGS} | xargs)" != "-Djetty.base=/var/lib/jetty -jar /opt/jetty-home/start.jar one two three --module=gcp" ]; then +if [ "$(echo ${JETTY_ARGS} | xargs)" != "--module=gcp" ]; then echo "JETTY_ARGS='$(echo ${JETTY_ARGS} | xargs)'" -elif [ "$(echo ${JAVA_OPTS} | xargs)" != "-java -options -Djetty.base=/var/lib/jetty -jar /opt/jetty-home/start.jar one two three --module=gcp" ]; then - echo "JAVA_OPTS='$(echo ${JAVA_OPTS} | xargs)'" +elif [ "$(echo $@ | xargs)" != "java -Djetty.base=/var/lib/jetty -jar /opt/jetty-home/start.jar -zero one two three --module=gcp" ]; then + echo "@='$(echo $@ | xargs)'" else echo OK fi diff --git a/jetty9/src/test/workspace/jetty-setup-default.bash b/jetty9/src/test/workspace/jetty-setup-default.bash index 04a018b5..2592b6f0 100755 --- a/jetty9/src/test/workspace/jetty-setup-default.bash +++ b/jetty9/src/test/workspace/jetty-setup-default.bash @@ -2,10 +2,10 @@ set - java start.jar two three JAVA_OPTS="-java -options" source /setup-env.d/50-jetty.bash -if [ "$(echo ${JETTY_ARGS} | xargs)" != "start.jar two three" ]; then - echo "JETTY_ARGS='${JETTY_ARGS}'" -elif [ "$(echo ${JAVA_OPTS} | xargs)" != "-java -options start.jar two three" ]; then - echo "JAVA_OPTS='${JAVA_OPTS}'" +if [ "$(echo ${JETTY_ARGS} | xargs)" != "" ]; then + echo "JETTY_ARGS='$(echo ${JETTY_ARGS} | xargs)'" +elif [ "$(echo $@ | xargs)" != "java start.jar two three" ]; then + echo "@='$(echo $@ | xargs)'" else echo OK fi diff --git a/jetty9/src/test/workspace/jetty-setup-modules.bash b/jetty9/src/test/workspace/jetty-setup-modules.bash index 82549217..050ed98d 100755 --- a/jetty9/src/test/workspace/jetty-setup-modules.bash +++ b/jetty9/src/test/workspace/jetty-setup-modules.bash @@ -1,6 +1,5 @@ #!/bin/bash set - java start.jar arg -JAVA_OPTS="-X" JETTY_PROPERTIES="prop0=value0,prop1=value1" JETTY_MODULES_ENABLE="mod0,mod1" JETTY_MODILES_DISABLE="gone0,gone1" @@ -9,10 +8,10 @@ touch $JETTY_BASE/start.d/gone0.ini touch $JETTY_BASE/start.d/gone1.ini source /setup-env.d/50-jetty.bash -if [ "$(echo ${JETTY_ARGS} | xargs)" != "start.jar arg prop0=value0 prop1=value1 --module=mod0 --module=mod1" ]; then - echo "JETTY_ARGS='${JETTY_ARGS}'" -elif [ "$(echo ${JAVA_OPTS} | xargs)" != "-X start.jar arg prop0=value0 prop1=value1 --module=mod0 --module=mod1" ]; then - echo "JAVA_OPTS='${JAVA_OPTS}'" +if [ "$(echo ${JETTY_ARGS} | xargs)" != "prop0=value0 prop1=value1 --module=mod0 --module=mod1" ]; then + echo "JETTY_ARGS='$(echo ${JETTY_ARGS} | xargs)'" +elif [ "$(echo $@ | xargs)" != "java start.jar arg prop0=value0 prop1=value1 --module=mod0 --module=mod1" ]; then + echo "@='$(echo $@ | xargs)'" elif [ -x JETTY_BASE/start.d/gone0.ini ]; then echo gone0.ini not deleted elif [ -x JETTY_BASE/start.d/gone1.ini ]; then diff --git a/jetty9/src/test/workspace/jetty-setup-unpack.bash b/jetty9/src/test/workspace/jetty-setup-unpack.bash index aa442228..80f62c65 100755 --- a/jetty9/src/test/workspace/jetty-setup-unpack.bash +++ b/jetty9/src/test/workspace/jetty-setup-unpack.bash @@ -1,12 +1,14 @@ #!/bin/bash rm -fr $JETTY_BASE/webapps/root $JETTY_BASE/webapps/root.war +trap "rm -rf $JETTY_BASE/webapps/root $JETTY_BASE/webapps/root.war" EXIT mkdir $JETTY_BASE/webapps/root echo original > $JETTY_BASE/webapps/root/index.html cd $JETTY_BASE/webapps/root jar cf ../root.war * cd .. + rm -fr $JETTY_BASE/webapps/root source /setup-env.d/50-jetty.bash @@ -31,6 +33,5 @@ if [ "$(cat $JETTY_BASE/webapps/root/index.html)" != "original" ]; then exit 1 fi - echo OK diff --git a/pom.xml b/pom.xml index dc9eea0b..1efa9fc5 100644 --- a/pom.xml +++ b/pom.xml @@ -29,9 +29,10 @@ UTF-8 UTF-8 yyyy-MM-dd-HH-mm + 1.0.1 4 - 3 - 9.${jetty9.minor.version}.${jetty9.dot.version}.v20170317 + 5 + 9.${jetty9.minor.version}.${jetty9.dot.version}.v20170502 ${docker.tag.prefix}9.${jetty9.minor.version} ${docker.tag.prefix}9.${jetty9.minor.version}-${maven.build.timestamp} diff --git a/tests/test-war-smoke/pom.xml b/tests/test-war-smoke/pom.xml index 389658d3..6b4beef9 100644 --- a/tests/test-war-smoke/pom.xml +++ b/tests/test-war-smoke/pom.xml @@ -34,6 +34,12 @@ 3.1.0 provided + + com.google.cloud + google-cloud-logging + ${gcloud.api.version} + test + com.google.cloud.runtimes.tests gcloud-testing-core diff --git a/tests/test-war-smoke/src/main/appengine/app.yaml b/tests/test-war-smoke/src/main/appengine/app.yaml index fef2315c..53494429 100644 --- a/tests/test-war-smoke/src/main/appengine/app.yaml +++ b/tests/test-war-smoke/src/main/appengine/app.yaml @@ -12,3 +12,6 @@ handlers: - url: /.* script: ignored secure: optional + +env_variables: + JAVA_USER_OPTS: -Djava.util.logging.config.file=WEB-INF/logging.properties diff --git a/tests/test-war-smoke/src/main/java/com/google/cloud/runtimes/jetty/test/smoke/ClassLoaderServlet.java b/tests/test-war-smoke/src/main/java/com/google/cloud/runtimes/jetty/test/smoke/ClassLoaderServlet.java new file mode 100644 index 00000000..9562cb5c --- /dev/null +++ b/tests/test-war-smoke/src/main/java/com/google/cloud/runtimes/jetty/test/smoke/ClassLoaderServlet.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 Google Inc. + * + * Licensed 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 com.google.cloud.runtimes.jetty.test.smoke; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Test that classes are correctly hidden from webapp. + */ +@WebServlet(urlPatterns = {"/classloader"}) +public class ClassLoaderServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/plain"); + + String[] hidden = { + "com.google.cloud.logging.Logging", + "org.eclipse.jetty.server.Server", + "com.google.cloud.BaseService", + "io.netty.channel.Channel", + "io.netty.util.Timer", + "org.slf4j.Logger" + }; + + int notFound = 0; + int found = 0; + + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + for (String name : hidden) { + try { + Class clazz = loader.loadClass(name); + resp.getWriter().printf("Context loaded %s from%s%n", name, clazz.getClassLoader()); + found++; + } catch (ClassNotFoundException e) { + resp.getWriter().printf("Context Not Found %s%n", name); + notFound++; + } + } + + resp.getWriter().printf("Found classes = %s (0 expected)%n", found); + resp.getWriter().printf("Not found classes = %s (%d expected)%n", notFound, hidden.length); + } +} diff --git a/tests/test-war-smoke/src/main/webapp/WEB-INF/logging.properties b/tests/test-war-smoke/src/main/webapp/WEB-INF/logging.properties new file mode 100644 index 00000000..267ac4d5 --- /dev/null +++ b/tests/test-war-smoke/src/main/webapp/WEB-INF/logging.properties @@ -0,0 +1,10 @@ +.level=INFO +org.eclipse.jetty.http.level=INFO +io.grpc.netty.level=INFO +sun.net.level=INFO + +handlers=com.google.cloud.logging.LoggingHandler +com.google.cloud.logging.LoggingHandler.level=FINE +com.google.cloud.logging.LoggingHandler.log=gae_app.log +com.google.cloud.logging.LoggingHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=%3$s: %5$s%6$s diff --git a/tests/test-war-smoke/src/test/java/com/google/cloud/runtimes/jetty/test/smoke/LoggingIntegrationTest.java b/tests/test-war-smoke/src/test/java/com/google/cloud/runtimes/jetty/test/smoke/LoggingIntegrationTest.java new file mode 100644 index 00000000..dd82ad4e --- /dev/null +++ b/tests/test-war-smoke/src/test/java/com/google/cloud/runtimes/jetty/test/smoke/LoggingIntegrationTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 Google Inc. + * + * Licensed 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 com.google.cloud.runtimes.jetty.test.smoke; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.api.gax.paging.Page; +import com.google.cloud.logging.LogEntry; +import com.google.cloud.logging.Logging; +import com.google.cloud.logging.Logging.EntryListOption; +import com.google.cloud.logging.LoggingOptions; +import com.google.cloud.logging.Severity; +import com.google.cloud.runtime.jetty.test.AbstractIntegrationTest; +import com.google.cloud.runtime.jetty.test.annotation.RemoteOnly; +import com.google.cloud.runtime.jetty.util.HttpUrlUtil; + +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.StringReader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.util.List; +import java.util.stream.Collectors; + +public class LoggingIntegrationTest extends AbstractIntegrationTest { + + + @Test + @RemoteOnly + public void testLogging() throws Exception { + // Create unique ID to relate request with log entries + String id = Long.toHexString(System.nanoTime()); + + // Hit the DumpServlet that will log the request path + URI target = getUri().resolve("/dump/info/" + id); + HttpURLConnection http = HttpUrlUtil.openTo(target); + assertThat(http.getResponseCode(), is(200)); + String responseBody = HttpUrlUtil.getResponseBody(http); + + List lines = + new BufferedReader(new StringReader(responseBody)).lines().collect(Collectors.toList()); + assertThat(lines.stream().filter(s -> s.startsWith("requestURI=")).findFirst().get(), + containsString(id)); + String traceId = lines.stream().filter(s -> s.startsWith("X-Cloud-Trace-Context: ")).findFirst() + .get().split("[ /]")[1]; + assertThat(traceId, Matchers.notNullValue()); + + LoggingOptions options = LoggingOptions.getDefaultInstance(); + + String filter = + "resource.type=gae_app AND resource.labels.module_id=smoke" + " AND textPayload:" + id; + + int expected = 2; + try (Logging logging = options.getService()) { + Page entries = logging.listLogEntries(EntryListOption.filter(filter)); + for (LogEntry entry : entries.iterateAll()) { + if (entry.getSeverity() == Severity.INFO) { + assertThat(entry.getLogName(), is("gae_app.log")); + assertThat(entry.getResource().getType(), is("gae_app")); + assertThat(entry.getResource().getLabels().get("module_id"), is("smoke")); + assertThat(entry.getResource().getLabels().get("zone"), Matchers.notNullValue()); + assertThat(entry.getLabels().get("appengine.googleapis.com/trace_id"), is(traceId)); + assertThat(entry.getLabels().get("appengine.googleapis.com/instance_name"), + Matchers.notNullValue()); + + if (entry.getPayload().toString().contains("JUL.info:/dump/info/")) { + expected--; + assertThat(entry.getPayload().toString(), Matchers.containsString(id)); + } + if (entry.getPayload().toString().contains("ServletContext.log:/dump/info")) { + expected--; + assertThat(entry.getPayload().toString(), Matchers.containsString(id)); + } + } + } + assertThat(expected, is(0)); + } + } + + @Test + public void testClassPath() throws Exception { + + URI target = getUri().resolve("/classloader"); + HttpURLConnection http = HttpUrlUtil.openTo(target); + assertThat(http.getResponseCode(), is(200)); + String responseBody = HttpUrlUtil.getResponseBody(http); + + Assert.assertThat(responseBody, Matchers.containsString("Found classes = 0 (0 expected)")); + Assert.assertThat(responseBody, Matchers.containsString("Not found classes = 6 (6 expected)")); + } +}