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

com.numdata.oss.log.ClassLogger Maven / Gradle / Ivy

There is a newer version: 1.22
Show newest version
/*
 * Copyright (c) 2017, Numdata BV, The Netherlands.
 * 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 Numdata 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 COPYRIGHT HOLDERS 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 NUMDATA BV 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 com.numdata.oss.log;

import java.lang.reflect.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

import com.numdata.oss.*;
import org.jetbrains.annotations.*;

/**
 * This class defines log services for logging information about a class. It may
 * use any underlying logging infrastructure.
 *
 * The current implementation tries to use {@code Log4jLogTarget}, which uses
 * the Log4j API for the underlying logging infrastructure. The {@code
 * ConsoleLogTarget} is used as fallback option.
 *
 * @author Peter S. Heijnen
 * @see Log4jTarget
 * @see ConsoleTarget
 */
@SuppressWarnings( { "SynchronizationOnStaticField", "AccessOfSystemProperties" } )
public final class ClassLogger
{
	/**
	 * Level for 'fatal' log messages.
	 */
	public static final int FATAL = 0;

	/**
	 * Level for 'error' log messages.
	 */
	public static final int ERROR = 1;

	/**
	 * Level for 'info' log messages.
	 */
	public static final int WARN = 2;

	/**
	 * Level for 'info' log messages.
	 */
	public static final int INFO = 3;

	/**
	 * Level for 'debug' log messages.
	 */
	public static final int DEBUG = 4;

	/**
	 * Level for 'trace' log messages.
	 */
	public static final int TRACE = 5;

	/**
	 * Pseudo-level to disable all log messages.
	 */
	public static final int NONE = -1;

	/**
	 * Pseudo-level to enable all log messages.
	 */
	public static final int ALL = 99;

	/**
	 * Use asynchronous logging. If {@code true}, log messages are send to log
	 * target on a separate thread, detached from the application thread; if
	 * {@code false}, log messages are send directly from the application
	 * thread.
	 */
	private static boolean ASYNCHRONOUS_LOGGING = true;

	/**
	 * Registered log targets.
	 */
	private static final List LOG_TARGETS = new ArrayList();

	/**
	 * Name of this log.
	 */
	private final String _name;

	/**
	 * Executor service used to send messages to the log.
	 */
	@SuppressWarnings( "StaticNonFinalField" )
	private static ExecutorService executorService = null;

	/**
	 * Get {@code ClassLogger} instance for the specified class.
	 *
	 * @param forClass Class to get the {@code ClassLogger} for.
	 *
	 * @return {@code ClassLogger} for the specified class.
	 */
	public static ClassLogger getFor( final Class forClass )
	{
		return new ClassLogger( forClass.getName() );
	}

	/**
	 * Get log targets. Note that this method returns an independent copy of the
	 * log target list.
	 *
	 * @return Log targets.
	 */
	public static List getLogTargets()
	{
		final List result;

		synchronized ( LOG_TARGETS )
		{
			result = new ArrayList( LOG_TARGETS );
		}

		return result;
	}

	/**
	 * Add a new log target.
	 *
	 * @param logTarget Log target to add.
	 */
	public static void addTarget( @NotNull final LogTarget logTarget )
	{
		synchronized ( LOG_TARGETS )
		{
			LOG_TARGETS.add( logTarget );
		}
	}

	/**
	 * Remove all log targets.
	 */
	public static void removeAllLogTargets()
	{
		synchronized ( LOG_TARGETS )
		{
			LOG_TARGETS.clear();
		}
	}

	/**
	 * Remove a log target. If the log target was not added, this method has no
	 * effect.
	 *
	 * @param logTarget Log target to remove.
	 */
	public static void removeTarget( final LogTarget logTarget )
	{
		synchronized ( LOG_TARGETS )
		{
			LOG_TARGETS.remove( logTarget );
		}
	}

	/**
	 * Parse log level string.
	 *
	 * @param level Log level as string.
	 *
	 * @return Log level;
	 *
	 * @throws IllegalArgumentException if the level could not be parsed.
	 */
	public static int parseLevel( @NotNull final String level )
	{
		final int result;

		if ( "NONE".equalsIgnoreCase( level ) )
		{
			result = NONE;
		}
		else if ( "FATAL".equalsIgnoreCase( level ) )
		{
			result = FATAL;
		}
		else if ( "ERROR".equalsIgnoreCase( level ) )
		{
			result = ERROR;
		}
		else if ( "WARN".equalsIgnoreCase( level ) )
		{
			result = WARN;
		}
		else if ( "INFO".equalsIgnoreCase( level ) )
		{
			result = INFO;
		}
		else if ( "DEBUG".equalsIgnoreCase( level ) )
		{
			result = DEBUG;
		}
		else if ( "TRACE".equalsIgnoreCase( level ) )
		{
			result = TRACE;
		}
		else if ( "ALL".equalsIgnoreCase( level ) )
		{
			result = ALL;
		}
		else
		{
			throw new IllegalArgumentException( level );
		}

		return result;
	}

