/*
* Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.message.internal;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.annotation.Priority;
import org.glassfish.jersey.internal.PropertiesDelegate;
Low level Tracing support API.
Use one instance per request.
Author: Libor Kramolis Since: 2.3
/**
* Low level Tracing support API.
*
* Use one instance per request.
*
* @author Libor Kramolis
* @since 2.3
*/
public abstract class TracingLogger {
TracingLogger
instance is placed in request context properties under this name. /**
* {@code TracingLogger} instance is placed in request context properties under this name.
*/
public static final String PROPERTY_NAME = TracingLogger.class.getName();
HTTP header prefix.
/**
* HTTP header prefix.
*/
private static final String HEADER_TRACING_PREFIX = "X-Jersey-Tracing-";
Request header name to change application default tracing level.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* Response header name format.
*/
private static final String HEADER_RESPONSE_FORMAT = HEADER_TRACING_PREFIX + "%03d";
Default event level.
/**
* Default event level.
*/
public static final Level DEFAULT_LEVEL = Level.TRACE;
JDK logger name prefix.
/**
* 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 HEADER_LOGGER
. /**
* 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.
/**
* Empty (no-op) tracing logger.
*/
private static final TracingLogger EMPTY = new TracingLogger() {
@Override
public boolean isLogEnabled(final Event event) {
return false;
}
@Override
public void log(final Event event, final Object... args) {
// no-op
}
@Override
public void logDuration(final Event event, final long fromTimestamp, final Object... args) {
// no-op
}
@Override
public long timestamp(final Event event) {
return -1;
}
@Override
public void flush(final MultivaluedMap<String, Object> headers) {
// no-op
}
};
Returns instance of TracingLogger
associated with current request processing (propertiesDelegate
). Params: - propertiesDelegate – request associated runtime properties. Can be
null
if not running on server side.
Returns: returns instance of TracingLogger
from propertiesDelegate
. Does not return null
.
/**
* 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(final 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.
Params: - threshold – tracing level threshold.
- loggerNameSuffix – tracing logger name suffix.
Returns: new tracing logger.
/**
* Create new Tracing logger.
*
* @param threshold tracing level threshold.
* @param loggerNameSuffix tracing logger name suffix.
* @return new tracing logger.
*/
public static TracingLogger create(final Level threshold, final String loggerNameSuffix) {
return new TracingLoggerImpl(threshold, loggerNameSuffix);
}
Get an empty (no-op) tracing logger instance.
Returns: empty tracing logger instance.
/**
* 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 propertiesDelegate
setting) and if event
can be logged (according to event.level
and threshold level set). Params: - event – event type to be tested
Returns: true
if event
can be logged
/**
* 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.
Params: - event – event type to be logged
- args – message arguments (in relation to
Event.messageFormat()
/**
* 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 fromTimestamp
is not set (i.e. -1
) then duration of event is 0
. Params: - event – event type to be logged
- fromTimestamp – logged event is running from the timestamp in nanos.
-1
in case event has no duration - args – message arguments (in relation to
Event.messageFormat()
/**
* 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.
Params: - event – event type to be logged
Returns: Current timestamp in nanos or -1
if tracing is not enabled
/**
* 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.
Params: - headers – message headers.
/**
* Stores collected tracing messages to response HTTP header.
*
* @param headers message headers.
*/
public abstract void flush(MultivaluedMap<String, Object> headers);
Real implementation of tracing logger.
/**
* 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(final 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(final Event event) {
return isEnabled(event.level());
}
@Override
public void log(final Event event, final Object... args) {
logDuration(event, -1, args);
}
@Override
public void logDuration(final Event event, final long fromTimestamp, final 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(final Event event) {
if (isEnabled(event.level())) {
return System.nanoTime();
}
return -1;
}
@Override
public void flush(final MultivaluedMap<String, Object> 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 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. Params: - event – Event type of log
- duration – Time duration of logged event. Can be
0
. - messageArgs – message arguments
/**
* 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)) {
logger.log(loggingLevel,
event.name() + ' ' + message.toString() + " [" + TracingInfo.formatDuration(duration) + " ms]");
}
}
}
private boolean isEnabled(final 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.
Params: - instance – instance to be formatted
Returns: Formatted info of instance.
/**
* 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(final 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.
Params: - instance – instance to be formatted
- textSB – Formatted info will be appended to
StringBuilder
/**
* 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(final Object instance, final 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.
Params: - response – response to be formatted
- textSB – Formatted info will be appended to
StringBuilder
/**
* 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(final Response response, final 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(final Response.StatusType statusInfo) {
return String.valueOf(statusInfo.getStatusCode()) + '/' + statusInfo.getFamily() + '|' + statusInfo.getReasonPhrase();
}
}
Level of tracing message.
/**
* Level of tracing message.
*/
public static enum Level {
Brief tracing information level.
/**
* Brief tracing information level.
*/
SUMMARY,
Detailed tracing information level.
/**
* Detailed tracing information level.
*/
TRACE,
Extremely detailed tracing information level.
/**
* Extremely detailed tracing information level.
*/
VERBOSE
}
Type of event.
/**
* Type of event.
*/
public static interface Event {
Name of event, should be unique.
Is logged by JDK logger.
Returns: event name.
/**
* 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.
Returns: event category.
/**
* 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.
Returns: event trace level.
/**
* 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 String.format(String, Object...)
format. Can be null. In that case message arguments are separated by space. Returns: message format
/**
* Message format. Use {@link String#format(String, Object...)} format.
* Can be null. In that case message arguments are separated by space.
*
* @return message format
*/
public String messageFormat();
}
}