All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.jersey.message.internal.TracingLogger Maven / Gradle / Ivy

Go to download

A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle (jaxrs-ri.jar). Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from the command line.

There is a newer version: 3.1.9
Show newest version
/*
 * Copyright (c) 2013, 2023 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 org.glassfish.jersey.JerseyPriorities;
import org.glassfish.jersey.internal.PropertiesDelegate;

/**
 * Low level Tracing support API.
 *
 * Use one instance per request.
 *
 * @author Libor Kramolis
 * @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(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 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(final PropertiesDelegate propertiesDelegate) {
        if (propertiesDelegate == null) {
            //not server side
            return EMPTY;
        }
        final Object tracingLogger = propertiesDelegate.getProperty(PROPERTY_NAME);
        return TracingLogger.class.isInstance(tracingLogger) ? (TracingLogger) 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(final Level threshold, final 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(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 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)) {
                    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.
         *
         * @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);
                final int priority = JerseyPriorities.getPriorityValue(instance.getClass(), -1);
                if (priority != -1) {
                    textSB.append(" #").append(priority);
                }
                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(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.
         *
         * @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.
     */
    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.
         *
         * @return message format
         */
        public String messageFormat();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy