You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by zm...@apache.org on 2015/08/26 23:00:17 UTC
[27/51] [partial] aurora git commit: Move packages from
com.twitter.common to org.apache.aurora.common
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/inject/DefaultProvider.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/inject/DefaultProvider.java b/commons/src/main/java/org/apache/aurora/common/inject/DefaultProvider.java
new file mode 100644
index 0000000..38fbe4d
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/inject/DefaultProvider.java
@@ -0,0 +1,166 @@
+/**
+ * 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 org.apache.aurora.common.inject;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+
+/**
+ * Provider that has a default value which can be overridden.
+ *
+ * The intended use of this class is:
+ * <pre>
+ * Default installer:
+ * bind(DefaultProvider.makeDefaultKey(Runnable.class, "mykey").toInstance(defaultRunnable);
+ * DefaultProvider.bindOrElse(Runnable.class, "mykey", binder());
+ *
+ * Custom override:
+ * bind(DefaultProvider.makeCustomKey(Runnable.class, "mykey")).toInstance(myCustomRunnable);
+ *
+ * Injection:
+ * {@literal Inject} Named("myKey") Runnable runnable;
+ *
+ * </pre>
+ *
+ * @param <T> the type of object this provides
+ *
+ * @author William Farner
+ * @author John Sirois
+ */
+public class DefaultProvider<T> implements Provider<T> {
+ private static final String DEFAULT_BINDING_KEY_SUFFIX = "_default";
+ private static final String CUSTOM_BINDING_KEY_SUFFIX = "_custom";
+
+ private final Key<T> defaultProviderKey;
+ private final Key<T> customProviderKey;
+
+ private Injector injector;
+
+ public DefaultProvider(Key<T> defaultProviderKey, Key<T> customProviderKey) {
+ this.defaultProviderKey = Preconditions.checkNotNull(defaultProviderKey);
+ this.customProviderKey = Preconditions.checkNotNull(customProviderKey);
+ Preconditions.checkArgument(!defaultProviderKey.equals(customProviderKey));
+ }
+
+ @Inject
+ public void setInjector(Injector injector) {
+ this.injector = injector;
+ }
+
+ @Override
+ public T get() {
+ Preconditions.checkNotNull(injector);
+ return injector.getBindings().containsKey(customProviderKey)
+ ? injector.getInstance(customProviderKey)
+ : injector.getInstance(defaultProviderKey);
+ }
+
+ /**
+ * Creates a DefaultProvider and installs a new module to {@code binder}, which will serve as
+ * an indirection layer for swapping the default binding with a custom one.
+ *
+ * @param customBinding The custom binding key.
+ * @param defaultBinding The default binding key.
+ * @param exposedBinding The exposed binding key.
+ * @param binder The binder to install bindings to.
+ * @param <T> The type of binding to make.
+ */
+ public static <T> void bindOrElse(final Key<T> customBinding, final Key<T> defaultBinding,
+ final Key<T> exposedBinding, Binder binder) {
+ Preconditions.checkNotNull(customBinding);
+ Preconditions.checkNotNull(defaultBinding);
+ Preconditions.checkNotNull(exposedBinding);
+ Preconditions.checkArgument(!customBinding.equals(defaultBinding)
+ && !customBinding.equals(exposedBinding));
+
+ binder.install(new AbstractModule() {
+ @Override protected void configure() {
+ Provider<T> defaultProvider = new DefaultProvider<T>(defaultBinding, customBinding);
+ requestInjection(defaultProvider);
+ bind(exposedBinding).toProvider(defaultProvider);
+ }
+ });
+ }
+
+ /**
+ * Convenience function for creating and installing a DefaultProvider. This will use internal
+ * suffixes to create names for the custom and default bindings. When bound this way, callers
+ * should use one of the functions such as {@link #makeDefaultBindingKey(String)} to set default
+ * and custom bindings.
+ *
+ * @param type The type of object to bind.
+ * @param exposedKey The exposed key.
+ * @param binder The binder to install to.
+ * @param <T> The type of binding to make.
+ */
+ public static <T> void bindOrElse(TypeLiteral<T> type, String exposedKey, Binder binder) {
+ bindOrElse(Key.get(type, Names.named(makeCustomBindingKey(exposedKey))),
+ Key.get(type, Names.named(makeDefaultBindingKey(exposedKey))),
+ Key.get(type, Names.named(exposedKey)),
+ binder);
+ }
+
+ /**
+ * Convenience method for calls to {@link #bindOrElse(TypeLiteral, String, Binder)}, that are not
+ * binding a parameterized type.
+ *
+ * @param type The class of the object to bind.
+ * @param exposedKey The exposed key.
+ * @param binder The binder to install to.
+ * @param <T> The type of binding to make.
+ */
+ public static <T> void bindOrElse(Class<T> type, String exposedKey, Binder binder) {
+ bindOrElse(TypeLiteral.get(type), exposedKey, binder);
+ }
+
+ public static String makeDefaultBindingKey(String rootKey) {
+ return rootKey + DEFAULT_BINDING_KEY_SUFFIX;
+ }
+
+ public static Named makeDefaultBindingName(String rootKey) {
+ return Names.named(makeDefaultBindingKey(rootKey));
+ }
+
+ public static <T> Key<T> makeDefaultKey(TypeLiteral<T> type, String rootKey) {
+ return Key.get(type, makeDefaultBindingName(rootKey));
+ }
+
+ public static <T> Key<T> makeDefaultKey(Class<T> type, String rootKey) {
+ return makeDefaultKey(TypeLiteral.get(type), rootKey);
+ }
+
+ public static String makeCustomBindingKey(String rootKey) {
+ return rootKey + CUSTOM_BINDING_KEY_SUFFIX;
+ }
+
+ public static Named makeCustomBindingName(String rootKey) {
+ return Names.named(makeCustomBindingKey(rootKey));
+ }
+
+ public static <T> Key<T> makeCustomKey(Class<T> type, String rootKey) {
+ return Key.get(type, makeCustomBindingName(rootKey));
+ }
+
+ public static <T> Key<T> makeCustomKey(TypeLiteral<T> type, String rootKey) {
+ return Key.get(type, makeCustomBindingName(rootKey));
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/inject/ProviderMethodModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/inject/ProviderMethodModule.java b/commons/src/main/java/org/apache/aurora/common/inject/ProviderMethodModule.java
new file mode 100644
index 0000000..7543ff1
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/inject/ProviderMethodModule.java
@@ -0,0 +1,32 @@
+/**
+ * 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 org.apache.aurora.common.inject;
+
+import com.google.inject.AbstractModule;
+
+/**
+ * A convenience base class for modules that do all their binding via provider methods.
+ *
+ * @author John Sirois
+ */
+public abstract class ProviderMethodModule extends AbstractModule {
+
+ /**
+ * Does no binding; subclasses should implement provider methods.
+ */
+ @Override
+ protected final void configure() {
+ // noop
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/inject/TimedInterceptor.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/inject/TimedInterceptor.java b/commons/src/main/java/org/apache/aurora/common/inject/TimedInterceptor.java
new file mode 100644
index 0000000..684e7bb
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/inject/TimedInterceptor.java
@@ -0,0 +1,106 @@
+/**
+ * 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 org.apache.aurora.common.inject;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.inject.Binder;
+import com.google.inject.matcher.Matchers;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.commons.lang.StringUtils;
+
+import org.apache.aurora.common.stats.SlidingStats;
+import org.apache.aurora.common.stats.TimeSeriesRepository;
+
+/**
+ * A method interceptor that exports timing information for methods annotated with
+ * {@literal @Timed}.
+ *
+ * @author John Sirois
+ */
+public final class TimedInterceptor implements MethodInterceptor {
+
+ /**
+ * Marks a method as a target for timing.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Timed {
+
+ /**
+ * The base name to export timing data with; empty to use the annotated method's name.
+ */
+ String value() default "";
+ }
+
+ private final LoadingCache<Method, SlidingStats> stats =
+ CacheBuilder.newBuilder().build(new CacheLoader<Method, SlidingStats>() {
+ @Override public SlidingStats load(Method method) {
+ return createStats(method);
+ }
+ });
+
+ private TimedInterceptor() {
+ // preserve for guice
+ }
+
+ private SlidingStats createStats(Method method) {
+ Timed timed = method.getAnnotation(Timed.class);
+ Preconditions.checkArgument(timed != null,
+ "TimedInterceptor can only be applied to @Timed methods");
+
+ String name = timed.value();
+ String statName = !StringUtils.isEmpty(name) ? name : method.getName();
+ return new SlidingStats(statName, "nanos");
+ }
+
+ @Override
+ public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+ // TODO(John Sirois): consider including a SlidingRate tracking thrown exceptions
+ SlidingStats stat = stats.get(methodInvocation.getMethod());
+ long start = System.nanoTime();
+ try {
+ return methodInvocation.proceed();
+ } finally {
+ stat.accumulate(System.nanoTime() - start);
+ }
+ }
+
+ /**
+ * Installs an interceptor in a guice {@link com.google.inject.Injector}, enabling
+ * {@literal @Timed} method interception in guice-provided instances. Requires that a
+ * {@link TimeSeriesRepository} is bound elsewhere.
+ *
+ * @param binder a guice binder to require bindings against
+ */
+ public static void bind(Binder binder) {
+ Preconditions.checkNotNull(binder);
+
+ Bindings.requireBinding(binder, TimeSeriesRepository.class);
+
+ TimedInterceptor interceptor = new TimedInterceptor();
+ binder.requestInjection(interceptor);
+ binder.bindInterceptor(Matchers.any(), Matchers.annotatedWith(Timed.class), interceptor);
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/io/Base64ZlibCodec.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/io/Base64ZlibCodec.java b/commons/src/main/java/org/apache/aurora/common/io/Base64ZlibCodec.java
new file mode 100644
index 0000000..ed36e2a
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/io/Base64ZlibCodec.java
@@ -0,0 +1,169 @@
+/**
+ * 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 org.apache.aurora.common.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.InflaterInputStream;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import com.google.common.io.ByteStreams;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Base64OutputStream;
+
+/**
+ * Utility class providing encoding and decoding methods to and from a string to a utf-8 encoded,
+ * zlib compressed, Base64 encoded representation of the string. For wider compatibility, the
+ * decoder can also automatically recognize GZIP (instead of plain zlib) compressed data too and
+ * decode it accordingly.
+ *
+ * @author Attila Szegedi
+ */
+public final class Base64ZlibCodec {
+ /**
+ * Thrown to indicate invalid data while decoding or unzipping.
+ *
+ * @author Attila Szegedi
+ */
+ public static class InvalidDataException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public InvalidDataException(String message) {
+ super(message);
+ }
+
+ public InvalidDataException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ /**
+ * Text encoding used by the Base64 output stream.
+ */
+ public static final String BASE64_TEXT_ENCODING = "ASCII";
+ private static final int ESTIMATED_PLAINTEXT_TO_ENCODED_RATIO = 4;
+
+ // Prefix all Base64-encoded, zlib compressed data must have
+ private static final byte[] ZLIB_HEADER_PREFIX = new byte[] { 120 };
+ // Prefix all Base64-encoded, GZIP compressed data must have
+ private static final byte[] GZIP_HEADER_PREFIX = new byte[] {31, -117, 8, 0, 0, 0, 0, 0, 0 };
+ private static final int DIAGNOSTIC_PREFIX_LENGTH = 16;
+ // Text encoding for char-to-byte transformation before compressing a stack trace
+ private static final Charset TEXT_ENCODING = com.google.common.base.Charsets.UTF_8;
+
+ private Base64ZlibCodec() {
+ // Utility class
+ }
+
+ /**
+ * Decodes a string. In addition to zlib, it also automatically detects GZIP compressed data and
+ * adjusts accordingly.
+ *
+ * @param encoded the encoded string, represented as a byte array of ASCII-encoded characters
+ * @return the decoded string
+ * @throws InvalidDataException if the string can not be decoded.
+ */
+ public static byte[] decode(String encoded) throws InvalidDataException {
+ Preconditions.checkNotNull(encoded);
+ return decompress(new Base64().decode(encoded));
+ }
+
+ private static byte[] decompress(byte[] compressed) throws InvalidDataException {
+ byte[] bytes;
+ try {
+ final InputStream bin = new ByteArrayInputStream(compressed);
+ final InputStream zin;
+ if (startsWith(compressed, GZIP_HEADER_PREFIX)) {
+ zin = new GZIPInputStream(bin);
+ } else if (startsWith(compressed, ZLIB_HEADER_PREFIX)) {
+ zin = new InflaterInputStream(bin);
+ } else {
+ throw new Base64ZlibCodec.InvalidDataException("Value doesn't start with either GZIP or zlib header");
+ }
+ try {
+ bytes = ByteStreams.toByteArray(zin);
+ } finally {
+ zin.close();
+ }
+ } catch (IOException e) {
+ throw new Base64ZlibCodec.InvalidDataException("zlib/GZIP decoding error", e);
+ }
+ return bytes;
+ }
+
+ private static boolean startsWith(byte[] value, byte[] prefix) {
+ final int pl = prefix.length;
+ if (value.length < pl) {
+ return false;
+ }
+ for (int i = 0; i < pl; ++i) {
+ if (value[i] != prefix[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Encodes a set of bytes.
+ *
+ * @param plain the non-encoded bytes
+ * @return the encoded string
+ */
+ public static String encode(byte[] plain) {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream(plain.length
+ / ESTIMATED_PLAINTEXT_TO_ENCODED_RATIO);
+ final OutputStream w = getDeflatingEncodingStream(out);
+ try {
+ w.write(plain);
+ w.close();
+ return out.toString(BASE64_TEXT_ENCODING);
+ } catch (UnsupportedEncodingException e) {
+ throw reportUnsupportedEncoding();
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ private static OutputStream getDeflatingEncodingStream(OutputStream out) {
+ return new DeflaterOutputStream(new Base64OutputStream(out, true,
+ Integer.MAX_VALUE, null));
+ }
+
+ /**
+ * Returns a writer that writes through to the specified output stream, utf-8 encoding,
+ * zlib compressing, and Base64 encoding its input along the way.
+ *
+ * @param out the output stream that receives the final output
+ * @return a writer for the input
+ */
+ public static Writer getEncodingWriter(OutputStream out) {
+ return new OutputStreamWriter(getDeflatingEncodingStream(out), TEXT_ENCODING);
+ }
+
+ private static AssertionError reportUnsupportedEncoding() {
+ return new AssertionError(String.format("JVM doesn't support the %s encoding", TEXT_ENCODING));
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/io/Codec.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/io/Codec.java b/commons/src/main/java/org/apache/aurora/common/io/Codec.java
new file mode 100644
index 0000000..94d1e36
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/io/Codec.java
@@ -0,0 +1,53 @@
+/**
+ * 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 org.apache.aurora.common.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A Codec represents a reversible encoding for a given type. Codecs are able to both
+ * {@link #deserialize(java.io.InputStream) read} items from streams and
+ * {@link #serialize(Object, java.io.OutputStream) write} items to streams.
+ *
+ * <p> TODO(John Sirois): consider whether this interface should optionally support null items to be
+ * read and written.
+ *
+ * @param <T> The type of object the Codec can handle.
+ *
+ * @author John Sirois
+ */
+public interface Codec<T> {
+
+ /**
+ * Writes a representation of {@code item} to the {@code sink} that can be read back by
+ * {@link #deserialize(java.io.InputStream)}.
+ *
+ * @param item the item to serialize
+ * @param sink the stream to write the item out to
+ * @throws IOException if there is a problem serializing the item
+ */
+ void serialize(T item, OutputStream sink) throws IOException;
+
+ /**
+ * Reads an item from the {@code source} stream that was written by
+ * {@link #serialize(Object, java.io.OutputStream)}.
+ *
+ * @param source the stream to read an item from
+ * @return the deserialized item
+ * @throws IOException if there is a problem reading an item
+ */
+ T deserialize(InputStream source) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/io/CompatibilityCodec.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/io/CompatibilityCodec.java b/commons/src/main/java/org/apache/aurora/common/io/CompatibilityCodec.java
new file mode 100644
index 0000000..c49c7dd
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/io/CompatibilityCodec.java
@@ -0,0 +1,95 @@
+/**
+ * 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 org.apache.aurora.common.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PushbackInputStream;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+
+/**
+ * A codec that composes two codecs: a primary and a compatibility codec. It always serializes with
+ * the primary codec, but can make a decision on deserialization based on the first few bytes of the
+ * serialized format whether to use the compatibility codec. This allows for easier transition
+ * between storage formats as the codec remains able to read the old serialized format.
+ *
+ * @author Attila Szegedi
+ *
+ * @param <T> the type of objects this codec is for.
+ */
+public class CompatibilityCodec<T> implements Codec<T> {
+ private final Codec<T> primaryCodec;
+ private final Codec<T> secondaryCodec;
+ private final int prefixLength;
+ private final Predicate<byte[]> discriminator;
+
+ private CompatibilityCodec(Codec<T> primaryCodec, Codec<T> secondaryCodec, int prefixLength,
+ Predicate<byte[]> discriminator) {
+ Preconditions.checkNotNull(primaryCodec);
+ Preconditions.checkNotNull(secondaryCodec);
+ this.primaryCodec = primaryCodec;
+ this.secondaryCodec = secondaryCodec;
+ this.prefixLength = prefixLength;
+ this.discriminator = discriminator;
+ }
+
+ /**
+ * Creates a new compatibility codec instance.
+ *
+ * @param primaryCodec the codec used to serialize objects, as well as deserialize them when the
+ * first byte of the serialized format matches the discriminator.
+ * @param secondaryCodec the codec used to deserialize objects when the first byte of the
+ * serialized format does not match the discriminator.
+ * @param prefixLength the length, in bytes, of the prefix of the message that is inspected for
+ * determining the format.
+ * @param discriminator a predicate that will receive an array of at most prefixLength bytes
+ * (it can receive less if the serialized format is shorter) and has to return true
+ * if the primary codec should be used for deserialization, otherwise false.
+ */
+ public static <T> CompatibilityCodec<T> create(Codec<T> primaryCodec, Codec<T> secondaryCodec,
+ int prefixLength, Predicate<byte[]> discriminator) {
+ return new CompatibilityCodec<T>(primaryCodec, secondaryCodec, prefixLength, discriminator);
+ }
+
+ @Override
+ public T deserialize(InputStream source) throws IOException {
+ final PushbackInputStream in = new PushbackInputStream(source, prefixLength);
+ final byte[] prefix = readAtMostPrefix(in);
+ in.unread(prefix);
+ return (discriminator.apply(prefix) ? primaryCodec : secondaryCodec).deserialize(in);
+ }
+
+ private byte[] readAtMostPrefix(InputStream in) throws IOException {
+ final byte[] prefix = new byte[prefixLength];
+ int read = 0;
+ do {
+ final int readNow = in.read(prefix, read, prefixLength - read);
+ if (readNow == -1) {
+ byte[] newprefix = new byte[read];
+ System.arraycopy(prefix, 0, newprefix, 0, read);
+ return newprefix;
+ }
+ read += readNow;
+ } while (read < prefixLength);
+ return prefix;
+ }
+
+ @Override
+ public void serialize(T item, OutputStream sink) throws IOException {
+ primaryCodec.serialize(item, sink);
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/io/FileUtils.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/io/FileUtils.java b/commons/src/main/java/org/apache/aurora/common/io/FileUtils.java
new file mode 100644
index 0000000..348e859
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/io/FileUtils.java
@@ -0,0 +1,193 @@
+/**
+ * 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 org.apache.aurora.common.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.UUID;
+
+import com.google.common.base.Preconditions;
+
+import org.apache.commons.lang.SystemUtils;
+
+import org.apache.aurora.common.base.ExceptionalClosure;
+import org.apache.aurora.common.base.ExceptionalFunction;
+
+/**
+ * Utility methods for working with files and directories.
+ *
+ * @author John Sirois
+ */
+public final class FileUtils {
+
+ /**
+ * A utility for creating and working with temporary files and directories.
+ */
+ public static class Temporary {
+ private static final int MAX_TMP_DIR_TRIES = 5;
+
+ private final File basedir;
+
+ /**
+ * Creates a new temporary utility that creates files and directories rooted at {@code basedir}.
+ *
+ * @param basedir The base directory to generate temporary files and directories in.
+ */
+ public Temporary(File basedir) {
+ Preconditions.checkNotNull(basedir);
+ this.basedir = basedir;
+ }
+
+ /**
+ * Returns a new empty temporary directory.
+ *
+ * @return a file representing the newly created directory.
+ * @throws IllegalStateException if a new temporary directory could not be created
+ */
+ public File createDir() {
+ File tempDir;
+ int tries = 0;
+ do {
+ // For sanity sake, die eventually if we keep failing to pick a new unique directory name.
+ if (++tries > MAX_TMP_DIR_TRIES) {
+ throw new IllegalStateException("Failed to create a new temp directory in "
+ + MAX_TMP_DIR_TRIES + " attempts, giving up");
+ }
+ tempDir = new File(basedir, UUID.randomUUID().toString());
+ } while (!tempDir.mkdir());
+ return tempDir;
+ }
+
+ /**
+ * Creates a new empty temporary file.
+ *
+ * @return a new empty temporary file
+ * @throws IOException if there was a problem creating a new temporary file
+ */
+ public File createFile() throws IOException {
+ return createFile(".tempfile");
+ }
+
+ /**
+ * Creates a new empty temporary file with the given filename {@code suffix}.
+ *
+ * @param suffix The suffix for the temporary file name
+ * @return a new empty temporary file
+ * @throws IOException if there was a problem creating a new temporary file
+ */
+ public File createFile(String suffix) throws IOException {
+ return File.createTempFile(FileUtils.class.getName(), suffix, basedir);
+ }
+
+ /**
+ * Creates a new temporary directory and executes the unit of {@code work} against it ensuring
+ * the directory and its contents are removed after the work completes normally or abnormally.
+ *
+ * @param work The unit of work to execute against the new temporary directory.
+ * @param <E> The type of exception this unit of work can throw.
+ * @throws E bubbled transparently when the unit of work throws
+ */
+ public <E extends Exception> void doWithDir(final ExceptionalClosure<File, E> work)
+ throws E {
+ Preconditions.checkNotNull(work);
+ doWithDir(new ExceptionalFunction<File, Void, E>() {
+ @Override public Void apply(File dir) throws E {
+ work.execute(dir);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Creates a new temporary directory and executes the unit of {@code work} against it ensuring
+ * the directory and its contents are removed after the work completes normally or abnormally.
+ *
+ * @param work The unit of work to execute against the new temporary directory.
+ * @param <T> The type of result this unit of work produces.
+ * @param <E> The type of exception this unit of work can throw.
+ * @return the result when the unit of work completes successfully
+ * @throws E bubbled transparently when the unit of work throws
+ */
+ public <T, E extends Exception> T doWithDir(ExceptionalFunction<File, T, E> work)
+ throws E {
+ Preconditions.checkNotNull(work);
+ return doWithTemp(createDir(), work);
+ }
+
+ /**
+ * Creates a new temporary file and executes the unit of {@code work} against it ensuring
+ * the file is removed after the work completes normally or abnormally.
+ *
+ * @param work The unit of work to execute against the new temporary file.
+ * @param <E> The type of exception this unit of work can throw.
+ * @throws E bubbled transparently when the unit of work throws
+ * @throws IOException if there was a problem creating a new temporary file
+ */
+ public <E extends Exception> void doWithFile(final ExceptionalClosure<File, E> work)
+ throws E, IOException {
+ Preconditions.checkNotNull(work);
+ doWithFile(new ExceptionalFunction<File, Void, E>() {
+ @Override public Void apply(File dir) throws E {
+ work.execute(dir);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Creates a new temporary file and executes the unit of {@code work} against it ensuring
+ * the file is removed after the work completes normally or abnormally.
+ *
+ * @param work The unit of work to execute against the new temporary file.
+ * @param <T> The type of result this unit of work produces.
+ * @param <E> The type of exception this unit of work can throw.
+ * @return the result when the unit of work completes successfully
+ * @throws E bubbled transparently when the unit of work throws
+ * @throws IOException if there was a problem creating a new temporary file
+ */
+ public <T, E extends Exception> T doWithFile(ExceptionalFunction<File, T, E> work)
+ throws E, IOException {
+ Preconditions.checkNotNull(work);
+ return doWithTemp(createFile(), work);
+ }
+
+ private static <T, E extends Exception> T doWithTemp(File file,
+ ExceptionalFunction<File, T, E> work) throws E {
+ try {
+ return work.apply(file);
+ } finally {
+ org.apache.commons.io.FileUtils.deleteQuietly(file);
+ }
+ }
+ }
+
+ /**
+ * A temporary based at the default system temporary directory.
+ */
+ public static final Temporary SYSTEM_TMP = new Temporary(SystemUtils.getJavaIoTmpDir());
+
+ /**
+ * Returns a new empty temporary directory.
+ *
+ * @return a file representing the newly created directory.
+ * @throws IllegalStateException if a new temporary directory could not be created
+ */
+ public static File createTempDir() {
+ return SYSTEM_TMP.createDir();
+ }
+
+ private FileUtils() {
+ // utility
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/io/JsonCodec.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/io/JsonCodec.java b/commons/src/main/java/org/apache/aurora/common/io/JsonCodec.java
new file mode 100644
index 0000000..1b955db
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/io/JsonCodec.java
@@ -0,0 +1,124 @@
+/**
+ * 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 org.apache.aurora.common.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.BitSet;
+
+import com.google.common.base.Preconditions;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * A {@code Codec} that can encode and decode objects to and from JSON using the GSON library
+ * (which in turn will use reflection). The codec uses the UTF-8 encoding.
+ *
+ * @author Attila Szegedi
+ */
+public class JsonCodec<T> implements Codec<T> {
+
+ private static final String ENCODING = "utf-8";
+
+ private final Class<T> clazz;
+ private final Gson gson;
+
+ /**
+ * Creates a new JSON codec instance for objects of the specified class.
+ *
+ * @param clazz the class of the objects the created codec is for.
+ * @return a newly constructed JSON codec instance for objects of the requested class.
+ */
+ public static <T> JsonCodec<T> create(Class<T> clazz) {
+ return new JsonCodec<T>(clazz, DefaultGsonHolder.instance);
+ }
+
+ /**
+ * Creates a new JSON codec instance for objects of the specified class and the specified Gson
+ * instance. You can use this method if you need to customize the behavior of the Gson
+ * serializer.
+ *
+ * @param clazz the class of the objects the created codec is for.
+ * @param gson the Gson instance to use for serialization/deserialization.
+ * @return a newly constructed JSON codec instance for objects of the requested class.
+ */
+ public static <T> JsonCodec<T> create(Class<T> clazz, Gson gson) {
+ return new JsonCodec<T>(clazz, gson);
+ }
+
+ private JsonCodec(Class<T> clazz, Gson gson) {
+ Preconditions.checkNotNull(clazz);
+ Preconditions.checkNotNull(gson);
+ this.clazz = clazz;
+ this.gson = gson;
+ }
+
+ private static final class DefaultGsonHolder {
+ static final Gson instance = new Gson();
+ }
+
+ /**
+ * Returns a Gson exclusion strategy that excludes Thrift synthetic fields from JSON
+ * serialization. You can pass it to a {@link GsonBuilder} to construct a customized {@link Gson}
+ * instance to use with {@link JsonCodec#create(Class, Gson)}.
+ *
+ * @return a Gson exclusion strategy for thrift synthetic fields.
+ */
+ public static ExclusionStrategy getThriftExclusionStrategy() {
+ return ThriftExclusionStrategy.instance;
+ }
+
+ private static final class ThriftExclusionStrategy implements ExclusionStrategy {
+ static final ExclusionStrategy instance = new ThriftExclusionStrategy();
+
+ public boolean shouldSkipClass(Class<?> clazz) {
+ return false;
+ }
+
+ public boolean shouldSkipField(FieldAttributes f) {
+ // Exclude Thrift synthetic fields
+ return f.getDeclaredClass() == BitSet.class && f.getName().equals("__isset_bit_vector");
+ }
+ }
+
+ @Override
+ public T deserialize(InputStream source) throws IOException {
+ return gson.fromJson(new InputStreamReader(source, ENCODING), clazz);
+ }
+
+ @Override
+ public void serialize(T item, OutputStream sink) throws IOException {
+ final Writer w = new OutputStreamWriter(new UnflushableOutputStream(sink), ENCODING);
+ gson.toJson(item, clazz, w);
+ w.flush();
+ }
+
+ private static class UnflushableOutputStream extends FilterOutputStream {
+ UnflushableOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ // Intentionally do nothing
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/io/Streamer.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/io/Streamer.java b/commons/src/main/java/org/apache/aurora/common/io/Streamer.java
new file mode 100644
index 0000000..9026760
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/io/Streamer.java
@@ -0,0 +1,54 @@
+/**
+ * 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 org.apache.aurora.common.io;
+
+import com.google.common.base.Predicate;
+import org.apache.aurora.common.base.Closure;
+
+/**
+ * Encapsulates iteration over a typed data stream that can be filtered.
+ *
+ * @author John Sirois
+ */
+public interface Streamer<T> {
+
+ /**
+ * Processes a stream fully. This may cause a database query to be executed, a file to be read
+ * or even just call {@link Iterable#iterator()} depending on the implementation. Implementations
+ * guaranty that any resources allocated opening the stream will be closed whether or not process
+ * completes normally.
+ *
+ * @param work a closure over the work to be done for each item in the stream.
+ */
+ void process(Closure<T> work);
+
+ /**
+ * Returns a {@code Streamer} that will process the same stream as this streamer, but will stop
+ * processing when encountering the first item for which {@code cond} is true.
+ *
+ * @param cond a predicate that returns {@code false} as long as the stream should keep being
+ * processed.
+ * @return a streamer that will process items until the condition triggers.
+ */
+ Streamer<T> endOn(Predicate<T> cond);
+
+ /**
+ * Returns a {@code Streamer} that will process the same stream as this streamer, but with any
+ * items failing the filter to be omitted from processing.
+ * @param filter a predicate that returns {@code true} if an item in the stream should be
+ * processed
+ * @return a filtered streamer
+ */
+ Streamer<T> filter(Predicate<T> filter);
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/io/ThriftCodec.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/io/ThriftCodec.java b/commons/src/main/java/org/apache/aurora/common/io/ThriftCodec.java
new file mode 100644
index 0000000..6644788
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/io/ThriftCodec.java
@@ -0,0 +1,104 @@
+/**
+ * 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 org.apache.aurora.common.io;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+import org.apache.aurora.common.base.MoreSuppliers;
+import org.apache.thrift.TBase;
+import org.apache.thrift.TException;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TCompactProtocol;
+import org.apache.thrift.protocol.TJSONProtocol;
+import org.apache.thrift.protocol.TProtocol;
+import org.apache.thrift.transport.TIOStreamTransport;
+import org.apache.thrift.transport.TTransport;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A {@code Codec} that can encode and decode thrift structs.
+ */
+public class ThriftCodec<T extends TBase> implements Codec<T> {
+
+ public static final Function<TTransport, TProtocol> JSON_PROTOCOL =
+ new Function<TTransport, TProtocol>() {
+ @Override public TProtocol apply(TTransport transport) {
+ return new TJSONProtocol(transport);
+ }
+ };
+
+ public static final Function<TTransport, TProtocol> BINARY_PROTOCOL =
+ new Function<TTransport, TProtocol>() {
+ @Override public TProtocol apply(TTransport transport) {
+ return new TBinaryProtocol(transport);
+ }
+ };
+
+ public static final Function<TTransport, TProtocol> COMPACT_PROTOCOL =
+ new Function<TTransport, TProtocol>() {
+ @Override public TProtocol apply(TTransport transport) {
+ return new TCompactProtocol(transport);
+ }
+ };
+
+ private final Supplier<T> templateSupplier;
+ private final Function<TTransport, TProtocol> protocolFactory;
+
+ public static <T extends TBase> ThriftCodec<T> create(final Class<T> thriftStructType,
+ Function<TTransport, TProtocol> protocolFactory) {
+ return new ThriftCodec<T>(MoreSuppliers.of(thriftStructType), protocolFactory);
+ }
+
+ /**
+ * @deprecated use {@link ThriftCodec#create(Class, Function)} instead.
+ */
+ @Deprecated
+ public ThriftCodec(final Class<T> thriftStructType,
+ Function<TTransport, TProtocol> protocolFactory) {
+ this(MoreSuppliers.of(thriftStructType), protocolFactory);
+ }
+
+ public ThriftCodec(Supplier<T> templateSupplier,
+ Function<TTransport, TProtocol> protocolFactory) {
+ this.templateSupplier = Preconditions.checkNotNull(templateSupplier);
+ this.protocolFactory = Preconditions.checkNotNull(protocolFactory);
+ }
+
+ @Override
+ public void serialize(T item, OutputStream sink) throws IOException {
+ Preconditions.checkNotNull(item);
+ Preconditions.checkNotNull(sink);
+ try {
+ item.write(protocolFactory.apply(new TIOStreamTransport(null, sink)));
+ } catch (TException e) {
+ throw new IOException("Problem serializing thrift struct: " + item, e);
+ }
+ }
+
+ @Override
+ public T deserialize(InputStream source) throws IOException {
+ Preconditions.checkNotNull(source);
+ T template = templateSupplier.get();
+ try {
+ template.read(protocolFactory.apply(new TIOStreamTransport(source, null)));
+ } catch (TException e) {
+ throw new IOException("Problem de-serializing thrift struct from stream", e);
+ }
+ return template;
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/logging/BufferedLog.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/logging/BufferedLog.java b/commons/src/main/java/org/apache/aurora/common/logging/BufferedLog.java
new file mode 100644
index 0000000..a83895d
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/logging/BufferedLog.java
@@ -0,0 +1,278 @@
+/**
+ * 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 org.apache.aurora.common.logging;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.apache.aurora.common.quantity.Amount;
+import org.apache.aurora.common.quantity.Time;
+import org.apache.aurora.common.stats.StatImpl;
+import org.apache.aurora.common.stats.Stats;
+
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Logger;
+
+/**
+ * Log that buffers requests before sending them to a wrapped log.
+ *
+ * @author William Farner
+ */
+public class BufferedLog<T, R> implements Log<T, Void> {
+ private static final Logger LOG = Logger.getLogger(BufferedLog.class.getName());
+
+ private static final ExecutorService DEFAULT_EXECUTOR_SERVICE =
+ Executors.newSingleThreadExecutor(
+ new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Log Pusher-%d").build());
+ private static final int DEFAULT_MAX_BUFFER_SIZE = 100000;
+
+ // TODO(William Farner): Change to use a ScheduledExecutorService instead of a timer.
+ private final TimerTask logPusher = new TimerTask() {
+ @Override public void run() {
+ flush();
+ }
+ };
+
+ // Local buffer of log messages.
+ private final List<T> localBuffer = Lists.newLinkedList();
+
+ // The log that is being buffered.
+ private Log<T, R> bufferedLog;
+
+ // Filter to determine when a log request should be retried.
+ private Predicate<R> retryFilter = null;
+
+ // Maximum number of log entries that can be buffered before truncation (lost messages).
+ private int maxBufferSize = DEFAULT_MAX_BUFFER_SIZE;
+
+ // Maximum buffer length before attempting to submit.
+ private int chunkLength;
+
+ // Maximum time for a message to sit in the buffer before attempting to flush.
+ private Amount<Integer, Time> flushInterval;
+
+ // Service to handle flushing the log.
+ private ExecutorService logSubmitService = DEFAULT_EXECUTOR_SERVICE;
+
+ private BufferedLog() {
+ // Created through builder.
+
+ Stats.export(new StatImpl<Integer>("scribe_buffer_size") {
+ public Integer read() { return getBacklog(); }
+ });
+ }
+
+ public static <T, R> Builder<T, R> builder() {
+ return new Builder<T, R>();
+ }
+
+ /**
+ * Starts the log submission service by scheduling a timer to periodically submit messages.
+ */
+ private void start() {
+ long flushIntervalMillis = flushInterval.as(Time.MILLISECONDS);
+
+ new Timer(true).scheduleAtFixedRate(logPusher, flushIntervalMillis, flushIntervalMillis);
+ }
+
+ /**
+ * Gets the current number of messages in the local buffer.
+ *
+ * @return The number of backlogged messages.
+ */
+ protected int getBacklog() {
+ synchronized (localBuffer) {
+ return localBuffer.size();
+ }
+ }
+
+ /**
+ * Stores a log entry, flushing immediately if the buffer length limit is exceeded.
+ *
+ * @param entry Entry to log.
+ */
+ @Override
+ public Void log(T entry) {
+ synchronized (localBuffer) {
+ localBuffer.add(entry);
+
+ if (localBuffer.size() >= chunkLength) {
+ logSubmitService.submit(logPusher);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public Void log(List<T> entries) {
+ for (T entry : entries) log(entry);
+
+ return null;
+ }
+
+ @Override
+ public void flush() {
+ List<T> buffer = copyBuffer();
+ if (buffer.isEmpty()) return;
+
+ R result = bufferedLog.log(buffer);
+
+ // Restore the buffer if the write was not successful.
+ if (retryFilter != null && retryFilter.apply(result)) {
+ LOG.warning("Log request failed, restoring spooled messages.");
+ restoreToLocalBuffer(buffer);
+ }
+ }
+
+ /**
+ * Creats a snapshot of the local buffer and clears the local buffer.
+ *
+ * @return A snapshot of the local buffer.
+ */
+ private List<T> copyBuffer() {
+ synchronized (localBuffer) {
+ List<T> bufferCopy = ImmutableList.copyOf(localBuffer);
+ localBuffer.clear();
+ return bufferCopy;
+ }
+ }
+
+ /**
+ * Restores log entries back to the local buffer. This can be used to commit entries back to the
+ * buffer after a flush operation failed.
+ *
+ * @param buffer The log entries to restore.
+ */
+ private void restoreToLocalBuffer(List<T> buffer) {
+ synchronized (localBuffer) {
+ int restoreRecords = Math.min(buffer.size(), maxBufferSize - localBuffer.size());
+
+ if (restoreRecords != buffer.size()) {
+ LOG.severe((buffer.size() - restoreRecords) + " log records truncated!");
+
+ if (restoreRecords == 0) return;
+ }
+
+ localBuffer.addAll(0, buffer.subList(buffer.size() - restoreRecords, buffer.size()));
+ }
+ }
+
+ /**
+ * Configures a BufferedLog object.
+ *
+ * @param <T> Log message type.
+ * @param <R> Log result type.
+ */
+ public static class Builder<T, R> {
+ private final BufferedLog<T, R> instance;
+
+ public Builder() {
+ instance = new BufferedLog<T, R>();
+ }
+
+ /**
+ * Specifies the log that should be buffered.
+ *
+ * @param bufferedLog Log to buffer requests to.
+ * @return A reference to the builder.
+ */
+ public Builder<T, R> buffer(Log<T, R> bufferedLog) {
+ instance.bufferedLog = bufferedLog;
+ return this;
+ }
+
+ /**
+ * Adds a custom retry filter that will be used to determine whether a log result {@code R}
+ * should be used to indicate that a log request should be retried. Log submit retry behavior
+ * is not defined when the filter throws uncaught exceptions.
+ *
+ * @param retryFilter Filter to determine whether to retry.
+ * @return A reference to the builder.
+ */
+ public Builder<T, R> withRetryFilter(Predicate<R> retryFilter) {
+ instance.retryFilter = retryFilter;
+ return this;
+ }
+
+ /**
+ * Specifies the maximum allowable buffer size, after which log records will be dropped to
+ * conserve memory.
+ *
+ * @param maxBufferSize Maximum buffer size.
+ * @return A reference to the builder.
+ */
+ public Builder<T, R> withMaxBuffer(int maxBufferSize) {
+ instance.maxBufferSize = maxBufferSize;
+ return this;
+ }
+
+ /**
+ * Specifies the desired number of log records to submit in each request.
+ *
+ * @param chunkLength Maximum number of records to accumulate before trying to submit.
+ * @return A reference to the builder.
+ */
+ public Builder<T, R> withChunkLength(int chunkLength) {
+ instance.chunkLength = chunkLength;
+ return this;
+ }
+
+ /**
+ * Specifies the maximum amount of time that a log entry may wait in the buffer before an
+ * attempt is made to flush the buffer.
+ *
+ * @param flushInterval Log flush interval.
+ * @return A reference to the builder.
+ */
+ public Builder<T, R> withFlushInterval(Amount<Integer, Time> flushInterval) {
+ instance.flushInterval = flushInterval;
+ return this;
+ }
+
+ /**
+ * Specifies the executor service to use for (synchronously or asynchronously) sending
+ * log entries.
+ *
+ * @param logSubmitService Log submit executor service.
+ * @return A reference to the builder.
+ */
+ public Builder<T, R> withExecutorService(ExecutorService logSubmitService) {
+ instance.logSubmitService = logSubmitService;
+ return this;
+ }
+
+ /**
+ * Creates the buffered log.
+ *
+ * @return The prepared buffered log.
+ */
+ public BufferedLog<T, R> build() {
+ Preconditions.checkArgument(instance.chunkLength > 0);
+ Preconditions.checkArgument(instance.flushInterval.as(Time.MILLISECONDS) > 0);
+ Preconditions.checkNotNull(instance.logSubmitService);
+ Preconditions.checkArgument(instance.chunkLength <= instance.maxBufferSize);
+
+ instance.start();
+
+ return instance;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/logging/Glog.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/logging/Glog.java b/commons/src/main/java/org/apache/aurora/common/logging/Glog.java
new file mode 100644
index 0000000..5bae399
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/logging/Glog.java
@@ -0,0 +1,208 @@
+/**
+ * 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 org.apache.aurora.common.logging;
+
+import javax.annotation.Nullable;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Throwables;
+
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+/**
+ * A utility that can format log records to match the format generated by glog:
+ * <pre>
+ * I0218 17:36:47.461 (source) (message)
+ * </pre>
+ */
+public final class Glog {
+
+ /**
+ * Classifies the importance of a log message.
+ */
+ public enum Level {
+
+ /**
+ * Indicates the message's classification is unknown. This most likely indicates a
+ * configuration or programming error that can be corrected by mapping the underlying log
+ * system's level appropriately.
+ */
+ UNKNOWN('U'),
+
+ /**
+ * Indicates the message is for debugging purposes only.
+ */
+ DEBUG('D'),
+
+ /**
+ * Indicates a message of general interest.
+ */
+ INFO('I'),
+
+ /**
+ * Indicates a warning message likely worth of attention.
+ */
+ WARNING('W'),
+
+ /**
+ * Indicates an unexpected error.
+ */
+ ERROR('E'),
+
+ /**
+ * Indicates a fatal exception generally paired with actions to shut down the errored process.
+ */
+ FATAL('F');
+
+ final char label;
+
+ private Level(char label) {
+ this.label = label;
+ }
+ }
+
+ /**
+ * An object that can provide details of a log record.
+ *
+ * @param <T> The type of log record the formatter handles.
+ */
+ public interface Formatter<T> {
+
+ /**
+ * Gets the message contained in the log record.
+ *
+ * @param record The record to extract a message from.
+ * @return The formatted message.
+ */
+ String getMessage(T record);
+
+ /**
+ * Gets the class name of the class that sent the log record for logging.
+ *
+ * @param record The record to extract a producing class name from.
+ * @return The producing class if known; otherwise {@code null}.
+ */
+ @Nullable
+ String getClassName(T record);
+
+ /**
+ * Gets the name of the method of within the class that sent the log record for logging.
+ *
+ * @param record The record to extract a producing method name from.
+ * @return The producing method name if known; otherwise {@code null}.
+ */
+ @Nullable
+ String getMethodName(T record);
+
+ /**
+ * Gets the level of the log record.
+ *
+ * @param record The record to extract a log level from.
+ * @return The record's log level. Can be {@code null} or {@link Level#UNKNOWN} if unknown.
+ */
+ @Nullable
+ Level getLevel(T record);
+
+ /**
+ * Gets the timestamp in milliseconds since the epoch when the log record was generated.
+ *
+ * @param record The record to extract a time stamp from.
+ * @return The log record's birth date.
+ */
+ long getTimeStamp(T record);
+
+ /**
+ * Gets the id of the thread that generated the log record.
+ *
+ * @param record The record to extract a thread id from.
+ * @return The id of the thread that generated the log record.
+ */
+ long getThreadId(T record);
+
+ /**
+ * Gets the exception associated with the log record if any.
+ *
+ * @param record The record to extract an exception from.
+ * @return The exception associated with the log record; may be {@code null}.
+ */
+ @Nullable
+ Throwable getThrowable(T record);
+ }
+
+ private static final DateTimeFormatter DATE_TIME_FORMATTER =
+ DateTimeFormat.forPattern("MMdd HH:mm:ss.SSS").withZone(DateTimeZone.UTC);
+
+ private static final int BASE_MESSAGE_LENGTH =
+ 1 // Level char.
+ + 4 // Month + day
+ + 1 // space
+ + 12 // Timestamp
+ + 1 // space
+ + 6 // THREAD
+ + 4 // Room for thread ID.
+ + 1; // space
+
+ /**
+ * Converts the given log record into a glog format log line using the given formatter.
+ *
+ * @param formatter A formatter that understands how to unpack the given log record.
+ * @param record A structure containing log data.
+ * @param <T> The type of log record.
+ * @return A glog formatted log line.
+ */
+ public static <T> String formatRecord(Formatter<T> formatter, T record) {
+ String message = formatter.getMessage(record);
+ int messageLength = BASE_MESSAGE_LENGTH
+ + 2 // Colon and space
+ + message.length();
+
+ String className = formatter.getClassName(record);
+ String methodName = null;
+ if (className != null) {
+ messageLength += className.length();
+ methodName = formatter.getMethodName(record);
+ if (methodName != null) {
+ messageLength += 1; // Period between class and method.
+ messageLength += methodName.length();
+ }
+ }
+
+ StringBuilder sb = new StringBuilder(messageLength)
+ .append(Objects.firstNonNull(formatter.getLevel(record), Level.UNKNOWN).label)
+ .append(DATE_TIME_FORMATTER.print(formatter.getTimeStamp(record)))
+ .append(" THREAD")
+ .append(formatter.getThreadId(record));
+
+ if (className != null) {
+ sb.append(' ').append(className);
+ if (methodName != null) {
+ sb.append('.').append(methodName);
+ }
+ }
+
+ sb.append(": ").append(message);
+ Throwable throwable = formatter.getThrowable(record);
+ if (throwable != null) {
+ sb.append('\n').append(Throwables.getStackTraceAsString(throwable));
+ }
+
+ return sb.append('\n').toString();
+ }
+
+ private Glog() {
+ // utility
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/logging/Log.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/logging/Log.java b/commons/src/main/java/org/apache/aurora/common/logging/Log.java
new file mode 100644
index 0000000..3f045f7
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/logging/Log.java
@@ -0,0 +1,45 @@
+/**
+ * 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 org.apache.aurora.common.logging;
+
+import java.util.List;
+
+/**
+ * Logs messages to scribe.
+ *
+ * @author William Farner
+ */
+public interface Log<T, R> {
+
+ /**
+ * Submits a log message.
+ *
+ * @param entry Entry to log.
+ * @return The result of the log request.
+ */
+ public R log(T entry);
+
+ /**
+ * Batch version of log.
+ *
+ * @param entries Entries to log.
+ * @return The result of the log request.
+ */
+ public R log(List<T> entries);
+
+ /**
+ * Flushes the log, attempting to purge any state that is only stored locally.
+ */
+ public void flush();
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/logging/LogFormatter.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/logging/LogFormatter.java b/commons/src/main/java/org/apache/aurora/common/logging/LogFormatter.java
new file mode 100644
index 0000000..0cb621d
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/logging/LogFormatter.java
@@ -0,0 +1,80 @@
+/**
+ * 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 org.apache.aurora.common.logging;
+
+import java.util.logging.Formatter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Log formatter to match the format generated by glog.
+ *
+ * @see Glog
+ */
+public class LogFormatter extends Formatter implements Glog.Formatter<LogRecord> {
+
+ private static final ImmutableMap<Level, Glog.Level> LEVEL_LABELS =
+ ImmutableMap.<Level, Glog.Level>builder()
+ .put(Level.FINEST, Glog.Level.DEBUG)
+ .put(Level.FINER, Glog.Level.DEBUG)
+ .put(Level.FINE, Glog.Level.DEBUG)
+ .put(Level.CONFIG, Glog.Level.INFO)
+ .put(Level.INFO, Glog.Level.INFO)
+ .put(Level.WARNING, Glog.Level.WARNING)
+ .put(Level.SEVERE, Glog.Level.ERROR)
+ .build();
+
+
+ @Override
+ public String format(final LogRecord record) {
+ return Glog.formatRecord(this, record);
+ }
+
+ @Override
+ public String getMessage(LogRecord record) {
+ return formatMessage(record);
+ }
+
+ @Override
+ public String getClassName(LogRecord record) {
+ return record.getSourceClassName();
+ }
+
+ @Override
+ public String getMethodName(LogRecord record) {
+ return record.getSourceMethodName();
+ }
+
+ @Override
+ public Glog.Level getLevel(LogRecord record) {
+ return LEVEL_LABELS.get(record.getLevel());
+ }
+
+ @Override
+ public long getTimeStamp(LogRecord record) {
+ return record.getMillis();
+ }
+
+ @Override
+ public long getThreadId(LogRecord record) {
+ return record.getThreadID();
+ }
+
+ @Override
+ public Throwable getThrowable(LogRecord record) {
+ return record.getThrown();
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/logging/LogUtil.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/logging/LogUtil.java b/commons/src/main/java/org/apache/aurora/common/logging/LogUtil.java
new file mode 100644
index 0000000..5f97cb2
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/logging/LogUtil.java
@@ -0,0 +1,90 @@
+/**
+ * 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 org.apache.aurora.common.logging;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.SystemUtils;
+
+import java.io.File;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+/**
+ * Logging utility functions.
+ *
+ * @author William Farner
+ */
+public class LogUtil {
+
+ private static final Logger LOG = Logger.getLogger(LogUtil.class.getName());
+
+ private static final String LOG_MANAGER_FILE_PROP = "java.util.logging.FileHandler.pattern";
+
+ @VisibleForTesting
+ static final File DEFAULT_LOG_DIR = new File("/var/log");
+
+ /**
+ * Gets the log directory as configured with the log manager. This will attempt to expand any
+ * directory wildcards that are included in log file property.
+ *
+ * @return The configured log directory.
+ */
+ public static File getLogManagerLogDir() {
+ return getLogManagerLogDir(LogManager.getLogManager().getProperty(LOG_MANAGER_FILE_PROP));
+ }
+
+ /**
+ * Gets the log directory as specified in a log file pattern. This will attempt to expand any
+ * directory wildcards that are included in log file property.
+ *
+ * @param logFilePattern The pattern to extract the log directory from.
+ * @return The configured log directory.
+ */
+ public static File getLogManagerLogDir(String logFilePattern) {
+ if (StringUtils.isEmpty(logFilePattern)) {
+ LOG.warning("Could not find log dir in logging property " + LOG_MANAGER_FILE_PROP
+ + ", reading from " + DEFAULT_LOG_DIR);
+ return DEFAULT_LOG_DIR;
+ }
+
+ String logDir = expandWildcard(logFilePattern, "%t", SystemUtils.JAVA_IO_TMPDIR);
+ logDir = expandWildcard(logDir, "%h", SystemUtils.USER_HOME);
+ File parent = new File(logDir).getParentFile();
+ return parent == null ? new File(".") : parent;
+ }
+
+ /**
+ * Expands a directory path wildcard within a file pattern string.
+ * Correctly handles cases where the replacement string does and does not contain a trailing
+ * slash.
+ *
+ * @param pattern File pattern string, which may or may not contain a wildcard.
+ * @param dirWildcard Wildcard string to expand.
+ * @param replacement Path component to expand wildcard to.
+ * @return {@code pattern} with all instances of {@code dirWildcard} replaced with
+ * {@code replacement}.
+ */
+ private static String expandWildcard(String pattern, String dirWildcard, String replacement) {
+ String replace = dirWildcard;
+ if (replacement.charAt(replacement.length() - 1) == '/') {
+ replace += '/';
+ }
+ return pattern.replaceAll(replace, replacement);
+ }
+
+ private LogUtil() {
+ // Utility class.
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/logging/RootLogConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/logging/RootLogConfig.java b/commons/src/main/java/org/apache/aurora/common/logging/RootLogConfig.java
new file mode 100644
index 0000000..7f010fd
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/logging/RootLogConfig.java
@@ -0,0 +1,339 @@
+/**
+ * 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 org.apache.aurora.common.logging;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+import org.apache.aurora.common.args.Arg;
+import org.apache.aurora.common.args.CmdLine;
+
+/**
+ * A configuration class for the root java.util.logging Logger.
+ *
+ * Defines flags to control the behavior behavior of the root logger similarly to Google's glog
+ * library (see http://code.google.com/p/google-glog ).
+ */
+public class RootLogConfig {
+ /**
+ * An enum reflecting log {@link Level} constants.
+ */
+ public enum LogLevel {
+ FINEST(Level.FINEST),
+ FINER(Level.FINER),
+ FINE(Level.FINE),
+ CONFIG(Level.CONFIG),
+ INFO(Level.INFO),
+ WARNING(Level.WARNING),
+ SEVERE(Level.SEVERE);
+
+ private final Level level;
+
+ private LogLevel(Level level) {
+ this.level = level;
+ }
+
+ private Level getLevel() {
+ return level;
+ }
+ }
+
+ @CmdLine(name = "logtostderr", help = "Log messages to stderr instead of logfiles.")
+ private static Arg<Boolean> LOGTOSTDERR = Arg.create(false);
+
+ @CmdLine(name = "alsologtostderr",
+ help = "Log messages to stderr, in addition to log files. Ignored when --logtostderr")
+ private static Arg<Boolean> ALSOLOGTOSTDERR = Arg.create(false);
+
+ @CmdLine(name = "vlog",
+ help = "The value is one of the constants in java.util.logging.Level. "
+ + "Shows all messages with level equal or higher "
+ + "than the value of this flag.")
+ private static Arg<LogLevel> VLOG = Arg.create(LogLevel.INFO);
+
+ @CmdLine(name = "vmodule",
+ help = "Per-class verbose level. The argument has to contain a comma-separated list "
+ + "of <class_name>=<log_level>. <class_name> is the full-qualified name of a "
+ + "class, <log_level> is one of the constants in java.util.logging.Level. "
+ + "<log_level> overrides any value given by --vlog.")
+ private static Arg<Map<Class<?>, LogLevel>> VMODULE =
+ Arg.<Map<Class<?>, LogLevel>>create(new HashMap<Class<?>, LogLevel>());
+
+ @CmdLine(name = "use_glog_formatter", help = "True to use the glog formatter exclusively.")
+ private static Arg<Boolean> USE_GLOG_FORMATTER = Arg.create(true);
+
+ /**
+ * Represents a logging configuration for java.util.logging.
+ */
+ public static final class Configuration {
+ boolean logToStderr = false;
+ boolean alsoLogToStderr = false;
+ boolean useGLogFormatter = true;
+ LogLevel vlog = LogLevel.INFO;
+ ImmutableMap<Class<?>, LogLevel> vmodule = ImmutableMap.of();
+ String rootLoggerName = "";
+
+ Configuration() {
+ // Guard for internal use only.
+ }
+
+ /**
+ * Returns {@code true} if configured to log just to stderr.
+ */
+ public boolean isLogToStderr() {
+ return logToStderr;
+ }
+
+ /**
+ * Returns {@code true} if configured to log to stderr in addition to log files..
+ */
+ public boolean isAlsoLogToStderr() {
+ return alsoLogToStderr;
+ }
+
+ /**
+ * Returns {@code true} if configured to log in google-glog format.
+ */
+ public boolean isUseGLogFormatter() {
+ return useGLogFormatter;
+ }
+
+ /**
+ * Returns the default global log level.
+ */
+ public LogLevel getVlog() {
+ return vlog;
+ }
+
+ /**
+ * Returns the custom log levels configured for individual classes.
+ */
+ public ImmutableMap<Class<?>, LogLevel> getVmodule() {
+ return vmodule;
+ }
+
+ /**
+ * Applies this configuration to the root log.
+ */
+ public void apply() {
+ RootLogConfig.configure(this);
+ }
+ }
+
+ /**
+ * A builder-pattern class used to perform the configuration programmatically
+ * (i.e. not through flags).
+ * Example:
+ * <code>
+ * RootLogConfig.builder().logToStderr(true).build().apply();
+ * </code>
+ */
+ public static final class Builder {
+ private final Configuration configuration;
+
+ private Builder() {
+ this.configuration = new Configuration();
+ }
+
+ /**
+ * Only log messages to stderr, instead of log files. Overrides alsologtostderr.
+ * Default: false.
+ *
+ * @param flag True to enable, false to disable.
+ * @return this Configuration object.
+ */
+ public Builder logToStderr(boolean flag) {
+ configuration.logToStderr = flag;
+ return this;
+ }
+
+ /**
+ * Also log messages to stderr, in addition to log files.
+ * Overridden by logtostderr.
+ * Default: false.
+ *
+ * @param flag True to enable, false to disable.
+ * @return this Configuration object.
+ */
+ public Builder alsoLogToStderr(boolean flag) {
+ configuration.alsoLogToStderr = flag;
+ return this;
+ }
+
+ /**
+ * Format log messages in one-line with a header, similar to google-glog.
+ * Default: false.
+ *
+ * @param flag True to enable, false to disable.
+ * @return this Configuration object.
+ */
+ public Builder useGLogFormatter(boolean flag) {
+ configuration.useGLogFormatter = flag;
+ return this;
+ }
+
+ /**
+ * Output log messages at least at the given verbosity level.
+ * Overridden by vmodule.
+ * Default: INFO
+ *
+ * @param level LogLevel enumerator for the minimum log message verbosity level that is output.
+ * @return this Configuration object.
+ */
+ public Builder vlog(LogLevel level) {
+ Preconditions.checkNotNull(level);
+ configuration.vlog = level;
+ return this;
+ }
+
+ /**
+ * Output log messages for a given set of classes at the associated verbosity levels.
+ * Overrides vlog.
+ * Default: no classes are treated specially.
+ *
+ * @param pairs Map of classes and correspoding log levels.
+ * @return this Configuration object.
+ */
+ public Builder vmodule(Map<Class<?>, LogLevel> pairs) {
+ Preconditions.checkNotNull(pairs);
+ configuration.vmodule = ImmutableMap.copyOf(pairs);
+ return this;
+ }
+
+ /**
+ * Returns the built up configuration.
+ */
+ public Configuration build() {
+ return configuration;
+ }
+
+ // Intercepts the root logger, for testing purposes only.
+ @VisibleForTesting
+ Builder rootLoggerName(String name) {
+ Preconditions.checkNotNull(name);
+ Preconditions.checkArgument(!name.isEmpty());
+ configuration.rootLoggerName = name;
+ return this;
+ }
+ }
+
+ /**
+ * Creates a new Configuration builder object.
+ *
+ * @return The builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Creates a logging configuration using flags
+ *
+ * @return The logging configuration specified via command line flags.
+ */
+ public static Configuration configurationFromFlags() {
+ return builder()
+ .logToStderr(LOGTOSTDERR.get())
+ .alsoLogToStderr(ALSOLOGTOSTDERR.get())
+ .useGLogFormatter(USE_GLOG_FORMATTER.get())
+ .vlog(VLOG.get())
+ .vmodule(VMODULE.get())
+ .build();
+ }
+
+ private static void configure(Configuration configuration) {
+ // Edit the properties of the root logger.
+ Logger rootLogger = Logger.getLogger(configuration.rootLoggerName);
+ if (configuration.logToStderr) {
+ setLoggerToStderr(rootLogger);
+ } else if (configuration.alsoLogToStderr) {
+ setLoggerToAlsoStderr(rootLogger);
+ }
+ if (configuration.useGLogFormatter) {
+ setGLogFormatter(rootLogger);
+ }
+ if (configuration.vlog != null) {
+ setVlog(rootLogger, configuration.vlog);
+ }
+ if (configuration.vmodule != null) {
+ setVmodules(configuration.vmodule);
+ }
+ }
+
+ private static void setLoggerToStderr(Logger logger) {
+ LogManager.getLogManager().reset();
+ setConsoleHandler(logger, true);
+ }
+
+ private static void setLoggerToAlsoStderr(Logger logger) {
+ setConsoleHandler(logger, false);
+ }
+
+ private static void setConsoleHandler(Logger logger, boolean removeOtherHandlers) {
+ Handler consoleHandler = null;
+ for (Handler h : logger.getHandlers()) {
+ if (h instanceof ConsoleHandler) {
+ consoleHandler = h;
+ } else if (removeOtherHandlers) {
+ logger.removeHandler(h);
+ }
+ }
+ if (consoleHandler == null) {
+ consoleHandler = new ConsoleHandler();
+ logger.addHandler(new ConsoleHandler());
+ }
+ consoleHandler.setLevel(Level.ALL);
+ consoleHandler.setFilter(null);
+ }
+
+ private static void setGLogFormatter(Logger logger) {
+ for (Handler h : logger.getHandlers()) {
+ h.setFormatter(new LogFormatter());
+ }
+ }
+
+ private static void setVmodules(Map<Class<?>, LogLevel> vmodules) {
+ for (Map.Entry<Class<?>, LogLevel> entry : vmodules.entrySet()) {
+ String className = entry.getKey().getName();
+ Logger logger = Logger.getLogger(className);
+ setVlog(logger, entry.getValue());
+ }
+ }
+
+ private static void setVlog(Logger logger, LogLevel logLevel) {
+ final Level newLevel = logLevel.getLevel();
+ logger.setLevel(newLevel);
+ do {
+ for (Handler handler : logger.getHandlers()) {
+ Level handlerLevel = handler.getLevel();
+ if (newLevel.intValue() < handlerLevel.intValue()) {
+ handler.setLevel(newLevel);
+ }
+ }
+ } while (logger.getUseParentHandlers() && (logger = logger.getParent()) != null);
+ }
+
+ // Utility class.
+ private RootLogConfig() {
+ }
+}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/logging/julbridge/JULBridgeHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/logging/julbridge/JULBridgeHandler.java b/commons/src/main/java/org/apache/aurora/common/logging/julbridge/JULBridgeHandler.java
new file mode 100644
index 0000000..8a9e18e
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/logging/julbridge/JULBridgeHandler.java
@@ -0,0 +1,196 @@
+/**
+ * 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 org.apache.aurora.common.logging.julbridge;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+
+import javax.annotation.Nullable;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * JUL Handler to convert JUL {@link LogRecord} messages into Log4j's {@link LoggingEvent} messages,
+ * and route them to a Log4J logger with the same name as the JUL logger.
+ */
+public class JULBridgeHandler extends Handler {
+ private static final String UNKNOWN_LOGGERNAME = "unknown";
+
+ /**
+ * Converts a JUL log record into a Log4J logging event.
+ *
+ * @param record the JUL log record to convert
+ * @param logger the Log4J logger to use for the logging event
+ * @param level the Log4J level to use for the logging event
+ * @param useExtendedLocationInfo if false, do no try to get source file and line informations
+ * @return a Log4J logging event
+ */
+ static LoggingEvent toLoggingEvent(LogRecord record, Logger logger, Level level,
+ boolean useExtendedLocationInfo) {
+
+ LocationInfo locationInfo = useExtendedLocationInfo
+ ? new LocationInfo(new Throwable(), record.getSourceClassName())
+ : new LocationInfo("?", record.getSourceClassName(), record.getSourceMethodName(), "?");
+
+ // Getting thread name from thread id? complicated...
+ String threadName = String.valueOf(record.getThreadID());
+ ThrowableInformation throwableInformation = record.getThrown() == null
+ ? null
+ : new ThrowableInformation(record.getThrown());
+
+ return new LoggingEvent(
+ record.getSourceClassName(),
+ logger,
+ record.getMillis(),
+ level,
+ formatMessage(record),
+ threadName,
+ throwableInformation,
+ null /* ndc */,
+ locationInfo,
+ null /* properties */);
+ }
+
+ /**
+ * Formats a log record message in a way similar to {@link Formatter#formatMessage(LogRecord)}.
+ *
+ * If the record contains a resource bundle, a lookup is done to find a localized version.
+ *
+ * If the record contains parameters, the message is formatted using
+ * {@link MessageFormat#format(String, Object...)}
+ *
+ * @param record the log record used to format the message
+ * @return a formatted string
+ */
+ static String formatMessage(LogRecord record) {
+ String message = record.getMessage();
+
+ // Look for a resource bundle
+ java.util.ResourceBundle catalog = record.getResourceBundle();
+ if (catalog != null) {
+ try {
+ message = catalog.getString(record.getMessage());
+ } catch (MissingResourceException e) {
+ // Not found? Fallback to original message string
+ message = record.getMessage();
+ }
+ }
+
+ Object parameters[] = record.getParameters();
+ if (parameters == null || parameters.length == 0) {
+ // No parameters? just return the message string
+ return message;
+ }
+
+ // Try formatting
+ try {
+ return MessageFormat.format(message, parameters);
+ } catch (IllegalArgumentException e) {
+ return message;
+ }
+ }
+
+ private final LoggerRepository loggerRepository;
+ private final boolean useExtendedLocationInfo;
+
+ /**
+ * Creates a new JUL handler. Equivalent to calling {@link #JULBridgeHandler(boolean)} passing
+ * <code>false</code> as argument.
+ */
+ public JULBridgeHandler() {
+ this(LogManager.getLoggerRepository(), false);
+ }
+
+ /**
+ * Creates a new JUL handler.
+ * Equivalent to calling {@link #JULBridgeHandler(LoggerRepository, boolean)} passing
+ * <code>LogManager.getLoggerRepository()</code> and <code>useExtendedLocationInfo</code> as
+ * arguments.
+ *
+ * @param useExtendedLocationInfo if true, try to add source filename and line info to log message
+ */
+ public JULBridgeHandler(boolean useExtendedLocationInfo) {
+ this(LogManager.getLoggerRepository(), useExtendedLocationInfo);
+ }
+
+ /**
+ * Creates a new JUL handler.
+ *
+ * @param loggerRepository Log4j logger repository where to get loggers from
+ * @param useExtendedLocationInfo if true, try to add source filename and line info to log message
+ * @throws NullPointerException if loggerRepository is null
+ */
+ public JULBridgeHandler(LoggerRepository loggerRepository, boolean useExtendedLocationInfo) {
+ this.loggerRepository = checkNotNull(loggerRepository);
+ this.useExtendedLocationInfo = useExtendedLocationInfo;
+ }
+
+ /**
+ * Gets a Log4J Logger with the same name as the logger name stored in the log record.
+ *
+ * @param record a JUL log record
+ * @return a Log4J logger with the same name, or name {@value #UNKNOWN_LOGGERNAME} if no name is
+ * present in the record.
+ */
+ Logger getLogger(LogRecord record) {
+ String loggerName = record.getLoggerName();
+ if (loggerName == null) {
+ loggerName = UNKNOWN_LOGGERNAME;
+ }
+
+ return loggerRepository.getLogger(loggerName);
+ }
+
+ /**
+ * Publishes the log record to a Log4J logger of the same name.
+ *
+ * Before formatting the message, level is converted and message is discarded if Log4j logger is
+ * not enabled for that level.
+ *
+ * @param record the record to publish
+ */
+ @Override
+ public void publish(@Nullable LogRecord record) {
+ // Ignore silently null records
+ if (record == null) {
+ return;
+ }
+
+ Logger log4jLogger = getLogger(record);
+ Level log4jLevel = JULBridgeLevelConverter.toLog4jLevel(record.getLevel());
+
+ if (log4jLogger.isEnabledFor(log4jLevel)) {
+ LoggingEvent event = toLoggingEvent(record, log4jLogger, log4jLevel, useExtendedLocationInfo);
+
+ log4jLogger.callAppenders(event);
+ }
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public void close() {}
+}