Skip to content

Commit

Permalink
Releasing gRPC context implementation.
Browse files Browse the repository at this point in the history
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
hagbard authored and Flogger Team committed Feb 10, 2021
1 parent 777ceb5 commit 79957c8
Show file tree
Hide file tree
Showing 5 changed files with 425 additions and 0 deletions.
57 changes: 57 additions & 0 deletions grpc/BUILD
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 grpc/src/main/java/com/google/common/flogger/grpc/GrpcContextData.java
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);
}
}
}
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() {}
}
}
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;
}
}
Loading

0 comments on commit 79957c8

Please sign in to comment.