
reactor.core.publisher.SignalLogger Maven / Gradle / Ivy
/*
* Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
*
* 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 reactor.core.publisher;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.LongConsumer;
import java.util.logging.Level;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import reactor.core.Fuseable;
import reactor.core.publisher.FluxOnAssembly.AssemblySnapshotException;
import reactor.util.Logger;
import reactor.util.Loggers;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;
/**
* A logging interceptor that intercepts all reactive calls and trace them.
* The logging level can be tuned using {@link Level}, but only FINEST, FINE, INFO,
* WARNING and SEVERE are taken into account.
*
* @author Stephane Maldini
*/
final class SignalLogger implements SignalPeek {
final static int CONTEXT_PARENT = 0b0100000000;
final static int SUBSCRIBE = 0b0010000000;
final static int ON_SUBSCRIBE = 0b0001000000;
final static int ON_NEXT = 0b0000100000;
final static int ON_ERROR = 0b0000010000;
final static int ON_COMPLETE = 0b0000001000;
final static int REQUEST = 0b0000000100;
final static int CANCEL = 0b0000000010;
final static int AFTER_TERMINATE = 0b0000000001;
final static int ALL =
CONTEXT_PARENT | CANCEL | ON_COMPLETE | ON_ERROR | REQUEST | ON_SUBSCRIBE | ON_NEXT | SUBSCRIBE;
final static AtomicLong IDS = new AtomicLong(1);
final Publisher source;
final Logger log;
final boolean fuseable;
final int options;
final Level level;
final String operatorLine;
final long id;
static final String LOG_TEMPLATE = "{}({})";
static final String LOG_TEMPLATE_FUSEABLE = "| {}({})";
SignalLogger(Publisher source,
@Nullable String category,
Level level,
boolean correlateStack,
SignalType... options) {
this(source, category, level, correlateStack, Loggers::getLogger, options);
}
SignalLogger(Publisher source,
@Nullable String category,
Level level,
boolean correlateStack,
Function loggerSupplier,
@Nullable SignalType... options) {
this.source = Objects.requireNonNull(source, "source");
this.id = IDS.getAndIncrement();
this.fuseable = source instanceof Fuseable;
if (correlateStack) {
operatorLine = FluxOnAssembly.extract(new AssemblySnapshotException().toString(), false);
}
else {
operatorLine = null;
}
boolean generated =
category == null || category.isEmpty() || category.endsWith(".");
category = generated && category == null ? "reactor." : category;
if (generated) {
if (source instanceof Mono) {
category += "Mono." + source.getClass()
.getSimpleName()
.replace("Mono", "");
}
else if (source instanceof ParallelFlux) {
category += "Parallel." + source.getClass()
.getSimpleName()
.replace("Parallel", "");
}
else {
category += "Flux." + source.getClass()
.getSimpleName()
.replace("Flux", "");
}
category += "." + id;
}
this.log = loggerSupplier.apply(category);
this.level = level;
if (options == null || options.length == 0) {
this.options = ALL;
}
else {
int opts = 0;
for (SignalType option : options) {
if (option == SignalType.CANCEL) {
opts |= CANCEL;
}
else if (option == SignalType.CURRENT_CONTEXT) {
opts |= CONTEXT_PARENT;
}
else if (option == SignalType.ON_SUBSCRIBE) {
opts |= ON_SUBSCRIBE;
}
else if (option == SignalType.REQUEST) {
opts |= REQUEST;
}
else if (option == SignalType.ON_NEXT) {
opts |= ON_NEXT;
}
else if (option == SignalType.ON_ERROR) {
opts |= ON_ERROR;
}
else if (option == SignalType.ON_COMPLETE) {
opts |= ON_COMPLETE;
}
else if (option == SignalType.SUBSCRIBE) {
opts |= SUBSCRIBE;
}
else if (option == SignalType.AFTER_TERMINATE) {
opts |= AFTER_TERMINATE;
}
}
this.options = opts;
}
}
@Override
@Nullable
public Object scanUnsafe(Attr key) {
if (key == Attr.PARENT) return source;
return null;
}
void log(Object... args) {
String line = fuseable ? LOG_TEMPLATE_FUSEABLE : LOG_TEMPLATE;
if (operatorLine != null) {
line = line + " " + operatorLine;
}
if (level == Level.FINEST) {
log.trace(line, args);
}
else if (level == Level.FINE) {
log.debug(line, args);
}
else if (level == Level.INFO) {
log.info(line, args);
}
else if (level == Level.WARNING) {
log.warn(line, args);
}
else if (level == Level.SEVERE) {
log.error(line, args);
}
}
@Override
@Nullable
public Consumer super Subscription> onSubscribeCall() {
if ((options & ON_SUBSCRIBE) == ON_SUBSCRIBE && (level != Level.INFO || log.isInfoEnabled())) {
return s -> log(SignalType.ON_SUBSCRIBE, subscriptionAsString(s), source);
}
return null;
}
@Nullable
@Override
public Consumer super Context> onCurrentContextCall() {
if ((options & CONTEXT_PARENT) == CONTEXT_PARENT && (level != Level.INFO || log
.isInfoEnabled())) {
return c -> log(SignalType.ON_CONTEXT, c, source);
}
return null;
}
static String subscriptionAsString(@Nullable Subscription s) {
if (s == null) {
return "null subscription";
}
StringBuilder asString = new StringBuilder();
if (s instanceof Fuseable.SynchronousSubscription) {
asString.append("[Synchronous Fuseable] ");
}
else if (s instanceof Fuseable.QueueSubscription) {
asString.append("[Fuseable] ");
}
Class extends Subscription> clazz = s.getClass();
String name = clazz.getCanonicalName();
if (name == null) {
name = clazz.getName();
}
name = name.replaceFirst(clazz.getPackage()
.getName() + ".", "");
asString.append(name);
return asString.toString();
}
@Override
@Nullable
public Consumer super IN> onNextCall() {
if ((options & ON_NEXT) == ON_NEXT && (level != Level.INFO || log.isInfoEnabled())) {
return d -> log(SignalType.ON_NEXT, d, source);
}
return null;
}
@Override
@Nullable
public Consumer super Throwable> onErrorCall() {
if ((options & ON_ERROR) == ON_ERROR && log.isErrorEnabled()) {
String line = fuseable ? LOG_TEMPLATE_FUSEABLE : LOG_TEMPLATE;
if (operatorLine != null) {
line = line + " " + operatorLine;
}
String s = line;
return e -> {
log.error(s, SignalType.ON_ERROR, e, source);
log.error("", e);
};
}
return null;
}
@Override
@Nullable
public Runnable onCompleteCall() {
if ((options & ON_COMPLETE) == ON_COMPLETE && (level != Level.INFO || log.isInfoEnabled())) {
return () -> log(SignalType.ON_COMPLETE, "", source);
}
return null;
}
@Override
@Nullable
public Runnable onAfterTerminateCall() {
if ((options & AFTER_TERMINATE) == AFTER_TERMINATE && (level != Level.INFO || log.isInfoEnabled())) {
return () -> log(SignalType.AFTER_TERMINATE, "", source);
}
return null;
}
@Override
@Nullable
public LongConsumer onRequestCall() {
if ((options & REQUEST) == REQUEST && (level != Level.INFO || log.isInfoEnabled())) {
return n -> log(SignalType.REQUEST,
Long.MAX_VALUE == n ? "unbounded" : n,
source);
}
return null;
}
@Override
@Nullable
public Runnable onCancelCall() {
if ((options & CANCEL) == CANCEL && (level != Level.INFO || log.isInfoEnabled())) {
return () -> log(SignalType.CANCEL, "", source);
}
return null;
}
@Override
public String toString() {
return "/loggers/" + log.getName() + "/" + id;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy