Skip to content
This repository was archived by the owner on Dec 12, 2024. It is now read-only.

Commit 30127c8

Browse files
authored
Stackdriver logging #68 (#81)
* Issue #68 added request context scope * Issue #68 added request context scope * Issue #68 added skeleton Logging Handler * Issue #68 work in progress * Issue #68 work in progress * working implementation using a threadlocal to stop looping * Issue #68 update to latest google-cloud-java LoggingHandler * Issue #68 Updated to latest gcloud API removed copied LoggingHandlers removed instanceid from monitored resource * Issue #68 Use same labels as nginx * Issue #68 Code cleanups after review * Issue #68 Removed stackdriver logging, so this just configures JUL to capture the traceid and log to stderr * renamed traceid to traceId * gcloud api not currently used * Tunnel traceId in parameters #68 * Simplified with traceId lookup in formatter #68 * Use 8.2 beta stackdriver * Improved formatting * use logger name rather than source * run jetty from the webapp working directory so logging configuration may be relative * Testing logging Test googleapis/google-cloud-java#1535 and jetty-9.4.1-SNAPSHOT * use released 9.4.1 * Issue #68 minor clean ups * Do not CD to non existant directory * improved README * fixed Cloud SDK * Released beta of google-cloud-logging * updated google-cloud API to 0.8.3-beta * added remote test to check logging * Stackdriver logging testing improved comments check for traceid * upgrade to 0.9.2 * Test for zone * upgrade to 10.0 gcloud API * enable gae module only of GAE_INSTANCE environment variable is set * improved gae.mod documentation * GCP module Rename gae module and configuration to gcp Split the jetty.commands into jetty.commands and gcp.commands moved commands file to jetty-base/config-scripts updated setup-env-ext to run the gcp.commands when image is run with a GAE_INSTANCE set * fixed warnings * turn off stackdriver logging by default. Added instructions to enable. * Updates Update to latest openjdk-runtime with setup-env.d Update to latest jetty release * Use the PLATFORM env var * Improved jetty startup Added JETTY_PROPERTIES, JETTY_MODULES_ENABLE & JETTY_MODULES_DISABLE Removed duplicate code fron 50-jetty.bash * use launcher URL * Trimmed GCP specific configuration * Added structure tests for jetty setup script Also fixed unpack bug found as a result * Support passing just args * fix merge * Fixed test workspace paths for cloud build * review feedback * working directory is the root webapp * Improve handling of various types of command line. Fixed problem with handling of command line like "ls /var" Requires duplication of test for java from openjdk-runtime docker-entrypoint.bash, which should be modified to avoid the duplication. * upgrade cgloud API version to 0.13.0-beta * use package target * tested with FINE log level * Simplify entrypoint args processing The $@ arg array is kept complete between all scripts. * remove debug * remove debug * Test that the logging dependencies are hidden from webapp classpath * update README.md with instructions to keep INFO level on io.grpc.netty.level=INFO * Updated to lasted openjdk-runtime * fixed classloader test * fixed TODO * Update to jetty 9.4.5 and gcloud 1.0.1-SNAPSHOT * upgraded to gcloud-logging 1.0.1 * turn off debug * updated README for latest 1.0.1 gcloud-logging * update classpath exclusion \o/
1 parent a71993a commit 30127c8

21 files changed

Lines changed: 496 additions & 43 deletions

File tree

