/*
 * Copyright (c) 2013, 2019 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 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
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:
/** * 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(); } }