	/**
	 * Parse log level string.
	 *
	 * @param level Log level as string.
	 *
	 * @return Log level;
	 *
	 * @throws NullPointerException if any argument is {@code null}.
	 * @throws IllegalArgumentException if the level could not be parsed.
	 */
	public static String getLevelName( final int level )
	{
		final String result;

		switch ( level )
		{
			case NONE:
				result = "NONE";
				break;

			case FATAL:
				result = "FATAL";
				break;

			case ERROR:
				result = "ERROR";
				break;

			case WARN:
				result = "WARN";
				break;

			case INFO:
				result = "INFO";
				break;

			case DEBUG:
				result = "DEBUG";
				break;

			case TRACE:
				result = "TRACE";
				break;

			case ALL:
				result = "ALL";
				break;

			default:
				throw new IllegalArgumentException( String.valueOf( level ) );
		}

		return result;
	}

	/*
	 * Static initializer sets up log targets.
	 */
	static
	{
		/*
		 * Use 'asynchronous.logging'
		 */
		try
		{
			ASYNCHRONOUS_LOGGING = "true".equals( System.getProperty( "asynchronous.logging" ) );
		}
		catch ( final SecurityException e )
		{
			/* ignore no access to system property */
		}

		/*
		 * Add {@link Log4jTarget} if Log4j classes are available and a
		 * configuration file (default: 'log4j.xml') is defined.
		 */
		final String l4jLoader = "org.apache.log4j.helpers.Loader";
		final String l4jOptionConverter = "org.apache.log4j.helpers.OptionConverter";

		Object l4jURL;

		final String l4jConfigurationOption = (String)invokeStatic( l4jOptionConverter, "getSystemProperty", new Class[] { String.class, String.class }, new Object[] { "log4j.configuration", null } );
		if ( l4jConfigurationOption == null )
		{
			l4jURL = invokeStatic( l4jLoader, "getResource", String.class, "log4j.xml" );
			if ( l4jURL == null )
			{
				l4jURL = invokeStatic( l4jLoader, "getResource", String.class, "log4j.properties" );
			}
		}
		else
		{
			try
			{
				l4jURL = new URL( l4jConfigurationOption );
			}
			catch ( final MalformedURLException ignored )
			{
				l4jURL = invokeStatic( l4jLoader, "getResource", String.class, l4jConfigurationOption );
			}
		}

		if ( l4jURL != null )
		{
			final Package thisPackage = ClassLogger.class.getPackage();
			final String className = thisPackage.getName() + ".Log4jTarget";

			final LogTarget log4jTarget = (LogTarget)invokeStatic( className, null, new Class[ 0 ], new Object[ 0 ] );
			if ( log4jTarget != null )
			{
				addTarget( log4jTarget );
			}
		}

		/*
		 * Add {@link LogFileTarget} if system properties are set.
		 */
		final LogFileTarget logFileTarget = LogFileTarget.getDefaultInstance();
		if ( logFileTarget != null )
		{
			addTarget( logFileTarget );
		}

		/*
		 * Add {@link LogServer} if system properties are set.
		 */
		final LogServer logServer = LogServer.getDefaultInstance();
		if ( logServer != null )
		{
			addTarget( logServer );
		}

		/*
		 * Add {@link JdkLoggingTarget} if system property is set.
		 */
		if ( Boolean.parseBoolean( System.getProperty( "jdk.logger" ) ) )
		{
			addTarget( new JdkLoggingTarget() );
		}

		/*
		 * Add {@link ConsoleTarget} if system properties are set, or
		 * if no log target has been added yet.
		 */
		final ConsoleTarget consoleTarget = new ConsoleTarget( LOG_TARGETS.isEmpty() ? INFO : NONE, System.err );
		if ( consoleTarget.getLevel() > NONE )
		{
			addTarget( new ConsoleTarget() );
		}
	}