README.md

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,90 @@ java $JAVA_OPTS \
112112
-jar $JETTY_HOME/start.jar \
113113
"$@"
114114
```
115+
## Logging
116+
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
117+
the container and its dependencies. Applications that also use the JUL API will inherit the same logging configuration.
115118

116-
The configuration of the jetty container in this image can be viewed by running the image locally:
119+
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).
120+
121+
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.
122+
123+
124+
### Providing `logging.properties` via the web application
125+
A new logging configuration file can be provided as part of the application (typically at `WEB-INF/logging.properties`)
126+
and the Java System Property `java.util.logging.config.file` updated to reference it.
127+
128+
When running in a GCP environment, the system property can be set in `app.yaml`:
129+
```yaml
130+
env_variables:
131+
JAVA_USER_OPTS: -Djava.util.logging.config.file=WEB-INF/logging.properties
117132
```
118-
docker run --rm -it launcher.gcr.io/google/jetty --list-config --list-modules
133+
134+
If the image is run directly, then a `-e` argument to the `docker run` command can be used to set the system property:
135+
136+
```bash
137+
docker run \
138+
-e JAVA_USER_OPTS=-Djava.util.logging.config.file=WEB-INF/logging.properties \
139+
...
140+
```
141+
142+
### Providing `logging.properties` via a custom image
143+
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.
144+
145+
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:
146+
147+
```Dockerfile
148+
FROM gcr.io/google-appengine/jetty
149+
ADD logging.properties /var/lib/jetty/etc/java-util-logging.properties
150+
...
119151
```
120152

153+
Alternately an entirely new location for the file can be provided and the environment amended in a `Dockerfile` like:
154+
155+
```Dockerfile
156+
FROM gcr.io/google-appengine/jetty
157+
ADD logging.properties /etc/logging.properties
158+
ENV JAVA_USER_OPTS -Djava.util.logging.config.file=/etc/logging.properties
159+
...
160+
```
161+
162+
### Providing `logging.properties` via docker run
163+
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:
164+
```shell
165+
docker run -it --rm \
166+
-v /mylocaldir/logging.properties:/etc/logging.properties \
167+
-e JAVA_USER_OPTS="-Djava.util.logging.config.file=/etc/logging.properties" \
168+
...
169+
```
170+
171+
### Enhanced Stackdriver Logging (BETA!)
172+
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:
173+
```
174+
.level=INFO
175+
io.grpc.netty.level=INFO
176+
sun.net.level=INFO
177+
178+
handlers=com.google.cloud.logging.LoggingHandler
179+
com.google.cloud.logging.LoggingHandler.level=FINE
180+
com.google.cloud.logging.LoggingHandler.log=gae_app.log
181+
com.google.cloud.logging.LoggingHandler.formatter=java.util.logging.SimpleFormatter
182+
java.util.logging.SimpleFormatter.format=%3$s: %5$s%6$s
183+
184+
```
185+
When deployed on the GCP Flex environment, an image so configured will automatically be configured with:
186+
* 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.
187+
* 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.
188+
* 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.
189+
190+
When deployed in other environments, logging enhancers can be manually configured by setting a comma separated list of class names as the
191+
`com.google.cloud.logging.LoggingHandler.enhancers` property.
192+
193+
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.
194+
195+
121196
## Extending the image
122197
The image produced by this project may be automatically used/extended by the Cloud SDK and/or App Engine maven plugin.
123-
Alternately it may be explicitly extended with a custom Dockerfile.
198+
Alternately it may be explicitly extended with a custom Dockerfile.
124199

125200
The latest released version of this image is available at `launcher.gcr.io/google/jetty`, alternately you may
126201
build and push your own version with the shell commands:

jetty9-base/pom.xml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@
3636
<type>jar</type>
3737
<scope>provided</scope>
3838
</dependency>
39+
<dependency>
40+
<groupId>com.google.cloud</groupId>
41+
<artifactId>google-cloud-logging</artifactId>
42+
<version>${gcloud.api.version}</version>
43+
<exclusions>
44+
<exclusion>
45+
<groupId>commons-logging</groupId>
46+
<artifactId>commons-logging</artifactId>
47+
</exclusion>
48+
</exclusions>
49+
</dependency>
3950
</dependencies>
4051

4152
<build>
@@ -96,7 +107,7 @@
96107
</execution>
97108
<execution>
98109
<id>gcp-jars</id>
99-
<phase>pre-integration-test</phase>
110+
<phase>package</phase>
100111
<goals>
101112
<goal>copy-dependencies</goal>
102113
</goals>
@@ -118,7 +129,7 @@
118129
<executions>
119130
<execution>
120131
<id>add-jetty-modules</id>
121-
<phase>pre-integration-test</phase>
132+
<phase>package</phase>
122133
<goals>
123134
<goal>exec</goal>
124135
</goals>
@@ -139,7 +150,7 @@
139150
<artifactId>maven-assembly-plugin</artifactId>
140151
<executions>
141152
<execution>
142-
<phase>pre-integration-test</phase>
153+
<phase>package</phase>
143154
<goals>
144155
<goal>single</goal>
145156
</goals>

jetty9-base/src/main/assembly/assembly.xml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,5 @@
3333
<exclude>**/META-INF/**</exclude>
3434
</excludes>
3535
</fileSet>
36-
<fileSet>
37-
<directory>${project.build.directory}</directory>
38-
<outputDirectory>jetty-base/lib/gcp</outputDirectory>
39-
<includes>
40-
<include>${artifactId}-${version}.jar</include>
41-
</includes>
42-
</fileSet>
4336
</fileSets>
4437
</assembly>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (C) 2016 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.runtimes.jetty9;
18+
19+
import com.google.cloud.logging.TraceLoggingEnhancer;
20+
21+
import org.eclipse.jetty.server.Request;
22+
import org.eclipse.jetty.server.handler.ContextHandler;
23+
import org.eclipse.jetty.server.handler.ContextHandler.Context;
24+
import org.eclipse.jetty.server.handler.ContextHandler.ContextScopeListener;
25+
26+
import java.util.logging.Level;
27+
import java.util.logging.Logger;
28+
29+
/**
30+
* A Jetty {@link ContextScopeListener} that is called whenever
31+
* a container managed thread enters or exits the scope of a context and/or request.
32+
* Used to maintain {@link ThreadLocal} references to the current request and
33+
* Google traceID, primarily for logging.
34+
* @see TracingLogHandler
35+
*/
36+
public class RequestContextScope implements ContextHandler.ContextScopeListener {
37+
static final Logger logger = Logger.getLogger(RequestContextScope.class.getName());
38+
39+
private static final String X_CLOUD_TRACE = "x-cloud-trace-context";
40+
private static final ThreadLocal<Integer> contextDepth = new ThreadLocal<>();
41+
42+
@Override
43+
public void enterScope(Context context, Request request, Object reason) {
44+
if (logger.isLoggable(Level.FINE)) {
45+
logger.fine("enterScope " + context);
46+
}
47+
if (request != null) {
48+
Integer depth = contextDepth.get();
49+
if (depth == null || depth.intValue() == 0) {
50+
depth = 1;
51+
String traceId = (String) request.getAttribute(X_CLOUD_TRACE);
52+
if (traceId == null) {
53+
traceId = request.getHeader(X_CLOUD_TRACE);
54+
if (traceId != null) {
55+
int slash = traceId.indexOf('/');
56+
if (slash >= 0) {
57+
traceId = traceId.substring(0, slash);
58+
}
59+
request.setAttribute(X_CLOUD_TRACE, traceId);
60+
TraceLoggingEnhancer.setCurrentTraceId(traceId);
61+
}
62+
} else {
63+
depth = depth + 1;
64+
}
65+
contextDepth.set(depth);
66+
}
67+
}
68+
}
69+
70+
@Override
71+
public void exitScope(Context context, Request request) {
72+
if (logger.isLoggable(Level.FINE)) {
73+
logger.fine("exitScope " + context);
74+
}
75+
Integer depth = contextDepth.get();
76+
if (depth != null) {
77+
if (depth > 1) {
78+
contextDepth.set(depth - 1);
79+
} else {
80+
contextDepth.remove();
81+
TraceLoggingEnhancer.setCurrentTraceId(null);
82+
}
83+
}
84+
}
85+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
--create-startd
2-
3-
--add-to-start=server,webapp,http,deploy,jsp,jstl,resources
2+
--approve-all-licenses
3+
--add-to-start=server,webapp,http,deploy,jsp,jstl,resources,logging-jul,jcl-slf4j
44

jetty9-base/src/main/jetty-base/etc/gcp-web.xml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
<?xml version="1.0"?>
22
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
33
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
4-
4+
55
<!-- Allow start exceptions to propagate -->
66
<Set name="throwUnavailableOnStartupException">true</Set>
77

8+
<!-- Add listener to capture request context to be used by logging -->
9+
<Call name="addEventListener">
10+
<Arg><New class="com.google.cloud.runtimes.jetty9.RequestContextScope"/></Arg>
11+
</Call>
12+
813
<!-- Protect app.yaml -->
914
<Get id="protected" name="ProtectedTargets"/>
1015
<Set name="ProtectedTargets">
@@ -16,6 +21,5 @@
1621
</Set>
1722

1823
<Set name="overrideDescriptor"><Property name="jetty.base"/>/etc/gcp-override-web.xml</Set>
19-
24+
2025
</Configure>
21-
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.level=INFO
2+
3+
handlers=java.util.logging.ConsoleHandler
4+
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
5+
java.util.logging.SimpleFormatter.format=%3$s: %5$s%6$s%n
6+
7+
## To use Stackdriver Logging replace the configuration above with the configuration below:
8+
# handlers=com.google.cloud.logging.LoggingHandler
9+
# com.google.cloud.logging.LoggingHandler.level=FINE
10+
# com.google.cloud.logging.LoggingHandler.log=gae_app.log
11+
# com.google.cloud.logging.LoggingHandler.resourceType=gae_app
12+
# com.google.cloud.logging.LoggingHandler.enhancers=com.google.cloud.logging.GaeFlexLoggingEnhancer
13+
# com.google.cloud.logging.LoggingHandler.formatter=java.util.logging.SimpleFormatter
14+
# java.util.logging.SimpleFormatter.format=%3$s: %5$s%6$s

jetty9-base/src/main/jetty-base/modules/gcp.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,6 @@ jetty.httpConfig.sendServerVersion?=true
4444
4545
# Don't send date header
4646
jetty.httpConfig.sendDateHeader?=false
47+
48+
# Hide the gcloud libraries from deployed webapps
49+
jetty.webapp.addServerClasses+=,${jetty.base.uri}/lib/gcp/
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (C) 2016 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.runtimes.jetty9;
18+
19+
import java.io.IOException;
20+
import java.util.logging.Logger;
21+
22+
import javax.annotation.PostConstruct;
23+
import javax.servlet.ServletException;
24+
import javax.servlet.annotation.WebServlet;
25+
import javax.servlet.http.HttpServlet;
26+
import javax.servlet.http.HttpServletRequest;
27+
import javax.servlet.http.HttpServletResponse;
28+
29+
30+
@SuppressWarnings("serial")
31+
@WebServlet(urlPatterns = {"/", "/test/*"}, name = "TestServlet")
32+
public class TestServlet extends HttpServlet {
33+
static final Logger log = Logger.getLogger(TestServlet.class.getName());
34+
35+
@PostConstruct
36+
private void myPostConstructMethod() {
37+
log.info("preconstructed");
38+
}
39+
40+
@Override
41+
public void init() throws ServletException {
42+
log.info("init info");
43+
getServletContext().log("init ServletContext.log");
44+
}
45+
46+
@Override
47+
public void doGet(HttpServletRequest request, HttpServletResponse response)
48+
throws ServletException, IOException {
49+
log.info("doGet info");
50+
getServletContext().log("doGet ServletContext.log");
51+
52+
if (request.getParameter("ex") != null) {
53+
try {
54+
throw (Throwable) Class.forName(request.getParameter("ex")).newInstance();
55+
} catch (ServletException | IOException ex) {
56+
throw ex;
57+
} catch (Throwable th) {
58+
throw new ServletException(th);
59+
}
60+
}
61+
response.getWriter().println("Log Test");
62+
}
63+
}

jetty9/src/main/docker/50-jetty.bash

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ if [ ! -e "$ROOT_DIR" -a -d /app ]; then
1919
ln -s /app "$ROOT_DIR"
2020
fi
2121

22-
# move command line args to $JETTY_ARGS
23-
export JETTY_ARGS="${@/$1/}"
24-
set - "$1"
25-
26-
# Check for start.jar
27-
if [ "$(echo $JETTY_ARGS | egrep start.jar | wc -l )" = "0" ]; then
28-
JETTY_ARGS="-Djetty.base=${JETTY_BASE} -jar ${JETTY_HOME}/start.jar $JETTY_ARGS"
22+
# If a webapp root directory exist, use it as the work directory
23+
if [ -d "$ROOT_DIR" ]; then
24+
cd "$ROOT_DIR"
2925
fi
3026

27+
# Calculate the Jetty args
28+
export JETTY_ARGS
29+
3130
# Add any Jetty properties to the JETTY_ARGS
3231
if [ "$JETTY_PROPERTIES" ]; then
3332
JETTY_ARGS="$JETTY_ARGS ${JETTY_PROPERTIES//,/ }"
@@ -52,7 +51,18 @@ if [ "$PLATFORM" = "gae" ]; then
5251
JETTY_ARGS="$JETTY_ARGS --module=gcp"
5352
fi
5453

55-
# add the JETTY_ARGS to the JAVA_OPTS
56-
export JAVA_OPTS="$JAVA_OPTS $JETTY_ARGS"
54+
# If command line is running java, then assume it is jetty and mix in JETTY_ARGS
55+
if [ "$1" = "java" ]; then
56+
# Check for jetty start.jar and prepend if missing
57+
if [ "$(echo $@ | egrep start.jar | wc -l )" = "0" ]; then
58+
shift
59+
set -- java -Djetty.base=${JETTY_BASE} -jar ${JETTY_HOME}/start.jar $@
60+
fi
5761

62+
# Append JETTY_ARGS
63+
if [ -n "$JETTY_ARGS" ]; then
64+
shift
65+
set -- java $@ $JETTY_ARGS
66+
fi
67+
fi
5868

0 commit comments

Comments
 (0)