Skip to content
This repository was archived by the owner on Nov 11, 2022. It is now read-only.

Commit 0aa4d9d

Browse files
lukecwikdavorbonaci
authored andcommitted
Add support for setting the default log level and also custom log level overrides on the Dataflow worker.
[] ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=87355253
1 parent cd845a9 commit 0aa4d9d

File tree

7 files changed

+371
-84
lines changed

7 files changed

+371
-84
lines changed

sdk/src/main/java/com/google/cloud/dataflow/sdk/options/DataflowPipelineOptions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
public interface DataflowPipelineOptions extends
3535
PipelineOptions, GcpOptions, ApplicationNameOptions, DataflowPipelineDebugOptions,
3636
DataflowPipelineWorkerPoolOptions, BigQueryOptions,
37-
GcsOptions, StreamingOptions, CloudDebuggerOptions {
37+
GcsOptions, StreamingOptions, CloudDebuggerOptions, DataflowWorkerLoggingOptions {
3838

3939
/**
4040
* GCS path for temporary files.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright (C) 2015 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* 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, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.google.cloud.dataflow.sdk.options;
17+
18+
import com.google.common.base.Preconditions;
19+
20+
import com.fasterxml.jackson.annotation.JsonCreator;
21+
import com.fasterxml.jackson.annotation.JsonValue;
22+
23+
import java.util.Arrays;
24+
25+
/**
26+
* Options which are used to control logging configuration on the Dataflow worker.
27+
*/
28+
public interface DataflowWorkerLoggingOptions extends PipelineOptions {
29+
/**
30+
* The set of log levels which can be used on the Dataflow worker.
31+
*/
32+
public enum Level {
33+
DEBUG, ERROR, INFO, TRACE, WARN
34+
}
35+
36+
/**
37+
* This option controls the default log level of all loggers without a
38+
* log level override.
39+
*/
40+
@Default.Enum("INFO")
41+
Level getDefaultWorkerLogLevel();
42+
void setDefaultWorkerLogLevel(Level level);
43+
44+
/**
45+
* This option controls the log levels for specifically named loggers.
46+
* <p>
47+
* Later options with equivalent names override earlier options.
48+
* <p>
49+
* See {@link WorkerLogLevelOverride} for more information on how to configure logging
50+
* on a per {@link Class}, {@link Package}, or name basis.
51+
*/
52+
WorkerLogLevelOverride[] getWorkerLogLevelOverrides();
53+
void setWorkerLogLevelOverrides(WorkerLogLevelOverride[] string);
54+
55+
/**
56+
* Defines a log level override for a specific class, package, or name.
57+
* <p>
58+
* {@link java.util.logging} is used on the Dataflow worker harness and supports
59+
* a logging hierarchy based off of names which are "." separated. It is a common
60+
* pattern to have the logger for a given class share the same name as the class itself.
61+
* Given the classes {@code a.b.c.Foo}, {@code a.b.c.Xyz}, and {@code a.b.Bar}, with
62+
* loggers named {@code "a.b.c.Foo"}, {@code "a.b.c.Xyz"}, and {@code "a.b.Bar"} respectively,
63+
* we can override the log levels:
64+
* <ul>
65+
* <li>for {@code Foo} by specifying the name {@code "a.b.c.Foo"} or the {@link Class}
66+
* representing {@code a.b.c.Foo}.
67+
* <li>for {@code Foo}, {@code Xyz}, and {@code Bar} by specifying the name {@code "a.b"} or
68+
* the {@link Package} representing {@code a.b}.
69+
* <li>for {@code Foo} and {@code Bar} by specifying both of their names or classes.
70+
* </ul>
71+
* Note that by specifying multiple overrides, the exact name followed by the closest parent
72+
* takes precedence.
73+
*/
74+
public static class WorkerLogLevelOverride {
75+
private static final String SEPARATOR = "#";
76+
77+
/**
78+
* Overrides the default log level for the passed in class.
79+
* <p>
80+
* This is equivalent to calling {@link #forName(String, Level)} and
81+
* passing in the {@link Class#getName() class name}.
82+
*/
83+
public static WorkerLogLevelOverride forClass(Class<?> klass, Level level) {
84+
Preconditions.checkNotNull(klass, "Expected class to be not null.");
85+
return forName(klass.getName(), level);
86+
}
87+
88+
/**
89+
* Overrides the default log level for the passed in package.
90+
* <p>
91+
* This is equivalent to calling {@link #forName(String, Level)} and
92+
* passing in the {@link Package#getName() package name}.
93+
*/
94+
public static WorkerLogLevelOverride forPackage(Package pkg, Level level) {
95+
Preconditions.checkNotNull(pkg, "Expected package to be not null.");
96+
return forName(pkg.getName(), level);
97+
}
98+
99+
/**
100+
* Overrides the default log level for the passed in name.
101+
* <p>
102+
* Note that because of the hierarchical nature of logger names, this will
103+
* override the log level of all loggers which have the passed in name or
104+
* a parent logger which has the passed in name.
105+
*/
106+
public static WorkerLogLevelOverride forName(String name, Level level) {
107+
Preconditions.checkNotNull(name, "Expected name to be not null.");
108+
Preconditions.checkNotNull(level,
109+
"Expected level to be one of %s.", Arrays.toString(Level.values()));
110+
return new WorkerLogLevelOverride(name, level);
111+
}
112+
113+
/**
114+
* Expects a value of the form {@code Name#Level}.
115+
*/
116+
@JsonCreator
117+
public static WorkerLogLevelOverride create(String value) {
118+
Preconditions.checkNotNull(value, "Expected value to be not null.");
119+
Preconditions.checkArgument(value.contains(SEPARATOR),
120+
"Expected '#' separator but none found within '%s'.", value);
121+
String[] parts = value.split(SEPARATOR, 2);
122+
Level level;
123+
try {
124+
level = Level.valueOf(parts[1]);
125+
} catch (IllegalArgumentException e) {
126+
throw new IllegalArgumentException(String.format(
127+
"Unsupported log level '%s' requested. Must be one of %s.",
128+
parts[1], Arrays.toString(Level.values())));
129+
}
130+
return forName(parts[0], level);
131+
}
132+
133+
private final String name;
134+
private final Level level;
135+
private WorkerLogLevelOverride(String name, Level level) {
136+
this.name = name;
137+
this.level = level;
138+
}
139+
140+
public String getName() {
141+
return name;
142+
}
143+
144+
public Level getLevel() {
145+
return level;
146+
}
147+
148+
@JsonValue
149+
@Override
150+
public String toString() {
151+
return name + SEPARATOR + level;
152+
}
153+
}
154+
}

sdk/src/main/java/com/google/cloud/dataflow/sdk/runners/worker/DataflowWorkerHarness.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,12 @@ public void uncaughtException(Thread t, Throwable e) {
9393
*/
9494
public static void main(String[] args) throws Exception {
9595
Thread.currentThread().setUncaughtExceptionHandler(WorkerUncaughtExceptionHandler.INSTANCE);
96-
new DataflowWorkerLoggingInitializer().initialize();
96+
DataflowWorkerLoggingInitializer.initialize();
9797

9898
DataflowWorkerHarnessOptions pipelineOptions =
9999
PipelineOptionsFactory.createFromSystemProperties();
100+
DataflowWorkerLoggingInitializer.configure(pipelineOptions);
101+
100102
final DataflowWorker worker = create(pipelineOptions);
101103
processWork(pipelineOptions, worker);
102104
}

sdk/src/main/java/com/google/cloud/dataflow/sdk/runners/worker/StreamingDataflowWorker.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,15 @@ static MapTask parseMapTask(String input) throws IOException {
9191
}
9292

9393
public static void main(String[] args) throws Exception {
94-
new DataflowWorkerLoggingInitializer().initialize();
94+
DataflowWorkerLoggingInitializer.initialize();
95+
DataflowWorkerHarnessOptions options =
96+
PipelineOptionsFactory.createFromSystemProperties();
97+
// TODO: Remove setting these options once we have migrated to passing
98+
// through the pipeline options.
99+
options.setAppName("StreamingWorkerHarness");
100+
options.setStreaming(true);
95101

102+
DataflowWorkerLoggingInitializer.configure(options);
96103
String hostport = System.getProperty("windmill.hostport");
97104
if (hostport == null) {
98105
throw new Exception("-Dwindmill.hostport must be set to the location of the windmill server");
@@ -112,13 +119,6 @@ public static void main(String[] args) throws Exception {
112119
(WindmillServerStub) Class.forName(WINDMILL_SERVER_CLASS_NAME)
113120
.getDeclaredConstructor(String.class).newInstance(hostport);
114121

115-
DataflowWorkerHarnessOptions options =
116-
PipelineOptionsFactory.createFromSystemProperties();
117-
// TODO: Remove setting these options once we have migrated to passing
118-
// through the pipeline options.
119-
options.setAppName("StreamingWorkerHarness");
120-
options.setStreaming(true);
121-
122122
StreamingDataflowWorker worker =
123123
new StreamingDataflowWorker(mapTasks, windmillServer, options);
124124
worker.start();

sdk/src/main/java/com/google/cloud/dataflow/sdk/runners/worker/logging/DataflowWorkerLoggingInitializer.java

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,20 @@
1616

1717
package com.google.cloud.dataflow.sdk.runners.worker.logging;
1818

19+
import static com.google.cloud.dataflow.sdk.options.DataflowWorkerLoggingOptions.Level.DEBUG;
20+
import static com.google.cloud.dataflow.sdk.options.DataflowWorkerLoggingOptions.Level.ERROR;
21+
import static com.google.cloud.dataflow.sdk.options.DataflowWorkerLoggingOptions.Level.INFO;
22+
import static com.google.cloud.dataflow.sdk.options.DataflowWorkerLoggingOptions.Level.TRACE;
23+
import static com.google.cloud.dataflow.sdk.options.DataflowWorkerLoggingOptions.Level.WARN;
24+
25+
import com.google.api.client.util.Lists;
26+
import com.google.cloud.dataflow.sdk.options.DataflowWorkerLoggingOptions;
27+
import com.google.cloud.dataflow.sdk.options.DataflowWorkerLoggingOptions.WorkerLogLevelOverride;
1928
import com.google.common.collect.ImmutableBiMap;
2029

2130
import java.io.File;
2231
import java.io.IOException;
32+
import java.util.List;
2333
import java.util.logging.FileHandler;
2434
import java.util.logging.Formatter;
2535
import java.util.logging.Handler;
@@ -28,47 +38,60 @@
2838
import java.util.logging.Logger;
2939

3040
/**
31-
* Sets up java.util.Logging configuration on the Dataflow Worker Harness with a
32-
* console and file logger. The console and file loggers use the
33-
* {@link DataflowWorkerLoggingFormatter} format. A user can override
34-
* the logging level and location by specifying the Java system properties
35-
* "dataflow.worker.logging.level" and "dataflow.worker.logging.location" respectively.
36-
* The default log level is INFO and the default location is a file named dataflow-worker.log
37-
* within the systems temporary directory.
41+
* Sets up {@link java.util.logging} configuration on the Dataflow worker with a
42+
* file logger. The file logger uses the {@link DataflowWorkerLoggingFormatter} format.
43+
* A user can override the logging level by customizing the options found within
44+
* {@link DataflowWorkerLoggingOptions}. A user can override the location by specifying the
45+
* Java system property "dataflow.worker.logging.location". The default log level is INFO
46+
* and the default location is a file named dataflow-worker.log within the systems temporary
47+
* directory.
3848
*/
3949
public class DataflowWorkerLoggingInitializer {
4050
private static final String DEFAULT_LOGGING_LOCATION =
4151
new File(System.getProperty("java.io.tmpdir"), "dataflow-worker.log").getPath();
4252
private static final String ROOT_LOGGER_NAME = "";
43-
public static final String DATAFLOW_WORKER_LOGGING_LEVEL = "dataflow.worker.logging.level";
44-
public static final String DATAFLOW_WORKER_LOGGING_LOCATION = "dataflow.worker.logging.location";
45-
public static final ImmutableBiMap<Level, String> LEVELS =
46-
ImmutableBiMap.<Level, String>builder()
47-
.put(Level.SEVERE, "ERROR")
48-
.put(Level.WARNING, "WARNING")
49-
.put(Level.INFO, "INFO")
50-
.put(Level.FINE, "DEBUG")
51-
.put(Level.FINEST, "TRACE")
53+
private static final String DATAFLOW_WORKER_LOGGING_LOCATION = "dataflow.worker.logging.location";
54+
static final ImmutableBiMap<Level, DataflowWorkerLoggingOptions.Level> LEVELS =
55+
ImmutableBiMap.<Level, DataflowWorkerLoggingOptions.Level>builder()
56+
.put(Level.SEVERE, ERROR)
57+
.put(Level.WARNING, WARN)
58+
.put(Level.INFO, INFO)
59+
.put(Level.FINE, DEBUG)
60+
.put(Level.FINEST, TRACE)
5261
.build();
53-
private static final String DEFAULT_LOG_LEVEL = LEVELS.get(Level.INFO);
5462

55-
public void initialize() {
56-
initialize(LogManager.getLogManager());
57-
}
63+
/**
64+
* This default log level is overridden by the log level found at
65+
* {@code DataflowWorkerLoggingOptions#getDefaultWorkerLogLevel()}.
66+
*/
67+
private static final DataflowWorkerLoggingOptions.Level DEFAULT_LOG_LEVEL =
68+
LEVELS.get(Level.INFO);
69+
70+
/* We need to store a reference to the configured loggers so that they are not
71+
* garbage collected. java.util.logging only has weak references to the loggers
72+
* so if they are garbage collection, our hierarchical configuration will be lost. */
73+
private static List<Logger> configuredLoggers = Lists.newArrayList();
74+
private static FileHandler fileHandler;
5875

59-
void initialize(LogManager logManager) {
76+
/**
77+
* Sets up the initial logging configuration.
78+
*/
79+
public static synchronized void initialize() {
80+
if (fileHandler != null) {
81+
return;
82+
}
6083
try {
61-
Level logLevel = LEVELS.inverse().get(
62-
System.getProperty(DATAFLOW_WORKER_LOGGING_LEVEL, DEFAULT_LOG_LEVEL));
84+
Level logLevel = LEVELS.inverse().get(DEFAULT_LOG_LEVEL);
6385
Formatter formatter = new DataflowWorkerLoggingFormatter();
6486

65-
FileHandler fileHandler = new FileHandler(
87+
fileHandler = new FileHandler(
6688
System.getProperty(DATAFLOW_WORKER_LOGGING_LOCATION, DEFAULT_LOGGING_LOCATION),
6789
true /* Append so that we don't squash existing logs */);
6890
fileHandler.setFormatter(formatter);
69-
fileHandler.setLevel(logLevel);
91+
fileHandler.setLevel(Level.ALL);
7092

7193
// Reset the global log manager, get the root logger and remove the default log handlers.
94+
LogManager logManager = LogManager.getLogManager();
7295
logManager.reset();
7396
Logger rootLogger = logManager.getLogger(ROOT_LOGGER_NAME);
7497
for (Handler handler : rootLogger.getHandlers()) {
@@ -81,4 +104,34 @@ void initialize(LogManager logManager) {
81104
throw new ExceptionInInitializerError(e);
82105
}
83106
}
107+
108+
/**
109+
* Reconfigures logging with the passed in options.
110+
*/
111+
public static synchronized void configure(DataflowWorkerLoggingOptions options) {
112+
initialize();
113+
if (options.getDefaultWorkerLogLevel() != null) {
114+
LogManager.getLogManager().getLogger(ROOT_LOGGER_NAME).setLevel(
115+
LEVELS.inverse().get(options.getDefaultWorkerLogLevel()));
116+
}
117+
/* We store a reference to all the custom loggers the user configured.
118+
* To make sure that these custom levels override the default logger level,
119+
* we break the parent chain and have the logger directly pass log records
120+
* to the file handler. */
121+
if (options.getWorkerLogLevelOverrides() != null) {
122+
for (WorkerLogLevelOverride loggerOverride : options.getWorkerLogLevelOverrides()) {
123+
Logger logger = Logger.getLogger(loggerOverride.getName());
124+
logger.setUseParentHandlers(false);
125+
logger.setLevel(LEVELS.inverse().get(loggerOverride.getLevel()));
126+
logger.addHandler(fileHandler);
127+
configuredLoggers.add(logger);
128+
}
129+
}
130+
}
131+
132+
// Visible for testing
133+
static void reset() {
134+
configuredLoggers = Lists.newArrayList();
135+
fileHandler = null;
136+
}
84137
}

0 commit comments

Comments
 (0)