	/**
	 * Utility method to call a static method using reflection.
	 *
	 * @param className  Fully qualified name of class whose method to invoke.
	 * @param methodName Name of method to invoke.
	 * @param argType    First and only argument type.
	 * @param argValue   First and only argument value.
	 *
	 * @return Result from inoked method ({@code null} if void).
	 */
	@Nullable
	private static Object invokeStatic( final String className, final String methodName, final Class argType, final Object argValue )
	{
		return invokeStatic( className, methodName, new Class[] { argType }, new Object[] { argValue } );
	}

	/**
	 * Utility method to call a static method using reflection.
	 *
	 * @param className  Fully qualified name of class whose method to invoke.
	 * @param methodName Name of method to invoke.
	 * @param argTypes   Argument types.
	 * @param argValues  Argument values.
	 *
	 * @return Result from inoked method ({@code null} if void).
	 */
	@Nullable
	private static Object invokeStatic( final String className, final String methodName, final Class[] argTypes, final Object[] argValues )
	{
		Object result = null;

		try
		{
			final Class theClass = Class.forName( className );

			if ( methodName == null )
			{
				final Constructor constructor = theClass.getConstructor( argTypes );
				result = constructor.newInstance( argValues );
			}
			else
			{
				final Method method = theClass.getMethod( methodName, argTypes );
				result = method.invoke( null, argValues );
			}
		}
		catch ( final ClassNotFoundException e )
		{ /* ignore */ }
		catch ( final InstantiationException e )
		{ /* ignore */ }
		catch ( final IllegalAccessException e )
		{ /* ignore */ }
		catch ( final InvocationTargetException e )
		{ /* ignore */ }
		catch ( final NoSuchMethodException e )
		{ /* ignore */ }

		return result;
	}

	/**
	 * Create logger with the specified name.
	 *
	 * @param name Log name (e.g. class name).
	 */
	ClassLogger( final String name )
	{
		_name = name;
	}

	/**
	 * Get name of this logger.
	 *
	 * @return Name of logger ( e.g. class name).
	 */
	public String getName()
	{
		return _name;
	}

	/**
	 * Test if the specified log level is enabled. This is true if the level is
	 * enabled for at least one of the registered log targets.
	 *
	 * @param level Log level.
	 *
	 * @return {@code true} if the log level is enabled; {@code false} if the
	 * log level is disabled.
	 */
	public boolean isLevelEnabled( final int level )
	{
		return isLevelEnabled( getName(), level );
	}

	/**
	 * Send log message with an associated throwable object to all registered
	 * log targets.
	 *
	 * @param level     Log level.
	 * @param message   Log message.
	 * @param throwable Throwable associated with log message.
	 */
	public void log( final int level, final String message, final Throwable throwable )
	{
		log( getName(), level, message, throwable );
	}

	/**
	 * Send log message with an associated throwable object to all registered
	 * log targets.
	 *
	 * @param name      Name of log.
	 * @param level     Log level.
	 * @param message   Log message.
	 * @param throwable Throwable associated with log message.
	 */
	static void log( final String name, final int level, final String message, final Throwable throwable )
	{
		if ( isLevelEnabled( name, level ) )
		{
			final Thread currentThread = Thread.currentThread();
			final String threadName = currentThread.getName();

			if ( ASYNCHRONOUS_LOGGING )
			{
				ExecutorService executor = executorService;
				if ( executor == null )
				{
					final DefaultThreadFactory threadFactory = new DefaultThreadFactory();
					threadFactory.setNamePrefix( ClassLogger.class.getName() );
					threadFactory.setDaemon( true );

					executor = Executors.newSingleThreadExecutor( threadFactory );
					executorService = executor;
				}

				executor.submit( new LogTask( name, level, message, throwable, threadName ) );
			}
			else
			{
				synchronized ( LOG_TARGETS )
				{
					for ( final LogTarget target : LOG_TARGETS )
					{
						target.log( name, level, message, throwable, threadName );
					}
				}
			}
		}
	}

	/**
	 * Test if the specified log level is enabled. This is true if the level is
	 * enabled for at least one of the registered log targets.
	 *
	 * @param name  Name of log (e.g. class name).
	 * @param level Log level.
	 *
	 * @return {@code true} if the log level is enabled; {@code false} if the
	 * log level is disabled.
	 */
	public static boolean isLevelEnabled( final String name, final int level )
	{
		boolean result = false;

		synchronized ( LOG_TARGETS )
		{
			for ( final LogTarget target : LOG_TARGETS )
			{
				if ( target.isLevelEnabled( name, level ) )
				{
					result = true;
					break;
				}
			}
		}

		return result;
	}

	/**
	 * Check if 'fatal' log messages are enabled.
	 *
	 * @return {@code true} if 'fatal' log messages are enabled; {@code false}
	 * otherwise.
	 */
	public boolean isFatalEnabled()
	{
		return isLevelEnabled( FATAL );
	}

	/**
	 * Check if 'error' log messages are enabled.
	 *
	 * @return {@code true} if 'error' log messages are enabled; {@code false}
	 * otherwise.
	 */
	public boolean isErrorEnabled()
	{
		return isLevelEnabled( ERROR );
	}

	/**
	 * Check if 'warn' log messages are enabled.
	 *
	 * @return {@code true} if 'warn' log messages are enabled; {@code false}
	 * otherwise.
	 */
	public boolean isWarnEnabled()
	{
		return isLevelEnabled( WARN );
	}

	/**
	 * Check if 'info' log messages are enabled.
	 *
	 * @return {@code true} if 'info' log messages are enabled; {@code false}
	 * otherwise.
	 */
	public boolean isInfoEnabled()
	{
		return isLevelEnabled( INFO );
	}

	/**
	 * Check if 'debug' log messages are enabled.
	 *
	 * @return {@code true} if 'debug' log messages are enabled; {@code false}
	 * otherwise.
	 */
	public boolean isDebugEnabled()
	{
		return isLevelEnabled( DEBUG );
	}

	/**
	 * Check if 'trace' log messages are enabled.
	 *
	 * @return {@code true} if 'trace' log messages are enabled; {@code false}
	 * otherwise.
	 */
	public boolean isTraceEnabled()
	{
		return isLevelEnabled( TRACE );
	}

	/**
	 * Log 'fatal' message.
	 *
	 * @param message Log message.
	 */
	public void fatal( final String message )
	{
		fatal( message, null );
	}

	/**
	 * Log 'error' message.
	 *
	 * @param message Log message.
	 */
	public void error( final String message )
	{
		error( message, null );
	}

	/**
	 * Log 'warn' message.
	 *
	 * @param message Log message.
	 */
	public void warn( final String message )
	{
		warn( message, null );
	}

	/**
	 * Log 'info' message.
	 *
	 * @param message Log message.
	 */
	public void info( final String message )
	{
		info( message, null );
	}

	/**
	 * Log 'debug' message.
	 *
	 * @param message Log message.
	 */
	public void debug( final String message )
	{
		debug( message, null );
	}

	/**
	 * Log 'trace' message.
	 *
	 * @param message Log message.
	 */
	public void trace( final String message )
	{
		trace( message, null );
	}

	/**
	 * Log 'fatal' message with an associated throwable object.
	 *
	 * @param message   Log message.
	 * @param throwable Throwable associated with log message.
	 */
	public void fatal( final String message, final Throwable throwable )
	{
		log( FATAL, message, throwable );
	}

	/**
	 * Log 'error' message with an associated throwable object.
	 *
	 * @param message   Log message.
	 * @param throwable Throwable associated with log message.
	 */
	public void error( final String message, final Throwable throwable )
	{
		log( ERROR, message, throwable );
	}

	/**
	 * Log 'warn' message with an associated throwable object.
	 *
	 * @param message   Log message.
	 * @param throwable Throwable associated with log message.
	 */
	public void warn( final String message, final Throwable throwable )
	{
		log( WARN, message, throwable );
	}

	/**
	 * Log 'info' message with an associated throwable object.
	 *
	 * @param message   Log message.
	 * @param throwable Throwable associated with log message.
	 */
	public void info( final String message, final Throwable throwable )
	{
		log( INFO, message, throwable );
	}

	/**
	 * Log 'debug' message with an associated throwable object.
	 *
	 * @param message   Log message.
	 * @param throwable Throwable associated with log message.
	 */
	public void debug( final String message, final Throwable throwable )
	{
		log( DEBUG, message, throwable );
	}

	/**
	 * Log 'trace' message with an associated throwable object.
	 *
	 * @param message   Log message.
	 * @param throwable Throwable associated with log message.
	 */
	public void trace( final String message, final Throwable throwable )
	{
		log( TRACE, message, throwable );
	}

	/**
	 * Log method entry.
	 *
	 * @param methodName Name of entered method.
	 */
	public void entering( final String methodName )
	{
		if ( isTraceEnabled() )
		{
			entering( methodName, (Object[])null );
		}
	}

