/*******************************************************************************
* Copyright (c) 2015, 2016 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 v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* 06/08/2015-2.6 Tomas Kraus
* - initial API and implementation.
******************************************************************************/
package org.eclipse.persistence.internal.logging;
import java.security.AccessController;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.persistence.internal.localization.LoggingLocalization;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedGetSystemProperty;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;
/**
* INTERNAL:
* Log to EclipseLink logger and optionally to standard error output.
* Standard error output logging is enabled by:
* <ul><li>{@code org.eclipse.persistence.jpa.log.stderr} for all logger categories</li>
* <li>{@code org.eclipse.persistence.jpa.<category>.log.stderr} for individual logger category</li></ul>
* Logger API is based on {@link SessionLog}.
* @since 2.7
*/
public class StdErrLogger {
/** Logger. */
private static final SessionLog LOGGER = AbstractSessionLog.getLog();
/** Standard error output logger property name prefix. */
private static final String PROPERTY_NAME_PREFIX = "eclipselink.log";
/** Standard error output logger property name suffix. */
private static final String PROPERTY_NAME_SUFFIX = "stderr";
/** Standard error output logger property name components separator. */
private static final char PROPERTY_NAME_SEPARATOR = '.';
/** {@link String} to separate logging category from message text in standard error output. */
private static final String CATEGORY_SEPARATOR = ": ";
/** {@link String} to prefix stack trace message in standard error output. */
private static final String STACK_TRACE_PREFIX = " - ";
/** Standard error output triggers for specific EclipseLink logging categories. */
private static final Map<String, Boolean> logEnabledCategory = initLogEnabledCategory();
// PERF: Global standard error output trigger to check before getting from Map.
/** Global standard error output trigger. EclipseLink logging categories are allowed to be sent to standard error
* output when value is set to {@code true}. No standard error output will be done when value is set
* to {@code false}. */
private static final boolean logEnabled = logEnabledCategory != null;
/**
* INTERNAL:
* Initialize {@logEnabledCategory} standard error output triggers for specific EclipseLink logging categories.
* @return Standard error output triggers {@link Map} for specific EclipseLink logging categories or {@code null}
* if no standard error output was enabled.
*/
private static Map<String, Boolean> initLogEnabledCategory() {
final int PropertyExtensionsLength = PROPERTY_NAME_PREFIX.length() + PROPERTY_NAME_SUFFIX.length() + 2;
final Map<String, Boolean> logEnabledMap = new HashMap<>(SessionLog.loggerCatagories.length);
boolean globalTrigger;
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
globalTrigger = Boolean.valueOf(AccessController.doPrivileged(new PrivilegedGetSystemProperty(
PROPERTY_NAME_PREFIX + PROPERTY_NAME_SEPARATOR + PROPERTY_NAME_SUFFIX)));
} else {
globalTrigger = Boolean.valueOf(System.getProperty(
PROPERTY_NAME_PREFIX + PROPERTY_NAME_SEPARATOR + PROPERTY_NAME_SUFFIX));
}
boolean enable = globalTrigger;
for (final String category : SessionLog.loggerCatagories) {
if (globalTrigger) {
logEnabledMap.put(category, Boolean.TRUE);
} else {
StringBuilder propertyKey = new StringBuilder(PropertyExtensionsLength + category.length());
propertyKey.append(PROPERTY_NAME_PREFIX);
propertyKey.append(PROPERTY_NAME_SEPARATOR);
propertyKey.append(category);
propertyKey.append(PROPERTY_NAME_SEPARATOR);
propertyKey.append(PROPERTY_NAME_SUFFIX);
boolean trigger;
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
trigger = Boolean.valueOf(AccessController.doPrivileged(
new PrivilegedGetSystemProperty(propertyKey.toString())));
} else {
trigger = Boolean.valueOf(System.getProperty(propertyKey.toString()));
}
logEnabledMap.put(category, Boolean.valueOf(trigger));
enable = enable || trigger;
}
}
if (!enable) {
logEnabledMap.clear();
}
return enable ? logEnabledMap : null;
}
/**
* INTERNAL:
* Check if a message of the given category would actually be logged to standard error output.
* @return Value of {@code true} when a message of the given {@code category} would actually be logged to standard
* error output or {@code false} otherwise.
*/
private static final boolean shouldLogToStdErr(final String category) {
final Boolean enabled = logEnabledCategory.get(category);
return enabled != null ? enabled.booleanValue() : false;
}
/**
* INTERNAL:
* Log message to standard error output.
* @param category The EclipseLink logging category.
* @param messageKey The {@link TraceLocalizationResource} log message key.
* @param arguments Arguments of the log message.
*/
private static void logStdErr(
final String category, final String messageKey, final Object... arguments) {
final String message = arguments == null || arguments.length == 0 ?
LoggingLocalization.buildMessage(messageKey) : LoggingLocalization.buildMessage(messageKey, arguments);
final int messageLength = message != null ? message.length() : 0;
final int categoryLength = category != null ? category.length() + CATEGORY_SEPARATOR.length() : 0;
if (categoryLength > 0 || messageLength > 0) {
final StringBuilder sb = new StringBuilder(categoryLength + messageLength);
if (categoryLength > 0) {
sb.append(category);
sb.append(CATEGORY_SEPARATOR);
}
if (messageLength > 0) {
sb.append(message);
}
System.err.println(sb.toString());
}
}
/**
* INTERNAL:
* Log message to standard error output.
* @param category The EclipseLink logging category.
* @param throwable {@link Throwable} to be logged.
*/
private static void logThrowableStdErr(final String category, final Throwable throwable) {
final int categoryLength = category != null ? category.length() + CATEGORY_SEPARATOR.length() : 0;
for (StackTraceElement ste : throwable.getStackTrace()) {
final String message = ste.toString();
StringBuilder sb = new StringBuilder(categoryLength + STACK_TRACE_PREFIX.length() + message.length());
if (categoryLength > 0) {
sb.append(category);
sb.append(CATEGORY_SEPARATOR);
}
sb.append(STACK_TRACE_PREFIX);
sb.append(message);
System.err.println(sb.toString());
}
}
/**
* INTERNAL:
* Check if a message of the given {@code category} and {@code level} would actually be logged by EclipseLink logger
* or to standard error output.
* @param level The log request level value.
* @param category The EclipseLink logging category.
* @return Value of {@code true} if message will be logged or {@code false} otherwise.
*/
public static final boolean shouldLog(final int level, final String category) {
return (logEnabled && shouldLogToStdErr(category)) || LOGGER.shouldLog(level, category);
}
/**
* INTERNAL:
* Log message with no arguments to EclipseLink logger and standard error output.
* @param level The log request level value.
* @param category The EclipseLink logging category.
* @param messageKey The {@link TraceLocalizationResource} log message key.
*/
public static final void log(final int level, final String category, final String messageKey) {
LOGGER.log(level, category, messageKey, null);
if (logEnabled && shouldLogToStdErr(category)) {
logStdErr(category, messageKey);
}
}
/**
* INTERNAL:
* Log message with arguments array to EclipseLink logger and standard error output.
* @param level The log request level value.
* @param category The EclipseLink logging category.
* @param messageKey {@link TraceLocalizationResource} message key.
* @param arguments Arguments of the log message.
*/
public static final void log(
final int level, final String category, final String messageKey, final Object... arguments) {
LOGGER.log(level, category, messageKey, arguments);
if (logEnabled && shouldLogToStdErr(category)) {
logStdErr(category, messageKey, arguments);
}
}
/**
* INTERNAL:
* Log {@link Throwable} to EclipseLink logger and standard error output.
* @param level The log request level value.
* @param category The EclipseLink logging category.
* @param throwable {@link Throwable} to be logged.
*/
public static final void logThrowable(final int level, final String category, final Throwable throwable) {
LOGGER.logThrowable(level, category, throwable);
if (logEnabled && shouldLogToStdErr(category)) {
logThrowableStdErr(category, throwable);
}
}
}