/*
* Copyright (C) NetStruxr, Inc. All rights reserved.
*
* This software is published under the terms of the NetStruxr
* Public Software License version 0.5, a copy of which has been
* included with this distribution in the LICENSE.NPL file. */
package er.extensions.logging;
import java.util.Enumeration;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.helpers.FormattingInfo;
import org.apache.log4j.helpers.PatternConverter;
import org.apache.log4j.helpers.PatternParser;
import org.apache.log4j.spi.LoggingEvent;
import com.webobjects.appserver.WOAdaptor;
import com.webobjects.appserver.WOApplication;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import er.extensions.formatters.ERXUnitAwareDecimalFormat;
import er.extensions.foundation.ERXDictionaryUtilities;
import er.extensions.foundation.ERXSimpleTemplateParser;
import er.extensions.foundation.ERXThreadStorage;
import er.extensions.foundation.ERXUtilities;
/**
* The ERXPatternLayout adds some additional (and needed) layout options. The
* first is by specifying an '@' character a full backtrace will be logged as
* part of the log event. The second is by specifying an '$' char the current
* application name of the WOApplication will be logged as part of the log
* event. Finally by specifying an '#' char the current port number on which the
* primary adaptor listens to will be logged as part of the log event.
*
* <pre>
* WebObjects Application Info Patterns
* Example: %W{n[i:p s]} -- MyApp[9300:2001 28]
*
* n: application name
* i: pid (process ID, provided through Java system property "com.webobjects.pid")
* p: primary adaptor's port number
* s: active session count
*
* Java VM (Virtual Machine) Info Patterns
* Example: %V{u used/f free} -- 75.22 MB used/12.86 MB free
*
* t: total memory
* u: used memory
* f: free memory in the current heap size (not max)
* m: max memory
*
* </pre>
*
*/
// ENHANCEME: Need access to ERXThreadStorage, also need more WO stuff, could
// opt for a WO char
// and then specify all of the things to log as formatting info for that
// converter.
public class ERXPatternLayout extends PatternLayout {
/**
*
* Used to update the layout pattern at runtime from the log4j configuration
* page
*
*/
private static ERXPatternLayout _instance;
public static ERXPatternLayout instance() {
if (ERXPatternLayout._instance == null) {
ERXPatternLayout._instance = new ERXPatternLayout();
}
return ERXPatternLayout._instance;
}
/**
* Default constructor. Uses the default conversion pattern.
*/
public ERXPatternLayout() {
this(PatternLayout.DEFAULT_CONVERSION_PATTERN);
}
/**
* Default constructor. Uses the specified conversion pattern.
*
* @param pattern
* layout to be used.
*/
public ERXPatternLayout(String pattern) {
super(pattern);
ERXPatternLayout._instance = this; // log4j will create one of these at
// runtime, and instance() will be
// used to find it from the log4j
// config page
}
/**
* Creates a pattern parser for the given pattern. This method is called
* implicitly by the log4j logging system.
*
* @param pattern
* to create the pattern parser for
* @return an ERXPatternParser for the given pattern
*/
@Override
public PatternParser createPatternParser(String pattern) {
return new ERXPatternParser(pattern == null ? PatternLayout.DEFAULT_CONVERSION_PATTERN : pattern);
}
}
/**
* Pattern parser extension that adds support for WebObjects specific patterns.
*/
class ERXPatternParser extends PatternParser {
/**
* Default constructor for a given pattern
*
* @param pattern
* to construct the parser for
*/
public ERXPatternParser(String pattern) {
super(pattern);
}
/**
* Creates a converter for a particular character. This is the method that
* adds the custom converters for WO.
*
* @param c
* char to add the converter for
*/
@Override
public void finalizeConverter(char c) {
switch (c) {
case '$':
addConverter(new AppNamePatternConverter(formattingInfo));
currentLiteral.setLength(0);
break;
case '#':
addConverter(new AdaptorPortNumberConverter(formattingInfo));
currentLiteral.setLength(0);
break;
case '@':
addConverter(new StackTracePatternConverter(formattingInfo));
currentLiteral.setLength(0);
break;
case 'W':
addConverter(new AppInfoPatternConverter(formattingInfo, extractOption()));
currentLiteral.setLength(0);
break;
case 'V':
addConverter(new JavaVMInfoPatternConverter(formattingInfo, extractOption()));
currentLiteral.setLength(0);
break;
case 'T':
addConverter(new ThreadStoragePatternConverter(formattingInfo, extractOption()));
currentLiteral.setLength(0);
break;
default:
super.finalizeConverter(c);
break;
}
}
/**
* The stack trace pattern is useful for logging full stack traces when a
* log event occurs.
*/
private class StackTracePatternConverter extends PatternConverter {
/**
* Package access level constructor.
*
* @param formattingInfo
* that is currently being used for this pattern converter.
*/
StackTracePatternConverter(FormattingInfo formattingInfo) {
super(formattingInfo);
}
/**
* For a given log event returns the string representation of a stack
* trace for the current logging call minus all of the log4j stack.
*
* @param event
* current logging event
* @return string representation of the current backtrace.
*/
@Override
public String convert(LoggingEvent event) {
NSArray parts = NSArray.componentsSeparatedByString(ERXUtilities.stackTrace(), "\n\t");
NSMutableArray subParts = new NSMutableArray();
boolean first = true;
for (Enumeration e = parts.reverseObjectEnumerator(); e.hasMoreElements();) {
String element = (String) e.nextElement();
if (element.indexOf("org.apache.log4j") != -1) {
break;
}
if (!first) {
subParts.insertObjectAtIndex(element, 0);
}
else {
first = false;
}
}
return "\t" + subParts.componentsJoinedByString("\n\t") + "\n";
}
}
/**
* The application name pattern converter is useful for logging the current
* application name in log statements.
*
* @deprecated
*/
@Deprecated
private class AppNamePatternConverter extends PatternConverter {
/** holds a reference to the app name */
String _appName;
/**
* Default package level constructor
*
* @param formattingInfo
* current pattern formatting information
*/
AppNamePatternConverter(FormattingInfo formattingInfo) {
super(formattingInfo);
}
/**
* Returns the current application name for the current logging event.
* If the application instance has not been created yet then "N/A" is
* logged.
*
* @param event
* a given logging event
* @return the current application name
*/
@Override
public String convert(LoggingEvent event) {
if (_appName == null) {
if (WOApplication.application() != null) {
_appName = WOApplication.application().name();
}
}
return _appName != null ? _appName : "N/A";
}
}
private class ThreadStoragePatternConverter extends PatternConverter {
private String key;
private NSArray keyParts;
private boolean isKeyPath;
ThreadStoragePatternConverter(FormattingInfo formattingInfo, String key) {
super(formattingInfo);
this.key = key;
isKeyPath = key != null && key.indexOf(".") != -1;
if (isKeyPath) {
keyParts = NSArray.componentsSeparatedByString(key, ".");
}
}
@Override
public String convert(LoggingEvent event) {
Object value = null;
if (!isKeyPath) {
value = key != null ? ERXThreadStorage.valueForKey(key) : ERXThreadStorage.map();
}
else {
value = ERXThreadStorage.map();
for (int j = 0; j < keyParts.count(); j++) {
String part = (String) keyParts.objectAtIndex(j);
if (j == 0) {
value = ERXThreadStorage.valueForKey(part);
}
else {
try {
value = NSKeyValueCoding.Utility.valueForKey(value, part);
}
catch (Throwable t) {
value = "ERR: " + part + " ->" + t.getMessage();
}
}
if (value == null) {
break;
}
}
}
return value != null ? value.toString() : null;
}
}
/**
* The adaptor port number pattern converter is useful for logging the
* current primary adaptor port in log statements.
*
* @deprecated
*/
@Deprecated
private class AdaptorPortNumberConverter extends PatternConverter {
/** holds a reference to the primary adaptor port */
String _portNumber;
/**
* Default package level constructor
*
* @param formattingInfo
* current pattern formatting information
*/
AdaptorPortNumberConverter(FormattingInfo formattingInfo) {
super(formattingInfo);
}
/**
* Returns the current port number on which the primary adaptor listens
* to. This will be the same number specified by WOPort launch argument.
* <p>
* If the application or adapter instance has not been created yet then
* "N/A" is logged.
*
* @param event
* a given logging event
* @return the current application name
*/
@Override
public String convert(LoggingEvent event) {
if (_portNumber == null) {
if (WOApplication.application() != null) {
// _portNumber =
// WOApplication.application().port().toString();
// WO 5.1.x -- Apple Ref# 2260519
NSArray adaptors = WOApplication.application().adaptors();
if (adaptors != null && adaptors.count() > 0) {
WOAdaptor primaryAdaptor = (WOAdaptor) adaptors.objectAtIndex(0);
_portNumber = String.valueOf(primaryAdaptor.port());
}
}
}
return _portNumber != null ? _portNumber : "N/A";
}
}
/**
* The <code>AppInfoPatternConverter</code> is useful for logging various
* info about the WebObjects application instance. See
* {@link ERXPatternLayout} for example/supported partterns.
*/
private class AppInfoPatternConverter extends PatternConverter {
/** Template parser to format logging events */
private ERXSimpleTemplateParser _templateParser;
/** Template used by _templateParser */
private String _template;
/**
* Flag to indicate if the constant values are set. The constant values
* are the part of application info that shouldn't change during the
* application's life span.
*/
private boolean _constantsInitialized = false;
/**
* Holds the values for the application info. Used by the template
* parser
*/
private NSMutableDictionary _appInfo;
/**
* Holds the default labels for the values. Note that the template
* parser will put "-" for undefined values by defauilt.
*/
private final NSDictionary _defaultLabels = ERXDictionaryUtilities.dictionaryWithObjectsAndKeys(new Object[] { "@@sessionCount@@", "sessionCount" });
/**
* Default package level constructor
*
* @param formattingInfo
* current pattern formatting information
* @param format
* string for the logging event format
*/
// FIXME: Work in progress - fixed template; format parameter will be
// ignored for now.
AppInfoPatternConverter(FormattingInfo formattingInfo, String format) {
super(formattingInfo);
_templateParser = new ERXSimpleTemplateParser("-");
// This will prevent the convert method to get into an infinite loop
// when debug level logging is enabled for the perser.
_templateParser.isLoggingDisabled = true;
_appInfo = new NSMutableDictionary();
_template = "@@appName@@[@@pid@@:@@portNumber@@ @@sessionCount@@]";
if(format != null && format.length() > 0) {
format = format.replaceFirst("(^|\\W)s(\\W|$)", "$1@@sessionCount@@$2");
format = format.replaceFirst("(^|\\W)n(\\W|$)", "$1@@appName@@$2");
format = format.replaceFirst("(^|\\W)p(\\W|$)", "$1@@portNumber@@$2");
format = format.replaceFirst("(^|\\W)i(\\W|$)", "$1@@pid@@$2");
_template = format;
}
}
/**
* Returns ...
* <p>
*
* ... has not been created yet then "-" is logged.
*
* @param event
* a given logging event
* @return the current application name
*/
@Override
public String convert(LoggingEvent event) {
WOApplication app = WOApplication.application();
if (app != null) {
if (!_constantsInitialized) {
String pid = System.getProperty("com.webobjects.pid");
if (pid != null) {
_appInfo.setObjectForKey(pid, "pid");
}
String appName = app.name();
if (appName != null) {
_appInfo.setObjectForKey(appName, "appName");
}
if (app.port() != null && app.port().intValue() > 0) {
_appInfo.setObjectForKey(app.port().toString(), "portNumber");
}
else {
// WO 5.1.x -- Apple Ref# 2260519
NSArray adaptors = app.adaptors();
if (adaptors != null && adaptors.count() > 0) {
WOAdaptor primaryAdaptor = (WOAdaptor) adaptors.objectAtIndex(0);
String portNumber = String.valueOf(primaryAdaptor.port());
if (portNumber != null) {
_appInfo.setObjectForKey(portNumber, "portNumber");
}
}
}
_template = _templateParser.parseTemplateWithObject(_template, "@@", _appInfo, _defaultLabels);
_constantsInitialized = true;
}
_appInfo.setObjectForKey(String.valueOf(app.activeSessionsCount()), "sessionCount");
}
return _templateParser.parseTemplateWithObject(_template, "@@", _appInfo);
}
}
/**
* The <code>JavaVMInfoPatternConverter</code> is useful for logging various
* info about the Java runtime and Virtual Machine that is running the
* application instance. See {@link ERXPatternLayout} for example/supported
* patterns.
*/
private class JavaVMInfoPatternConverter extends PatternConverter {
/** */
private Runtime _runtime;
/** */
private ERXUnitAwareDecimalFormat _decimalFormatter;
/** Template parser to format logging events */
private ERXSimpleTemplateParser _templateParser;
/** Template used by _templateParser */
private String _template;
/**
* Flag to indicate if the constant values are set. The constant values
* are the part of application info that shouldn't change during the
* application's life span.
*/
private boolean _constantsInitialized = false;
/** Holds the values for the JavaVM info. Used by the template parser */
private NSMutableDictionary _jvmInfo;
/**
* Holds the default labels for the values. Note that the template
* parser will put "-" for undefined values by default.
*/
private final NSDictionary _defaultLabels = null;
/**
* Default package level constructor
*
* @param formattingInfo
* current pattern formatting information
* @param format
* string for the logging event format
*/
// FIXME: Work in progress - fixed template; format parameter will be
// ignored for now.
JavaVMInfoPatternConverter(FormattingInfo formattingInfo, String format) {
super(formattingInfo);
_runtime = Runtime.getRuntime();
_decimalFormatter = new ERXUnitAwareDecimalFormat(ERXUnitAwareDecimalFormat.BYTE);
_decimalFormatter.setMaximumFractionDigits(2);
_templateParser = new ERXSimpleTemplateParser("-");
// This will prevent the convert method to get into an infinite loop
// when debug level logging is enabled for the parser.
_templateParser.isLoggingDisabled = true;
_jvmInfo = new NSMutableDictionary();
format = format.replaceFirst("(^|\\W)u(\\W|$)", "$1@@usedMemory@@$2");
format = format.replaceFirst("(^|\\W)f(\\W|$)", "$1@@freeMemory@@$2");
format = format.replaceFirst("(^|\\W)t(\\W|$)", "$1@@totalMemory@@$2");
format = format.replaceFirst("(^|\\W)m(\\W|$)", "$1@@maxMemory@@$2");
_template = format;
}
/**
* Returns ...
* <p>
*
* ... has not been created yet then "-" is logged.
*
* @param event
* a given logging event
* @return the current application name
*/
@Override
public String convert(LoggingEvent event) {
if (!_constantsInitialized) {
// Initialize constants here
_constantsInitialized = true;
}
long totalMemory = _runtime.totalMemory();
long freeMemory = _runtime.freeMemory();
long maxMemory = _runtime.maxMemory();
long usedMemory = totalMemory - freeMemory;
_jvmInfo.setObjectForKey(_decimalFormatter.format(totalMemory), "totalMemory");
_jvmInfo.setObjectForKey(_decimalFormatter.format(freeMemory), "freeMemory");
_jvmInfo.setObjectForKey(_decimalFormatter.format(usedMemory), "usedMemory");
_jvmInfo.setObjectForKey(_decimalFormatter.format(maxMemory), "maxMemory");
return _templateParser.parseTemplateWithObject(_template, "@@", _jvmInfo);
}
}
}