org.xbib.helianthus.common.logging.DefaultRequestLog Maven / Gradle / Ivy
package org.xbib.helianthus.common.logging;
import static java.util.Objects.requireNonNull;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.COMPLETE;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.REQUEST_CONTENT;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.REQUEST_END;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.REQUEST_ENVELOPE;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.REQUEST_START;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.RESPONSE_CONTENT;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.RESPONSE_END;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.RESPONSE_ENVELOPE;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.RESPONSE_START;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.SCHEME;
import static org.xbib.helianthus.common.logging.RequestLogAvailability.STATUS_CODE;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import io.netty.channel.Channel;
import org.xbib.helianthus.common.RequestContext;
import org.xbib.helianthus.common.RpcResponse;
import org.xbib.helianthus.common.Scheme;
import org.xbib.helianthus.common.SerializationFormat;
import org.xbib.helianthus.common.SessionProtocol;
import org.xbib.helianthus.common.util.TextFormatter;
/**
* Default {@link RequestLog} implementation.
*/
public class DefaultRequestLog implements RequestLog, RequestLogBuilder {
private static final AtomicIntegerFieldUpdater flagsUpdater =
AtomicIntegerFieldUpdater.newUpdater(DefaultRequestLog.class, "flags");
private static final int FLAGS_REQUEST_END_WITHOUT_CONTENT =
REQUEST_END.setterFlags() & ~REQUEST_CONTENT.setterFlags();
private static final int FLAGS_RESPONSE_END_WITHOUT_CONTENT =
RESPONSE_END.setterFlags() & ~RESPONSE_CONTENT.setterFlags();
private static final int STRING_BUILDER_CAPACITY = 512;
private final RequestContext ctx;
/**
* Updated by {@link #flagsUpdater}.
*/
@SuppressWarnings("unused")
private volatile int flags;
private final List listeners = new ArrayList<>(4);
private volatile boolean requestContentDeferred;
private volatile boolean responseContentDeferred;
private long requestStartTimeMillis;
private long requestStartTimeNanos;
private long requestEndTimeNanos;
private long requestLength;
private Throwable requestCause;
private long responseStartTimeMillis;
private long responseStartTimeNanos;
private long responseEndTimeNanos;
private long responseLength;
private Throwable responseCause;
private Channel channel;
private SessionProtocol sessionProtocol;
private SerializationFormat serializationFormat = SerializationFormat.NONE;
private String host;
private String method;
private String path;
private int statusCode = -1;
private Object requestEnvelope;
private Object responseEnvelope;
private Object requestContent;
private Object rawRequestContent;
private Object responseContent;
private Object rawResponseContent;
private volatile int requestStrFlags = -1;
private volatile int responseStrFlags = -1;
private String requestStr;
private String responseStr;
/**
* Creates a new instance.
*/
public DefaultRequestLog(RequestContext ctx) {
this.ctx = requireNonNull(ctx, "ctx");
}
@Override
public Set availabilities() {
return RequestLogAvailabilitySet.of(flags);
}
@Override
public boolean isAvailable(RequestLogAvailability availability) {
return isAvailable(availability.getterFlags());
}
@Override
public boolean isAvailable(RequestLogAvailability... availabilities) {
return isAvailable(getterFlags(availabilities));
}
@Override
public boolean isAvailable(Iterable availabilities) {
return isAvailable(getterFlags(availabilities));
}
private boolean isAvailable(int interestedFlags) {
return (flags & interestedFlags) == interestedFlags;
}
private static boolean isAvailable(int flags, RequestLogAvailability availability) {
final int interestedFlags = availability.getterFlags();
return (flags & interestedFlags) == interestedFlags;
}
private boolean isAvailabilityAlreadyUpdated(RequestLogAvailability availability) {
return isAvailable(availability.setterFlags());
}
@Override
public void addListener(RequestLogListener listener, RequestLogAvailability availability) {
requireNonNull(listener, "listener");
requireNonNull(availability, "availability");
addListener(listener, availability.getterFlags());
}
@Override
public void addListener(RequestLogListener listener, RequestLogAvailability... availabilities) {
requireNonNull(listener, "listener");
requireNonNull(availabilities, "availabilities");
addListener(listener, getterFlags(availabilities));
}
@Override
public void addListener(RequestLogListener listener, Iterable availabilities) {
requireNonNull(listener, "listener");
requireNonNull(availabilities, "availabilities");
addListener(listener, getterFlags(availabilities));
}
private void addListener(RequestLogListener listener, int interestedFlags) {
if (interestedFlags == 0) {
throw new IllegalArgumentException("no availability specified");
}
if (isAvailable(interestedFlags)) {
// No need to add to 'listeners'.
RequestLogListenerInvoker.invokeOnRequestLog(listener, this);
return;
}
final ListenerEntry e = new ListenerEntry(listener, interestedFlags);
final RequestLogListener[] satisfiedListeners;
synchronized (listeners) {
listeners.add(e);
satisfiedListeners = removeSatisfiedListeners();
}
notifyListeners(satisfiedListeners);
}
private static int getterFlags(RequestLogAvailability[] availabilities) {
int flags = 0;
for (RequestLogAvailability a : availabilities) {
flags |= a.getterFlags();
}
return flags;
}
private static int getterFlags(Iterable availabilities) {
int flags = 0;
for (RequestLogAvailability a : availabilities) {
flags |= a.getterFlags();
}
return flags;
}
@Override
public RequestContext context() {
return ctx;
}
@Override
public void startRequest(
Channel channel, SessionProtocol sessionProtocol, String host, String method, String path) {
startRequest0(channel, sessionProtocol, host, method, path, true);
}
private void startRequest0(Channel channel, SessionProtocol sessionProtocol, String host, String method, String path,
boolean updateAvailability) {
if (isAvailabilityAlreadyUpdated(REQUEST_START)) {
return;
}
requestStartTimeNanos = System.nanoTime();
requestStartTimeMillis = System.currentTimeMillis();
this.channel = channel;
this.sessionProtocol = sessionProtocol;
this.host = host;
this.method = method;
this.path = path;
if (updateAvailability) {
updateAvailability(REQUEST_START);
}
}
@Override
public long requestStartTimeMillis() {
ensureAvailability(REQUEST_START);
return requestStartTimeMillis;
}
@Override
public long requestDurationNanos() {
ensureAvailability(REQUEST_END);
return requestEndTimeNanos - requestStartTimeNanos;
}
@Override
public Throwable requestCause() {
ensureAvailability(REQUEST_END);
return requestCause;
}
@Override
public Channel channel() {
ensureAvailability(REQUEST_START);
return channel;
}
@Override
public SessionProtocol sessionProtocol() {
ensureAvailability(REQUEST_START);
return sessionProtocol;
}
@Override
public String host() {
ensureAvailability(REQUEST_START);
return host;
}
@Override
public String method() {
ensureAvailability(REQUEST_START);
return method;
}
@Override
public String path() {
ensureAvailability(REQUEST_START);
return path;
}
@Override
public SerializationFormat serializationFormat() {
ensureAvailability(SCHEME);
return serializationFormat;
}
@Override
public void serializationFormat(SerializationFormat serializationFormat) {
if (isAvailabilityAlreadyUpdated(SCHEME)) {
return;
}
this.serializationFormat = requireNonNull(serializationFormat, "serializationFormat");
updateAvailability(SCHEME);
}
@Override
public Scheme scheme() {
ensureAvailability(SCHEME);
return Scheme.of(serializationFormat, sessionProtocol);
}
@Override
public long requestLength() {
ensureAvailability(REQUEST_END);
return requestLength;
}
@Override
public void requestLength(long requestLength) {
if (requestLength < 0) {
throw new IllegalArgumentException("requestLength: " + requestLength + " (expected: >= 0)");
}
if (isAvailable(REQUEST_END)) {
return;
}
this.requestLength = requestLength;
}
@Override
public void increaseRequestLength(long deltaBytes) {
if (deltaBytes < 0) {
throw new IllegalArgumentException("deltaBytes: " + deltaBytes + " (expected: >= 0)");
}
if (isAvailable(REQUEST_END)) {
return;
}
requestLength += deltaBytes;
}
@Override
public Object requestEnvelope() {
ensureAvailability(REQUEST_ENVELOPE);
return requestEnvelope;
}
@Override
public void requestEnvelope(Object requestEnvelope) {
if (isAvailabilityAlreadyUpdated(REQUEST_ENVELOPE)) {
return;
}
this.requestEnvelope = requestEnvelope;
updateAvailability(REQUEST_ENVELOPE);
}
@Override
public Object requestContent() {
ensureAvailability(REQUEST_CONTENT);
return requestContent;
}
@Override
public void requestContent(Object requestContent, Object rawRequestContent) {
if (isAvailabilityAlreadyUpdated(REQUEST_CONTENT)) {
return;
}
this.requestContent = requestContent;
this.rawRequestContent = rawRequestContent;
updateAvailability(REQUEST_CONTENT);
}
@Override
public Object rawRequestContent() {
ensureAvailability(REQUEST_CONTENT);
return rawRequestContent;
}
@Override
public void deferRequestContent() {
if (isAvailabilityAlreadyUpdated(REQUEST_CONTENT)) {
return;
}
requestContentDeferred = true;
}
@Override
public boolean isRequestContentDeferred() {
return requestContentDeferred;
}
@Override
public void endRequest() {
endRequest0(null);
}
@Override
public void endRequest(Throwable requestCause) {
endRequest0(requireNonNull(requestCause, "requestCause"));
}
private void endRequest0(Throwable requestCause) {
final int flags = requestCause == null && requestContentDeferred ? FLAGS_REQUEST_END_WITHOUT_CONTENT
: REQUEST_END.setterFlags();
if (isAvailable(flags)) {
return;
}
startRequest0(null, null, null, null, null, false);
requestEndTimeNanos = System.nanoTime();
this.requestCause = requestCause;
updateAvailability(flags);
}
@Override
public void startResponse() {
startResponse0(true);
}
private void startResponse0(boolean updateAvailability) {
if (isAvailabilityAlreadyUpdated(RESPONSE_START)) {
return;
}
responseStartTimeNanos = System.nanoTime();
responseStartTimeMillis = System.currentTimeMillis();
if (updateAvailability) {
updateAvailability(RESPONSE_START);
}
}
@Override
public long responseStartTimeMillis() {
ensureAvailability(RESPONSE_START);
return responseStartTimeMillis;
}
@Override
public long responseDurationNanos() {
ensureAvailability(RESPONSE_END);
return responseEndTimeNanos - responseStartTimeNanos;
}
@Override
public Throwable responseCause() {
ensureAvailability(RESPONSE_END);
return responseCause;
}
@Override
public int statusCode() {
ensureAvailability(STATUS_CODE);
return statusCode;
}
@Override
public void statusCode(int statusCode) {
if (isAvailabilityAlreadyUpdated(STATUS_CODE)) {
return;
}
this.statusCode = statusCode;
updateAvailability(STATUS_CODE);
}
@Override
public long responseLength() {
ensureAvailability(RESPONSE_END);
return responseLength;
}
@Override
public void responseLength(long responseLength) {
if (responseLength < 0) {
throw new IllegalArgumentException("responseLength: " + responseLength + " (expected: >= 0)");
}
if (isAvailable(RESPONSE_END)) {
return;
}
this.responseLength = responseLength;
}
@Override
public void increaseResponseLength(long deltaBytes) {
if (deltaBytes < 0) {
throw new IllegalArgumentException("deltaBytes: " + deltaBytes + " (expected: >= 0)");
}
if (isAvailable(RESPONSE_END)) {
return;
}
responseLength += deltaBytes;
}
@Override
public Object responseEnvelope() {
ensureAvailability(RESPONSE_ENVELOPE);
return responseEnvelope;
}
@Override
public void responseEnvelope(Object responseEnvelope) {
if (isAvailabilityAlreadyUpdated(RESPONSE_ENVELOPE)) {
return;
}
this.responseEnvelope = responseEnvelope;
updateAvailability(RESPONSE_ENVELOPE);
}
@Override
public Object responseContent() {
ensureAvailability(RESPONSE_CONTENT);
return responseContent;
}
@Override
public void responseContent(Object responseContent, Object rawResponseContent) {
if (isAvailabilityAlreadyUpdated(RESPONSE_CONTENT)) {
return;
}
if (responseContent instanceof RpcResponse &&
!((RpcResponse) responseContent).isDone()) {
throw new IllegalArgumentException("responseContent must be complete: " + responseContent);
}
this.responseContent = responseContent;
this.rawResponseContent = rawResponseContent;
updateAvailability(RESPONSE_CONTENT);
}
@Override
public Object rawResponseContent() {
ensureAvailability(RESPONSE_CONTENT);
return rawResponseContent;
}
@Override
public void deferResponseContent() {
if (isAvailabilityAlreadyUpdated(RESPONSE_CONTENT)) {
return;
}
responseContentDeferred = true;
}
@Override
public boolean isResponseContentDeferred() {
return responseContentDeferred;
}
@Override
public void endResponse() {
endResponse0(null);
}
@Override
public void endResponse(Throwable responseCause) {
endResponse0(requireNonNull(responseCause, "responseCause"));
}
private void endResponse0(Throwable responseCause) {
final int flags = responseCause == null && responseContentDeferred ? FLAGS_RESPONSE_END_WITHOUT_CONTENT
: RESPONSE_END.setterFlags();
if (isAvailable(flags)) {
return;
}
startResponse0(false);
responseEndTimeNanos = System.nanoTime();
this.responseCause = responseCause;
updateAvailability(flags);
}
@Override
public long totalDurationNanos() {
ensureAvailability(COMPLETE);
return responseEndTimeNanos - requestStartTimeNanos;
}
private void updateAvailability(RequestLogAvailability a) {
updateAvailability(a.setterFlags());
}
private void updateAvailability(int flags) {
for (;;) {
final int oldAvailability = this.flags;
final int newAvailability = oldAvailability | flags;
if (flagsUpdater.compareAndSet(this, oldAvailability, newAvailability)) {
if (oldAvailability != newAvailability) {
final RequestLogListener[] satisfiedListeners;
synchronized (listeners) {
satisfiedListeners = removeSatisfiedListeners();
}
notifyListeners(satisfiedListeners);
}
break;
}
}
}
private RequestLogListener[] removeSatisfiedListeners() {
if (listeners.isEmpty()) {
return null;
}
final int flags = this.flags;
final int maxNumListeners = listeners.size();
final Iterator i = listeners.iterator();
RequestLogListener[] satisfied = null;
int numSatisfied = 0;
do {
final ListenerEntry e = i.next();
final int interestedFlags = e.interestedFlags;
if ((flags & interestedFlags) == interestedFlags) {
i.remove();
if (satisfied == null) {
satisfied = new RequestLogListener[maxNumListeners];
}
satisfied[numSatisfied++] = e.listener;
}
} while (i.hasNext());
return satisfied;
}
private void notifyListeners(RequestLogListener[] listeners) {
if (listeners == null) {
return;
}
for (RequestLogListener l : listeners) {
if (l == null) {
break;
}
RequestLogListenerInvoker.invokeOnRequestLog(l, this);
}
}
@Override
public String toString() {
final String req = toStringRequestOnly();
final String res = toStringResponseOnly();
final StringBuilder buf = new StringBuilder(5 + req.length() + 6 + res.length() + 1);
return buf.append("{req=") // 5
.append(req)
.append(", res=") // 6
.append(res)
.append('}') // 1
.toString();
}
@Override
public String toStringRequestOnly() {
final int flags = this.flags & 0xFFFF; // Only interested in the bits related with request.
if (requestStrFlags == flags) {
return requestStr;
}
final StringBuilder buf = new StringBuilder(STRING_BUILDER_CAPACITY);
buf.append('{');
if (isAvailable(flags, REQUEST_START)) {
buf.append("startTime=");
TextFormatter.appendEpoch(buf, requestStartTimeMillis);
if (isAvailable(flags, REQUEST_END)) {
buf.append(", length=");
TextFormatter.appendSize(buf, requestLength);
buf.append(", duration=");
TextFormatter.appendElapsed(buf, requestDurationNanos());
if (requestCause != null) {
buf.append(", cause=").append(requestCause);
}
}
buf.append(", scheme=");
if (isAvailable(flags, SCHEME)) {
buf.append(scheme().uriText());
} else {
buf.append(SerializationFormat.UNKNOWN.uriText())
.append('+')
.append(sessionProtocol.uriText());
}
buf.append(", host=").append(host)
.append(", method=").append(method)
.append(", path=").append(path);
if (isAvailable(flags, REQUEST_ENVELOPE) && requestEnvelope != null) {
buf.append(", envelope=").append(requestEnvelope);
}
if (isAvailable(flags, REQUEST_CONTENT) && requestContent != null) {
buf.append(", content=").append(requestContent);
}
}
buf.append('}');
requestStr = buf.toString();
requestStrFlags = flags;
return requestStr;
}
@Override
public String toStringResponseOnly() {
final int flags = this.flags & 0xFFFF0000; // Only interested in the bits related with response.
if (responseStrFlags == flags) {
return responseStr;
}
final StringBuilder buf = new StringBuilder(STRING_BUILDER_CAPACITY);
buf.append('{');
if (isAvailable(flags, RESPONSE_START)) {
buf.append("startTime=");
TextFormatter.appendEpoch(buf, responseStartTimeMillis);
if (isAvailable(flags, RESPONSE_END)) {
buf.append(", length=");
TextFormatter.appendSize(buf, responseLength);
buf.append(", duration=");
TextFormatter.appendElapsed(buf, responseDurationNanos());
if (isAvailable(flags, REQUEST_START)) {
buf.append(", totalDuration=");
TextFormatter.appendElapsed(buf, totalDurationNanos());
}
if (responseCause != null) {
buf.append(", cause=").append(responseCause);
}
}
if (isAvailable(flags, STATUS_CODE)) {
buf.append(", statusCode=").append(statusCode);
}
if (isAvailable(flags, RESPONSE_ENVELOPE) && responseEnvelope != null) {
buf.append(", envelope=").append(responseEnvelope);
}
if (isAvailable(flags, RESPONSE_CONTENT) && responseContent != null) {
buf.append(", content=").append(responseContent);
}
}
buf.append('}');
responseStr = buf.toString();
responseStrFlags = flags;
return responseStr;
}
private static final class ListenerEntry {
final RequestLogListener listener;
final int interestedFlags;
ListenerEntry(RequestLogListener listener, int interestedFlags) {
this.listener = listener;
this.interestedFlags = interestedFlags;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy