-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Releasing gRPC context implementation.
Enable by including the new "flogger-grpc-context" artifact and setting the "flogger.logging_context" system property to: com.google.common.flogger.grpc.GrpcContextDataProvider#getInstance Eventually it is hoped that this will be auto-configured via the class path, but for now a system property is required. RELNOTES=Open-sourcing the gRPC context implementation. PiperOrigin-RevId: 356806259
- Loading branch information
Showing
5 changed files
with
425 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Copyright 2019 Google Inc. All Rights Reserved. | ||
# Author: [email protected] (David Beaumont) | ||
# | ||
# Description: | ||
# Flogger grpc context implementation. | ||
|
||
load("//tools:maven.bzl", "pom_file") | ||
load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") | ||
load("@google_bazel_common//testing:test_defs.bzl", "gen_java_tests") | ||
|
||
package(default_visibility = ["//:internal"]) | ||
|
||
GRPC_CONTEXT_SRCS = glob(["src/main/java/**/*.java"]) | ||
|
||
java_library( | ||
name = "grpc_context", | ||
srcs = GRPC_CONTEXT_SRCS, | ||
tags = ["maven_coordinates=com.google.flogger:flogger-grpc-context:${project.version}"], | ||
deps = [ | ||
"//api", | ||
"@google_bazel_common//third_party/java/checker_framework_annotations", | ||
"@google_bazel_common//third_party/java/error_prone:annotations", | ||
"@google_bazel_common//third_party/java/grpc:context", | ||
], | ||
) | ||
|
||
pom_file( | ||
name = "pom", | ||
artifact_id = "flogger-grpc-context", | ||
artifact_name = "Flogger GRPC Context", | ||
targets = [":grpc_context"], | ||
) | ||
|
||
javadoc_library( | ||
name = "javadoc", | ||
srcs = GRPC_CONTEXT_SRCS, | ||
root_packages = ["com.google.common.flogger.grpc"], | ||
deps = [":grpc_context"], | ||
) | ||
|
||
# ---- Unit Tests ---- | ||
|
||
gen_java_tests( | ||
name = "grpc_tests", | ||
srcs = glob(["src/test/java/**/*.java"]), | ||
deps = [ | ||
":grpc_context", | ||
"//api", | ||
"//api:testing", | ||
"@google_bazel_common//third_party/java/auto:service", | ||
"@google_bazel_common//third_party/java/guava", | ||
"@google_bazel_common//third_party/java/guava:testlib", | ||
"@google_bazel_common//third_party/java/junit", | ||
"@google_bazel_common//third_party/java/mockito", | ||
"@google_bazel_common//third_party/java/truth", | ||
], | ||
) |
133 changes: 133 additions & 0 deletions
133
grpc/src/main/java/com/google/common/flogger/grpc/GrpcContextData.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/* | ||
* 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.grpc; | ||
|
||
import com.google.common.flogger.context.LogLevelMap; | ||
import com.google.common.flogger.context.ScopeMetadata; | ||
import com.google.common.flogger.context.Tags; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
import java.util.logging.Level; | ||
import org.checkerframework.checker.nullness.compatqual.NullableDecl; | ||
|
||
/** A mutable thread-safe holder for context scoped logging information. */ | ||
final class GrpcContextData { | ||
static GrpcContextData create(@NullableDecl GrpcContextData parent) { | ||
return new GrpcContextData(parent); | ||
} | ||
|
||
static Tags getTagsFor(@NullableDecl GrpcContextData context) { | ||
if (context != null) { | ||
Tags tags = context.tagRef.get(); | ||
if (tags != null) { | ||
return tags; | ||
} | ||
} | ||
return Tags.empty(); | ||
} | ||
|
||
static ScopeMetadata getMetadataFor(@NullableDecl GrpcContextData context) { | ||
if (context != null) { | ||
ScopeMetadata metadata = context.metadataRef.get(); | ||
if (metadata != null) { | ||
return metadata; | ||
} | ||
} | ||
return ScopeMetadata.none(); | ||
} | ||
|
||
static boolean shouldForceLoggingFor( | ||
@NullableDecl GrpcContextData context, String loggerName, Level level) { | ||
if (context != null) { | ||
LogLevelMap map = context.logLevelMapRef.get(); | ||
if (map != null) { | ||
return map.getLevel(loggerName).intValue() <= level.intValue(); | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private abstract static class ScopedReference<T> { | ||
private final AtomicReference<T> value; | ||
|
||
ScopedReference(@NullableDecl T initialValue) { | ||
this.value = new AtomicReference<>(initialValue); | ||
} | ||
|
||
@NullableDecl | ||
final T get() { | ||
return value.get(); | ||
} | ||
|
||
// Note: If we could use Java 1.8 runtime libraries, this would just be "accumulateAndGet()", | ||
// but gRPC is Java 1.7 compatible: https://github.com/grpc/grpc-java/blob/master/README.md | ||
final void mergeFrom(@NullableDecl T delta) { | ||
if (delta != null) { | ||
T current; | ||
do { | ||
current = get(); | ||
} while (!value.compareAndSet(current, current != null ? merge(current, delta) : delta)); | ||
} | ||
} | ||
|
||
abstract T merge(T current, T delta); | ||
} | ||
|
||
private final ScopedReference<Tags> tagRef; | ||
private final ScopedReference<ScopeMetadata> metadataRef; | ||
private final ScopedReference<LogLevelMap> logLevelMapRef; | ||
|
||
private GrpcContextData(@NullableDecl GrpcContextData parent) { | ||
this.tagRef = | ||
new ScopedReference<Tags>(parent != null ? parent.tagRef.get() : null) { | ||
@Override | ||
Tags merge(Tags current, Tags delta) { | ||
return current.merge(delta); | ||
} | ||
}; | ||
this.metadataRef = | ||
new ScopedReference<ScopeMetadata>(parent != null ? parent.metadataRef.get() : null) { | ||
@Override | ||
ScopeMetadata merge(ScopeMetadata current, ScopeMetadata delta) { | ||
return current.concatenate(delta); | ||
} | ||
}; | ||
this.logLevelMapRef = | ||
new ScopedReference<LogLevelMap>(parent != null ? parent.logLevelMapRef.get() : null) { | ||
@Override | ||
LogLevelMap merge(LogLevelMap current, LogLevelMap delta) { | ||
return current.merge(delta); | ||
} | ||
}; | ||
} | ||
|
||
void addTags(@NullableDecl Tags tags) { | ||
tagRef.mergeFrom(tags); | ||
} | ||
|
||
void addMetadata(@NullableDecl ScopeMetadata metadata) { | ||
metadataRef.mergeFrom(metadata); | ||
} | ||
|
||
void applyLogLevelMap(@NullableDecl LogLevelMap logLevelMap) { | ||
if (logLevelMap != null) { | ||
// Set the global flag to trigger testing of the log level map from now on (we only apply a | ||
// log level map to an active context or one that's about to become active). | ||
GrpcContextDataProvider.INSTANCE.setLogLevelMapFlag(); | ||
logLevelMapRef.mergeFrom(logLevelMap); | ||
} | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
grpc/src/main/java/com/google/common/flogger/grpc/GrpcContextDataProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/* | ||
* 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.grpc; | ||
|
||
import com.google.common.flogger.context.ContextDataProvider; | ||
import com.google.common.flogger.context.ScopeMetadata; | ||
import com.google.common.flogger.context.ScopedLoggingContext; | ||
import com.google.common.flogger.context.Tags; | ||
import io.grpc.Context; | ||
import java.util.logging.Level; | ||
import org.checkerframework.checker.nullness.compatqual.NullableDecl; | ||
|
||
/** A gRPC context based implementation of Flogger's ContextDataProvider. */ | ||
public final class GrpcContextDataProvider extends ContextDataProvider { | ||
// Package visible since the config instance needs to poke the "hasLogLevelMap" flags. This field | ||
// should be the only thing that needs to be initialized when this class is loaded, since class | ||
// loading can occur during logging platform initialization. | ||
static final GrpcContextDataProvider INSTANCE = new GrpcContextDataProvider(); | ||
|
||
/** Invoked during logging platform initialization (so not allowed to do any logging). */ | ||
public static ContextDataProvider getInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
// Strictly a singleton (even though it has mutable state). | ||
private GrpcContextDataProvider() {} | ||
|
||
// When this is false we can skip some work for every log statement. This is set to true if _any_ | ||
// context adds a log level map at any point (this is generally rare and only used for targeted | ||
// debugging so will often never occur during normal application use). This is never reset. | ||
private volatile boolean hasLogLevelMap = false; | ||
|
||
// For use by GrpcScopedLoggingContext (same package). We cannot define the keys in there because | ||
// this class must not depend on GrpcScopedLoggingContext during static initialization. We must | ||
// also delay initializing this value (via a lazy-holder) to avoid any risks during logger | ||
// initialization. | ||
static Context.Key<GrpcContextData> getContextKey() { | ||
return KeyHolder.GRPC_SCOPE; | ||
} | ||
|
||
/** Returns the current context data, or {@code null} if we are not in a context. */ | ||
@NullableDecl | ||
static GrpcContextData currentContext() { | ||
return getContextKey().get(); | ||
} | ||
|
||
/** Sets the flag to enable checking for a log level map after one is set for the first time. */ | ||
void setLogLevelMapFlag() { | ||
hasLogLevelMap = true; | ||
} | ||
|
||
@Override | ||
public ScopedLoggingContext getContextApiSingleton() { | ||
// This is a lazy-holder to avoid requiring the API instance to be initiated at the same time | ||
// as the LoggingContext (which is created as the Platform instance is initialized). By doing | ||
// this we break any static initialization cycles and allow the config API perform its own | ||
// logging if necessary. DO NOT cache the config instance in a static field in this class!! | ||
return GrpcScopedLoggingContext.getGrpcConfigInstance(); | ||
} | ||
|
||
@Override | ||
public Tags getTags() { | ||
return GrpcContextData.getTagsFor(currentContext()); | ||
} | ||
|
||
@Override | ||
public ScopeMetadata getMetadata() { | ||
return GrpcContextData.getMetadataFor(currentContext()); | ||
} | ||
|
||
@Override | ||
public boolean shouldForceLogging(String loggerName, Level level, boolean isEnabledByLevel) { | ||
// Shortcutting boolean saves doing any work in the commonest case (this code is called for | ||
// every log statement, which is 100-1000 times more than just the enabled log statements). | ||
return hasLogLevelMap | ||
&& GrpcContextData.shouldForceLoggingFor(currentContext(), loggerName, level); | ||
} | ||
|
||
// Static lazy-holder to avoid needing to call unknown code during Flogger initialization. While | ||
// gRPC context keys don't trigger any logging now, it's not certain that this is guaranteed. | ||
private static final class KeyHolder { | ||
private static final Context.Key<GrpcContextData> GRPC_SCOPE = | ||
Context.key("Flogger gRPC scope"); | ||
|
||
private KeyHolder() {} | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
grpc/src/main/java/com/google/common/flogger/grpc/GrpcScopedLoggingContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/* | ||
* 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.grpc; | ||
|
||
import static com.google.common.flogger.util.Checks.checkNotNull; | ||
|
||
import com.google.common.flogger.MetadataKey; | ||
import com.google.common.flogger.context.LogLevelMap; | ||
import com.google.common.flogger.context.ScopeMetadata; | ||
import com.google.common.flogger.context.ScopedLoggingContext; | ||
import com.google.common.flogger.context.Tags; | ||
import com.google.errorprone.annotations.CheckReturnValue; | ||
import io.grpc.Context; | ||
|
||
/** | ||
* A gRPC context based implementation of Flogger's scoped logging context API. This is a lazily | ||
* loaded singleton instance returned from {@link GrpcContextDataProvider#getContextApiSingleton()} | ||
* which provides application code with a mechanism for controlling logging contexts. | ||
* | ||
* <p>It is vital that this class is lazily loaded (rather than being loaded when the logging | ||
* platform is configured) since other classes it uses may well use fluent loggers. | ||
*/ | ||
final class GrpcScopedLoggingContext extends ScopedLoggingContext { | ||
private static final ScopedLoggingContext INSTANCE = new GrpcScopedLoggingContext(); | ||
|
||
static ScopedLoggingContext getGrpcConfigInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
@Override | ||
@CheckReturnValue | ||
public ScopedLoggingContext.Builder newScope() { | ||
return new ScopedLoggingContext.Builder() { | ||
@Override | ||
public LoggingScope install() { | ||
GrpcContextData newContextData = | ||
GrpcContextData.create(GrpcContextDataProvider.currentContext()); | ||
newContextData.addTags(getTags()); | ||
newContextData.addMetadata(getMetadata()); | ||
newContextData.applyLogLevelMap(getLogLevelMap()); | ||
return installContextData(newContextData); | ||
} | ||
}; | ||
} | ||
|
||
private static LoggingScope installContextData(GrpcContextData newContextData) { | ||
// Capture these variables outside the lambda. | ||
Context context = | ||
Context.current().withValue(GrpcContextDataProvider.getContextKey(), newContextData); | ||
@SuppressWarnings("MustBeClosedChecker") | ||
Context prev = context.attach(); | ||
return () -> context.detach(prev); | ||
} | ||
|
||
@Override | ||
public boolean addTags(Tags tags) { | ||
checkNotNull(tags, "tags"); | ||
GrpcContextData context = GrpcContextDataProvider.currentContext(); | ||
if (context != null) { | ||
context.addTags(tags); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public <T> boolean addMetadata(MetadataKey<T> key, T value) { | ||
// Serves as the null pointer check, and we don't care much about the extra allocation in the | ||
// case where there's no context, because that should be very rare (and the singleton is small). | ||
ScopeMetadata metadata = ScopeMetadata.singleton(key, value); | ||
GrpcContextData context = GrpcContextDataProvider.currentContext(); | ||
if (context != null) { | ||
context.addMetadata(metadata); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean applyLogLevelMap(LogLevelMap logLevelMap) { | ||
checkNotNull(logLevelMap, "log level map"); | ||
GrpcContextData context = GrpcContextDataProvider.currentContext(); | ||
if (context != null) { | ||
context.applyLogLevelMap(logLevelMap); | ||
return true; | ||
} | ||
return false; | ||
} | ||
} |
Oops, something went wrong.