/* * $Id$ * * Copyright 2006, The jCoderZ.org Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the jCoderZ.org Project nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jcoderz.phoenix.checkstyle; import java.util.HashSet; import java.util.Set; import java.util.StringTokenizer; import java.util.logging.Level; import com.puppycrawl.tools.checkstyle.api.Check; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.TokenTypes; /** * This check makes sure that a class' source code does not * contain illegal logging levels in calls to the Java Logging * API. Such a check is interesting when you have a logging * message framework that uses generated classes for messages up to * a certain level in order to guarantee certain information * in these messages when written to the log file. * One example scenario is when log file entries should be sent * to a management console where an operator is looking at the * messages and needs to take actions depending on the severity * of the information he receives. In such cases it is important * that these logging messages need a special format to contain * all the necessary information. In our special case an XML * was used from which exception or warning message classes * had been created. Those classes had to be used for all log * levels INFO and above as these logging records were * automatically forwarded to the operators sitting in from of * a management console, monitoring the application behaviour. * For debugging purposes the developer was allowed to use * only the logging levels below INFO so that no debugging * message accidentally was sent to the management console. * * The default configuration of this check is: * <ul> * <li>LoggerName = logger</li> * <li>AllowedLoggerMethods * = fine,finer,entering,exiting,throwing,finest</li> * <li>LogCallMaxLevel = FINE</li> * </ul> * The class' logger instance variable must have the name 'logger'. * Only calls to the methods 'fine', 'finer', 'entering', 'exiting', * 'throwing', and 'finest' are allowed. For the methods 'log', * 'logp', and 'logrb' only the logging level FINE and below is * allowed. You can override these settings by specifying * the properties in the checkstyle configuration file. * */ public class LoggingLevel extends Check { private static final int [] TOKEN_LIST = new int[] {TokenTypes.METHOD_CALL}; /** The required name of the logger. Default is 'logger'. */ private static final String DEFAULT_LOGGER_NAME = "logger"; /** This is the maximum allowed level for logXYZ() calls */ private static final Level LOG_CALL_MAX_ALLOWED_LEVEL = Level.FINE; /** This prefix covers the methods: log, logp, logrb */ private static final String LOG_CALL_PREFIX = "log"; /** A set of all logger method names besides logXYZ(). */ private static final Set LOGGER_METHODS = new HashSet(); /** A set of allowed logger method names besides logXYZ(). */ private static final Set ALLOWED_LOGGER_METHODS = new HashSet(); private String mLoggerName = DEFAULT_LOGGER_NAME; private Level mLogCallMaxLevel = LOG_CALL_MAX_ALLOWED_LEVEL; static { // All logger methods LOGGER_METHODS.add("severe"); LOGGER_METHODS.add("warning"); LOGGER_METHODS.add("info"); LOGGER_METHODS.add("config"); LOGGER_METHODS.add("fine"); LOGGER_METHODS.add("finer"); LOGGER_METHODS.add("entering"); LOGGER_METHODS.add("exiting"); LOGGER_METHODS.add("throwing"); LOGGER_METHODS.add("finest"); // The allowed logger methods ALLOWED_LOGGER_METHODS.add("fine"); ALLOWED_LOGGER_METHODS.add("finer"); ALLOWED_LOGGER_METHODS.add("entering"); ALLOWED_LOGGER_METHODS.add("exiting"); ALLOWED_LOGGER_METHODS.add("throwing"); ALLOWED_LOGGER_METHODS.add("finest"); } /** * Sets the name of the logger instance. * The default name is 'logger'. * * @param loggerName The name of the logger. */ public void setLoggerName (final String loggerName) { mLoggerName = loggerName; } /** * Sets the names of allowed logger methods. * The default names are: 'entering', 'exiting', 'throwing', 'fine', * 'finer', and 'finest'. * * @param allowedLoggerMethods The names of allowed logger methods, * separated by colons. */ public void setAllowedLoggerMethods (final String allowedLoggerMethods) { final StringTokenizer st = new StringTokenizer( allowedLoggerMethods, ","); ALLOWED_LOGGER_METHODS.clear(); while (st.hasMoreTokens()) { final String tok = st.nextToken(); if (tok != null) { ALLOWED_LOGGER_METHODS.add(tok.trim()); } } } /** * Sets the maximum allowed level for logger methods * starting with 'log' (log, logp, logrb). * The default level is 'FINE'. * * @param logCallMaxLevel The maximum allowed logger level for * the methods log, logp, and logrb. */ public void setLogCallMaxLevel (final String logCallMaxLevel) { mLogCallMaxLevel = Level.parse(logCallMaxLevel); } /** {@inheritDoc} */ public int[] getDefaultTokens () { final int [] rc = new int[TOKEN_LIST.length]; System.arraycopy(TOKEN_LIST, 0, rc, 0, rc.length); return rc; } /** {@inheritDoc} */ public void visitToken (final DetailAST ast) { switch (ast.getType()) { case TokenTypes.METHOD_CALL: visitMethodCall(ast); default: break; } } /** * Visits a method call token. Since we are interested in logger.method calls * only, the first child must be a DOT type, otherwise no interest. * * @param methCall The visited token */ private void visitMethodCall (final DetailAST methCall) { final DetailAST dot = methCall.findFirstToken(TokenTypes.DOT); if (dot != null) { // the first child of the dot-Token is the variable name, which is // 'logger' in our case, otherwise not interested. final DetailAST varName = dot.findFirstToken(TokenTypes.IDENT); if (varName.getText().equals(mLoggerName)) { visitLoggerCall(methCall, varName); } } } /** * Visits a logger call. The supplied node is the logger variable, the next * sibling the called logger method. * * @param methCall The method call node. * @param logger The Logger variable used in the logger call. */ private void visitLoggerCall ( final DetailAST methCall, final DetailAST logger) { final DetailAST method = (DetailAST) logger.getNextSibling(); if (method != null) { final String methodName = method.getText(); if (methodName.startsWith(LOG_CALL_PREFIX)) { visitExpressionList(methCall, method); } else { // ignore all other methods than those defined above if (LOGGER_METHODS.contains(methodName) && !ALLOWED_LOGGER_METHODS.contains(methodName)) { logDisallowedLoggerMethod(method); } } } } /** * Checks the first parameter given to the supplied method call * * @param methCall The method call node. * @param method The called logger method. */ private void visitExpressionList ( final DetailAST methCall, final DetailAST method) { final DetailAST expressionList = methCall. findFirstToken(TokenTypes.ELIST); if (expressionList != null) { final DetailAST level = expressionList.findFirstToken(TokenTypes.EXPR); if (level != null) { final DetailAST dot = level.findFirstToken(TokenTypes.DOT); visitLoggerLevel(methCall, dot); } } } private void visitLoggerLevel ( final DetailAST methCall, final DetailAST dot) { /* in this case we are interested in the last child, which gives the log level. */ final DetailAST logLevel = dot.findFirstToken(TokenTypes.IDENT); if (logLevel != null) { final DetailAST levelName = (DetailAST) logLevel.getNextSibling(); if (levelName != null) { final Level level = Level.parse(levelName.getText()); if (level.intValue() > mLogCallMaxLevel.intValue()) { logDisallowedLogLevel(levelName); } } } } /** * Logs the disallowed logger level. * * @param level The disallowed log level. */ private void logDisallowedLogLevel (final DetailAST level) { // trace.loglevel=Maximum allowed log level for trace log is // ''{0}'' but was ''{1}''. log(level.getLineNo(), "trace.loglevel", new Object[] {mLogCallMaxLevel, level.getText()}); } /** * Logs the disallowed logger method call. * * @param method The disallowed logger method. */ private void logDisallowedLoggerMethod (final DetailAST method) { // trace.logmethod=Logger method ''{0}'' is not allowed. log(method.getLineNo(), "trace.logmethod", new Object[] {method.getText()}); } }