/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2017 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
* https://oss.oracle.com/licenses/CDDL+GPL-1.1
* or 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 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.admin.amx.impl.mbean;
import com.sun.common.util.logging.LoggingConfigImpl;
import com.sun.enterprise.server.logging.GFFileHandler;
import com.sun.enterprise.server.logging.diagnostics.MessageIdCatalog;
import com.sun.enterprise.server.logging.logviewer.backend.LogFilter;
import java.io.Serializable;
import java.util.*;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.management.*;
import org.glassfish.admin.amx.core.Util;
import org.glassfish.admin.amx.impl.util.InjectedValues;
import static org.glassfish.admin.amx.logging.LogAnalyzer.*;
import static org.glassfish.admin.amx.logging.LogFileAccess.*;
import static org.glassfish.admin.amx.logging.LogRecordEmitter.*;
import org.glassfish.admin.amx.logging.Logging;
import org.glassfish.admin.amx.util.*;
import org.glassfish.admin.amx.util.jmx.JMXUtil;
import org.glassfish.admin.amx.util.jmx.NotificationBuilder;
import org.glassfish.external.amx.AMXGlassfish;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.server.ServerEnvironmentImpl;
import org.jvnet.hk2.annotations.Service;
//import com.sun.enterprise.server.logging.LoggingImplHook;
/**
* Implementation of {@link Logging}. <p> The following is a GlassFish V2
* comment, and needs work for v3:<br> AMX Logging MBean is hooked directly into
* the logging subsystem via
* com.sun.enterprise.server.logging.FileandSyslogHandler which uses
* com.sun.enterprise.server.logging.AMXLoggingHook to instantiate and call an
* instance of LoggingImpl.
*/
public final class LoggingImpl extends AMXImplBase //implements /*Logging,*/ LoggingImplHook
{
private final Map<Level, String> mLevelToNotificationTypeMap;
private final Map<String, NotificationBuilder> mNotificationTypeToNotificationBuilderMap;
private static final String SERVER_LOG_NAME = "server.log";
private static final String ACCESS_LOG_NAME = "access.log";
private final LoggingConfigImpl loggingConfig;
private final GFFileHandler gfFileHandler;
private final LogFilter logFilter;
private final MessageIdCatalog msgIdCatalog;
private final Logger logger;
private final ServiceLocator mHabitat;
final String FILE_SEP;
private final String mServerName;
/**
* Used internally to get the Logging ObjectName for a particular server
* Logging MBean is a special-case because it needs to load as early as
* possible.
*/
public static ObjectName getObjectName(final String serverName) {
final String requiredProps = Util.makeRequiredProps(Util.deduceType(Logging.class), serverName);
final String ServerRootMonitorType = "ServerRootMonitor";
final String parentProp = Util.makeProp(ServerRootMonitorType, serverName);
final String props = Util.concatenateProps(requiredProps, parentProp);
return Util.newObjectName(AMXGlassfish.DEFAULT.amxJMXDomain(), props);
}
/**
*/
public LoggingImpl(final ObjectName parent, final String serverName) {
super(parent, Logging.class);
mServerName = serverName;
FILE_SEP = System.getProperty("file.separator");
mLevelToNotificationTypeMap = initLevelToNotificationTypeMap();
mNotificationTypeToNotificationBuilderMap = new HashMap<String, NotificationBuilder>();
final ServerEnvironmentImpl env = InjectedValues.getInstance().getServerEnvironment();
loggingConfig = new LoggingConfigImpl();
loggingConfig.setupConfigDir(env.getConfigDirPath(), env.getLibPath());
msgIdCatalog = new MessageIdCatalog();
mHabitat = InjectedValues.getInstance().getHabitat();
gfFileHandler = mHabitat.getService(GFFileHandler.class);
logFilter = mHabitat.getService(LogFilter.class);
logger = Logger.getAnonymousLogger();
}
/**
* Hook for subclass to modify anything in MBeanInfo.
*
* @Override
*/
/*
@Override
protected MBeanInfo
postRegisterModifyMBeanInfo( final MBeanInfo info )
{
final MBeanOperationInfo[] ops = info.getOperations();
final int idx = JMXUtil.findMBeanOperationInfo( info, "queryServerLog", null);
final MBeanOperationInfo op = ops[idx];
ops[idx] = new MBeanOperationInfo( op.getName(), op.getDescription(),
op.getSignature(), Map.class.getName(),
MBeanOperationInfo.INFO );
return JMXUtil.newMBeanInfo( info, ops );
}
*/
private static MBeanNotificationInfo[] SELF_NOTIFICATION_INFOS = null;
/**
* getMBeanInfo() can be called frequently. By making this static, we avoid
* needlessly creating new Objects.
*/
private static synchronized MBeanNotificationInfo[] getSelfNotificationInfos() {
if (SELF_NOTIFICATION_INFOS == null) {
final String[] types = SetUtil.toStringArray(ALL_LOG_RECORD_NOTIFICATION_TYPES);
final MBeanNotificationInfo selfInfo = new MBeanNotificationInfo(
types, Notification.class.getName(), "LogRecord notifications");
SELF_NOTIFICATION_INFOS = new MBeanNotificationInfo[]{selfInfo};
}
return (SELF_NOTIFICATION_INFOS);
}
public MBeanNotificationInfo[] getNotificationInfo() {
final MBeanNotificationInfo[] superInfos = super.getNotificationInfo();
final MBeanNotificationInfo[] all =
JMXUtil.mergeMBeanNotificationInfos(superInfos, getSelfNotificationInfos());
return all;
}
/**
* FIXME
*/
private void unimplemented() {
throw new RuntimeException("Not implemented.");
}
public void setModuleLogLevel(
final String module,
final String level) {
try {
loggingConfig.setLoggingProperty(module + ".level", level);
} catch (java.io.IOException e) {
logger.log(Level.SEVERE, "Can not set module log level");
}
}
public String getModuleLogLevel(final String module) {
try {
Map<String, String> props = loggingConfig.getLoggingProperties();
if (props != null) {
return props.get(module + ".level");
}
return null;
} catch (java.io.IOException e) {
logger.log(Level.SEVERE, "Can not get module log level");
return null;
}
}
public Map<String, String> getLoggingProperties() {
try {
Map<String, String> props = loggingConfig.getLoggingProperties();
return props;
} catch (java.io.IOException e) {
logger.log(Level.WARNING, "Can not get module log level");
return null;
}
}
public void updateLoggingProperties(final Map<String, String> properties) {
try {
loggingConfig.updateLoggingProperties(properties);
} catch (java.io.IOException e) {
logger.log(Level.WARNING, "Can not get module log level");
}
}
public int getLogLevelListenerCount(final Level logLevel) {
final String notifType = logLevelToNotificationType(logLevel);
final int count = getNotificationEmitter().getNotificationTypeListenerCount(notifType);
return (count);
}
public String[] getLogFileKeys() {
unimplemented();
return new String[]{SERVER_KEY, ACCESS_KEY};
}
public synchronized String[] getLogFileNames(final String key) {
String[] result = null;
if (SERVER_KEY.equals(key)) {
} else {
throw new IllegalArgumentException(key);
}
return result;
}
public Map<String, String> getLoggingAttributes() {
String gfHandler = "com.sun.enterprise.server.logging.GFFileHandler";
String sysHandler = "com.sun.enterprise.server.logging.SyslogHandler";
try {
Map<String, String> props = loggingConfig.getLoggingProperties();
if (props == null) {
return null;
}
Map<String, String> attributes = new HashMap<String, String>();
attributes.put(gfHandler + ".file", props.get(gfHandler + ".file"));
attributes.put(gfHandler + ".rotationTimelimitInMinutes", props.get(gfHandler + ".rotationTimelimitInMinutes"));
attributes.put(gfHandler + ".rotationLimitInBytes", props.get(gfHandler + ".rotationLimitInBytes"));
attributes.put(gfHandler + ".logtoConsole", props.get(gfHandler + ".logtoConsole"));
attributes.put(gfHandler + ".flushFrequency", props.get(gfHandler + ".flushFrequency"));
attributes.put("handlers", props.get("handlers"));
attributes.put(sysHandler + ".useSystemLogging", props.get(sysHandler + ".useSystemLogging"));
return attributes;
} catch (java.io.IOException e) {
logger.log(Level.WARNING, "Can not get logging attributes");
return null;
}
}
public void updateLoggingAttributes(final Map<String, String> properties) {
try {
loggingConfig.updateLoggingProperties(properties);
} catch (java.io.IOException e) {
logger.log(Level.WARNING, "Can not set logging attributes");
}
}
public synchronized void rotateAllLogFiles() {
gfFileHandler.rotate();
}
public synchronized void rotateLogFile(final String key) {
if (ACCESS_KEY.equals(key)) {
throw new IllegalArgumentException("not supported: " + key);
//getLogMBean().rotateAccessLog();
} else if (SERVER_KEY.equals(key)) {
gfFileHandler.rotate();
} else {
throw new IllegalArgumentException("" + key);
}
}
private Properties attributesToProps(List<Attribute> attrs) {
final Properties props = new Properties();
if (attrs != null) {
for (Attribute attr : attrs) {
final Object value = attr.getValue();
if (value == null) {
throw new IllegalArgumentException(attr.getName() + "=" + null);
}
props.put(attr.getName(), value.toString());
}
}
return (props);
}
private List<Serializable[]> convertQueryResult(final AttributeList queryResult) {
// extract field descriptions into a String[]
final AttributeList fieldAttrs = (AttributeList) ((Attribute) queryResult.get(0)).getValue();
final String[] fieldHeaders = new String[fieldAttrs.size()];
for (int i = 0; i < fieldHeaders.length; ++i) {
final Attribute attr = (Attribute) fieldAttrs.get(i);
fieldHeaders[ i] = (String) attr.getValue();
}
final List<List<Serializable>> srcRecords = TypeCast.asList(
((Attribute) queryResult.get(1)).getValue());
// create the new results, making the first Object[] be the field headers
final List<Serializable[]> results = new ArrayList<Serializable[]>(srcRecords.size());
results.add(fieldHeaders);
// extract every record
for (int recordIdx = 0; recordIdx < srcRecords.size(); ++recordIdx) {
final List<Serializable> record = srcRecords.get(recordIdx);
assert (record.size() == fieldHeaders.length);
final Serializable[] fieldValues = new Serializable[fieldHeaders.length];
for (int fieldIdx = 0; fieldIdx < fieldValues.length; ++fieldIdx) {
fieldValues[ fieldIdx] = record.get(fieldIdx);
}
results.add(fieldValues);
}
return results;
}
// code in LogBean.java code in v2
public List<Serializable[]> queryServerLog(
String name,
long startIndex,
boolean searchForward,
int maximumNumberOfResults,
Long fromTime,
Long toTime,
String logLevel,
Set<String> modules,
List<Attribute> nameValuePairs,
String anySearch) {
final List<Serializable[]> result = queryServerLogInternal(
name, startIndex, searchForward, maximumNumberOfResults,
fromTime, toTime, logLevel, modules, nameValuePairs, anySearch);
return result;
}
private List<Serializable[]> queryServerLogInternal(
final String name,
final long startIndex,
final boolean searchForward,
final int maximumNumberOfResults,
final Long fromTime,
final Long toTime,
final String logLevel,
final Set<String> modules,
final List<Attribute> nameValuePairs,
final String anySearch) {
if (name == null) {
throw new IllegalArgumentException("use MOST_RECENT_NAME, not null");
}
boolean sortAscending = true;
List<String> moduleList = null;
if (modules != null) {
moduleList = ListUtil.newListFromCollection(modules);
}
final Properties props = attributesToProps(nameValuePairs);
String actualName;
if (MOST_RECENT_NAME.equals(name)) {
actualName = null;
} else {
actualName = name;
}
if (!searchForward) {
sortAscending = false;
}
final AttributeList result = logFilter.getLogRecordsUsingQuery(actualName,
Long.valueOf(startIndex),
searchForward, sortAscending,
maximumNumberOfResults,
fromTime == null ? null
: new Date(fromTime),
toTime == null ? null
: new Date(toTime),
logLevel, false, moduleList, props, anySearch);
return convertQueryResult(result);
}
public Map<String, Number>[] getErrorInfo() {
unimplemented();
final List<Map<String, Object>> infos = null; //getLogMBean().getErrorInformation();
final Map<String, Number>[] results = TypeCast.asArray(new HashMap[infos.size()]);
for (int i = 0; i < results.length; ++i) {
final Map<String, Object> info = infos.get(i);
assert (info.keySet().size() == 3);
final Long timestamp = Long.parseLong(info.get(TIMESTAMP_KEY).toString());
final Integer severeCount = Integer.parseInt(info.get(SEVERE_COUNT_KEY).toString());
final Integer warningCount = Integer.parseInt(info.get(WARNING_COUNT_KEY).toString());
final Map<String, Number> item = new HashMap<String, Number>(info.size());
item.put(TIMESTAMP_KEY, timestamp);
item.put(SEVERE_COUNT_KEY, severeCount);
item.put(WARNING_COUNT_KEY, warningCount);
results[ i] = item;
}
return results;
}
private static final Integer INTEGER_0 = Integer.valueOf(0);
private static final Map<String, Integer> EMPTY_ERROR_DISTRIBUTION_MAP =
Collections.emptyMap();
private static final Set<String> LEGAL_DISTRIBUTION_LEVELS =
SetUtil.newUnmodifiableStringSet(
Level.SEVERE.toString(), Level.WARNING.toString());
public Map<String, Integer> getErrorDistribution(long timestamp, String level) {
if (!LEGAL_DISTRIBUTION_LEVELS.contains(level)) {
throw new IllegalArgumentException(level);
}
unimplemented();
Map<String, Integer> result =
null; //getLogMBean().getErrorDistribution( timestamp, Level.parse( level ) );
// query may return null instead of an empty Map
if (result != null) {
// Ensure that no module has a null count
for (final Map.Entry<String, Integer> me : result.entrySet()) {
if (me.getValue() == null) {
result.put(me.getKey(), INTEGER_0);
}
}
} else {
// never return a null Map, only an empty one
result = EMPTY_ERROR_DISTRIBUTION_MAP;
}
return result;
}
public void setKeepErrorStatisticsForIntervals(final int num) {
unimplemented();
//getLogMBean().setKeepErrorStatisticsForIntervals( num );
}
public int getKeepErrorStatisticsForIntervals() {
return 0;
/*
unimplemented();
return getLogMBean().getKeepErrorStatisticsForIntervals();
*/
}
public void setErrorStatisticsIntervalMinutes(final long minutes) {
unimplemented();
//getLogMBean().setErrorStatisticsIntervalDuration( minutes );
}
public long getErrorStatisticsIntervalMinutes() {
return 0;
/*
unimplemented();
return getLogMBean().getErrorStatisticsIntervalDuration();
*/
}
public String[] getLoggerNames() {
return EMPTY_STRING_ARRAY;
/*unimplemented();
final List<String> names =
TypeCast.checkList( getLogMBean().getLoggerNames(), String.class );
return names.toArray( EMPTY_STRING_ARRAY ); */
}
public String[] getLoggerNamesUnder(final String loggerName) {
unimplemented();
/*
final List<String> names = TypeCast.checkList(
getLogMBean().getLoggerNamesUnder( loggerName ), String.class );
return names.toArray( EMPTY_STRING_ARRAY );
*/
return null;
}
public String[] getDiagnosticCauses(final String messageID, final String moduleName) {
final List<String> causes = msgIdCatalog.getDiagnosticCausesForMessageId(messageID, moduleName);
String[] result = null;
if (causes != null) {
result = CollectionUtil.toArray(causes, String.class);
}
return result;
}
public String[] getDiagnosticChecks(String messageID, String moduleName) {
final List<String> checks = msgIdCatalog.getDiagnosticChecksForMessageId(messageID, moduleName);
String[] result = null;
if (checks != null) {
result = CollectionUtil.toArray(checks, String.class);
}
return result;
}
public String getDiagnosticURI(final String messageID) {
unimplemented();
return null; //getLogMBean().getDiagnosticURIForMessageId( messageID );
}
private static final Object[] LEVELS_AND_NOTIF_TYPES = new Object[]{
Level.SEVERE, LOG_RECORD_SEVERE_NOTIFICATION_TYPE,
Level.WARNING, LOG_RECORD_WARNING_NOTIFICATION_TYPE,
Level.INFO, LOG_RECORD_INFO_NOTIFICATION_TYPE,
Level.CONFIG, LOG_RECORD_CONFIG_NOTIFICATION_TYPE,
Level.FINE, LOG_RECORD_FINE_NOTIFICATION_TYPE,
Level.FINER, LOG_RECORD_FINER_NOTIFICATION_TYPE,
Level.FINEST, LOG_RECORD_FINEST_NOTIFICATION_TYPE,};
private static Map<Level, String> initLevelToNotificationTypeMap() {
final Map<Level, String> m = new HashMap<Level, String>();
for (int i = 0; i < LEVELS_AND_NOTIF_TYPES.length; i += 2) {
final Level level = (Level) LEVELS_AND_NOTIF_TYPES[ i];
final String notifType = (String) LEVELS_AND_NOTIF_TYPES[ i + 1];
m.put(level, notifType);
}
return (Collections.unmodifiableMap(m));
}
private String logLevelToNotificationType(final Level level) {
return mLevelToNotificationTypeMap.get(level);
}
protected void preRegisterDone()
throws Exception {
initNotificationTypeToNotificationBuilderMap(getObjectName());
}
private void initNotificationTypeToNotificationBuilderMap(final ObjectName objectName) {
mNotificationTypeToNotificationBuilderMap.clear();
for (final String notifType : ALL_LOG_RECORD_NOTIFICATION_TYPES) {
mNotificationTypeToNotificationBuilderMap.put(
notifType,
new NotificationBuilder(notifType, objectName));
}
}
private NotificationBuilder notificationTypeToNotificationBuilder(final String notificationType) {
NotificationBuilder builder =
mNotificationTypeToNotificationBuilderMap.get(notificationType);
assert (builder != null);
return builder;
}
private Map<String, Serializable> logRecordToMap(
final LogRecord record,
final String recordAsString) {
final Map<String, Serializable> m = new HashMap<String, Serializable>();
m.put(LOG_RECORD_AS_STRING_KEY, recordAsString);
m.put(LOG_RECORD_LEVEL_KEY, record.getLevel());
m.put(LOG_RECORD_LOGGER_NAME_KEY, record.getLoggerName());
m.put(LOG_RECORD_MESSAGE_KEY, record.getMessage());
m.put(LOG_RECORD_MILLIS_KEY, record.getMillis());
m.put(LOG_RECORD_SEQUENCE_NUMBER_KEY, record.getSequenceNumber());
m.put(LOG_RECORD_SOURCE_CLASS_NAME_KEY, record.getSourceClassName());
m.put(LOG_RECORD_SOURCE_METHOD_NAME_KEY, record.getSourceMethodName());
m.put(LOG_RECORD_THREAD_ID_KEY, record.getThreadID());
final Throwable thrown = record.getThrown();
if (thrown != null) {
final Throwable mapped = new ThrowableMapper(thrown).map();
m.put(LOG_RECORD_THROWN_KEY, mapped);
final Throwable rootCause = ExceptionUtil.getRootCause(thrown);
if (rootCause != thrown) {
final Throwable mappedRootCause = new ThrowableMapper(rootCause).map();
m.put(LOG_RECORD_ROOT_CAUSE_KEY, mappedRootCause);
}
}
return m;
}
private long mMyThreadID = -1;
/**
* Internal use only, called by
* com.sun.enterprise.server.logging.AMXLoggingHook.
*/
public void privateLoggingHook(
final LogRecord logRecord,
final Formatter formatter) {
//debug( "LoggingImpl.privateLoggingHook: " + formatter.format( logRecord ) );
if (logRecord.getThreadID() == mMyThreadID) {
debug("privateLoggingHook: recusive call!!!");
throw new RuntimeException("recursive call");
}
synchronized (this) {
mMyThreadID = Thread.currentThread().getId();
final Level level = logRecord.getLevel();
try {
// don't construct a Notification if there are no listeners.
if (getLogLevelListenerCount(level) != 0) {
final String notifType = logLevelToNotificationType(level);
final NotificationBuilder builder =
notificationTypeToNotificationBuilder(notifType);
// Notification.getMessage() will be the formatted log record
final String logRecordAsString = formatter.format(logRecord);
final Map<String, Serializable> userData =
logRecordToMap(logRecord, logRecordAsString);
final Notification notif =
builder.buildNewWithMap(logRecordAsString, userData);
debug("privateLoggingHook: sending: " + notif);
sendNotification(notif);
} else {
// debug( "privateLogHook: no listeners for level " + level );
}
} finally {
mMyThreadID = -1;
}
}
}
public void
testEmitLogMessage( final String level, final String message )
{
setMBeanLogLevel( level );
debug( "testEmitLogMessage: logging: message = " + message );
getLogger().log( Level.parse( level ), message );
}
}