	/**
	 * Log method entry.
	 *
	 * @param methodName Name of entered method.
	 * @param args       Method arguments.
	 */
	public void entering( final String methodName, final Object... args )
	{
		if ( isTraceEnabled() )
		{
			final StringBuffer sb = new StringBuffer();
			sb.append( ">>> " );
			appendClassName( sb, getName() );
			sb.append( '.' );
			sb.append( methodName );
			sb.append( '(' );

			if ( args != null )
			{
				for ( int i = 0; i < args.length; i++ )
				{
					sb.append( ( i > 0 ) ? ", " : " " );
					appendValue( sb, args[ i ] );
					sb.append( ' ' );
				}
			}

			sb.append( ')' );
			trace( sb.toString(), null );
		}
	}

	/**
	 * Log method exit.
	 *
	 * @param methodName Name of exited method.
	 */
	public void exiting( final String methodName )
	{
		if ( isTraceEnabled() )
		{
			final StringBuffer sb = new StringBuffer();
			sb.append( "<<< " );
			appendClassName( sb, getName() );
			sb.append( '.' );
			sb.append( methodName );
			trace( sb.toString(), null );
		}
	}

	/**
	 * Log method exit with result value.
	 *
	 * @param methodName Name of exited method.
	 * @param result     Result value.
	 *
	 * @return Result value (returned as-is).
	 */
	public  T exiting( final String methodName, final T result )
	{
		if ( isTraceEnabled() )
		{
			final StringBuffer sb = new StringBuffer();
			sb.append( "<<< " );
			appendClassName( sb, getName() );
			sb.append( '.' );
			sb.append( methodName );
			sb.append( " returned " );
			appendValue( sb, result );
			trace( sb.toString(), null );
		}

		return result;
	}

	/**
	 * Log method exit with result value.
	 *
	 * @param methodName Name of exited method.
	 * @param result     Result value.
	 *
	 * @return Result value (returned as-is).
	 */
	public boolean exiting( final String methodName, final boolean result )
	{
		if ( isTraceEnabled() )
		{
			exiting( methodName, result ? Boolean.TRUE : Boolean.FALSE );
		}

		return result;
	}

	/**
	 * Log method exit with result value.
	 *
	 * @param methodName Name of exited method.
	 * @param result     Result value.
	 *
	 * @return Result value (returned as-is).
	 */
	public int exiting( final String methodName, final int result )
	{
		if ( isTraceEnabled() )
		{
			exiting( methodName, Integer.valueOf( result ) );
		}

		return result;
	}

	/**
	 * Log method exit with result value.
	 *
	 * @param methodName Name of exited method.
	 * @param result     Result value.
	 *
	 * @return Result value (returned as-is).
	 */
	public long exiting( final String methodName, final long result )
	{
		if ( isTraceEnabled() )
		{
			exiting( methodName, Long.valueOf( result ) );
		}

		return result;
	}

	/**
	 * Log method exit with result value.
	 *
	 * @param methodName Name of exited method.
	 * @param result     Result value.
	 *
	 * @return Result value (returned as-is).
	 */
	public float exiting( final String methodName, final float result )
	{
		if ( isTraceEnabled() )
		{
			exiting( methodName, new Float( result ) );
		}

		return result;
	}

	/**
	 * Log method exit with result value.
	 *
	 * @param methodName Name of exited method.
	 * @param result     Result value.
	 *
	 * @return Result value (returned as-is).
	 */
	public double exiting( final String methodName, final double result )
	{
		if ( isTraceEnabled() )
		{
			exiting( methodName, new Double( result ) );
		}

		return result;
	}

	/**
	 * Log throwable to be thrown.
	 *
	 * @param methodName Method from which the exception is thrown.
	 * @param throwable  Throwable that is thrown.
	 *
	 * @return Throwable that is thrown (returned as-is).
	 */
	public  T throwing( final String methodName, final T throwable )
	{
		return throwing( TRACE, methodName, throwable );
	}

	/**
	 * Log throwable to be thrown.
	 *
	 * @param logLevel   Level to log message at.
	 * @param methodName Method from which the exception is thrown.
	 * @param throwable  Throwable that is thrown.
	 *
	 * @return Throwable that is thrown (returned as-is).
	 */
	public  T throwing( final int logLevel, final String methodName, final T throwable )
	{
		if ( isLevelEnabled( logLevel ) )
		{
			final Class thowableClass = throwable.getClass();

			final StringBuffer sb = new StringBuffer();
			sb.append( "!!! " );
			appendClassName( sb, getName() );
			sb.append( '.' );
			sb.append( methodName );
			sb.append( " threw " );
			appendClassName( sb, thowableClass.getName() );
			sb.append( "( \"" );
			sb.append( throwable.getMessage() );
			sb.append( "\" )" );
			log( logLevel, sb.toString(), throwable );
		}

		return throwable;
	}

	/**
	 * Append object value to the string buffer. This method tries to provide
	 * the best suitable string representation of the object (e.g. recognizing
	 * arrays, primative types, strings, etc).
	 *
	 * @param sb    String buffer to append to.
	 * @param value Value to print.
	 */
	private static void appendValue( final StringBuffer sb, final Object value )
	{
		if ( value == null )
		{
			sb.append( "null" );
		}
		else if ( value instanceof String )
		{
			sb.append( '"' );
			sb.append( (String)value );
			sb.append( '"' );
		}
		else if ( ( value instanceof Boolean )
		          || ( value instanceof Number ) )
		{
			sb.append( value );
		}
		else if ( value instanceof Enum )
		{
			final Enum enumValue = (Enum)value;
			final Class enumClass = enumValue.getDeclaringClass();
			sb.append( enumClass.getName() );
			sb.append( '.' );
			sb.append( enumValue.name() );
		}
		else
		{
			final Class argClass = value.getClass();
			if ( argClass.isPrimitive() )
			{
				sb.append( value );
			}
			else
			{
				if ( argClass.isArray() )
				{
					appendClassName( sb, argClass.getComponentType() );
					sb.append( "[ " );
					sb.append( Array.getLength( value ) );
					sb.append( " ]" );
				}
				else
				{
					appendClassName( sb, argClass );
					if ( value instanceof Collection )
					{
						sb.append( "(size=" );
						sb.append( ( (Collection)value ).size() );
						sb.append( ')' );
					}
				}
			}
		}
	}

	/**
	 * Append name of the specified class to the string buffer. Note that only
	 * the class name (not the package name) is included. Arrays are handled by
	 * appending '[]' for each dimension.
	 *
	 * @param sb     String buffer to append to.
	 * @param aClass Class whose name should be appended.
	 */
	private static void appendClassName( final StringBuffer sb, final Class aClass )
	{
		if ( aClass.isArray() )
		{
			appendClassName( sb, aClass.getComponentType() );
			sb.append( "[]" );
		}
		else
		{
			appendClassName( sb, aClass.getName() );
		}
	}

	/**
	 * Append the class name from the specified fully qualified class name to
	 * the string buffer. The package name is stripped from the class name.
	 *
	 * @param sb            String buffer to append to.
	 * @param fullClassName Fully qualified class name whose name to append.
	 */
	private static void appendClassName( final StringBuffer sb, final String fullClassName )
	{
		final int len = fullClassName.length();
		int pos = fullClassName.lastIndexOf( (int)'.' ) + 1;
		if ( pos <= 0 || pos == len )
		{
			sb.append( fullClassName );
		}
		else
		{
			sb.ensureCapacity( sb.length() + len - pos );
			for ( ; pos < len; pos++ )
			{
				sb.append( fullClassName.charAt( pos ) );
			}
		}
	}

	/**
	 * This task sends a log messages to all registred log targets.
	 */
	private static class LogTask
	implements Runnable
	{
		/**
		 * Name of log.
		 */
		private final String _name;

		/**
		 * Log level.
		 */
		private final int _level;

		/**
		 * Log message.
		 */
		private final String _message;

		/**
		 * Throwable associated with log message.
		 */
		private final Throwable _throwable;

		/**
		 * Identifies the thread that produced the log message.
		 */
		private final String _threadName;

		/**
		 * Create task.
		 *
		 * @param name       Name of log.
		 * @param level      Log level.
		 * @param message    Log message.
		 * @param throwable  Throwable associated with log message.
		 * @param threadName Identifies the thread that produced the log
		 *                   message.
		 */
		private LogTask( final String name, final int level, final String message, final Throwable throwable, final String threadName )
		{
			_name = name;
			_level = level;
			_message = message;
			_throwable = throwable;
			_threadName = threadName;
		}

		@Override
		public void run()
		{
			synchronized ( LOG_TARGETS )
			{
				for ( final LogTarget target : LOG_TARGETS )
				{
					target.log( _name, _level, _message, _throwable, _threadName );
				}
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy