org.glassfish.jersey.message.internal.TracingLogger Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jersey-all Show documentation
Show all versions of jersey-all Show documentation
jersey-all is a rebundled verison of Jersey as one OSGi bundle.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2013-2014 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.jersey.message.internal;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.annotation.Priority;
import org.glassfish.jersey.internal.PropertiesDelegate;
/**
* Low level Tracing support API.
*
* Use one instance per request.
*
* @author Libor Kramolis (libor.kramolis at oracle.com)
* @since 2.3
*/
public abstract class TracingLogger {
/**
* {@code TracingLogger} instance is placed in request context properties under this name.
*/
public static final String PROPERTY_NAME = TracingLogger.class.getName();
/**
* HTTP header prefix.
*/
private static final String HEADER_TRACING_PREFIX = "X-Jersey-Tracing-";
/**
* Request header name to change application default tracing level.
*/
public static final String HEADER_THRESHOLD = HEADER_TRACING_PREFIX + "Threshold";
/**
* Request header name to switch on request tracing.
* Make sense in case of tracing support enabled by ON_DEMAND value.
*/
public static final String HEADER_ACCEPT = HEADER_TRACING_PREFIX + "Accept";
/**
* Request header name to set JDK logger name suffix to identify a request logs.
*/
public static final String HEADER_LOGGER = HEADER_TRACING_PREFIX + "Logger";
/**
* Response header name format.
*/
private static final String HEADER_RESPONSE_FORMAT = HEADER_TRACING_PREFIX + "%03d";
/**
* Default event level.
*/
public static final Level DEFAULT_LEVEL = Level.TRACE;
/**
* JDK logger name prefix.
*/
private static final String TRACING_LOGGER_NAME_PREFIX = "org.glassfish.jersey.tracing";
/**
* Default JDK logger name suffix. This can be overwrite by header {@link #HEADER_LOGGER}.
*/
private static final String DEFAULT_LOGGER_NAME_SUFFIX = "general";
/**
* Empty (no-op) tracing logger.
*/
private static final TracingLogger EMPTY = new TracingLogger() {
@Override
public boolean isLogEnabled(Event event) {
return false;
}
@Override
public void log(Event event, Object... args) {
// no-op
}
@Override
public void logDuration(Event event, long fromTimestamp, Object... args) {
// no-op
}
@Override
public long timestamp(Event event) {
return -1;
}
@Override
public void flush(MultivaluedMap headers) {
// no-op
}
};
/**
* Returns instance of {@code TracingLogger} associated with current request processing
* ({@code propertiesDelegate}).
*
* @param propertiesDelegate request associated runtime properties. Can be {@code null} if not running on server side.
* @return returns instance of {@code TracingLogger} from {@code propertiesDelegate}. Does not return {@code null}.
*/
// TODO look for places where getInstance(RequestProcessingContext) would make sense
public static TracingLogger getInstance(PropertiesDelegate propertiesDelegate) {
if (propertiesDelegate == null) {
//not server side
return EMPTY;
}
final TracingLogger tracingLogger = (TracingLogger) propertiesDelegate.getProperty(PROPERTY_NAME);
return (tracingLogger != null) ? tracingLogger : EMPTY;
}
/**
* Create new Tracing logger.
*
* @param threshold tracing level threshold.
* @param loggerNameSuffix tracing logger name suffix.
* @return new tracing logger.
*/
public static TracingLogger create(Level threshold, String loggerNameSuffix) {
return new TracingLoggerImpl(threshold, loggerNameSuffix);
}
/**
* Get an empty (no-op) tracing logger instance.
*
* @return empty tracing logger instance.
*/
public static TracingLogger empty() {
return EMPTY;
}
/**
* Test if a tracing support is enabled (according to {@code propertiesDelegate} setting) and
* if {@code event} can be logged (according to {@code event.level} and threshold level set).
*
* @param event event type to be tested
* @return {@code true} if {@code event} can be logged
*/
public abstract boolean isLogEnabled(Event event);
/**
* Try to log event according to event level and request context threshold level setting.
*
* @param event event type to be logged
* @param args message arguments (in relation to {@link org.glassfish.jersey.message.internal.TracingLogger.Event#messageFormat()}
*/
public abstract void log(Event event, Object... args);
/**
* Try to log event according to event level and request context threshold level setting.
*
* If logging support is switched on for current request and event setting the method computes duration of event and log
* message. If {@code fromTimestamp} is not set (i.e. {@code -1}) then duration of event
* is {@code 0}.
*
* @param event event type to be logged
* @param fromTimestamp logged event is running from the timestamp in nanos. {@code -1} in case event has no duration
* @param args message arguments (in relation to {@link org.glassfish.jersey.message.internal.TracingLogger.Event#messageFormat()}
*/
public abstract void logDuration(Event event, long fromTimestamp, Object... args);
/**
* If logging support is switched on for current request and event setting the method returns current timestamp in nanos.
*
* @param event event type to be logged
* @return Current timestamp in nanos or {@code -1} if tracing is not enabled
*/
public abstract long timestamp(Event event);
/**
* Stores collected tracing messages to response HTTP header.
*
* @param headers message headers.
*/
public abstract void flush(MultivaluedMap headers);
/**
* Real implementation of tracing logger.
*/
private static final class TracingLoggerImpl extends TracingLogger {
private final Logger logger;
private final Level threshold;
private final TracingInfo tracingInfo;
public TracingLoggerImpl(Level threshold, String loggerNameSuffix) {
this.threshold = threshold;
this.tracingInfo = new TracingInfo();
loggerNameSuffix = loggerNameSuffix != null ? loggerNameSuffix : DEFAULT_LOGGER_NAME_SUFFIX;
this.logger = Logger.getLogger(TRACING_LOGGER_NAME_PREFIX + "." + loggerNameSuffix);
}
@Override
public boolean isLogEnabled(Event event) {
return isEnabled(event.level());
}
@Override
public void log(Event event, Object... args) {
logDuration(event, -1, args);
}
@Override
public void logDuration(Event event, long fromTimestamp, Object... args) {
if (isEnabled(event.level())) {
final long toTimestamp;
if (fromTimestamp == -1) {
toTimestamp = -1;
} else {
toTimestamp = System.nanoTime();
}
long duration = 0;
if ((fromTimestamp != -1) && (toTimestamp != -1)) {
duration = toTimestamp - fromTimestamp;
}
logImpl(event, duration, args);
}
}
@Override
public long timestamp(Event event) {
if (isEnabled(event.level())) {
return System.nanoTime();
}
return -1;
}
@Override
public void flush(final MultivaluedMap headers) {
final String[] messages = tracingInfo.getMessages();
for (int i = 0; i < messages.length; i++) {
headers.putSingle(String.format(TracingLogger.HEADER_RESPONSE_FORMAT, i), messages[i]);
}
}
/**
* Log message for specified event type.
*
* The event contains name, category, level and also message format.
* Message format will be formatted by parameter {@code messageArgs} is used to format event.
* If there is no message format then each message arg is separated by space.
* Final message also contains event name (JDK Log) or category (HTTP header) and time stamp.
*
* @param event Event type of log
* @param duration Time duration of logged event. Can be {@code 0}.
* @param messageArgs message arguments
*/
private void logImpl(final Event event, final long duration, final Object... messageArgs) {
if (isEnabled(event.level())) {
final String[] messageArgsStr = new String[messageArgs.length];
for (int i = 0; i < messageArgs.length; i++) {
messageArgsStr[i] = formatInstance(messageArgs[i]);
}
final TracingInfo.Message message = new TracingInfo.Message(event, duration, messageArgsStr);
tracingInfo.addMessage(message);
final java.util.logging.Level loggingLevel;
switch (event.level()) {
case SUMMARY:
loggingLevel = java.util.logging.Level.FINE;
break;
case TRACE:
loggingLevel = java.util.logging.Level.FINER;
break;
case VERBOSE:
loggingLevel = java.util.logging.Level.FINEST;
break;
default:
loggingLevel = java.util.logging.Level.OFF;
}
if (logger.isLoggable(loggingLevel)) {
StringBuilder textSB = new StringBuilder();
textSB.append(event.name()).append(' ').append(message.toString());
textSB.append(" [").append(TracingInfo.formatDuration(duration)).append(" ms]");
logger.log(loggingLevel, textSB.toString());
}
}
}
private boolean isEnabled(Level level) {
return threshold.ordinal() >= level.ordinal();
}
/**
* Format info of instance.
*
* It shows its class name, identity hash code, and following info if available: priority value, response detail.
*
* @param instance instance to be formatted
* @return Formatted info of instance.
*/
private static String formatInstance(Object instance) {
final StringBuilder textSB = new StringBuilder();
if (instance == null) {
textSB.append("null");
} else if ((instance instanceof Number) || (instance instanceof String) || (instance instanceof Method)) {
textSB.append(instance.toString());
} else if (instance instanceof Response.StatusType) {
textSB.append(formatStatusInfo((Response.StatusType) instance));
} else {
textSB.append('[');
formatInstance(instance, textSB);
if (instance.getClass().isAnnotationPresent(Priority.class)) {
textSB.append(" #").append(instance.getClass().getAnnotation(Priority.class).value());
}
if (instance instanceof WebApplicationException) {
formatResponse(((WebApplicationException) instance).getResponse(), textSB);
} else if (instance instanceof Response) {
formatResponse(((Response) instance), textSB);
}
textSB.append(']');
}
return textSB.toString();
}
/**
* Basic format of instance - just class name and identity hash code.
*
* @param instance instance to be formatted
* @param textSB Formatted info will be appended to {@code StringBuilder}
*/
private static void formatInstance(Object instance, StringBuilder textSB) {
textSB.append(instance.getClass().getName()).append(" @").
append(Integer.toHexString(System.identityHashCode(instance)));
}
/**
* Format of response - status code, status family, reason phrase and info about entity.
*
* @param response response to be formatted
* @param textSB Formatted info will be appended to {@code StringBuilder}
*/
private static void formatResponse(Response response, StringBuilder textSB) {
textSB.append(" <").append(formatStatusInfo(response.getStatusInfo())).append('|');
if (response.hasEntity()) {
formatInstance(response.getEntity(), textSB);
} else {
textSB.append("-no-entity-");
}
textSB.append('>');
}
private static String formatStatusInfo(Response.StatusType statusInfo) {
final StringBuilder textSB = new StringBuilder();
textSB.append(statusInfo.getStatusCode()).append('/').
append(statusInfo.getFamily()).append('|').
append(statusInfo.getReasonPhrase());
return textSB.toString();
}
}
/**
* Level of tracing message.
*/
public static enum Level {
/**
* Brief tracing information level.
*/
SUMMARY,
/**
* Detailed tracing information level.
*/
TRACE,
/**
* Extremely detailed tracing information level.
*/
VERBOSE
}
/**
* Type of event.
*/
public static interface Event {
/**
* Name of event, should be unique.
* Is logged by JDK logger.
*
* @return event name.
*/
public String name();
/**
* Category of event, more events share same category.
* Is used to format response HTTP header.
*
* @return event category.
*/
public String category();
/**
* Level of event.
* Is used to check if the event is logged according to application/request settings.
*
* @return event trace level.
*/
public Level level();
/**
* Message format. Use {@link String#format(String, Object...)} format.
* Can be null. In that case message arguments are separated by space
* (see {@link TracingLoggerImpl#logImpl(Event, long, Object...)}.
*
* @return message format
*/
public String messageFormat();
}
}