Skip to content

Commit

Permalink
First cut of new public API for scoped log contexts.
Browse files Browse the repository at this point in the history
This is designed to work with gRPC context (which should allow it to work in and outside Google) but it would not be directly compatible with TraceLoggingContext, so there'll need to be a bit of a migration and probably a parallel running period.

RELNOTES=Adding initial common API for per-request logging configuration support (implementation to follow).

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=283435398
  • Loading branch information
hagbard authored and cgdecker committed Dec 6, 2019
1 parent 0c8ae48 commit e0493d7
Show file tree
Hide file tree
Showing 15 changed files with 2,052 additions and 98 deletions.
39 changes: 36 additions & 3 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ package(default_visibility = ["//:internal"])

CHECKS_SRCS = ["src/main/java/com/google/common/flogger/util/Checks.java"]

METHOD_CALLER_SRCS = ["src/main/java/com/google/common/flogger/util/StaticMethodCaller.java"]

TAGS_SRCS = [
"src/main/java/com/google/common/flogger/backend/KeyValueFormatter.java",
"src/main/java/com/google/common/flogger/backend/KeyValueHandler.java",
"src/main/java/com/google/common/flogger/backend/Tags.java",
]

CONTEXT_SRCS = glob(["src/main/java/com/google/common/flogger/context/*.java"])

LAZY_ARGS_SRCS = [
"src/main/java/com/google/common/flogger/LazyArg.java",
"src/main/java/com/google/common/flogger/LazyArgs.java",
Expand Down Expand Up @@ -42,7 +46,9 @@ java_library(
srcs = glob(
["src/main/java/**/*.java"],
exclude = CHECKS_SRCS +
METHOD_CALLER_SRCS +
TAGS_SRCS +
CONTEXT_SRCS +
LAZY_ARGS_SRCS +
LOG_SITE_SRCS +
LOG_SITE_HELPER_SRCS +
Expand All @@ -68,7 +74,6 @@ java_library(
":tags",
"@google_bazel_common//third_party/java/checker_framework_annotations",
"@google_bazel_common//third_party/java/error_prone:annotations",
"@google_bazel_common//third_party/java/jsr305_annotations",
],
)

Expand All @@ -84,9 +89,11 @@ pom_file(
targets = [
":api",
":checks",
":context",
":lazy_args",
":log_site",
":platform_provider",
":reflection_utils",
":stack",
":tags",
# :config should be here, but that causes a circular reference to :api. Thankfully, :config
Expand All @@ -101,10 +108,12 @@ jarjar_library(
jars = [
":api",
":checks",
":context",
":config",
":lazy_args",
":log_site",
":platform_provider",
":reflection_utils",
":stack",
":tags",
],
Expand All @@ -116,8 +125,10 @@ jarjar_library(
":libapi-src.jar",
":libchecks-src.jar",
":libconfig-src.jar",
":libcontext-src.jar",
":liblazy_args-src.jar",
":liblog_site-src.jar",
":libreflection_utils-src.jar",
":libstack-src.jar",
":libtags-src.jar",
# TODO(ronshapiro): consider generating PlatformProvider as a source file so it can be
Expand All @@ -143,6 +154,16 @@ java_library(
javacopts = ["-source 1.6 -target 1.6"],
)

java_library(
name = "reflection_utils",
srcs = METHOD_CALLER_SRCS,
javacopts = ["-source 1.6 -target 1.6"],
deps = [
":checks",
"@google_bazel_common//third_party/java/checker_framework_annotations",
],
)

# A separate library for exposing just the 'Tags' class for use with the
# TraceLoggingContext.
#
Expand All @@ -157,6 +178,18 @@ java_library(
],
)

java_library(
name = "context",
srcs = CONTEXT_SRCS,
javacopts = ["-source 1.6 -target 1.6"],
deps = [
":checks",
":reflection_utils",
"@google_bazel_common//third_party/java/checker_framework_annotations",
"@google_bazel_common//third_party/java/error_prone:annotations",
],
)

# A separate library for exposing just the 'LazyArgs' classes, since these are
# support classes which can be imported and used freely in general code.
java_library(
Expand Down Expand Up @@ -227,7 +260,7 @@ java_library(
visibility = ["//visibility:public"],
deps = [
":api",
":checks",
":reflection_utils",
"@google_bazel_common//third_party/java/checker_framework_annotations",
],
)
Expand Down Expand Up @@ -336,14 +369,14 @@ gen_java_tests(
deps = [
":api",
":config",
":context",
":log_sites",
":system_backend",
":testing",
"@google_bazel_common//third_party/java/auto:service",
"@google_bazel_common//third_party/java/checker_framework_annotations",
"@google_bazel_common//third_party/java/guava",
"@google_bazel_common//third_party/java/guava:testlib",
"@google_bazel_common//third_party/java/jsr305_annotations",
"@google_bazel_common//third_party/java/junit",
"@google_bazel_common//third_party/java/mockito",
"@google_bazel_common//third_party/java/truth",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@

package com.google.common.flogger.backend.system;

import static com.google.common.flogger.util.StaticMethodCaller.callGetterFromSystemProperty;

import com.google.common.flogger.backend.LoggerBackend;
import com.google.common.flogger.backend.Platform;
import com.google.common.flogger.backend.Tags;
import com.google.common.flogger.util.Checks;
import java.util.logging.Level;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;

/**
* The default fluent logger platform for a server-side Java environment. The default platform
Expand Down Expand Up @@ -49,21 +49,22 @@
*/
// Non-final for testing.
public class DefaultPlatform extends Platform {
private static final String BACKEND_FACTORY = "backend_factory";
private static final String LOGGING_CONTEXT = "logging_context";
private static final String CLOCK = "clock";
// System property names for properties expected to define "getters" for platform attributes.
private static final String BACKEND_FACTORY = "flogger.backend_factory";
private static final String LOGGING_CONTEXT = "flogger.logging_context";
private static final String CLOCK = "flogger.clock";

private final BackendFactory backendFactory;
private final LoggingContext context;
private final Clock clock;
private final LogCallerFinder callerFinder;

public DefaultPlatform() {
BackendFactory factory = resolveAttribute(BACKEND_FACTORY, BackendFactory.class);
BackendFactory factory = callGetterFromSystemProperty(BACKEND_FACTORY, BackendFactory.class);
this.backendFactory = (factory != null) ? factory : SimpleBackendFactory.getInstance();
LoggingContext context = resolveAttribute(LOGGING_CONTEXT, LoggingContext.class);
LoggingContext context = callGetterFromSystemProperty(LOGGING_CONTEXT, LoggingContext.class);
this.context = (context != null) ? context : EmptyLoggingContext.getInstance();
Clock clock = resolveAttribute(CLOCK, Clock.class);
Clock clock = callGetterFromSystemProperty(CLOCK, Clock.class);
this.clock = (clock != null) ? clock : SystemClock.getInstance();
// TODO(dbeaumont): Figure out how to handle StackWalker when it becomes available (Java9).
this.callerFinder = StackBasedCallerFinder.getInstance();
Expand Down Expand Up @@ -111,56 +112,4 @@ protected String getConfigInfoImpl() {
+ "LoggingContext: " + context + "\n"
+ "LogCallerFinder: " + callerFinder + "\n";
}

/**
* Helper to call a static no-arg getter to obtain an instance of a specified type. This is used
* for platform aspects which are optional, but are expected to have a singleton available.
*
* @return the return value of the specified static no-argument method, or null if the method
* cannot be called or the returned value is of the wrong type.
*/
@NullableDecl
private static <T> T resolveAttribute(String attributeName, Class<T> type) {
String getter = readProperty(attributeName);
if (getter == null) {
return null;
}
int idx = getter.indexOf('#');
if (idx <= 0 || idx == getter.length() - 1) {
error("invalid getter (expected <class>#<method>): %s\n", getter);
return null;
}
return callStaticMethod(getter.substring(0, idx), getter.substring(idx + 1), type);
}

private static String readProperty(String attributeName) {
Checks.checkNotNull(attributeName, "attribute name");
String propertyName = "flogger." + attributeName;
try {
return System.getProperty(propertyName);
} catch (SecurityException e) {
error("cannot read property name %s: %s", propertyName, e);
}
return null;
}

private static <T> T callStaticMethod(String className, String methodName, Class<T> type) {
try {
return type.cast(Class.forName(className).getMethod(methodName).invoke(null));
} catch (ClassNotFoundException e) {
// Expected if an optional aspect is not being used (no error).
} catch (ClassCastException e) {
error("cannot cast result of calling '%s#%s' to '%s': %s\n",
className, methodName, type.getName(), e);
} catch (Exception e) {
// Catches SecurityException *and* ReflexiveOperationException (which doesn't exist in 1.6).
error("cannot call expected no-argument static method '%s#%s': %s\n",
className, methodName, e);
}
return null;
}

private static void error(String msg, Object... args) {
System.err.println(DefaultPlatform.class + ": " + String.format(msg, args));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright (C) 2019 The Flogger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.common.flogger.context;

import static com.google.common.flogger.util.StaticMethodCaller.callGetterFromSystemProperty;

import java.util.logging.Level;

/**
* An API for injecting scoped metadata for log statements (either globally or on a per-request
* basis). This class is intended for subclassing by the logging context implementation, which can
* then be installed into fluent loggers by setting the {@code "flogger.context"} system property.
* Thiis class is not a public API and should never need to be invoked directly by application code.
*
* <p>Note that since this class (and any installed implementation sub-class) is loaded when the
* logging platform is loaded, care must be taken to avoid cyclic references during static
* initialisation. This means that no static fields or static initialization can reference fluent
* loggers or the logging platform (either directly or indirectly).
*/
public abstract class ContextDataProvider {
// Visible only for the no-op implementation.
static final String CONTEXT_PROVIDER_PROPERTY = "flogger.context";

/**
* Returns the singleton instance of the context data provider for use by logging platform
* implementations. This method should not be called by general application code, and the {@code
* ContextDataProvider} class should never need to be used directly outside of fluent logger
* platform implementations.
*/
public static ContextDataProvider getInstance() {
return LazyHolder.INSTANCE;
}

/**
* Returns the context API with which users can create and modify the state of logging contexts
* within an application. This method should be overridden by subclasses to provide the specific
* implementation of the API.
*
* <p>This method should never be called directly (other than in tests) and users should always go
* via {@link ScopedLoggingContext#getInstance}, without needing to reference this class at all.
*
* <p>If an implementation wishes to allow logging from the context API class, that class must be
* lazily loaded when this method is called (e.g. using a "lazy holder"). Failure to do so is
* likely to result in errors during the initialization of the logging platform classes.
*/
public abstract ScopedLoggingContext getContextApiSingleton();

/**
* Returns whether the given logger should have logging forced at the specified level. When
* logging is forced for a log statement it will be emitted regardless or the normal log level
* configuration of the logger and ignoring any rate limiting or other filtering.
*
* <p>Implementations which do not support forcing logging should return {@code false}.
*
* <p>{@code loggerName} can be used to look up specific configuration, such as log level, for the
* logger, to decide if a log statement should be forced. This information might vary depending on
* the context in which this call is made, so the result should not be cached.
*
* <p>{@code isEnabledByLevel} indicates that the log statement is enabled according to its log
* level, but a {@code true} value does not necessarily indicate that logging will occur, due to
* rate limiting or other conditional logging mechanisms. To bypass conditional logging and ensure
* that an enabled log statement will be emitted, this method should return {@code true} if {@code
* isEnabledByLevel} was {@code true}.
*
* <p>WARNING: This method MUST complete quickly and without allocating any memory. It is invoked
* for every log statement regardless of logging configuration, so any implementation must go to
* every possible length to be efficient.
*
* @param loggerName the fully qualified logger name (e.g. "com.example.SomeClass")
* @param level the level of the log statement being invoked
* @param isEnabledByLevel whether the logger is enabled at the given level
*/
public abstract boolean shouldForceLogging(
String loggerName, Level level, boolean isEnabledByLevel);

/**
* Returns a set of tags to be added to a log statement. These tags can be used to provide
* additional contextual metadata to log statements (e.g. request IDs).
*
* <p>Implementations which do not support {@link Tags} should return {@code Tags.empty()}.
*/
public Tags getTags() {
return Tags.empty();
}

private static final class LazyHolder {
private static final ContextDataProvider INSTANCE = loadContext();

private static ContextDataProvider loadContext() {
ContextDataProvider ctx =
callGetterFromSystemProperty(CONTEXT_PROVIDER_PROPERTY, ContextDataProvider.class);
return ctx != null ? ctx : new NoOpContextDataProvider();
}
}
}
Loading

0 comments on commit e0493d7

Please sign in to comment.