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 ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2013 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}.
*/
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();
}
}