diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
commit | adc854b798c1cfe3bfd4c27d68d5cee38ca617da (patch) | |
tree | 6aed8b4923ca428942cbaa7e848d50237a3d31e0 /logging/src/main/java | |
parent | 1c0fed63c71ddb230f3b304aac12caffbedf2f21 (diff) | |
download | libcore-adc854b798c1cfe3bfd4c27d68d5cee38ca617da.zip libcore-adc854b798c1cfe3bfd4c27d68d5cee38ca617da.tar.gz libcore-adc854b798c1cfe3bfd4c27d68d5cee38ca617da.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'logging/src/main/java')
21 files changed, 6152 insertions, 0 deletions
diff --git a/logging/src/main/java/java/util/logging/ConsoleHandler.java b/logging/src/main/java/java/util/logging/ConsoleHandler.java new file mode 100644 index 0000000..a88cf0c --- /dev/null +++ b/logging/src/main/java/java/util/logging/ConsoleHandler.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.logging; + +/** + * A handler that writes log messages to the standard output stream + * {@code System.err}. + * <p> + * This handler reads the following properties from the log manager to + * initialize itself: + * <ul> + * <li>java.util.logging.ConsoleHandler.level specifies the logging level, + * defaults to {@code Level.INFO} if this property is not found or has an + * invalid value. + * <li>java.util.logging.ConsoleHandler.filter specifies the name of the filter + * class to be associated with this handler, defaults to {@code null} if this + * property is not found or has an invalid value. + * <li>java.util.logging.ConsoleHandler.formatter specifies the name of the + * formatter class to be associated with this handler, defaults to + * {@code java.util.logging.SimpleFormatter} if this property is not found or + * has an invalid value. + * <li>java.util.logging.ConsoleHandler.encoding specifies the encoding this + * handler will use to encode log messages, defaults to {@code null} if this + * property is not found or has an invalid value. + * </ul> + * </p> + * <p> + * This class is not thread-safe. + * </p> + * + * @since Android 1.0 + */ +public class ConsoleHandler extends StreamHandler { + + /** + * Constructs a {@code ConsoleHandler} object. + * + * @since Android 1.0 + */ + public ConsoleHandler() { + super(System.err); + } + + /** + * Closes this handler. The {@code System.err} is flushed but not closed. + * + * @since Android 1.0 + */ + @Override + public void close() { + super.close(false); + } + + /** + * Logs a record if necessary. A flush operation will be done. + * + * @param record + * the log record to be logged. + * + * @since Android 1.0 + */ + @Override + public void publish(LogRecord record) { + super.publish(record); + super.flush(); + + } +} diff --git a/logging/src/main/java/java/util/logging/ErrorManager.java b/logging/src/main/java/java/util/logging/ErrorManager.java new file mode 100644 index 0000000..6f5084c --- /dev/null +++ b/logging/src/main/java/java/util/logging/ErrorManager.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.logging; + +import org.apache.harmony.logging.internal.nls.Messages; + +/** + * An error reporting facility for {@link Handler} implementations to record any + * error that may happen during logging. {@code Handlers} should report errors + * to an {@code ErrorManager}, instead of throwing exceptions, which would + * interfere with the log issuer's execution. + * + * @since Android 1.0 + */ +public class ErrorManager { + + /** + * The error code indicating a failure that does not fit in any of the + * specific types of failures that follow. + * + * @since Android 1.0 + */ + public static final int GENERIC_FAILURE = 0; + + /** + * The error code indicating a failure when writing to an output stream. + * + * @since Android 1.0 + */ + public static final int WRITE_FAILURE = 1; + + /** + * The error code indicating a failure when flushing an output stream. + * + * @since Android 1.0 + */ + public static final int FLUSH_FAILURE = 2; + + /** + * The error code indicating a failure when closing an output stream. + * + * @since Android 1.0 + */ + public static final int CLOSE_FAILURE = 3; + + /** + * The error code indicating a failure when opening an output stream. + * + * @since Android 1.0 + */ + public static final int OPEN_FAILURE = 4; + + /** + * The error code indicating a failure when formatting the error messages. + * + * @since Android 1.0 + */ + public static final int FORMAT_FAILURE = 5; + + @SuppressWarnings("nls") + private static final String[] FAILURES = new String[] { "GENERIC_FAILURE", + "WRITE_FAILURE", "FLUSH_FAILURE", "CLOSE_FAILURE", "OPEN_FAILURE", + "FORMAT_FAILURE" }; + + /** + * An indicator for determining if the error manager has been called at + * least once before. + */ + private boolean called; + + /** + * Constructs an instance of {@code ErrorManager}. + * + * @since Android 1.0 + */ + public ErrorManager() { + super(); + } + + /** + * <p> + * Reports an error using the given message, exception and error code. This + * implementation will write out the message to {@link System#err} on the + * first call and all subsequent calls are ignored. A subclass of this class + * should override this method. + * </p> + * + * @param message + * the error message, which may be {@code null}. + * @param exception + * the exception associated with the error, which may be + * {@code null}. + * @param errorCode + * the error code that identifies the type of error; see the + * constant fields of this class for possible values. + * + * @since Android 1.0 + */ + public void error(String message, Exception exception, int errorCode) { + synchronized (this) { + if (called) { + return; + } + called = true; + } + System.err.println(this.getClass().getName() + + ": " + FAILURES[errorCode]); //$NON-NLS-1$ + if (message != null) { + // logging.1E=Error message - {0} + System.err.println(Messages.getString("logging.1E", message)); //$NON-NLS-1$ + } + if (exception != null) { + // logging.1F=Exception - {0} + System.err.println(Messages.getString("logging.1F", exception)); //$NON-NLS-1$ + } + } +} diff --git a/logging/src/main/java/java/util/logging/FileHandler.java b/logging/src/main/java/java/util/logging/FileHandler.java new file mode 100644 index 0000000..af71a6d --- /dev/null +++ b/logging/src/main/java/java/util/logging/FileHandler.java @@ -0,0 +1,655 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.logging; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Hashtable; + +import org.apache.harmony.logging.internal.nls.Messages; + +/** + * A {@code FileHandler} writes logging records into a specified file or a + * rotating set of files. + * <p> + * When a set of files is used and a given amount of data has been written to + * one file, then this file is closed and another file is opened. The name of + * these files are generated by given name pattern, see below for details. + * </p> + * <p> + * By default, the I/O buffering mechanism is enabled, but when each log record + * is complete, it is flushed out. + * </p> + * <p> + * {@code XMLFormatter} is the default formatter for {@code FileHandler}. + * </p> + * <p> + * {@code FileHandler} reads the following {@code LogManager} properties for + * initialization; if a property is not defined or has an invalid value, a + * default value is used. + * <ul> + * <li>java.util.logging.FileHandler.level specifies the level for this + * {@code Handler}, defaults to {@code Level.ALL}.</li> + * <li>java.util.logging.FileHandler.filter specifies the {@code Filter} class + * name, defaults to no {@code Filter}.</li> + * <li>java.util.logging.FileHandler.formatter specifies the {@code Formatter} + * class, defaults to {@code java.util.logging.XMLFormatter}.</li> + * <li>java.util.logging.FileHandler.encoding specifies the character set + * encoding name, defaults to the default platform encoding.</li> + * <li>java.util.logging.FileHandler.limit specifies the maximum number of + * bytes to write to any one file, defaults to zero, which means no limit.</li> + * <li>java.util.logging.FileHandler.count specifies how many output files to + * rotate, defaults to 1.</li> + * <li>java.util.logging.FileHandler.pattern specifies name pattern for the + * output files. See below for details. Defaults to "%h/java%u.log".</li> + * <li>java.util.logging.FileHandler.append specifies whether this + * {@code FileHandler} should append onto existing files, defaults to + * {@code false}.</li> + * </ul> + * </p> + * <p> + * Name pattern is a string that may include some special substrings, which will + * be replaced to generate output files: + * </p> + * <ul> + * <li>"/" represents the local pathname separator</li> + * <li>"%t" represents the system's temporary directory</li> + * <li>"%h" represents the home directory of the current user, which is + * specified by "user.home" system property</li> + * <li>"%g" represents the generation number to distinguish rotated logs</li> + * <li>"%u" represents a unique number to resolve conflicts</li> + * <li>"%%" represents the percent sign character '%'</li> + * </ul> + * <p> + * Normally, the generation numbers are not larger than the given file count and + * follow the sequence 0, 1, 2.... If the file count is larger than one, but the + * generation field("%g") has not been specified in the pattern, then the + * generation number after a dot will be added to the end of the file name. + * </p> + * <p> + * The "%u" unique field is used to avoid conflicts and is set to 0 at first. If + * one {@code FileHandler} tries to open the filename which is currently in use + * by another process, it will repeatedly increment the unique number field and + * try again. If the "%u" component has not been included in the file name + * pattern and some contention on a file does occur, then a unique numerical + * value will be added to the end of the filename in question immediately to the + * right of a dot. The generation of unique IDs for avoiding conflicts is only + * guaranteed to work reliably when using a local disk file system. + * </p> + * @since Android 1.0 + */ +public class FileHandler extends StreamHandler { + + private static final String LCK_EXT = ".lck"; //$NON-NLS-1$ + + private static final int DEFAULT_COUNT = 1; + + private static final int DEFAULT_LIMIT = 0; + + private static final boolean DEFAULT_APPEND = false; + + private static final String DEFAULT_PATTERN = "%h/java%u.log"; //$NON-NLS-1$ + + // maintain all file locks hold by this process + private static final Hashtable<String, FileLock> allLocks = new Hashtable<String, FileLock>(); + + // the count of files which the output cycle through + private int count; + + // the size limitation in byte of log file + private int limit; + + // whether the FileHandler should open a existing file for output in append + // mode + private boolean append; + + // the pattern for output file name + private String pattern; + + // maintain a LogManager instance for convenience + private LogManager manager; + + // output stream, which can measure the output file length + private MeasureOutputStream output; + + // used output file + private File[] files; + + // output file lock + FileLock lock = null; + + // current output file name + String fileName = null; + + // current unique ID + int uniqueID = -1; + + /** + * Construct a {@code FileHandler} using {@code LogManager} properties or + * their default value. + * + * @throws IOException + * if any I/O error occurs. + * @throws SecurityException + * if a security manager exists and it determines that the + * caller does not have the required permissions to control this + * handler; required permissions include + * {@code LogPermission("control")}, + * {@code FilePermission("write")} etc. + * @since Android 1.0 + */ + public FileHandler() throws IOException { + init(null, null, null, null); + } + + // init properties + private void init(String p, Boolean a, Integer l, Integer c) + throws IOException { + // check access + manager = LogManager.getLogManager(); + manager.checkAccess(); + initProperties(p, a, l, c); + initOutputFiles(); + } + + private void initOutputFiles() throws FileNotFoundException, IOException { + while (true) { + // try to find a unique file which is not locked by other process + uniqueID++; + // FIXME: improve performance here + for (int generation = 0; generation < count; generation++) { + // cache all file names for rotation use + files[generation] = new File(parseFileName(generation)); + } + fileName = files[0].getAbsolutePath(); + synchronized (allLocks) { + /* + * if current process has held lock for this fileName continue + * to find next file + */ + if (null != allLocks.get(fileName)) { + continue; + } + if (files[0].exists() + && (!append || files[0].length() >= limit)) { + for (int i = count - 1; i > 0; i--) { + if (files[i].exists()) { + files[i].delete(); + } + files[i - 1].renameTo(files[i]); + } + } + FileOutputStream fileStream = new FileOutputStream(fileName + + LCK_EXT); + FileChannel channel = fileStream.getChannel(); + /* + * if lock is unsupported and IOException thrown, just let the + * IOException throws out and exit otherwise it will go into an + * undead cycle + */ + lock = channel.tryLock(); + if (null == lock) { + try { + fileStream.close(); + } catch (Exception e) { + // ignore + } + continue; + } + allLocks.put(fileName, lock); + break; + } + } + // BEGIN android-modified + output = new MeasureOutputStream( + new BufferedOutputStream( + new FileOutputStream(fileName, append), 8192), + files[0].length()); + // END android-modified + setOutputStream(output); + } + + private void initProperties(String p, Boolean a, Integer l, Integer c) { + super.initProperties("ALL", null, "java.util.logging.XMLFormatter", //$NON-NLS-1$//$NON-NLS-2$ + null); + String className = this.getClass().getName(); + pattern = (null == p) ? getStringProperty(className + ".pattern", //$NON-NLS-1$ + DEFAULT_PATTERN) : p; + if (null == pattern || "".equals(pattern)) { //$NON-NLS-1$ + // logging.19=Pattern cannot be empty + throw new NullPointerException(Messages.getString("logging.19")); //$NON-NLS-1$ + } + append = (null == a) ? getBooleanProperty(className + ".append", //$NON-NLS-1$ + DEFAULT_APPEND) : a.booleanValue(); + count = (null == c) ? getIntProperty(className + ".count", //$NON-NLS-1$ + DEFAULT_COUNT) : c.intValue(); + limit = (null == l) ? getIntProperty(className + ".limit", //$NON-NLS-1$ + DEFAULT_LIMIT) : l.intValue(); + count = count < 1 ? DEFAULT_COUNT : count; + limit = limit < 0 ? DEFAULT_LIMIT : limit; + files = new File[count]; + } + + void findNextGeneration() { + super.close(); + for (int i = count - 1; i > 0; i--) { + if (files[i].exists()) { + files[i].delete(); + } + files[i - 1].renameTo(files[i]); + } + try { + // BEGIN android-modified + output = new MeasureOutputStream( + new BufferedOutputStream( + new FileOutputStream(files[0]), + 8192)); + // END android-modified + } catch (FileNotFoundException e1) { + // logging.1A=Error happened when open log file. + this.getErrorManager().error(Messages.getString("logging.1A"), //$NON-NLS-1$ + e1, ErrorManager.OPEN_FAILURE); + } + setOutputStream(output); + } + + /** + * Transform the pattern to the valid file name, replacing any patterns, and + * applying generation and uniqueID if present. + * + * @param gen + * generation of this file + * @return transformed filename ready for use. + */ + private String parseFileName(int gen) { + int cur = 0; + int next = 0; + boolean hasUniqueID = false; + boolean hasGeneration = false; + + // TODO privilege code? + + String tempPath = System.getProperty("java.io.tmpdir"); //$NON-NLS-1$ + boolean tempPathHasSepEnd = (tempPath == null ? false : tempPath + .endsWith(File.separator)); + + String homePath = System.getProperty("user.home"); //$NON-NLS-1$ + boolean homePathHasSepEnd = (homePath == null ? false : homePath + .endsWith(File.separator)); + + StringBuilder sb = new StringBuilder(); + pattern = pattern.replace('/', File.separatorChar); + + char[] value = pattern.toCharArray(); + while ((next = pattern.indexOf('%', cur)) >= 0) { + if (++next < pattern.length()) { + switch (value[next]) { + case 'g': + sb.append(value, cur, next - cur - 1).append(gen); + hasGeneration = true; + break; + case 'u': + sb.append(value, cur, next - cur - 1).append(uniqueID); + hasUniqueID = true; + break; + case 't': + /* + * we should probably try to do something cute here like + * lookahead for adjacent '/' + */ + sb.append(value, cur, next - cur - 1).append(tempPath); + if (!tempPathHasSepEnd) { + sb.append(File.separator); + } + break; + case 'h': + sb.append(value, cur, next - cur - 1).append(homePath); + if (!homePathHasSepEnd) { + sb.append(File.separator); + } + break; + case '%': + sb.append(value, cur, next - cur - 1).append('%'); + break; + default: + sb.append(value, cur, next - cur); + } + cur = ++next; + } else { + // fail silently + } + } + + sb.append(value, cur, value.length - cur); + + if (!hasGeneration && count > 1) { + sb.append(".").append(gen); //$NON-NLS-1$ + } + + if (!hasUniqueID && uniqueID > 0) { + sb.append(".").append(uniqueID); //$NON-NLS-1$ + } + + return sb.toString(); + } + + // get boolean LogManager property, if invalid value got, using default + // value + private boolean getBooleanProperty(String key, boolean defaultValue) { + String property = manager.getProperty(key); + if (null == property) { + return defaultValue; + } + boolean result = defaultValue; + if ("true".equalsIgnoreCase(property)) { //$NON-NLS-1$ + result = true; + } else if ("false".equalsIgnoreCase(property)) { //$NON-NLS-1$ + result = false; + } + return result; + } + + // get String LogManager property, if invalid value got, using default value + private String getStringProperty(String key, String defaultValue) { + String property = manager.getProperty(key); + return property == null ? defaultValue : property; + } + + // get int LogManager property, if invalid value got, using default value + private int getIntProperty(String key, int defaultValue) { + String property = manager.getProperty(key); + int result = defaultValue; + if (null != property) { + try { + result = Integer.parseInt(property); + } catch (Exception e) { + // ignore + } + } + return result; + } + + /** + * Constructs a new {@code FileHandler}. The given name pattern is used as + * output filename, the file limit is set to zero (no limit), the file count + * is set to one; the remaining configuration is done using + * {@code LogManager} properties or their default values. This handler write + * to only one file without size limit. + * + * @param pattern + * the name pattern for the output file. + * @throws IOException + * if any I/O error occurs. + * @throws SecurityException + * if a security manager exists and it determines that the + * caller does not have the required permissions to control this + * handler; required permissions include + * {@code LogPermission("control")}, + * {@code FilePermission("write")} etc. + * @throws IllegalArgumentException + * if the pattern is empty. + * @throws NullPointerException + * if the pattern is {@code null}. + * @since Android 1.0 + */ + public FileHandler(String pattern) throws IOException { + if (pattern.equals("")) { //$NON-NLS-1$ + // logging.19=Pattern cannot be empty + throw new IllegalArgumentException(Messages.getString("logging.19")); //$NON-NLS-1$ + } + init(pattern, null, Integer.valueOf(DEFAULT_LIMIT), Integer + .valueOf(DEFAULT_COUNT)); + } + + /** + * Construct a new {@code FileHandler}. The given name pattern is used as + * output filename, the file limit is set to zero (no limit), the file count + * is initialized to one and the value of {@code append} becomes the new + * instance's append mode. The remaining configuration is done using + * {@code LogManager} properties. This handler write to only one file + * without size limit. + * + * @param pattern + * the name pattern for the output file. + * @param append + * the append mode. + * @throws IOException + * if any I/O error occurs. + * @throws SecurityException + * if a security manager exists and it determines that the + * caller does not have the required permissions to control this + * handler; required permissions include + * {@code LogPermission("control")}, + * {@code FilePermission("write")} etc. + * @throws IllegalArgumentException + * if {@code pattern} is empty. + * @throws NullPointerException + * if {@code pattern} is {@code null}. + * @since Android 1.0 + */ + public FileHandler(String pattern, boolean append) throws IOException { + if (pattern.equals("")) { //$NON-NLS-1$ + throw new IllegalArgumentException(Messages.getString("logging.19")); //$NON-NLS-1$ + } + + init(pattern, Boolean.valueOf(append), Integer.valueOf(DEFAULT_LIMIT), + Integer.valueOf(DEFAULT_COUNT)); + } + + /** + * Construct a new {@code FileHandler}. The given name pattern is used as + * output filename, the maximum file size is set to {@code limit} and the + * file count is initialized to {@code count}. The remaining configuration + * is done using {@code LogManager} properties. This handler is configured + * to write to a rotating set of count files, when the limit of bytes has + * been written to one output file, another file will be opened instead. + * + * @param pattern + * the name pattern for the output file. + * @param limit + * the data amount limit in bytes of one output file, can not be + * negative. + * @param count + * the maximum number of files to use, can not be less than one. + * @throws IOException + * if any I/O error occurs. + * @throws SecurityException + * if a security manager exists and it determines that the + * caller does not have the required permissions to control this + * handler; required permissions include + * {@code LogPermission("control")}, + * {@code FilePermission("write")} etc. + * @throws IllegalArgumentException + * if {@code pattern} is empty, {@code limit < 0} or + * {@code count < 1}. + * @throws NullPointerException + * if {@code pattern} is {@code null}. + * @since Android 1.0 + */ + public FileHandler(String pattern, int limit, int count) throws IOException { + if (pattern.equals("")) { //$NON-NLS-1$ + throw new IllegalArgumentException(Messages.getString("logging.19")); //$NON-NLS-1$ + } + if (limit < 0 || count < 1) { + // logging.1B=The limit and count property must be larger than 0 and + // 1, respectively + throw new IllegalArgumentException(Messages.getString("logging.1B")); //$NON-NLS-1$ + } + init(pattern, null, Integer.valueOf(limit), Integer.valueOf(count)); + } + + /** + * Construct a new {@code FileHandler}. The given name pattern is used as + * output filename, the maximum file size is set to {@code limit}, the file + * count is initialized to {@code count} and the append mode is set to + * {@code append}. The remaining configuration is done using + * {@code LogManager} properties. This handler is configured to write to a + * rotating set of count files, when the limit of bytes has been written to + * one output file, another file will be opened instead. + * + * @param pattern + * the name pattern for the output file. + * @param limit + * the data amount limit in bytes of one output file, can not be + * negative. + * @param count + * the maximum number of files to use, can not be less than one. + * @param append + * the append mode. + * @throws IOException + * if any I/O error occurs. + * @throws SecurityException + * if a security manager exists and it determines that the + * caller does not have the required permissions to control this + * handler; required permissions include + * {@code LogPermission("control")}, + * {@code FilePermission("write")} etc. + * @throws IllegalArgumentException + * if {@code pattern} is empty, {@code limit < 0} or + * {@code count < 1}. + * @throws NullPointerException + * if {@code pattern} is {@code null}. + * @since Android 1.0 + */ + public FileHandler(String pattern, int limit, int count, boolean append) + throws IOException { + if (pattern.equals("")) { //$NON-NLS-1$ + throw new IllegalArgumentException(Messages.getString("logging.19")); //$NON-NLS-1$ + } + if (limit < 0 || count < 1) { + // logging.1B=The limit and count property must be larger than 0 and + // 1, respectively + throw new IllegalArgumentException(Messages.getString("logging.1B")); //$NON-NLS-1$ + } + init(pattern, Boolean.valueOf(append), Integer.valueOf(limit), Integer + .valueOf(count)); + } + + /** + * Flushes and closes all opened files. + * + * @throws SecurityException + * if a security manager exists and it determines that the + * caller does not have the required permissions to control this + * handler; required permissions include + * {@code LogPermission("control")}, + * {@code FilePermission("write")} etc. + * @since Android 1.0 + */ + @Override + public void close() { + // release locks + super.close(); + allLocks.remove(fileName); + try { + FileChannel channel = lock.channel(); + lock.release(); + channel.close(); + File file = new File(fileName + LCK_EXT); + file.delete(); + } catch (IOException e) { + // ignore + } + } + + /** + * Publish a {@code LogRecord}. + * + * @param record + * the log record to publish. + * @since Android 1.0 + */ + @Override + public void publish(LogRecord record) { + super.publish(record); + flush(); + if (limit > 0 && output.getLength() >= limit) { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + findNextGeneration(); + return null; + } + }); + } + } + + /** + * This output stream uses the decorator pattern to add measurement features + * to OutputStream which can detect the total size(in bytes) of output, the + * initial size can be set. + */ + static class MeasureOutputStream extends OutputStream { + + OutputStream wrapped; + + long length; + + public MeasureOutputStream(OutputStream stream, long currentLength) { + wrapped = stream; + length = currentLength; + } + + public MeasureOutputStream(OutputStream stream) { + this(stream, 0); + } + + @Override + public void write(int oneByte) throws IOException { + wrapped.write(oneByte); + length++; + } + + @Override + public void write(byte[] bytes) throws IOException { + wrapped.write(bytes); + length += bytes.length; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + wrapped.write(b, off, len); + length += len; + } + + @Override + public void close() throws IOException { + wrapped.close(); + } + + @Override + public void flush() throws IOException { + wrapped.flush(); + } + + public long getLength() { + return length; + } + + public void setLength(long newLength) { + length = newLength; + } + } +} diff --git a/logging/src/main/java/java/util/logging/Filter.java b/logging/src/main/java/java/util/logging/Filter.java new file mode 100644 index 0000000..e81f216 --- /dev/null +++ b/logging/src/main/java/java/util/logging/Filter.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.logging; + +/** + * A {@code Filter} provides a mechanism for exercising fine-grained control + * over which records get logged. + * + * @since Android 1.0 + */ +public interface Filter { + + /** + * Checks {@code record} to determine if it should be logged. + * + * @param record + * the {@link LogRecord} to be checked. + * @return {@code true} if the supplied log record needs to be logged, + * {@code false} otherwise. + * @since Android 1.0 + */ + boolean isLoggable(LogRecord record); +} diff --git a/logging/src/main/java/java/util/logging/Formatter.java b/logging/src/main/java/java/util/logging/Formatter.java new file mode 100644 index 0000000..2941c24 --- /dev/null +++ b/logging/src/main/java/java/util/logging/Formatter.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package java.util.logging; + +import java.text.MessageFormat; +import java.util.ResourceBundle; + +/** + * {@code Formatter} objects are used to format {@link LogRecord} objects into a + * string representation. Head and tail strings are sometimes used to wrap a set + * of records. The {@code getHead} and {@code getTail} methods are used for this + * purpose. + * + * @since Android 1.0 + */ +public abstract class Formatter { + + /* + * ------------------------------------------------------------------- + * Constructors + * ------------------------------------------------------------------- + */ + + /** + * Constructs a {@code Formatter} object. + * + * @since Android 1.0 + */ + protected Formatter() { + super(); + } + + /* + * ------------------------------------------------------------------- + * Methods + * ------------------------------------------------------------------- + */ + + /** + * Converts a {@link LogRecord} object into a string representation. The + * resulted string is usually localized and includes the message field of + * the record. + * + * @param r + * the log record to be formatted into a string. + * @return the formatted string. + * @since Android 1.0 + */ + public abstract String format(LogRecord r); + + /** + * Formats a {@code LogRecord} object into a localized string + * representation. This is a convenience method for subclasses of {@code + * Formatter}. + * <p> + * The message string is firstly localized using the {@code ResourceBundle} + * object associated with the supplied {@code LogRecord}. + * </p> + * <p> + * Notice : if message contains "{0", then java.text.MessageFormat is used. + * Otherwise no formatting is performed. + * </p> + * + * @param r + * the log record to be formatted. + * @return the string resulted from the formatting. + * @since Android 1.0 + */ + public String formatMessage(LogRecord r) { + String pattern = r.getMessage(); + ResourceBundle rb = null; + // try to localize the message string first + if (null != (rb = r.getResourceBundle())) { + try { + pattern = rb.getString(pattern); + } catch (Exception e) { + pattern = r.getMessage(); + } + } + if (null != pattern) { + Object[] params = r.getParameters(); + /* + * if the message contains "{0", use java.text.MessageFormat to + * format the string + */ + if (pattern.indexOf("{0") >= 0 && null != params //$NON-NLS-1$ + && params.length > 0) { + try { + pattern = MessageFormat.format(pattern, params); + } catch (IllegalArgumentException e) { + pattern = r.getMessage(); + } + } + } + return pattern; + } + + /** + * Gets the head string used to wrap a set of log records. This base class + * always returns an empty string. + * + * @param h + * the target handler. + * @return the head string used to wrap a set of log records, empty in this + * implementation. + * @since Android 1.0 + */ + @SuppressWarnings("unused") + public String getHead(Handler h) { + return ""; //$NON-NLS-1$ + } + + /** + * Gets the tail string used to wrap a set of log records. This base class + * always returns the empty string. + * + * @param h + * the target handler. + * @return the tail string used to wrap a set of log records, empty in this + * implementation. + * @since Android 1.0 + */ + @SuppressWarnings("unused") + public String getTail(Handler h) { + return ""; //$NON-NLS-1$ + } + +} + diff --git a/logging/src/main/java/java/util/logging/Handler.java b/logging/src/main/java/java/util/logging/Handler.java new file mode 100644 index 0000000..d28bce0 --- /dev/null +++ b/logging/src/main/java/java/util/logging/Handler.java @@ -0,0 +1,453 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package java.util.logging; + +import java.nio.charset.Charset; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.io.UnsupportedEncodingException; + +import org.apache.harmony.logging.internal.nls.Messages; + +/** + * A {@code Handler} object accepts a logging request and exports the desired + * messages to a target, for example, a file, the console, etc. It can be + * disabled by setting its logging level to {@code Level.OFF}. + * + * @since Android 1.0 + */ +public abstract class Handler { + + /* + * ------------------------------------------------------------------- + * Constants + * ------------------------------------------------------------------- + */ + private static final Level DEFAULT_LEVEL = Level.ALL; + + /* + * ------------------------------------------------------------------- + * Instance variables + * ------------------------------------------------------------------- + */ + + // the error manager to report errors during logging + private ErrorManager errorMan; + + // the character encoding used by this handler + private String encoding; + + // the logging level + private Level level; + + // the formatter used to export messages + private Formatter formatter; + + // the filter used to filter undesired messages + private Filter filter; + + // class name, used for property reading + private String prefix; + + /* + * ------------------------------------------------------------------- + * Constructors + * ------------------------------------------------------------------- + */ + + /** + * Constructs a {@code Handler} object with a default error manager instance + * {@code ErrorManager}, the default encoding, and the default logging + * level {@code Level.ALL}. It has no filter and no formatter. + * + * @since Android 1.0 + */ + protected Handler() { + this.errorMan = new ErrorManager(); + this.level = DEFAULT_LEVEL; + this.encoding = null; + this.filter = null; + this.formatter = null; + this.prefix = this.getClass().getName(); + } + + /* + * ------------------------------------------------------------------- + * Methods + * ------------------------------------------------------------------- + */ + + // get a instance from given class name, using Class.forName() + private Object getDefaultInstance(String className) { + Object result = null; + if (null == className) { + return result; + } + try { + result = Class.forName(className).newInstance(); + } catch (Exception e) { + //ignore + } + return result; + } + + // get a instance from given class name, using context classloader + private Object getCustomizeInstance(final String className) + throws Exception { + Class<?> c = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() { + public Class<?> run() throws Exception { + ClassLoader loader = Thread.currentThread() + .getContextClassLoader(); + if (null == loader) { + loader = ClassLoader.getSystemClassLoader(); + } + return loader.loadClass(className); + } + }); + return c.newInstance(); + } + + // print error message in some format + void printInvalidPropMessage(String key, String value, Exception e) { + // logging.12=Invalid property value for + String msg = new StringBuilder().append(Messages.getString("logging.12")) //$NON-NLS-1$ + .append(prefix).append(":").append(key).append("/").append( //$NON-NLS-1$//$NON-NLS-2$ + value).toString(); + errorMan.error(msg, e, ErrorManager.GENERIC_FAILURE); + } + + /* + * init the common properties, including filter, level, formatter, and + * encoding + */ + @SuppressWarnings("unused") + void initProperties(String defaultLevel, String defaultFilter, + String defaultFormatter, String defaultEncoding) { + LogManager manager = LogManager.getLogManager(); + + //set filter + final String filterName = manager.getProperty(prefix + ".filter"); //$NON-NLS-1$ + if (null != filterName) { + try { + filter = (Filter) getCustomizeInstance(filterName); + } catch (Exception e1) { + printInvalidPropMessage("filter", filterName, e1); //$NON-NLS-1$ + filter = (Filter) getDefaultInstance(defaultFilter); + } + } else { + filter = (Filter) getDefaultInstance(defaultFilter); + } + + //set level + String levelName = manager.getProperty(prefix + ".level"); //$NON-NLS-1$ + if (null != levelName) { + try { + level = Level.parse(levelName); + } catch (Exception e) { + printInvalidPropMessage("level", levelName, e); //$NON-NLS-1$ + level = Level.parse(defaultLevel); + } + } else { + level = Level.parse(defaultLevel); + } + + //set formatter + final String formatterName = manager.getProperty(prefix + ".formatter"); //$NON-NLS-1$ + if (null != formatterName) { + try { + formatter = (Formatter) getCustomizeInstance(formatterName); + } catch (Exception e) { + printInvalidPropMessage("formatter", formatterName, e); //$NON-NLS-1$ + formatter = (Formatter) getDefaultInstance(defaultFormatter); + } + } else { + formatter = (Formatter) getDefaultInstance(defaultFormatter); + } + + //set encoding + final String encodingName = manager.getProperty(prefix + ".encoding"); //$NON-NLS-1$ + try { + internalSetEncoding(encodingName); + } catch (UnsupportedEncodingException e) { + printInvalidPropMessage("encoding", encodingName, e); //$NON-NLS-1$ + } + } + + /** + * Closes this handler. A flush operation will be performed and all the + * associated resources will be freed. Client applications should not use + * this handler after closing it. + * + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * + * @since Android 1.0 + */ + public abstract void close(); + + /** + * Flushes any buffered output. + * + * @since Android 1.0 + */ + public abstract void flush(); + + /** + * Accepts a logging request and sends it to the the target. + * + * @param record + * the log record to be logged; {@code null} records are ignored. + * @since Android 1.0 + */ + public abstract void publish(LogRecord record); + + /** + * Gets the character encoding used by this handler, {@code null} for + * default encoding. + * + * @return the character encoding used by this handler. + * @since Android 1.0 + */ + public String getEncoding() { + return this.encoding; + } + + /** + * Gets the error manager used by this handler to report errors during + * logging. + * + * @return the error manager used by this handler. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public ErrorManager getErrorManager() { + LogManager.getLogManager().checkAccess(); + return this.errorMan; + } + + /** + * Gets the filter used by this handler. + * + * @return the filter used by this handler (possibly {@code null}). + * @since Android 1.0 + */ + public Filter getFilter() { + return this.filter; + } + + /** + * Gets the formatter used by this handler to format the logging messages. + * + * @return the formatter used by this handler (possibly {@code null}). + * @since Android 1.0 + */ + public Formatter getFormatter() { + return this.formatter; + } + + /** + * Gets the logging level of this handler, records with levels lower than + * this value will be dropped. + * + * @return the logging level of this handler. + * @since Android 1.0 + */ + public Level getLevel() { + return this.level; + } + + /** + * Determines whether the supplied log record needs to be logged. The + * logging levels will be checked as well as the filter. + * + * @param record + * the log record to be checked. + * @return {@code true} if the supplied log record needs to be logged, + * otherwise {@code false}. + * @since Android 1.0 + */ + public boolean isLoggable(LogRecord record) { + if (null == record) { + throw new NullPointerException(); + } + if (this.level.intValue() == Level.OFF.intValue()) { + return false; + } else if (record.getLevel().intValue() >= this.level.intValue()) { + return null == this.filter || this.filter.isLoggable(record); + } + return false; + } + + /** + * Reports an error to the error manager associated with this handler, + * {@code ErrorManager} is used for that purpose. No security checks are + * done, therefore this is compatible with environments where the caller + * is non-privileged. + * + * @param msg + * the error message, may be {@code null}. + * @param ex + * the associated exception, may be {@code null}. + * @param code + * an {@code ErrorManager} error code. + * @since Android 1.0 + */ + protected void reportError(String msg, Exception ex, int code) { + this.errorMan.error(msg, ex, code); + } + + /** + * Sets the character encoding used by this handler. A {@code null} value + * indicates the use of the default encoding. This internal method does + * not check security. + * + * @param newEncoding + * the character encoding to set. + * @throws UnsupportedEncodingException + * if the specified encoding is not supported by the runtime. + * @since Android 1.0 + */ + void internalSetEncoding(String newEncoding) + throws UnsupportedEncodingException { + // accepts "null" because it indicates using default encoding + if (null == newEncoding) { + this.encoding = null; + } else { + if (Charset.isSupported(newEncoding)) { + this.encoding = newEncoding; + } else { + // logging.13=The encoding "{0}" is not supported. + throw new UnsupportedEncodingException(Messages.getString( + "logging.13", //$NON-NLS-1$ + newEncoding)); + } + + } + } + + /** + * Sets the character encoding used by this handler, {@code null} indicates + * a default encoding. + * + * @param encoding + * the character encoding to set. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @throws UnsupportedEncodingException + * if the specified encoding is not supported by the runtime. + * @since Android 1.0 + */ + public void setEncoding(String encoding) throws SecurityException, + UnsupportedEncodingException { + LogManager.getLogManager().checkAccess(); + internalSetEncoding(encoding); + } + + /** + * Sets the error manager for this handler. + * + * @param em + * the error manager to set. + * @throws NullPointerException + * if {@code em} is {@code null}. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public void setErrorManager(ErrorManager em) { + LogManager.getLogManager().checkAccess(); + if (null == em) { + throw new NullPointerException(); + } + this.errorMan = em; + } + + /** + * Sets the filter to be used by this handler. + * + * @param newFilter + * the filter to set, may be {@code null}. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public void setFilter(Filter newFilter) { + LogManager.getLogManager().checkAccess(); + this.filter = newFilter; + } + + /** + * Sets the formatter to be used by this handler. This internal method does + * not check security. + * + * @param newFormatter + * the formatter to set. + */ + void internalSetFormatter(Formatter newFormatter) { + if (null == newFormatter) { + throw new NullPointerException(); + } + this.formatter = newFormatter; + } + + /** + * Sets the formatter to be used by this handler. + * + * @param newFormatter + * the formatter to set. + * @throws NullPointerException + * if {@code newFormatter} is {@code null}. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public void setFormatter(Formatter newFormatter) { + LogManager.getLogManager().checkAccess(); + internalSetFormatter(newFormatter); + } + + /** + * Sets the logging level of the messages logged by this handler, levels + * lower than this value will be dropped. + * + * @param newLevel + * the logging level to set. + * @throws NullPointerException + * if {@code newLevel} is {@code null}. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public void setLevel(Level newLevel) { + if (null == newLevel) { + throw new NullPointerException(); + } + LogManager.getLogManager().checkAccess(); + this.level = newLevel; + } +} + diff --git a/logging/src/main/java/java/util/logging/Level.java b/logging/src/main/java/java/util/logging/Level.java new file mode 100644 index 0000000..32ba017 --- /dev/null +++ b/logging/src/main/java/java/util/logging/Level.java @@ -0,0 +1,403 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.logging; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.apache.harmony.logging.internal.nls.Messages; +// BEGIN android-changed +import dalvik.system.VMStack; +// END android-changed + +/** + * {@code Level} objects are used to indicate the level of logging. There are a + * set of predefined logging levels, each associated with an integer value. + * Enabling a certain logging level also enables all logging levels with larger + * values. + * <p> + * The predefined levels in ascending order are FINEST, FINER, FINE, CONFIG, + * INFO, WARNING, SEVERE. There are two additional predefined levels, which are + * ALL and OFF. ALL indicates logging all messages, and OFF indicates logging no + * messages. + * </p> + * @since Android 1.0 + */ +public class Level implements Serializable { + + private static final long serialVersionUID = -8176160795706313070L; + + private static final List<Level> levels = new ArrayList<Level>(9); + + /** + * The OFF level provides no logging messages. + * + * @since Android 1.0 + */ + public static final Level OFF = new Level("OFF", Integer.MAX_VALUE); //$NON-NLS-1$ + + /** + * The SEVERE level provides severe failure messages. + * + * @since Android 1.0 + */ + public static final Level SEVERE = new Level("SEVERE", 1000); //$NON-NLS-1$ + + /** + * The WARNING level provides warnings. + * + * @since Android 1.0 + */ + public static final Level WARNING = new Level("WARNING", 900); //$NON-NLS-1$ + + /** + * The INFO level provides informative messages. + * + * @since Android 1.0 + */ + public static final Level INFO = new Level("INFO", 800); //$NON-NLS-1$ + + /** + * The CONFIG level provides static configuration messages. + * + * @since Android 1.0 + */ + public static final Level CONFIG = new Level("CONFIG", 700); //$NON-NLS-1$ + + /** + * The FINE level provides tracing messages. + * + * @since Android 1.0 + */ + public static final Level FINE = new Level("FINE", 500); //$NON-NLS-1$ + + /** + * The FINER level provides more detailed tracing messages. + * + * @since Android 1.0 + */ + public static final Level FINER = new Level("FINER", 400); //$NON-NLS-1$ + + /** + * The FINEST level provides highly detailed tracing messages. + * + * @since Android 1.0 + */ + public static final Level FINEST = new Level("FINEST", 300); //$NON-NLS-1$ + + /** + * The ALL level provides all logging messages. + * + * @since Android 1.0 + */ + public static final Level ALL = new Level("ALL", Integer.MIN_VALUE); //$NON-NLS-1$ + + /** + * Parses a level name into a {@code Level} object. + * + * @param name + * the name of the desired {@code level}, which cannot be + * {@code null}. + * @return the level with the specified name. + * @throws NullPointerException + * if {@code name} is {@code null}. + * @throws IllegalArgumentException + * if {@code name} is not valid. + * @since Android 1.0 + */ + public static Level parse(String name) throws IllegalArgumentException { + // BEGIN android-note + // final modifier removed and IAE added to get closer to the RI + // copied from newer version of harmony + // END android-note + if (name == null) { + // logging.1C=The 'name' parameter is null. + throw new NullPointerException(Messages.getString("logging.1C")); //$NON-NLS-1$ + } + + boolean isNameAnInt; + int nameAsInt; + try { + nameAsInt = Integer.parseInt(name); + isNameAnInt = true; + } catch (NumberFormatException e) { + nameAsInt = 0; + isNameAnInt = false; + } + + synchronized (levels) { + for (Level level : levels) { + if (name.equals(level.getName())) { + return level; + } + } + + if (isNameAnInt) { + /* + * Loop through levels a second time, so that the + * returned instance will be passed on the order of construction. + */ + for (Level level : levels) { + if (nameAsInt == level.intValue()) { + return level; + } + } + } + } + + if (!isNameAnInt) { + // logging.1D=Cannot parse this name: {0} + throw new IllegalArgumentException(Messages.getString("logging.1D", name)); //$NON-NLS-1$ + } + + return new Level(name, nameAsInt); + } + + /** + * The name of this Level. + * + * @serial + */ + private final String name; + + /** + * The integer value indicating the level. + * + * @serial + */ + private final int value; + + /** + * The name of the resource bundle used to localize the level name. + * + * @serial + */ + private final String resourceBundleName; + + /** + * The resource bundle associated with this level, used to localize the + * level name. + */ + private transient ResourceBundle rb; + + /** + * Constructs an instance of {@code Level} taking the supplied name and + * level value. + * + * @param name + * the name of the level. + * @param level + * an integer value indicating the level. + * @throws NullPointerException + * if {@code name} is {@code null}. + * @since Android 1.0 + */ + protected Level(String name, int level) { + this(name, level, null); + } + + /** + * Constructs an instance of {@code Level} taking the supplied name, level + * value and resource bundle name. + * + * @param name + * the name of the level. + * @param level + * an integer value indicating the level. + * @param resourceBundleName + * the name of the resource bundle to use. + * @throws NullPointerException + * if {@code name} is {@code null}. + * @since Android 1.0 + */ + protected Level(String name, int level, String resourceBundleName) { + if (name == null) { + // logging.1C=The 'name' parameter is null. + throw new NullPointerException(Messages.getString("logging.1C")); //$NON-NLS-1$ + } + this.name = name; + this.value = level; + this.resourceBundleName = resourceBundleName; + if (resourceBundleName != null) { + try { + rb = ResourceBundle.getBundle(resourceBundleName, + // BEGIN android-changed + Locale.getDefault(), VMStack.getCallingClassLoader()); + // BEGIN android-changed + } catch (MissingResourceException e) { + rb = null; + } + } + synchronized (levels) { + levels.add(this); + } + } + + /** + * Gets the name of this level. + * + * @return this level's name. + * @since Android 1.0 + */ + public String getName() { + return this.name; + } + + /** + * Gets the name of the resource bundle associated with this level. + * + * @return the name of this level's resource bundle. + * @since Android 1.0 + */ + public String getResourceBundleName() { + return this.resourceBundleName; + } + + /** + * Gets the integer value indicating this level. + * + * @return this level's integer value. + * @since Android 1.0 + */ + public final int intValue() { + return this.value; + } + + /** + * <p> + * Serialization helper method to maintain singletons and add any new + * levels. + * </p> + * + * @return the resolved instance. + */ + private Object readResolve() { + synchronized (levels) { + for (Level level : levels) { + if (value != level.value) { + continue; + } + if (!name.equals(name)) { + continue; + } + if (resourceBundleName == level.resourceBundleName) { + return level; + } else if (resourceBundleName != null + && resourceBundleName.equals(level.resourceBundleName)) { + return level; + } + } + // This is a new value, so add it. + levels.add(this); + return this; + } + } + + /** + * Serialization helper to setup transient resource bundle instance. + * + * @param in + * the input stream to read the instance data from. + * @throws IOException + * if an IO error occurs. + * @throws ClassNotFoundException + * if a class is not found. + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + if (resourceBundleName != null) { + try { + rb = ResourceBundle.getBundle(resourceBundleName); + } catch (MissingResourceException e) { + rb = null; + } + } + } + + /** + * Gets the localized name of this level. The default locale is used. If no + * resource bundle is associated with this level then the original level + * name is returned. + * + * @return the localized name of this level. + * @since Android 1.0 + */ + public String getLocalizedName() { + if (rb == null) { + return name; + } + + try { + return rb.getString(name); + } catch (MissingResourceException e) { + return name; + } + } + + /** + * Compares two {@code Level} objects for equality. They are considered to + * be equal if they have the same level value. + * + * @param o + * the other object to compare this level to. + * @return {@code true} if this object equals to the supplied object, + * {@code false} otherwise. + * @since Android 1.0 + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof Level)) { + return false; + } + + return ((Level) o).intValue() == this.value; + } + + /** + * Returns the hash code of this {@code Level} object. + * + * @return this level's hash code. + * @since Android 1.0 + */ + @Override + public int hashCode() { + return this.value; + } + + /** + * Returns the string representation of this {@code Level} object. In + * this case, it is the level's name. + * + * @return the string representation of this level. + * @since Android 1.0 + */ + @Override + public final String toString() { + return this.name; + } +} diff --git a/logging/src/main/java/java/util/logging/LogManager.java b/logging/src/main/java/java/util/logging/LogManager.java new file mode 100644 index 0000000..8409b81 --- /dev/null +++ b/logging/src/main/java/java/util/logging/LogManager.java @@ -0,0 +1,631 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.logging; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; +// BEGIN android-removed +// import java.lang.management.ManagementFactory; +// import java.lang.reflect.Method; +// import javax.management.MBeanServer; +// import javax.management.ObjectName; +// import javax.management.ObjectInstance; +// import javax.management.MalformedObjectNameException; +// END android-removed + +import org.apache.harmony.logging.internal.nls.Messages; + +/** + * {@code LogManager} is used to maintain configuration properties of the + * logging framework, and to manage a hierarchical namespace of all named + * {@code Logger} objects. + * <p> + * + * There is only one global {@code LogManager} instance in the + * application, which can be get by calling static method + * {@link #getLogManager()}. This instance is created and + * initialized during class initialization and cannot be changed. + * </p> + * <p> + * The {@code LogManager} class can be specified by + * java.util.logging.manager system property, if the property is unavailable or + * invalid, the default class {@link java.util.logging.LogManager} will + * be used. + * </p> + * <p> + * When initialization, {@code LogManager} read its configuration from a + * properties file, which by default is the "lib/logging.properties" in the JRE + * directory. + * </p> + * <p> + * However, two optional system properties can be used to customize the initial + * configuration process of {@code LogManager}. + * <ul> + * <li>"java.util.logging.config.class"</li> + * <li>"java.util.logging.config.file"</li> + * </ul> + * </p> + * <p> + * These two properties can be set in three ways, by the Preferences API, by the + * "java" command line property definitions, or by system property definitions + * passed to JNI_CreateJavaVM. + * </p> + * <p> + * The "java.util.logging.config.class" should specifies a class name. If it is + * set, this given class will be loaded and instantiated during + * {@code LogManager} initialization, so that this object's default + * constructor can read the initial configuration and define properties for + * {@code LogManager}. + * </p> + * <p> + * If "java.util.logging.config.class" property is not set, or it is invalid, or + * some exception is thrown during the instantiation, then the + * "java.util.logging.config.file" system property can be used to specify a + * properties file. The {@code LogManager} will read initial + * configuration from this file. + * </p> + * <p> + * If neither of these properties is defined, or some exception is thrown + * during these two properties using, the {@code LogManager} will read + * its initial configuration from default properties file, as described above. + * </p> + * <p> + * The global logging properties may include: + * <ul> + * <li>"handlers". This property's values should be a list of class names for + * handler classes separated by whitespace, these classes must be subclasses of + * {@code Handler} and each must have a default constructor, these + * classes will be loaded, instantiated and registered as handlers on the root + * {@code Logger} (the {@code Logger} named ""). These + * {@code Handler}s maybe initialized lazily.</li> + * <li>"config". The property defines a list of class names separated by + * whitespace. Each class must have a default constructor, in which it can + * update the logging configuration, such as levels, handlers, or filters for + * some logger, etc. These classes will be loaded and instantiated during + * {@code LogManager} configuration</li> + * </ul> + * </p> + * <p> + * This class, together with any handler and configuration classes associated + * with it, <b>must</b> be loaded from the system classpath when + * {@code LogManager} configuration occurs. + * </p> + * <p> + * Besides global properties, the properties for loggers and Handlers can be + * specified in the property files. The names of these properties will start + * with the complete dot separated names for the handlers or loggers. + * </p> + * <p> + * In the {@code LogManager}'s hierarchical namespace, + * {@code Loggers} are organized based on their dot separated names. For + * example, "x.y.z" is child of "x.y". + * </p> + * <p> + * Levels for {@code Loggers} can be defined by properties whose name end + * with ".level". Thus "alogger.level" defines a level for the logger named as + * "alogger" and for all its children in the naming hierarchy. Log levels + * properties are read and applied in the same order as they are specified in + * the property file. The root logger's level can be defined by the property + * named as ".level". + * </p> + * <p> + * All methods on this type can be taken as being thread safe. + * </p> + * + */ +public class LogManager { + /* + * ------------------------------------------------------------------- + * Class variables + * ------------------------------------------------------------------- + */ + + // The line separator of the underlying OS + // Use privileged code to read the line.separator system property + private static final String lineSeparator = + getPrivilegedSystemProperty("line.separator"); //$NON-NLS-1$ + + // The shared logging permission + private static final LoggingPermission perm = new LoggingPermission( + "control", null); //$NON-NLS-1$ + + // the singleton instance + static LogManager manager; + + /** + * The {@code String} value of the {@link LoggingMXBean}'s ObjectName. + * + * @since Android 1.0 + */ + public static final String LOGGING_MXBEAN_NAME = + "java.util.logging:type=Logging"; //$NON-NLS-1$ + + /** + * Get the {@code LoggingMXBean} instance. this implementation always throws + * an UnsupportedOperationException. + * + * @return the {@code LoggingMXBean} instance + */ + public static LoggingMXBean getLoggingMXBean() { + // BEGIN android-added + throw new UnsupportedOperationException(); + // END android-added + // BEGIN android-removed + // try { + // ObjectName loggingMXBeanName = new ObjectName(LOGGING_MXBEAN_NAME); + // MBeanServer platformBeanServer = + // ManagementFactory.getPlatformMBeanServer(); + // Set loggingMXBeanSet = platformBeanServer.queryMBeans( + // loggingMXBeanName, null); + // + // if (loggingMXBeanSet.size() != 1) { + // // logging.21=There Can Be Only One logging MX bean. + // throw new AssertionError(Messages.getString("logging.21")); + // } + // + // Iterator i = loggingMXBeanSet.iterator(); + // ObjectInstance loggingMXBeanOI = (ObjectInstance) i.next(); + // String lmxbcn = loggingMXBeanOI.getClassName(); + // Class lmxbc = Class.forName(lmxbcn); + // Method giMethod = lmxbc.getDeclaredMethod("getInstance"); + // giMethod.setAccessible(true); + // LoggingMXBean lmxb = (LoggingMXBean) + // giMethod.invoke(null, new Object[] {}); + // + // return lmxb; + // } catch (Exception e) { + // //TODO + // //e.printStackTrace(); + // } + // // logging.22=Exception occurred while getting the logging MX bean. + // throw new AssertionError(Messages.getString("logging.22")); //$NON-NLS-1$ + // END android-removed + } + + /* + * ------------------------------------------------------------------- + * Instance variables + * ------------------------------------------------------------------- + */ + //FIXME: use weak reference to avoid heap memory leak + private Hashtable<String, Logger> loggers; + + // the configuration properties + private Properties props; + + // the property change listener + private PropertyChangeSupport listeners; + + /* + * ------------------------------------------------------------------- + * Global initialization + * ------------------------------------------------------------------- + */ + + static { + // init LogManager singleton instance + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + String className = System.getProperty( + "java.util.logging.manager"); //$NON-NLS-1$ + + if (null != className) { + manager = (LogManager) getInstanceByClass(className); + } + if (null == manager) { + manager = new LogManager(); + } + + // read configuration + try { + manager.readConfiguration(); + } catch (Exception e) { + e.printStackTrace(); + } + + // if global logger has been initialized, set root as its parent + Logger root = new Logger("", null); //$NON-NLS-1$ + root.setLevel(Level.INFO); + Logger.global.setParent(root); + + manager.addLogger(root); + manager.addLogger(Logger.global); + return null; + } + }); + } + + /** + * Default constructor. This is not public because there should be only one + * {@code LogManager} instance, which can be get by + * {@code LogManager.getLogManager(}. This is protected so that + * application can subclass the object. + */ + protected LogManager() { + loggers = new Hashtable<String, Logger>(); + props = new Properties(); + listeners = new PropertyChangeSupport(this); + // add shutdown hook to ensure that the associated resource will be + // freed when JVM exits + AccessController.doPrivileged(new PrivilegedAction<Void>() { + public Void run() { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + reset(); + } + }); + return null; + } + }); + } + + /* + * ------------------------------------------------------------------- + * Methods + * ------------------------------------------------------------------- + */ + /* + * Package private utilities Returns the line separator of the underlying + * OS. + */ + static String getSystemLineSeparator() { + return lineSeparator; + } + + /** + * Check that the caller has {@code LoggingPermission("control")} so + * that it is trusted to modify the configuration for logging framework. If + * the check passes, just return, otherwise {@code SecurityException} + * will be thrown. + * + * @throws SecurityException + * if there is a security manager in operation and the invoker + * of this method does not have the required security permission + * {@code LoggingPermission("control")} + */ + public void checkAccess() { + if (null != System.getSecurityManager()) { + System.getSecurityManager().checkPermission(perm); + } + } + + /** + * Add a given logger into the hierarchical namespace. The + * {@code Logger.addLogger()} factory methods call this method to add newly + * created Logger. This returns false if a logger with the given name has + * existed in the namespace + * <p> + * Note that the {@code LogManager} may only retain weak references to + * registered loggers. In order to prevent {@code Logger} objects from being + * unexpectedly garbage collected it is necessary for <i>applications</i> + * to maintain references to them. + * </p> + * + * @param logger + * the logger to be added. + * @return true if the given logger is added into the namespace + * successfully, false if the given logger exists in the namespace. + */ + public synchronized boolean addLogger(Logger logger) { + String name = logger.getName(); + if (null != loggers.get(name)) { + return false; + } + addToFamilyTree(logger, name); + loggers.put(name, logger); + logger.setManager(this); + return true; + } + + + private void addToFamilyTree(Logger logger, String name) { + Logger parent = null; + // find parent + int lastSeparator; + String parentName = name; + while ((lastSeparator = parentName.lastIndexOf('.')) != -1) { + parentName = parentName.substring(0, lastSeparator); + parent = loggers.get(parentName); + if (parent != null) { + logger.internalSetParent(parent); + break; + } else if (getProperty(parentName+".level") != null || //$NON-NLS-1$ + getProperty(parentName+".handlers") != null) { //$NON-NLS-1$ + parent = Logger.getLogger(parentName); + logger.internalSetParent(parent); + break; + } + } + if (parent == null && null != (parent = loggers.get(""))) { //$NON-NLS-1$ + logger.internalSetParent(parent); + } + + // find children + //TODO: performance can be improved here? + Collection<Logger> allLoggers = loggers.values(); + for (Logger child : allLoggers) { + Logger oldParent = child.getParent(); + if (parent == oldParent + && (name.length() == 0 || child.getName().startsWith( + name + '.'))) { + child.setParent(logger); + if (null != oldParent) { + //-- remove from old parent as the parent has been changed + oldParent.removeChild(child); + } + } + } + } + + /** + * Get the logger with the given name. + * + * @param name + * name of logger + * @return logger with given name, or {@code null} if nothing is found. + */ + public synchronized Logger getLogger(String name) { + return loggers.get(name); + } + + /** + * Get a {@code Enumeration} of all registered logger names. + * + * @return enumeration of registered logger names + */ + public synchronized Enumeration<String> getLoggerNames() { + return loggers.keys(); + } + + /** + * Get the global {@code LogManager} instance. + * + * @return the global {@code LogManager} instance + */ + public static LogManager getLogManager() { + return manager; + } + + /** + * Get the value of property with given name. + * + * @param name + * the name of property + * @return the value of property + */ + public String getProperty(String name) { + return props.getProperty(name); + } + + /** + * Re-initialize the properties and configuration. The initialization + * process is same as the {@code LogManager} instantiation. + * <p> + * Notice : No {@code PropertyChangeEvent} are fired. + * </p> + * + * @throws IOException + * if any IO related problems happened. + * @throws SecurityException + * if security manager exists and it determines that caller does + * not have the required permissions to perform this action. + */ + public void readConfiguration() throws IOException { + checkAccess(); + // check config class + String configClassName = System.getProperty( + "java.util.logging.config.class"); //$NON-NLS-1$ + if (null == configClassName || null == getInstanceByClass(configClassName)) { + // if config class failed, check config file + String configFile = System.getProperty( + "java.util.logging.config.file"); //$NON-NLS-1$ + + if (null == configFile) { + // if cannot find configFile, use default logging.properties + configFile = new StringBuilder().append( + System.getProperty("java.home")).append(File.separator) //$NON-NLS-1$ + .append("lib").append(File.separator).append( //$NON-NLS-1$ + "logging.properties").toString(); //$NON-NLS-1$ + } + + InputStream input = null; + try { + // BEGIN android-removed + // input = new BufferedInputStream(new FileInputStream(configFile)); + // END android-removed + + // BEGIN android-added + try { + input = new BufferedInputStream( + new FileInputStream(configFile), 8192); + } catch (Exception ex) { + // consult fixed resource as a last resort + input = new BufferedInputStream( + getClass().getResourceAsStream( + "logging.properties"), 8192); + } + // END android-added + readConfigurationImpl(input); + } finally { + if (input != null) { + try { + input.close(); + } catch (Exception e) {// ignore + } + } + } + } + } + + // use privilege code to get system property + static String getPrivilegedSystemProperty(final String key) { + return AccessController.doPrivileged(new PrivilegedAction<String>() { + public String run() { + return System.getProperty(key); + } + }); + } + + // use SystemClassLoader to load class from system classpath + static Object getInstanceByClass(final String className) { + try { + Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass( + className); + return clazz.newInstance(); + } catch (Exception e) { + try { + Class<?> clazz = Thread.currentThread() + .getContextClassLoader().loadClass(className); + return clazz.newInstance(); + } catch (Exception innerE) { + //logging.20=Loading class "{0}" failed + System.err.println(Messages.getString( + "logging.20", className)); //$NON-NLS-1$ + System.err.println(innerE); + return null; + } + } + + } + + // actual initialization process from a given input stream + private synchronized void readConfigurationImpl(InputStream ins) + throws IOException { + reset(); + props.load(ins); + + // parse property "config" and apply setting + String configs = props.getProperty("config"); //$NON-NLS-1$ + if (null != configs) { + StringTokenizer st = new StringTokenizer(configs, " "); //$NON-NLS-1$ + while (st.hasMoreTokens()) { + String configerName = st.nextToken(); + getInstanceByClass(configerName); + } + } + + // set levels for logger + Collection<Logger> allLoggers = loggers.values(); + for(Logger logger : allLoggers){ + String property = props.getProperty( + logger.getName()+".level"); //$NON-NLS-1$ + if(null != property){ + logger.setLevel(Level.parse(property)); + } + } + listeners.firePropertyChange(null, null, null); + } + + + /** + * Re-initialize the properties and configuration from the given + * {@code InputStream} + * <p> + * Notice : No {@code PropertyChangeEvent} are fired. + * </p> + * + * @param ins + * the input stream + * @throws IOException + * if any IO related problems happened. + * @throws SecurityException + * if security manager exists and it determines that caller does + * not have the required permissions to perform this action. + */ + public void readConfiguration(InputStream ins) throws IOException { + checkAccess(); + readConfigurationImpl(ins); + } + + /** + * Reset configuration. + * <p> + * All handlers are closed and removed from any named loggers. All loggers' + * level is set to null, except the root logger's level is set to + * {@code Level.INFO}. + * </p> + * + * @throws SecurityException + * if security manager exists and it determines that caller does + * not have the required permissions to perform this action. + */ + public void reset() { + checkAccess(); + props = new Properties(); + Enumeration<String> names = getLoggerNames(); + while(names.hasMoreElements()){ + String name = names.nextElement(); + Logger logger = getLogger(name); + if(logger != null){ + logger.reset(); + } + } + Logger root = loggers.get(""); //$NON-NLS-1$ + if (null != root) { + root.setLevel(Level.INFO); + } + } + + /** + * Add a {@code PropertyChangeListener}, which will be invoked when + * the properties are reread. + * + * @param l + * the {@code PropertyChangeListener} to be added. + * @throws SecurityException + * if security manager exists and it determines that caller does + * not have the required permissions to perform this action. + */ + public void addPropertyChangeListener(PropertyChangeListener l) { + if(l == null){ + throw new NullPointerException(); + } + checkAccess(); + listeners.addPropertyChangeListener(l); + } + + /** + * Remove a {@code PropertyChangeListener}, do nothing if the given + * listener is not found. + * + * @param l + * the {@code PropertyChangeListener} to be removed. + * @throws SecurityException + * if security manager exists and it determines that caller does + * not have the required permissions to perform this action. + */ + public void removePropertyChangeListener(PropertyChangeListener l) { + checkAccess(); + listeners.removePropertyChangeListener(l); + } +} diff --git a/logging/src/main/java/java/util/logging/LogRecord.java b/logging/src/main/java/java/util/logging/LogRecord.java new file mode 100644 index 0000000..b8a98ef --- /dev/null +++ b/logging/src/main/java/java/util/logging/LogRecord.java @@ -0,0 +1,539 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.logging; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.apache.harmony.logging.internal.nls.Messages; + +/** + * A {@code LogRecord} object represents a logging request. It is passed between + * the logging framework and individual logging handlers. Client applications + * should not modify a {@code LogRecord} object that has been passed into the + * logging framework. + * <p> + * The {@code LogRecord} class will infer the source method name and source + * class name the first time they are accessed if the client application didn't + * specify them explicitly. This automatic inference is based on the analysis of + * the call stack and is not guaranteed to be precise. Client applications + * should force the initialization of these two fields by calling + * {@code getSourceClassName} or {@code getSourceMethodName} if they expect to + * use them after passing the {@code LogRecord} object to another thread or + * transmitting it over RMI. + * </p> + * + * @since Android 1.0 + */ +public class LogRecord implements Serializable { + + private static final long serialVersionUID = 5372048053134512534L; + + // The major byte used in serialization. + private static final int MAJOR = 1; + + // The minor byte used in serialization. + private static final int MINOR = 4; + + // Store the current value for the sequence number. + private static long currentSequenceNumber = 0; + + // Store the id for each thread. + private static ThreadLocal<Integer> currentThreadId = new ThreadLocal<Integer>(); + + // The base id as the starting point for thread ID allocation. + private static int initThreadId = 0; + + /** + * The logging level. + * + * @serial + */ + private Level level; + + /** + * The sequence number. + * + * @serial + */ + private long sequenceNumber; + + /** + * The name of the class that issued the logging call. + * + * @serial + */ + private String sourceClassName; + + /** + * The name of the method that issued the logging call. + * + * @serial + */ + private String sourceMethodName; + + /** + * The original message text. + * + * @serial + */ + private String message; + + /** + * The ID of the thread that issued the logging call. + * + * @serial + */ + private int threadID; + + /** + * The time that the event occurred, in milliseconds since 1970. + * + * @serial + */ + private long millis; + + /** + * The associated {@code Throwable} object if any. + * + * @serial + */ + private Throwable thrown; + + /** + * The name of the source logger. + * + * @serial + */ + private String loggerName; + + /** + * The name of the resource bundle used to localize the log message. + * + * @serial + */ + private String resourceBundleName; + + // The associated resource bundle if any. + private transient ResourceBundle resourceBundle; + + // The parameters. + private transient Object[] parameters; + + // If the source method and source class has been initialized + private transient boolean sourceInited; + + /** + * Constructs a {@code LogRecord} object using the supplied the logging + * level and message. The millis property is set to the current time. The + * sequence property is set to a new unique value, allocated in increasing + * order within the virtual machine. The thread ID is set to a unique value + * for the current thread. All other properties are set to {@code null}. + * + * @param level + * the logging level, may not be {@code null}. + * @param msg + * the raw message. + * @throws NullPointerException + * if {@code level} is {@code null}. + * @since Android 1.0 + */ + public LogRecord(Level level, String msg) { + if (null == level) { + // logging.4=The 'level' parameter is null. + throw new NullPointerException(Messages.getString("logging.4")); //$NON-NLS-1$ + } + this.level = level; + this.message = msg; + this.millis = System.currentTimeMillis(); + + synchronized (LogRecord.class) { + this.sequenceNumber = currentSequenceNumber++; + Integer id = currentThreadId.get(); + if (null == id) { + this.threadID = initThreadId; + currentThreadId.set(Integer.valueOf(initThreadId++)); + } else { + this.threadID = id.intValue(); + } + } + + this.sourceClassName = null; + this.sourceMethodName = null; + this.loggerName = null; + this.parameters = null; + this.resourceBundle = null; + this.resourceBundleName = null; + this.thrown = null; + } + + /** + * Gets the logging level. + * + * @return the logging level. + * @since Android 1.0 + */ + public Level getLevel() { + return level; + } + + /** + * Sets the logging level. + * + * @param level + * the level to set. + * @throws NullPointerException + * if {@code level} is {@code null}. + * @since Android 1.0 + */ + public void setLevel(Level level) { + if (null == level) { + // logging.4=The 'level' parameter is null. + throw new NullPointerException(Messages.getString("logging.4")); //$NON-NLS-1$ + } + this.level = level; + } + + /** + * Gets the name of the logger. + * + * @return the logger name. + * @since Android 1.0 + */ + public String getLoggerName() { + return loggerName; + } + + /** + * Sets the name of the logger. + * + * @param loggerName + * the logger name to set. + * @since Android 1.0 + */ + public void setLoggerName(String loggerName) { + this.loggerName = loggerName; + } + + /** + * Gets the raw message. + * + * @return the raw message, may be {@code null}. + * @since Android 1.0 + */ + public String getMessage() { + return message; + } + + /** + * Sets the raw message. When this record is formatted by a logger that has + * a localization resource bundle that contains an entry for {@code message}, + * then the raw message is replaced with its localized version. + * + * @param message + * the raw message to set, may be {@code null}. + * @since Android 1.0 + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * Gets the time when this event occurred, in milliseconds since 1970. + * + * @return the time when this event occurred, in milliseconds since 1970. + * @since Android 1.0 + */ + public long getMillis() { + return millis; + } + + /** + * Sets the time when this event occurred, in milliseconds since 1970. + * + * @param millis + * the time when this event occurred, in milliseconds since 1970. + * @since Android 1.0 + */ + public void setMillis(long millis) { + this.millis = millis; + } + + /** + * Gets the parameters. + * + * @return the array of parameters or {@code null} if there are no + * parameters. + * @since Android 1.0 + */ + public Object[] getParameters() { + return parameters; + } + + /** + * Sets the parameters. + * + * @param parameters + * the array of parameters to set, may be {@code null}. + * @since Android 1.0 + */ + public void setParameters(Object[] parameters) { + this.parameters = parameters; + } + + /** + * Gets the resource bundle used to localize the raw message during + * formatting. + * + * @return the associated resource bundle, {@code null} if none is + * available or the message is not localizable. + * @since Android 1.0 + */ + public ResourceBundle getResourceBundle() { + return resourceBundle; + } + + /** + * Sets the resource bundle. + * + * @param resourceBundle + * the resource bundle to set, may be {@code null}. + * @since Android 1.0 + */ + public void setResourceBundle(ResourceBundle resourceBundle) { + this.resourceBundle = resourceBundle; + } + + /** + * Gets the name of the resource bundle. + * + * @return the name of the resource bundle, {@code null} if none is + * available or the message is not localizable. + * @since Android 1.0 + */ + public String getResourceBundleName() { + return resourceBundleName; + } + + /** + * Sets the name of the resource bundle. + * + * @param resourceBundleName + * the name of the resource bundle to set. + * @since Android 1.0 + */ + public void setResourceBundleName(String resourceBundleName) { + this.resourceBundleName = resourceBundleName; + } + + /** + * Gets the sequence number. + * + * @return the sequence number. + * @since Android 1.0 + */ + public long getSequenceNumber() { + return sequenceNumber; + } + + /** + * Sets the sequence number. It is usually not necessary to call this method + * to change the sequence number because the number is allocated when this + * instance is constructed. + * + * @param sequenceNumber + * the sequence number to set. + * @since Android 1.0 + */ + public void setSequenceNumber(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + /** + * Gets the name of the class that is the source of this log record. This + * information can be changed, may be {@code null} and is untrusted. + * + * @return the name of the source class of this log record (possiblity {@code null}) + * @since Android 1.0 + */ + public String getSourceClassName() { + initSource(); + return sourceClassName; + } + + /* + * Init the sourceClass and sourceMethod fields. + */ + private void initSource() { + if (!sourceInited) { + StackTraceElement[] elements = (new Throwable()).getStackTrace(); + int i = 0; + String current = null; + FINDLOG: for (; i < elements.length; i++) { + current = elements[i].getClassName(); + if (current.equals(Logger.class.getName())) { + break FINDLOG; + } + } + while(++i<elements.length && elements[i].getClassName().equals(current)) { + // do nothing + } + if (i < elements.length) { + this.sourceClassName = elements[i].getClassName(); + this.sourceMethodName = elements[i].getMethodName(); + } + sourceInited = true; + } + } + + /** + * Sets the name of the class that is the source of this log record. + * + * @param sourceClassName + * the name of the source class of this log record, may be + * {@code null}. + * @since Android 1.0 + */ + public void setSourceClassName(String sourceClassName) { + sourceInited = true; + this.sourceClassName = sourceClassName; + } + + /** + * Gets the name of the method that is the source of this log record. + * + * @return the name of the source method of this log record. + * @since Android 1.0 + */ + public String getSourceMethodName() { + initSource(); + return sourceMethodName; + } + + /** + * Sets the name of the method that is the source of this log record. + * + * @param sourceMethodName + * the name of the source method of this log record, may be + * {@code null}. + * @since Android 1.0 + */ + public void setSourceMethodName(String sourceMethodName) { + sourceInited = true; + this.sourceMethodName = sourceMethodName; + } + + /** + * Gets a unique ID of the thread originating the log record. Every thread + * becomes a different ID. + * <p> + * Notice : the ID doesn't necessary map the OS thread ID + * </p> + * + * @return the ID of the thread originating this log record. + * @since Android 1.0 + */ + public int getThreadID() { + return threadID; + } + + /** + * Sets the ID of the thread originating this log record. + * + * @param threadID + * the new ID of the thread originating this log record. + * @since Android 1.0 + */ + public void setThreadID(int threadID) { + this.threadID = threadID; + } + + /** + * Gets the {@code Throwable} object associated with this log record. + * + * @return the {@code Throwable} object associated with this log record. + * @since Android 1.0 + */ + public Throwable getThrown() { + return thrown; + } + + /** + * Sets the {@code Throwable} object associated with this log record. + * + * @param thrown + * the new {@code Throwable} object to associate with this log + * record. + * @since Android 1.0 + */ + public void setThrown(Throwable thrown) { + this.thrown = thrown; + } + + /* + * Customized serialization. + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeByte(MAJOR); + out.writeByte(MINOR); + if (null == parameters) { + out.writeInt(-1); + } else { + out.writeInt(parameters.length); + for (Object element : parameters) { + out.writeObject(null == element ? null : element.toString()); + } + } + } + + /* + * Customized deserialization. + */ + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + in.defaultReadObject(); + byte major = in.readByte(); + byte minor = in.readByte(); + //only check MAJOR version + if (major != MAJOR) { + // logging.5=Different version - {0}.{1} + throw new IOException(Messages.getString("logging.5", major, minor)); //$NON-NLS-1$ + } + + int length = in.readInt(); + if (length >= 0) { + parameters = new Object[length]; + for (int i = 0; i < parameters.length; i++) { + parameters[i] = in.readObject(); + } + } + if (null != resourceBundleName) { + try { + resourceBundle = Logger.loadResourceBundle(resourceBundleName); + } catch (MissingResourceException e) { + // Cannot find the specified resource bundle + resourceBundle = null; + } + } + } +} diff --git a/logging/src/main/java/java/util/logging/Logger.java b/logging/src/main/java/java/util/logging/Logger.java new file mode 100644 index 0000000..cd88ca0 --- /dev/null +++ b/logging/src/main/java/java/util/logging/Logger.java @@ -0,0 +1,1495 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.logging; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.StringTokenizer; + +import org.apache.harmony.logging.internal.nls.Messages; + +/** + * Loggers are used to log records to certain outputs, including file, console, + * etc. They use various handlers to actually do the output-dependent + * operations. + * <p> + * Client applications can get named loggers by calling the {@code getLogger} + * methods. They can also get anonymous loggers by calling the + * {@code getAnonymousLogger} methods. Named loggers are organized in a + * namespace hierarchy managed by a log manager. The naming convention is + * usually the same as java package's naming convention, that is using + * dot-separated strings. Anonymous loggers do not belong to any namespace. + * </p> + * <p> + * Loggers "inherit" log level setting from their parent if their own level is + * set to {@code null}. This is also true for the resource bundle. The logger's + * resource bundle is used to localize the log messages if no resource bundle + * name is given when a log method is called. If {@code getUseParentHandlers()} + * returns {@code true}, loggers also inherit their parent's handlers. In this + * context, "inherit" only means that "behavior" is inherited. The internal + * field values will not change, for example, {@code getLevel()} still returns + * {@code null}. + * </p> + * <p> + * When loading a given resource bundle, the logger first tries to use the + * context classloader. If that fails, it tries the system classloader. And if + * that still fails, it searches up the class stack and uses each class's + * classloader to try to locate the resource bundle. + * </p> + * <p> + * Some log methods accept log requests that do not specify the source class and + * source method. In these cases, the logging framework will automatically infer + * the calling class and method, but this is not guaranteed to be accurate. + * </p> + * <p> + * Once a {@code LogRecord} object has been passed into the logging framework, + * it is owned by the logging framework and the client applications should not + * use it any longer. + * </p> + * <p> + * All methods of this class are thread-safe. + * </p> + * + * @see LogManager + * @since Android 1.0 + */ +public class Logger { + + /** + * The global logger is provided as convenience for casual use. + * + * @since Android 1.0 + */ + public final static Logger global = new Logger("global", null); //$NON-NLS-1$ + + // the name of this logger + private volatile String name; + + // the parent logger of this logger + private Logger parent; + + // the logging level of this logger + private volatile Level levelObjVal; + + // the logging level as int of this logger + private volatile int levelIntVal; + + // the filter + private Filter filter; + + // the name of the resource bundle used to localize logging messages + private String resBundleName; + + // the loaded resource bundle according to the specified name + private ResourceBundle resBundle; + + // the handlers attached to this logger + private List<Handler> handlers; + + /* + * flag indicating whether to notify parent's handlers on receiving a log + * request + */ + private boolean notifyParentHandlers; + + // flag indicating whether this logger is named or anonymous + private boolean isNamed; + + private List<Logger> childs; + + private LogManager manager; + + // BEGIN android-changed + private volatile boolean handlerInited; + // END android-changed + + + /* + * ------------------------------------------------------------------- + * Constructors + * ------------------------------------------------------------------- + */ + + /** + * Constructs a {@code Logger} object with the supplied name and resource + * bundle name; {@code notifiyParentHandlers} is set to {@code true}. + * <p> + * Notice : Loggers use a naming hierarchy. Thus "z.x.y" is a child of "z.x". + * </p> + * + * @param name + * the name of this logger, may be {@code null} for anonymous + * loggers. + * @param resourceBundleName + * the name of the resource bundle used to localize logging + * messages, may be {@code null}. + * @throws MissingResourceException + * if the specified resource bundle can not be loaded. + * @since Android 1.0 + */ + protected Logger(String name, String resourceBundleName) { + // try to load the specified resource bundle first + if (null == resourceBundleName) { + this.resBundleName = null; + this.resBundle = null; + } else { + this.resBundle = loadResourceBundle(resourceBundleName); + this.resBundleName = resourceBundleName; + } + this.name = name; + this.parent = null; + this.filter = null; + this.childs = new ArrayList<Logger>(); + this.notifyParentHandlers = true; + // any logger is not anonymous by default + this.isNamed = true; + + //-- 'null' means that level will be inherited from parent (see getLevel) + //-- Level.INFO is default level if we don't set it. It will be + //-- changed to parent level or to configLevel after adding to the + //-- family tree. As of this, actually, setting to Level.INFO is + //-- not needed here. + this.levelObjVal = null; + this.levelIntVal = Level.INFO.intValue(); + } + + //-- should be called under the lm lock + private void setLevelImpl(Level newLevel) { + // update levels for the whole hierarchy + int oldVal = levelIntVal; + levelObjVal = newLevel; + if (null == newLevel) { + levelIntVal = null != parent + ? parent.levelIntVal + : Level.INFO.intValue(); + } else { + levelIntVal = newLevel.intValue(); + } + if (oldVal != levelIntVal) { + forceChildsToInherit(); + } + } + + //-- should be called under the lm lock + private void forceChildsToInherit() { + for (Logger child : childs) { + if (null == child.levelObjVal) { // should inherit + child.setLevelImpl(null); + } + } + } + + /* + * ------------------------------------------------------------------- + * Methods + * ------------------------------------------------------------------- + */ + + /** + * Load the specified resource bundle, use privileged code. + * + * @param resourceBundleName + * the name of the resource bundle to load, cannot be {@code null}. + * @return the loaded resource bundle. + * @throws MissingResourceException + * if the specified resource bundle can not be loaded. + */ + static ResourceBundle loadResourceBundle(String resourceBundleName) { + // try context class loader to load the resource + ClassLoader cl = AccessController.doPrivileged( + new PrivilegedAction<ClassLoader>() { + public ClassLoader run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + if (null != cl) { + try { + return ResourceBundle.getBundle(resourceBundleName, Locale + .getDefault(), cl); + } catch (MissingResourceException e) { + // Failed to load using context classloader, ignore + } + } + // try system class loader to load the resource + cl = AccessController.doPrivileged( + new PrivilegedAction<ClassLoader>() { + public ClassLoader run() { + return ClassLoader.getSystemClassLoader(); + } + }); + if (null != cl) { + try { + return ResourceBundle.getBundle(resourceBundleName, Locale + .getDefault(), cl); + } catch (MissingResourceException e) { + // Failed to load using system classloader, ignore + } + } + // try all class loaders up the class stack + final Class<?>[] classes = AccessController + .doPrivileged(new PrivilegedAction<Class<?>[]>() { + public Class<?>[] run() { + return (new PrivateSecurityManager()) + .privateGetClassContext(); + } + }); + // the first class, which is PrivateSecurityManager, is skipped + for (int i = 1; i < classes.length; i++) { + final int index = i; + try { + cl = AccessController.doPrivileged( + new PrivilegedAction<ClassLoader>() { + public ClassLoader run() { + return classes[index].getClassLoader(); + } + }); + if (null == cl) { + continue; + } + return ResourceBundle.getBundle(resourceBundleName, Locale + .getDefault(), cl); + } catch (MissingResourceException e) { + // Failed to load using the current class's classloader, ignore + } + } + // logging.8=Failed to load the specified resource bundle "{0}". + throw new MissingResourceException(Messages.getString("logging.8", //$NON-NLS-1$ + resourceBundleName), resourceBundleName, null); + } + + /** + * Gets an anonymous logger to use internally in a thread. Anonymous loggers + * are not registered in the log manager's namespace. No security checks + * will be performed when updating an anonymous logger's control settings. + * <p> + * The anonymous loggers' parent is set to be the root logger. This way it + * inherits the default logging level and handlers from the root logger. + * </p> + * + * @return a new instance of anonymous logger. + * @since Android 1.0 + */ + public static Logger getAnonymousLogger() { + return getAnonymousLogger(null); + } + + /** + * Gets an anonymous logger to use internally in a thread. Anonymous loggers + * are not registered in the log manager's namespace. No security checks + * will be performed when updating an anonymous logger's control settings. + * <p> + * The anonymous loggers' parent is set to be the root logger. This way it + * inherits default logging level and handlers from the root logger. + * </p> + * + * @param resourceBundleName + * the name of the resource bundle used to localize log messages. + * @return a new instance of anonymous logger. + * @throws MissingResourceException + * if the specified resource bundle can not be loaded. + * @since Android 1.0 + */ + public static Logger getAnonymousLogger(String resourceBundleName) { + final Logger l = new Logger(null, resourceBundleName); + l.isNamed = false; + l.internalSetParent(LogManager.getLogManager().getLogger("")); //$NON-NLS-1$ + return l; + } + + /* + * Check whether the same resource bundle has been specified. + * Synchronize to ensure the consistency between resource bundle + * and its name. + */ + private static void updateResourceBundle(Logger l, String resourceBundleName) { + synchronized (l) { + if (null == l.getResourceBundleName()) { + if(null == resourceBundleName){ + return; + } + /* + * load the resource bundle if none is specified + * before + */ + l.resBundle = loadResourceBundle(resourceBundleName); + l.resBundleName = resourceBundleName; + } else if (!l.getResourceBundleName().equals(resourceBundleName)) { + /* + * throw exception if the specified resource bundles + * are inconsistent with each other, i.e., different + * names + */ + // logging.9=The specified resource bundle name "{0}" is + // inconsistent with the existing one "{1}". + throw new IllegalArgumentException(Messages.getString( + "logging.9", //$NON-NLS-1$ + resourceBundleName, l.getResourceBundleName())); + } + } + } + + /* + * Gets a named logger associated with the supplied resource bundle. This + * method accepts null resource bundle name. The method body is synchronized + * on the instance of the LogManager to insure the consistency of the whole + * operation. + */ + private static Logger getLoggerWithRes(String name, + String resourceBundleName, boolean hasResourceName) { + LogManager man = LogManager.getLogManager(); + Logger l = null; + synchronized (man) { + // Try to find an existing logger with the specified name + l = man.getLogger(name); + // If no existing logger with the same name, create a new one + if (null == l) { + l = new Logger(name, resourceBundleName); + man.addLogger(l); + return l; + } + } + if (hasResourceName) { + updateResourceBundle(l, resourceBundleName); + } + return l; + } + + /** + * Gets a named logger. The returned logger may already exist or may be + * newly created. In the latter case, its level will be set to the + * configured level according to the {@code LogManager}'s properties. + * + * @param name + * the name of the logger to get, cannot be {@code null}. + * @return a named logger. + * @since Android 1.0 + */ + public static Logger getLogger(String name) { + return getLoggerWithRes(name, null, false); + } + + /** + * Gets a named logger associated with the supplied resource bundle. The + * resource bundle will be used to localize logging messages. + * + * @param name + * the name of the logger to get, cannot be {@code null}. + * @param resourceBundleName + * the name of the resource bundle, may be {@code null}. + * @throws IllegalArgumentException + * if the logger identified by {@code name} is associated with a + * resource bundle and its name is not equal to + * {@code resourceBundleName}. + * @throws MissingResourceException + * if the name of the resource bundle cannot be found. + * @return a named logger. + * @since Android 1.0 + */ + public static Logger getLogger(String name, String resourceBundleName) { + return getLoggerWithRes(name, resourceBundleName, true); + } + + /** + * Adds a handler to this logger. The {@code name} will be fed with log + * records received by this logger. + * + * @param handler + * the handler object to add, cannot be {@code null}. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public void addHandler(Handler handler) { + if (null == handler) { + // logging.A=The 'handler' parameter is null. + throw new NullPointerException(Messages.getString("logging.A")); //$NON-NLS-1$ + } + // Anonymous loggers can always add handlers + if (this.isNamed) { + LogManager.getLogManager().checkAccess(); + } + initHandler(); + synchronized(this){ + this.handlers.add(handler); + } + } + + /* + * Be cautious to avoid deadlock when using this method, it gets lock on manager + * at first, and then gets lock on this Logger, so any methods should not hold + * lock on this Logger when invoking this method. + */ + private void initHandler() { + if(!handlerInited){ + synchronized (this) { + if (!handlerInited) { + // BEGIN android-added + /* + * Force LogManager to be initialized, since its + * class init code performs necessary one-time setup. + */ + LogManager.getLogManager(); + // END android-added + if (handlers == null) { + handlers = new ArrayList<Handler>(); + } + if (manager == null) { + return; + } + + String handlerStr = manager + .getProperty("".equals(name) ? "handlers" : name + ".handlers"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (null == handlerStr) { + return; + } + StringTokenizer st = new StringTokenizer(handlerStr, " "); //$NON-NLS-1$ + while (st.hasMoreTokens()) { + String handlerName = st.nextToken(); + // BEGIN android-changed + // deal with non-existing handler + try { + Handler handler = (Handler) LogManager + .getInstanceByClass(handlerName); + handlers.add(handler); + String level = manager.getProperty(handlerName + + ".level"); //$NON-NLS-1$ + if (null != level) { + handler.setLevel(Level.parse(level)); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + // END android-changed + } + handlerInited = true; + } + } + } + } + + /** + * Gets all the handlers associated with this logger. + * + * @return an array of all the handlers associated with this logger. + * @since Android 1.0 + */ + public Handler[] getHandlers() { + initHandler(); + synchronized(this){ + return handlers.toArray(new Handler[handlers.size()]); + } + } + + /** + * Removes a handler from this logger. If the specified handler does not + * exist then this method has no effect. + * + * @param handler + * the handler to be removed. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public void removeHandler(Handler handler) { + // Anonymous loggers can always remove handlers + if (this.isNamed) { + LogManager.getLogManager().checkAccess(); + } + if (null == handler) { + return; + } + initHandler(); + synchronized(this){ + this.handlers.remove(handler); + } + } + + /** + * Gets the filter used by this logger. + * + * @return the filter used by this logger, may be {@code null}. + * @since Android 1.0 + */ + public Filter getFilter() { + return this.filter; + } + + /** + * Sets the filter used by this logger. + * + * @param newFilter + * the filter to set, may be {@code null}. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public void setFilter(Filter newFilter) { + // Anonymous loggers can always set the filter + if (this.isNamed) { + LogManager.getLogManager().checkAccess(); + } + filter = newFilter; + } + + /** + * Gets the logging level of this logger. A {@code null} level indicates + * that this logger inherits its parent's level. + * + * @return the logging level of this logger. + * @since Android 1.0 + */ + public Level getLevel() { + return levelObjVal; + } + + /** + * Sets the logging level for this logger. A {@code null} level indicates + * that this logger will inherit its parent's level. + * + * @param newLevel + * the logging level to set. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public void setLevel(Level newLevel) { + // Anonymous loggers can always set the level + if (this.isNamed) { + LogManager.getLogManager().checkAccess(); + } + synchronized (LogManager.getLogManager()) { + setLevelImpl(newLevel); + } + } + + /** + * Gets the flag which indicates whether to use the handlers of this + * logger's parent to publish incoming log records, potentially recursively + * up the namespace. + * + * @return {@code true} if set to use parent's handlers, {@code false} + * otherwise. + * @since Android 1.0 + */ + public boolean getUseParentHandlers() { + return this.notifyParentHandlers; + } + + /** + * Sets the flag which indicates whether to use the handlers of this + * logger's parent, potentially recursively up the namespace. + * + * @param notifyParentHandlers + * the new flag indicating whether to use the parent's handlers. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public void setUseParentHandlers(boolean notifyParentHandlers) { + // Anonymous loggers can always set the useParentHandlers flag + if (this.isNamed) { + LogManager.getLogManager().checkAccess(); + } + this.notifyParentHandlers = notifyParentHandlers; + } + + /** + * Gets the nearest parent of this logger in the namespace, a {@code null} + * value will be returned if called on the root logger. + * + * @return the parent of this logger in the namespace. + * @since Android 1.0 + */ + public Logger getParent() { + return parent; + } + + /** + * Sets the parent of this logger in the namespace. This method should + * usually be used by the {@code LogManager} object only. This method does + * not check security. + * + * @param newParent + * the parent logger to set. + * @since Android 1.0 + */ + void internalSetParent(Logger newParent) { + //All hierarchy related modifications should get LogManager lock at first + synchronized(LogManager.getLogManager()){ + parent = newParent; + // -- update level after setting a parent. + // -- if level == null we should inherit the parent's level + if (null == levelObjVal) { + setLevelImpl(levelObjVal); + } + newParent.addChild(this); + } + } + + /** + * Sets the parent of this logger in the namespace. This method should be + * used by the {@code LogManager} object only. + * + * @param parent + * the parent logger to set. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + public void setParent(Logger parent) { + if (null == parent) { + // logging.B=The 'parent' parameter is null. + throw new NullPointerException(Messages.getString("logging.B")); //$NON-NLS-1$ + } + // even anonymous loggers are checked + LogManager.getLogManager().checkAccess(); + internalSetParent(parent); + } + + final void addChild(Logger logger) { + childs.add(logger); + } + + final void removeChild(Logger child) { + childs.remove(child); + } + + + /** + * Gets the name of this logger, {@code null} for anonymous loggers. + * + * @return the name of this logger. + * @since Android 1.0 + */ + public String getName() { + return this.name; + } + + /** + * Gets the loaded resource bundle used by this logger to localize logging + * messages. If the value is {@code null}, the parent's resource bundle will be + * inherited. + * + * @return the loaded resource bundle used by this logger. + * @since Android 1.0 + */ + public ResourceBundle getResourceBundle() { + return this.resBundle; + } + + /** + * Gets the name of the loaded resource bundle used by this logger to + * localize logging messages. If the value is {@code null}, the parent's resource + * bundle name will be inherited. + * + * @return the name of the loaded resource bundle used by this logger. + * @since Android 1.0 + */ + public String getResourceBundleName() { + return this.resBundleName; + } + + /** + * This method is for compatibility. Tests written to the reference + * implementation API imply that the isLoggable() method is not called + * directly. This behavior is important because subclass may override + * isLoggable() method, so that affect the result of log methods. + */ + private boolean internalIsLoggable(Level l) { + int effectiveLevel = levelIntVal; + if (effectiveLevel == Level.OFF.intValue()) { + // always return false if the effective level is off + return false; + } + return l.intValue() >= effectiveLevel; + } + + /** + * Determines whether this logger will actually log messages of the + * specified level. The effective level used to do the determination may be + * inherited from its parent. The default level is {@code Level.INFO}. + * + * @param l + * the level to check. + * @return {@code true} if this logger will actually log this level, + * otherwise {@code false}. + * @since Android 1.0 + */ + public boolean isLoggable(Level l) { + return internalIsLoggable(l); + } + + /* + * Sets the resource bundle and its name for a supplied LogRecord object. + * This method first tries to use this logger's resource bundle if any, + * otherwise try to inherit from this logger's parent, recursively up the + * namespace. Synchronize to ensure the consistency between resource bundle + * and its name. + */ + private void setResourceBundle(LogRecord record) { + if (null != this.resBundleName) { + record.setResourceBundle(this.resBundle); + record.setResourceBundleName(this.resBundleName); + } else { + Logger anyParent = this.parent; + // no need to synchronize here, because if resBundleName + // is not null, there is no chance to modify it + while (null != anyParent) { + if (null != anyParent.resBundleName) { + record.setResourceBundle(anyParent.resBundle); + record.setResourceBundleName(anyParent.resBundleName); + return; + } + anyParent = anyParent.parent; + } + } + } + + /** + * Logs a message indicating that a method has been entered. A log record + * with log level {@code Level.FINER}, log message "ENTRY", the specified + * source class name and source method name is submitted for logging. + * + * @param sourceClass + * the calling class name. + * @param sourceMethod + * the method name. + * @since Android 1.0 + */ + public void entering(String sourceClass, String sourceMethod) { + if (internalIsLoggable(Level.FINER)) { + LogRecord record = new LogRecord(Level.FINER, "ENTRY"); //$NON-NLS-1$ + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message indicating that a method has been entered. A log record + * with log level {@code Level.FINER}, log message "ENTRY", the specified + * source class name, source method name and one parameter is submitted for + * logging. + * + * @param sourceClass + * the source class name. + * @param sourceMethod + * the source method name. + * @param param + * the parameter for the method call. + * @since Android 1.0 + */ + public void entering(String sourceClass, String sourceMethod, Object param) { + if (internalIsLoggable(Level.FINER)) { + LogRecord record = new LogRecord(Level.FINER, "ENTRY" + " {0}"); //$NON-NLS-1$ //$NON-NLS-2$ + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + record.setParameters(new Object[] { param }); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message indicating that a method has been entered. A log record + * with log level {@code Level.FINER}, log message "ENTRY", the specified + * source class name, source method name and array of parameters is + * submitted for logging. + * + * @param sourceClass + * the source class name. + * @param sourceMethod + * the source method name. + * @param params + * an array of parameters for the method call. + * @since Android 1.0 + */ + public void entering(String sourceClass, String sourceMethod, + Object[] params) { + if (internalIsLoggable(Level.FINER)) { + String msg = "ENTRY"; //$NON-NLS-1$ + if (null != params) { + StringBuilder msgBuffer = new StringBuilder("ENTRY"); //$NON-NLS-1$ + for (int i = 0; i < params.length; i++) { + msgBuffer.append(" {" + i + "}"); //$NON-NLS-1$ //$NON-NLS-2$ + } + msg = msgBuffer.toString(); + } + LogRecord record = new LogRecord(Level.FINER, msg); + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + record.setParameters(params); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message indicating that a method is exited. A log record with log + * level {@code Level.FINER}, log message "RETURN", the specified source + * class name and source method name is submitted for logging. + * + * @param sourceClass + * the calling class name. + * @param sourceMethod + * the method name. + * @since Android 1.0 + */ + public void exiting(String sourceClass, String sourceMethod) { + if (internalIsLoggable(Level.FINER)) { + LogRecord record = new LogRecord(Level.FINER, "RETURN"); //$NON-NLS-1$ + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message indicating that a method is exited. A log record with log + * level {@code Level.FINER}, log message "RETURN", the specified source + * class name, source method name and return value is submitted for logging. + * + * @param sourceClass + * the source class name. + * @param sourceMethod + * the source method name. + * @param result + * the return value of the method call. + * @since Android 1.0 + */ + public void exiting(String sourceClass, String sourceMethod, Object result) { + if (internalIsLoggable(Level.FINER)) { + LogRecord record = new LogRecord(Level.FINER, "RETURN" + " {0}"); //$NON-NLS-1$ //$NON-NLS-2$ + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + record.setParameters(new Object[] { result }); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message indicating that an exception is thrown. A log record with + * log level {@code Level.FINER}, log message "THROW", the specified source + * class name, source method name and the {@code Throwable} object is + * submitted for logging. + * + * @param sourceClass + * the source class name. + * @param sourceMethod + * the source method name. + * @param thrown + * the {@code Throwable} object. + * @since Android 1.0 + */ + public void throwing(String sourceClass, String sourceMethod, + Throwable thrown) { + if (internalIsLoggable(Level.FINER)) { + LogRecord record = new LogRecord(Level.FINER, "THROW"); //$NON-NLS-1$ + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + record.setThrown(thrown); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of level {@code Level.SEVERE}; the message is transmitted + * to all subscribed handlers. + * + * @param msg + * the message to log. + * @since Android 1.0 + */ + public void severe(String msg) { + if (internalIsLoggable(Level.SEVERE)) { + LogRecord record = new LogRecord(Level.SEVERE, msg); + record.setLoggerName(this.name); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of level {@code Level.WARNING}; the message is + * transmitted to all subscribed handlers. + * + * @param msg + * the message to log. + * @since Android 1.0 + */ + public void warning(String msg) { + if (internalIsLoggable(Level.WARNING)) { + LogRecord record = new LogRecord(Level.WARNING, msg); + record.setLoggerName(this.name); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of level {@code Level.INFO}; the message is transmitted + * to all subscribed handlers. + * + * @param msg + * the message to log. + * @since Android 1.0 + */ + public void info(String msg) { + if (internalIsLoggable(Level.INFO)) { + LogRecord record = new LogRecord(Level.INFO, msg); + record.setLoggerName(this.name); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of level {@code Level.CONFIG}; the message is transmitted + * to all subscribed handlers. + * + * @param msg + * the message to log. + * @since Android 1.0 + */ + public void config(String msg) { + if (internalIsLoggable(Level.CONFIG)) { + LogRecord record = new LogRecord(Level.CONFIG, msg); + record.setLoggerName(this.name); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of level {@code Level.FINE}; the message is transmitted + * to all subscribed handlers. + * + * @param msg + * the message to log. + * @since Android 1.0 + */ + public void fine(String msg) { + if (internalIsLoggable(Level.FINE)) { + LogRecord record = new LogRecord(Level.FINE, msg); + record.setLoggerName(this.name); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of level {@code Level.FINER}; the message is transmitted + * to all subscribed handlers. + * + * @param msg + * the message to log. + * @since Android 1.0 + */ + public void finer(String msg) { + if (internalIsLoggable(Level.FINER)) { + LogRecord record = new LogRecord(Level.FINER, msg); + record.setLoggerName(this.name); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of level {@code Level.FINEST}; the message is transmitted + * to all subscribed handlers. + * + * @param msg + * the message to log. + * @since Android 1.0 + */ + public void finest(String msg) { + if (internalIsLoggable(Level.FINEST)) { + LogRecord record = new LogRecord(Level.FINEST, msg); + record.setLoggerName(this.name); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of the specified level. The message is transmitted to all + * subscribed handlers. + * + * @param logLevel + * the level of the specified message. + * @param msg + * the message to log. + * @since Android 1.0 + */ + public void log(Level logLevel, String msg) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + record.setLoggerName(this.name); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of the specified level with the supplied parameter. The + * message is then transmitted to all subscribed handlers. + * + * @param logLevel + * the level of the given message. + * @param msg + * the message to log. + * @param param + * the parameter associated with the event that is logged. + * @since Android 1.0 + */ + public void log(Level logLevel, String msg, Object param) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + record.setLoggerName(this.name); + record.setParameters(new Object[] { param }); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of the specified level with the supplied parameter array. + * The message is then transmitted to all subscribed handlers. + * + * @param logLevel + * the level of the given message + * @param msg + * the message to log. + * @param params + * the parameter array associated with the event that is logged. + * @since Android 1.0 + */ + public void log(Level logLevel, String msg, Object[] params) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + record.setLoggerName(this.name); + record.setParameters(params); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of the specified level with the supplied {@code Throwable} + * object. The message is then transmitted to all subscribed handlers. + * + * @param logLevel + * the level of the given message. + * @param msg + * the message to log. + * @param thrown + * the {@code Throwable} object associated with the event that is + * logged. + * @since Android 1.0 + */ + public void log(Level logLevel, String msg, Throwable thrown) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + record.setLoggerName(this.name); + record.setThrown(thrown); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a given log record. Only records with a logging level that is equal + * or greater than this logger's level will be submitted to this logger's + * handlers for logging. If {@code getUseParentHandlers()} returns {@code + * true}, the log record will also be submitted to the handlers of this + * logger's parent, potentially recursively up the namespace. + * <p> + * Since all other log methods call this method to actually perform the + * logging action, subclasses of this class can override this method to + * catch all logging activities. + * </p> + * + * @param record + * the log record to be logged. + * @since Android 1.0 + */ + public void log(LogRecord record) { + if (internalIsLoggable(record.getLevel())) { + // apply the filter if any + Filter f = filter; + if (null != f && !f.isLoggable(record)) { + return; + } + initHandler(); + /* + * call the handlers of this logger, throw any exception that + * occurs + */ + Handler[] allHandlers = getHandlers(); + for (Handler element : allHandlers) { + element.publish(record); + } + // call the parent's handlers if set useParentHandlers + Logger temp = this; + Logger theParent = temp.parent; + while (theParent != null && temp.getUseParentHandlers()) { + Handler[] ha = theParent.getHandlers(); + for (Handler element : ha) { + element.publish(record); + } + temp = theParent; + theParent = temp.parent; + } + } + } + + /** + * Logs a message of the given level with the specified source class name + * and source method name. + * + * @param logLevel + * the level of the given message. + * @param sourceClass + * the source class name. + * @param sourceMethod + * the source method name. + * @param msg + * the message to be logged. + * @since Android 1.0 + */ + public void logp(Level logLevel, String sourceClass, String sourceMethod, + String msg) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of the given level with the specified source class name, + * source method name and parameter. + * + * @param logLevel + * the level of the given message + * @param sourceClass + * the source class name + * @param sourceMethod + * the source method name + * @param msg + * the message to be logged + * @param param + * the parameter associated with the event that is logged. + * @since Android 1.0 + */ + public void logp(Level logLevel, String sourceClass, String sourceMethod, + String msg, Object param) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + record.setParameters(new Object[] { param }); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of the given level with the specified source class name, + * source method name and parameter array. + * + * @param logLevel + * the level of the given message. + * @param sourceClass + * the source class name. + * @param sourceMethod + * the source method name. + * @param msg + * the message to be logged. + * @param params + * the parameter array associated with the event that is logged. + * @since Android 1.0 + */ + public void logp(Level logLevel, String sourceClass, String sourceMethod, + String msg, Object[] params) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + record.setParameters(params); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of the given level with the specified source class name, + * source method name and {@code Throwable} object. + * + * @param logLevel + * the level of the given message. + * @param sourceClass + * the source class name. + * @param sourceMethod + * the source method name. + * @param msg + * the message to be logged. + * @param thrown + * the {@code Throwable} object. + * @since Android 1.0 + */ + public void logp(Level logLevel, String sourceClass, String sourceMethod, + String msg, Throwable thrown) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + record.setThrown(thrown); + setResourceBundle(record); + log(record); + } + } + + /** + * Logs a message of the given level with the specified source class name + * and source method name, using the given resource bundle to localize the + * message. If {@code bundleName} is null, the empty string or not valid then + * the message is not localized. + * + * @param logLevel + * the level of the given message. + * @param sourceClass + * the source class name. + * @param sourceMethod + * the source method name. + * @param bundleName + * the name of the resource bundle used to localize the message. + * @param msg + * the message to be logged. + * @since Android 1.0 + */ + public void logrb(Level logLevel, String sourceClass, String sourceMethod, + String bundleName, String msg) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + if (null != bundleName) { + try { + record.setResourceBundle(loadResourceBundle(bundleName)); + } catch (MissingResourceException e) { + // ignore + } + record.setResourceBundleName(bundleName); + } + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + log(record); + } + } + + /** + * Logs a message of the given level with the specified source class name, + * source method name and parameter, using the given resource bundle to + * localize the message. If {@code bundleName} is null, the empty string + * or not valid then the message is not localized. + * + * @param logLevel + * the level of the given message. + * @param sourceClass + * the source class name. + * @param sourceMethod + * the source method name. + * @param bundleName + * the name of the resource bundle used to localize the message. + * @param msg + * the message to be logged. + * @param param + * the parameter associated with the event that is logged. + * @since Android 1.0 + */ + public void logrb(Level logLevel, String sourceClass, String sourceMethod, + String bundleName, String msg, Object param) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + if (null != bundleName) { + try { + record.setResourceBundle(loadResourceBundle(bundleName)); + } catch (MissingResourceException e) { + // ignore + } + record.setResourceBundleName(bundleName); + } + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + record.setParameters(new Object[] { param }); + log(record); + } + } + + /** + * Logs a message of the given level with the specified source class name, + * source method name and parameter array, using the given resource bundle + * to localize the message. If {@code bundleName} is null, the empty string + * or not valid then the message is not localized. + * + * @param logLevel + * the level of the given message. + * @param sourceClass + * the source class name. + * @param sourceMethod + * the source method name. + * @param bundleName + * the name of the resource bundle used to localize the message. + * @param msg + * the message to be logged. + * @param params + * the parameter array associated with the event that is logged. + * @since Android 1.0 + */ + public void logrb(Level logLevel, String sourceClass, String sourceMethod, + String bundleName, String msg, Object[] params) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + if (null != bundleName) { + try { + record.setResourceBundle(loadResourceBundle(bundleName)); + } catch (MissingResourceException e) { + // ignore + } + record.setResourceBundleName(bundleName); + } + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + record.setParameters(params); + log(record); + } + } + + /** + * Logs a message of the given level with the specified source class name, + * source method name and {@code Throwable} object, using the given resource + * bundle to localize the message. If {@code bundleName} is null, the empty + * string or not valid then the message is not localized. + * + * @param logLevel + * the level of the given message + * @param sourceClass + * the source class name + * @param sourceMethod + * the source method name + * @param bundleName + * the name of the resource bundle used to localize the message. + * @param msg + * the message to be logged. + * @param thrown + * the {@code Throwable} object. + * @since Android 1.0 + */ + public void logrb(Level logLevel, String sourceClass, String sourceMethod, + String bundleName, String msg, Throwable thrown) { + if (internalIsLoggable(logLevel)) { + LogRecord record = new LogRecord(logLevel, msg); + if (null != bundleName) { + try { + record.setResourceBundle(loadResourceBundle(bundleName)); + } catch (MissingResourceException e) { + // ignore + } + record.setResourceBundleName(bundleName); + } + record.setLoggerName(this.name); + record.setSourceClassName(sourceClass); + record.setSourceMethodName(sourceMethod); + record.setThrown(thrown); + log(record); + } + } + + /* + * This security manager is used to access the class context. + */ + static class PrivateSecurityManager extends SecurityManager { + public Class<?>[] privateGetClassContext() { + return super.getClassContext(); + } + } + + void setManager(LogManager manager) { + if(this.manager != manager){ + this.manager = manager; + handlerInited = false; + } + //init level here, but let handlers be for lazy loading + String configedLevel = manager.getProperty(name+ ".level"); //$NON-NLS-1$ + if (null != configedLevel) { + try { + setLevel(Level.parse(configedLevel)); + } catch (IllegalArgumentException e) { + //ignore + } + } + } + + synchronized void reset() { + levelObjVal = null; + levelIntVal = Level.INFO.intValue(); + if(handlers != null){ + for (Handler element : handlers) { + // close all handlers, when unknown exceptions happen, + // ignore them and go on + try { + element.close(); + } catch (Exception e) { + // Ignored. + } + } + handlers.clear(); + } + handlerInited = false; + } +} + diff --git a/logging/src/main/java/java/util/logging/LoggingMXBean.java b/logging/src/main/java/java/util/logging/LoggingMXBean.java new file mode 100644 index 0000000..f6b49a6 --- /dev/null +++ b/logging/src/main/java/java/util/logging/LoggingMXBean.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.logging; + +import java.util.List; + +/** + * {@code LoggingMXBean} is the management interface for the logging sub-system. + * <p> + * The ObjectName for identifying the {@code LoggingMXBean} in a bean server is + * {@link LogManager#LOGGING_MXBEAN_NAME}. + * </p> + * + * @since Android 1.0 + */ +public interface LoggingMXBean { + /** + * Gets the string value of the logging level of a logger. An empty string + * is returned when the logger's level is defined by its parent. A + * {@code null} is returned if the specified logger does not exist. + * + * @param loggerName + * the name of the logger lookup. + * @return a {@code String} if the logger is found, otherwise {@code null}. + * @see Level#getName() + * @since Android 1.0 + */ + String getLoggerLevel(String loggerName); + + /** + * Gets a list of all currently registered logger names. This is performed + * using the {@link LogManager#getLoggerNames()}. + * + * @return a list of logger names. + * @since Android 1.0 + */ + List<String> getLoggerNames(); + + /** + * Gets the name of the parent logger of a logger. If the logger doesn't + * exist then {@code null} is returned. If the logger is the root logger, + * then an empty {@code String} is returned. + * + * @param loggerName + * the name of the logger to lookup. + * @return a {@code String} if the logger was found, otherwise {@code null}. + * @since Android 1.0 + */ + String getParentLoggerName(String loggerName); + + /** + * Sets the log level of a logger. LevelName set to {@code null} means the + * level is inherited from the nearest non-null ancestor. + * + * @param loggerName + * the name of the logger to set the level on, which must not be + * {@code null}. + * @param levelName + * the level to set on the logger, which may be {@code null}. + * @throws IllegalArgumentException + * if {@code loggerName} is not a registered logger or if + * {@code levelName} is not null and not valid. + * @throws SecurityException + * if a security manager exists and the caller doesn't have + * LoggingPermission("control"). + * @see Level#parse(String) + * @since Android 1.0 + */ + void setLoggerLevel(String loggerName, String levelName); +} diff --git a/logging/src/main/java/java/util/logging/LoggingPermission.java b/logging/src/main/java/java/util/logging/LoggingPermission.java new file mode 100644 index 0000000..fb6d4f8 --- /dev/null +++ b/logging/src/main/java/java/util/logging/LoggingPermission.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package java.util.logging; + +import java.io.Serializable; +import java.security.BasicPermission; +import java.security.Guard; + +import org.apache.harmony.logging.internal.nls.Messages; + +/** + * The permission required to control the logging when run with a + * {@code SecurityManager}. + * + */ +public final class LoggingPermission extends BasicPermission implements Guard, + Serializable { + + //for serialization compatibility with J2SE 1.4.2 + private static final long serialVersionUID =63564341580231582L; + + + /* + * ------------------------------------------------------------------- + * Constructors + * ------------------------------------------------------------------- + */ + + /** + * Constructs a {@code LoggingPermission} object required to control the + * logging. The {@code SecurityManager} checks the permissions. + * <p> + * {@code LoggingPermission} objects are created by the security policy code + * and depends on the security policy file, therefore programmers shouldn't + * normally use them directly. + * </p> + * + * @param name + * currently must be "control". + * @param actions + * currently must be either {@code null} or the empty string. + * @throws IllegalArgumentException + * if name null or different from {@code string} control. + */ + public LoggingPermission(String name, String actions) { + super(name, actions); + if (!"control".equals(name)) { //$NON-NLS-1$ + // logging.6=Name must be "control". + throw new IllegalArgumentException(Messages.getString("logging.6")); //$NON-NLS-1$ + } + if (null != actions && !"".equals(actions)) { //$NON-NLS-1$ + // logging.7=Actions must be either null or the empty string. + throw new IllegalArgumentException(Messages.getString("logging.7")); //$NON-NLS-1$ + } + } + +} + diff --git a/logging/src/main/java/java/util/logging/MemoryHandler.java b/logging/src/main/java/java/util/logging/MemoryHandler.java new file mode 100644 index 0000000..c1e8670 --- /dev/null +++ b/logging/src/main/java/java/util/logging/MemoryHandler.java @@ -0,0 +1,295 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.logging; + +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; + +import org.apache.harmony.logging.internal.nls.Messages; + + +/** + * A {@code Handler} put the description of log events into a cycled memory + * buffer. + * <p> + * Mostly this {@code MemoryHandler} just puts the given {@code LogRecord} into + * the internal buffer and doesn't perform any formatting or any other process. + * When the buffer is full, the earliest buffered records will be discarded. + * </p> + * <p> + * Every {@code MemoryHandler} has a target handler, and push action can be + * triggered so that all buffered records will be output to the target handler + * and normally the latter will publish the records. After the push action, the + * buffer will be cleared. + * </p> + * <p> + * The push action can be triggered in three ways: + * <ul> + * <li>The push method is called explicitly</li> + * <li>When a new {@code LogRecord} is put into the internal buffer, and it has + * a level which is not less than the specified push level.</li> + * <li>A subclass extends this {@code MemoryHandler} and call push method + * implicitly according to some criteria.</li> + * </ul> + * </p> + * <p> + * {@code MemoryHandler} will read following {@code LogManager} properties for + * initialization, if given properties are not defined or has invalid values, + * default value will be used. + * <ul> + * <li>java.util.logging.MemoryHandler.level specifies the level for this + * {@code Handler}, defaults to {@code Level.ALL}.</li> + * <li>java.util.logging.MemoryHandler.filter specifies the {@code Filter} + * class name, defaults to no {@code Filter}.</li> + * <li>java.util.logging.MemoryHandler.size specifies the buffer size in number + * of {@code LogRecord}, defaults to 1000.</li> + * <li>java.util.logging.MemoryHandler.push specifies the push level, defaults + * to level.SEVERE.</li> + * <li>java.util.logging.MemoryHandler.target specifies the class of the target + * {@code Handler}, no default value, which means this property must be + * specified either by property setting or by constructor.</li> + * </ul> + * </p> + */ +public class MemoryHandler extends Handler { + + //default maximum buffered number of LogRecord + private static final int DEFAULT_SIZE = 1000; + //target handler + private Handler target; + + //buffer size + private int size = DEFAULT_SIZE; + + //push level + private Level push = Level.SEVERE; + + //LogManager instance for convenience + private final LogManager manager = LogManager.getLogManager(); + + //buffer + private LogRecord[] buffer; + + //current position in buffer + private int cursor; + + /** + * Default constructor, construct and init a {@code MemoryHandler} using + * {@code LogManager} properties or default values. + * + * @throws RuntimeException + * if property value are invalid and no default value could be + * used. + */ + public MemoryHandler() { + super(); + String className = this.getClass().getName(); + //init target + final String targetName = manager.getProperty(className+".target"); //$NON-NLS-1$ + try { + Class<?> targetClass = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>(){ + public Class<?> run() throws Exception{ + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + if(loader == null){ + loader = ClassLoader.getSystemClassLoader(); + } + return loader.loadClass(targetName); + } + }); + target = (Handler) targetClass.newInstance(); + } catch (Exception e) { + // logging.10=Cannot load target handler:{0} + throw new RuntimeException(Messages.getString("logging.10", //$NON-NLS-1$ + targetName)); + } + //init size + String sizeString = manager.getProperty(className+".size"); //$NON-NLS-1$ + if (null != sizeString) { + try { + size = Integer.parseInt(sizeString); + if(size <= 0){ + size = DEFAULT_SIZE; + } + } catch (Exception e) { + printInvalidPropMessage(className+".size", sizeString, e); //$NON-NLS-1$ + } + } + //init push level + String pushName = manager.getProperty(className+".push"); //$NON-NLS-1$ + if (null != pushName) { + try { + push = Level.parse(pushName); + } catch (Exception e) { + printInvalidPropMessage(className+".push", pushName, e); //$NON-NLS-1$ + } + } + //init other properties which are common for all Handler + initProperties("ALL", null, "java.util.logging.SimpleFormatter", null); //$NON-NLS-1$//$NON-NLS-2$ + buffer = new LogRecord[size]; + } + + /** + * Construct and init a {@code MemoryHandler} using given target, size and + * push level, other properties using {@code LogManager} properties or + * default values. + * + * @param target + * the given {@code Handler} to output + * @param size + * the maximum number of buffered {@code LogRecord}, greater than + * zero + * @param pushLevel + * the push level + * @throws IllegalArgumentException + * if {@code size}<=0 + * @throws RuntimeException + * if property value are invalid and no default value could be + * used. + */ + public MemoryHandler(Handler target, int size, Level pushLevel) { + if (size <= 0) { + // logging.11=Size must be positive. + throw new IllegalArgumentException(Messages.getString("logging.11")); //$NON-NLS-1$ + } + target.getLevel(); + pushLevel.intValue(); + this.target = target; + this.size = size; + this.push = pushLevel; + initProperties("ALL", null, "java.util.logging.SimpleFormatter", null); //$NON-NLS-1$//$NON-NLS-2$ + buffer = new LogRecord[size]; + } + + /** + * Close this handler and target handler, free all associated resources. + * + * @throws SecurityException + * if security manager exists and it determines that caller does + * not have the required permissions to control this handler. + */ + @Override + public void close() { + manager.checkAccess(); + target.close(); + setLevel(Level.OFF); + } + + /** + * Call target handler to flush any buffered output. Note that this doesn't + * cause this {@code MemoryHandler} to push. + */ + @Override + public void flush() { + target.flush(); + } + + /** + * Put a given {@code LogRecord} into internal buffer. If given record is + * not loggable, just return. Otherwise it is stored in the buffer. + * Furthermore if the record's level is not less than the push level, the + * push action is triggered to output all the buffered records to the target + * handler, and the target handler will publish them. + * + * @param record + * the log record + */ + @Override + public synchronized void publish(LogRecord record) { + if (!isLoggable(record)) { + return; + } + if (cursor >= size) { + cursor = 0; + } + buffer[cursor++] = record; + if (record.getLevel().intValue() >= push.intValue()) { + push(); + } + return; + } + + /** + * Return the push level. + * + * @return the push level + */ + public Level getPushLevel() { + return push; + } + + /** + * <p> + * Check if given {@code LogRecord} would be put into this + * {@code MemoryHandler}'s internal buffer. + * </p> + * <p> + * The given {@code LogRecord} is loggable if and only if it has appropriate + * level and it pass any associated filter's check. + * </p> + * <p> + * Note that the push level is not used for this check. + * </p> + * + * @param record + * the given {@code LogRecord} + * @return the given {@code LogRecord} if it should be logged, {@code false} + * if {@code LogRecord} is {@code null}. + */ + @Override + public boolean isLoggable(LogRecord record) { + return super.isLoggable(record); + } + + /** + * Triggers a push action to output all buffered records to the target handler, + * and the target handler will publish them. Then the buffer is cleared. + */ + public void push() { + for (int i = cursor; i < size; i++) { + if(null != buffer[i]) { + target.publish(buffer[i]); + } + buffer[i] = null; + } + for (int i = 0; i < cursor; i++) { + if(null != buffer[i]) { + target.publish(buffer[i]); + } + buffer[i] = null; + } + cursor = 0; + } + + /** + * Set the push level. The push level is used to check the push action + * triggering. When a new {@code LogRecord} is put into the internal + * buffer and its level is not less than the push level, the push action + * will be triggered. Note that set new push level won't trigger push action. + * + * @param newLevel + * the new level to set. + * @throws SecurityException + * if security manager exists and it determines that caller + * does not have the required permissions to control this handler. + */ + public void setPushLevel(Level newLevel) { + manager.checkAccess(); + newLevel.intValue(); + this.push = newLevel; + } +} diff --git a/logging/src/main/java/java/util/logging/SimpleFormatter.java b/logging/src/main/java/java/util/logging/SimpleFormatter.java new file mode 100644 index 0000000..1595796 --- /dev/null +++ b/logging/src/main/java/java/util/logging/SimpleFormatter.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package java.util.logging; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.MessageFormat; +import java.util.Date; + +/** + * {@code SimpleFormatter} can be used to print a summary of the information + * contained in a {@code LogRecord} object in a human readable format. + * @since Android 1.0 + */ +public class SimpleFormatter extends Formatter { + /** + * Constructs a new {@code SimpleFormatter}. + * + * @since Android 1.0 + */ + public SimpleFormatter() { + super(); + } + + /** + * Converts a {@link LogRecord} object into a human readable string + * representation. + * + * @param r + * the log record to be formatted into a string. + * @return the formatted string. + * @since Android 1.0 + */ + @Override + public String format(LogRecord r) { + StringBuilder sb = new StringBuilder(); + sb.append(MessageFormat.format("{0, date} {0, time} ", //$NON-NLS-1$ + new Object[] { new Date(r.getMillis()) })); + sb.append(r.getSourceClassName()).append(" "); //$NON-NLS-1$ + sb.append(r.getSourceMethodName()).append(LogManager.getSystemLineSeparator()); //$NON-NLS-1$ + sb.append(r.getLevel().getName()).append(": "); //$NON-NLS-1$ + sb.append(formatMessage(r)).append(LogManager.getSystemLineSeparator()); + if (null != r.getThrown()) { + sb.append("Throwable occurred: "); //$NON-NLS-1$ + Throwable t = r.getThrown(); + PrintWriter pw = null; + try { + StringWriter sw = new StringWriter(); + pw = new PrintWriter(sw); + t.printStackTrace(pw); + sb.append(sw.toString()); + } finally { + if(pw != null){ + try { + pw.close(); + } catch (Exception e) { + // ignore + } + } + } + } + return sb.toString(); + } +} + diff --git a/logging/src/main/java/java/util/logging/SocketHandler.java b/logging/src/main/java/java/util/logging/SocketHandler.java new file mode 100644 index 0000000..8626007 --- /dev/null +++ b/logging/src/main/java/java/util/logging/SocketHandler.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package java.util.logging; + +import java.net.Socket; +import java.io.BufferedOutputStream; +import java.io.IOException; + +import org.apache.harmony.logging.internal.nls.Messages; + +/** + * A handler that writes log messages to a socket connection. + * <p> + * This handler reads the following properties from the log manager to + * initialize itself: + * <ul> + * <li>java.util.logging.ConsoleHandler.level specifies the logging level, + * defaults to {@code Level.ALL} if this property is not found or has an invalid + * value. + * <li>java.util.logging.SocketHandler.filter specifies the name of the filter + * class to be associated with this handler, defaults to {@code null} if this + * property is not found or has an invalid value. + * <li>java.util.logging.SocketHandler.formatter specifies the name of the + * formatter class to be associated with this handler, defaults to + * {@code java.util.logging.XMLFormatter} if this property is not found or has + * an invalid value. + * <li>java.util.logging.SocketHandler.encoding specifies the encoding this + * handler will use to encode log messages, defaults to {@code null} if this + * property is not found or has an invalid value. + * <li>java.util.logging.SocketHandler.host specifies the name of the host that + * this handler should connect to. There's no default value for this property. + * <li>java.util.logging.SocketHandler.encoding specifies the port number that + * this handler should connect to. There's no default value for this property. + * </ul> + * </p> + * <p> + * This handler buffers the outgoing messages, but flushes each time a log + * record has been published. + * </p> + * <p> + * This class is not thread-safe. + * </p> + */ +public class SocketHandler extends StreamHandler { + // default level + private static final String DEFAULT_LEVEL = "ALL"; //$NON-NLS-1$ + + // default formatter + private static final String DEFAULT_FORMATTER = "java.util.logging.XMLFormatter"; //$NON-NLS-1$ + + // the socket connection + private Socket socket; + + /** + * Constructs a {@code SocketHandler} object using the properties read by + * the log manager, including the host name and port number. Default + * formatting uses the XMLFormatter class and level is set to ALL. + * + * @throws IOException + * if failed to connect to the specified host and port. + * @throws IllegalArgumentException + * if the host name or port number is illegal. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission to control this handler. + */ + public SocketHandler() throws IOException { + super(DEFAULT_LEVEL, null, DEFAULT_FORMATTER, null); + initSocket(LogManager.getLogManager().getProperty( + "java.util.logging.SocketHandler.host"), LogManager //$NON-NLS-1$ + .getLogManager().getProperty( + "java.util.logging.SocketHandler.port")); //$NON-NLS-1$ + } + + /** + * Constructs a {@code SocketHandler} object using the specified host name + * and port number together with other properties read by the log manager. + * Default formatting uses the XMLFormatter class and level is set to ALL. + * + * @param host + * the host name + * @param port + * the port number + * @throws IOException + * if failed to connect to the specified host and port. + * @throws IllegalArgumentException + * if the host name or port number is illegal. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission to control this handler. + */ + public SocketHandler(String host, int port) throws IOException { + super(DEFAULT_LEVEL, null, DEFAULT_FORMATTER, null); + initSocket(host, String.valueOf(port)); + } + + // Initialize the socket connection and prepare the output stream + private void initSocket(String host, String port) throws IOException { + // check the validity of the host name + if (null == host || "".equals(host)) { //$NON-NLS-1$ + // logging.C=Illegal host argument. + throw new IllegalArgumentException(Messages.getString("logging.C")); //$NON-NLS-1$ + } + // check the validity of the port number + int p = 0; + try { + p = Integer.parseInt(port); + } catch (NumberFormatException e) { + // logging.D=Illegal port argument. + throw new IllegalArgumentException(Messages.getString("logging.D")); //$NON-NLS-1$ + } + if (p <= 0) { + // logging.D=Illegal port argument. + throw new IllegalArgumentException(Messages.getString("logging.D")); //$NON-NLS-1$ + } + // establish the network connection + try { + this.socket = new Socket(host, p); + } catch (IOException e) { + // logging.E=Failed to establish the network connection. + getErrorManager().error(Messages.getString("logging.E"), e, //$NON-NLS-1$ + ErrorManager.OPEN_FAILURE); + throw e; + } + // BEGIN android-modified + super.internalSetOutputStream(new BufferedOutputStream(this.socket + .getOutputStream(), 8192)); + // END android-modified + } + + /** + * Closes this handler. The network connection to the host is also closed. + * + * @throws SecurityException + * If a security manager determines that the caller does not + * have the required permission to control this handler. + */ + @Override + public void close() { + try { + super.close(); + if (null != this.socket) { + this.socket.close(); + this.socket = null; + } + } catch (Exception e) { + // logging.F=Exception occurred when closing the socket handler. + getErrorManager().error(Messages.getString("logging.F"), e, //$NON-NLS-1$ + ErrorManager.CLOSE_FAILURE); + } + } + + /** + * Logs a record if necessary. A flush operation will be done afterwards. + * + * @param record + * the log record to be logged. + */ + @Override + public void publish(LogRecord record) { + super.publish(record); + super.flush(); + } + +} diff --git a/logging/src/main/java/java/util/logging/StreamHandler.java b/logging/src/main/java/java/util/logging/StreamHandler.java new file mode 100644 index 0000000..ee12190 --- /dev/null +++ b/logging/src/main/java/java/util/logging/StreamHandler.java @@ -0,0 +1,367 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package java.util.logging; + +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +import org.apache.harmony.logging.internal.nls.Messages; + +/** + * A {@code StreamHandler} object writes log messages to an output stream, that + * is, objects of the class {@link java.io.OutputStream}. + * <p> + * A {@code StreamHandler} object reads the following properties from the log + * manager to initialize itself: + * <ul> + * <li>java.util.logging.StreamHandler.level specifies the logging level, + * defaults to {@code Level.INFO} if this property is not found or has an + * invalid value. + * <li>java.util.logging.StreamHandler.filter specifies the name of the filter + * class to be associated with this handler, defaults to {@code null} if this + * property is not found or has an invalid value. + * <li>java.util.logging.StreamHandler.formatter specifies the name of the + * formatter class to be associated with this handler, defaults to + * {@code java.util.logging.SimpleFormatter} if this property is not found or + * has an invalid value. + * <li>java.util.logging.StreamHandler.encoding specifies the encoding this + * handler will use to encode log messages, defaults to {@code null} if this + * property is not found or has an invalid value. + * </ul> + * </p> + * <p> + * This class is not thread-safe. + * </p> + * + * @since Android 1.0 + */ +public class StreamHandler extends Handler { + // the output stream this handler writes to + private OutputStream os; + + // the writer that writes to the output stream + private Writer writer; + + // the flag indicating whether the writer has been initialized + private boolean writerNotInitialized; + + /** + * Constructs a {@code StreamHandler} object. The new stream handler + * does not have an associated output stream. + * + * @since Android 1.0 + */ + public StreamHandler() { + initProperties("INFO", null, "java.util.logging.SimpleFormatter", //$NON-NLS-1$//$NON-NLS-2$ + null); + this.os = null; + this.writer = null; + this.writerNotInitialized = true; + } + + /** + * Constructs a {@code StreamHandler} object with the supplied output + * stream. Default properties are read. + * + * @param os + * the output stream this handler writes to. + */ + StreamHandler(OutputStream os) { + this(); + this.os = os; + } + + /** + * Constructs a {@code StreamHandler} object. The specified default values + * will be used if the corresponding properties are not found in the log + * manager's properties. + */ + StreamHandler(String defaultLevel, String defaultFilter, + String defaultFormatter, String defaultEncoding) { + initProperties(defaultLevel, defaultFilter, defaultFormatter, + defaultEncoding); + this.os = null; + this.writer = null; + this.writerNotInitialized = true; + } + + /** + * Constructs a {@code StreamHandler} object with the supplied output stream + * and formatter. + * + * @param os + * the output stream this handler writes to. + * @param formatter + * the formatter this handler uses to format the output. + * @throws NullPointerException + * if {@code os} or {@code formatter} is {@code null}. + * @since Android 1.0 + */ + public StreamHandler(OutputStream os, Formatter formatter) { + this(); + if (os == null) { + // logging.2=The OutputStream parameter is null + throw new NullPointerException(Messages.getString("logging.2")); //$NON-NLS-1$ + } + if (formatter == null) { + // logging.3=The Formatter parameter is null. + throw new NullPointerException(Messages.getString("logging.3")); //$NON-NLS-1$ + } + this.os = os; + internalSetFormatter(formatter); + } + + // initialize the writer + private void initializeWritter() { + this.writerNotInitialized = false; + if (null == getEncoding()) { + this.writer = new OutputStreamWriter(this.os); + } else { + try { + this.writer = new OutputStreamWriter(this.os, getEncoding()); + } catch (UnsupportedEncodingException e) { + /* + * Should not happen because it's checked in + * super.initProperties(). + */ + } + } + write(getFormatter().getHead(this)); + } + + // Write a string to the output stream. + private void write(String s) { + try { + this.writer.write(s); + } catch (Exception e) { + // logging.14=Exception occurred when writing to the output stream. + getErrorManager().error(Messages.getString("logging.14"), e, //$NON-NLS-1$ + ErrorManager.WRITE_FAILURE); + } + } + + /** + * Sets the output stream this handler writes to. Note it does nothing else. + * + * @param newOs + * the new output stream + */ + void internalSetOutputStream(OutputStream newOs) { + this.os = newOs; + } + + + /** + * Sets the output stream this handler writes to. If there's an existing + * output stream, the tail string of the associated formatter will be + * written to it. Then it will be flushed, closed and replaced with + * {@code os}. + * + * @param os + * the new output stream. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @throws NullPointerException + * if {@code os} is {@code null}. + */ + protected void setOutputStream(OutputStream os) { + if (null == os) { + throw new NullPointerException(); + } + LogManager.getLogManager().checkAccess(); + close(true); + this.writer = null; + this.os = os; + this.writerNotInitialized = true; + } + + /** + * Sets the character encoding used by this handler. A {@code null} value + * indicates that the default encoding should be used. + * + * @param encoding + * the character encoding to set. + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @throws UnsupportedEncodingException + * if the specified encoding is not supported by the runtime. + * @since Android 1.0 + */ + @Override + public void setEncoding(String encoding) throws SecurityException, + UnsupportedEncodingException { + //flush first before set new encoding + this.flush(); + super.setEncoding(encoding); + // renew writer only if the writer exists + if (null != this.writer) { + if (null == getEncoding()) { + this.writer = new OutputStreamWriter(this.os); + } else { + try { + this.writer = new OutputStreamWriter(this.os, getEncoding()); + } catch (UnsupportedEncodingException e) { + /* + * Should not happen because it's checked in + * super.initProperties(). + */ + throw new AssertionError(e); + } + } + } + } + + /** + * Closes this handler, but the underlying output stream is only closed if + * {@code closeStream} is {@code true}. Security is not checked. + * + * @param closeStream + * whether to close the underlying output stream. + */ + void close(boolean closeStream) { + if (null != this.os) { + if (this.writerNotInitialized) { + initializeWritter(); + } + write(getFormatter().getTail(this)); + try { + this.writer.flush(); + if (closeStream) { + this.writer.close(); + this.writer = null; + this.os = null; + } + } catch (Exception e) { + // logging.15=Exception occurred when closing the output stream. + getErrorManager().error(Messages.getString("logging.15"), e, //$NON-NLS-1$ + ErrorManager.CLOSE_FAILURE); + } + } + } + + /** + * Closes this handler. The tail string of the formatter associated with + * this handler is written out. A flush operation and a subsequent close + * operation is then performed upon the output stream. Client applications + * should not use a handler after closing it. + * + * @throws SecurityException + * if a security manager determines that the caller does not + * have the required permission. + * @since Android 1.0 + */ + @Override + public void close() { + LogManager.getLogManager().checkAccess(); + close(true); + } + + /** + * Flushes any buffered output. + * + * @since Android 1.0 + */ + @Override + public void flush() { + if (null != this.os) { + try { + if (null != this.writer) { + this.writer.flush(); + } else { + this.os.flush(); + } + } catch (Exception e) { + // logging.16=Exception occurred while flushing the output stream. + getErrorManager().error(Messages.getString("logging.16"), //$NON-NLS-1$ + e, ErrorManager.FLUSH_FAILURE); + } + } + } + + /** + * Accepts a logging request. The log record is formatted and written to the + * output stream if the following three conditions are met: + * <ul> + * <li>the supplied log record has at least the required logging level; + * <li>the supplied log record passes the filter associated with this + * handler, if any; + * <li>the output stream associated with this handler is not {@code null}. + * </ul> + * If it is the first time a log record is written out, the head string of + * the formatter associated with this handler is written out first. + * + * @param record + * the log record to be logged. + * @since Android 1.0 + */ + @Override + public synchronized void publish(LogRecord record) { + try { + if (this.isLoggable(record)) { + if (this.writerNotInitialized) { + initializeWritter(); + } + String msg = null; + try { + msg = getFormatter().format(record); + } catch (Exception e) { + // logging.17=Exception occurred while formatting the log record. + getErrorManager().error(Messages.getString("logging.17"), //$NON-NLS-1$ + e, ErrorManager.FORMAT_FAILURE); + } + write(msg); + } + } catch (Exception e) { + // logging.18=Exception occurred while logging the record. + getErrorManager().error(Messages.getString("logging.18"), e, //$NON-NLS-1$ + ErrorManager.GENERIC_FAILURE); + } + } + + /** + * Determines whether the supplied log record needs to be logged. The + * logging levels are checked as well as the filter. The output stream of + * this handler is also checked. If it is {@code null}, this method returns + * {@code false}. + * <p> + * Notice : Case of no output stream will return {@code false}. + * </p> + * + * @param record + * the log record to be checked. + * @return {@code true} if {@code record} needs to be logged, {@code false} + * otherwise. + * @since Android 1.0 + */ + @Override + public boolean isLoggable(LogRecord record) { + if (null == record) { + return false; + } + if (null != this.os && super.isLoggable(record)) { + return true; + } + return false; + } + +} diff --git a/logging/src/main/java/java/util/logging/XMLFormatter.java b/logging/src/main/java/java/util/logging/XMLFormatter.java new file mode 100644 index 0000000..6279d8c --- /dev/null +++ b/logging/src/main/java/java/util/logging/XMLFormatter.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package java.util.logging; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.text.MessageFormat; +import java.util.Date; +import java.util.ResourceBundle; + +/** + * Formatter to convert a {@link LogRecord} into an XML string. The DTD + * specified in Appendix A to the Java Logging APIs specification is used. + * {@code XMLFormatter} uses the output handler's encoding if it is specified, + * otherwise the default platform encoding is used instead. UTF-8 is the + * recommended encoding. + * + * @since Android 1.0 + */ +public class XMLFormatter extends Formatter { + + private static final String lineSeperator = LogManager + .getSystemLineSeparator(); + + private static final String indent = " "; //$NON-NLS-1$ + + /** + * Constructs a new {@code XMLFormatter}. + * + * @since Android 1.0 + */ + public XMLFormatter() { + super(); + } + + /** + * Converts a {@code LogRecord} into an XML string. + * + * @param r + * the log record to be formatted. + * @return the log record formatted as an XML string. + * @since Android 1.0 + */ + @Override + public String format(LogRecord r) { + //call a method of LogRecord to ensure not null + long time = r.getMillis(); + //format to date + String date = MessageFormat.format("{0, date} {0, time}", //$NON-NLS-1$ + new Object[] { new Date(time) }); + + StringBuilder sb = new StringBuilder(); + sb.append(("<record>")).append(lineSeperator); //$NON-NLS-1$ + sb.append(indent).append(("<date>")).append(date).append(("</date>")) //$NON-NLS-1$ //$NON-NLS-2$ + .append(lineSeperator); + sb.append(indent).append(("<millis>")).append(time).append( //$NON-NLS-1$ + ("</millis>")).append(lineSeperator); //$NON-NLS-1$ + sb.append(indent).append(("<sequence>")).append(r.getSequenceNumber()) //$NON-NLS-1$ + .append(("</sequence>")).append(lineSeperator); //$NON-NLS-1$ + if (null != r.getLoggerName()) { + sb.append(indent).append(("<logger>")).append(r.getLoggerName()) //$NON-NLS-1$ + .append(("</logger>")).append(lineSeperator); //$NON-NLS-1$ + } + sb.append(indent).append(("<level>")).append(r.getLevel().getName()) //$NON-NLS-1$ + .append(("</level>")).append(lineSeperator); //$NON-NLS-1$ + if (null != r.getSourceClassName()) { + sb.append(indent).append(("<class>")) //$NON-NLS-1$ + .append(r.getSourceClassName()).append(("</class>")) //$NON-NLS-1$ + .append(lineSeperator); + } + if (null != r.getSourceMethodName()) { + sb.append(indent).append(("<method>")).append( //$NON-NLS-1$ + r.getSourceMethodName()).append(("</method>")).append( //$NON-NLS-1$ + lineSeperator); + } + sb.append(indent).append(("<thread>")).append(r.getThreadID()).append( //$NON-NLS-1$ + ("</thread>")).append(lineSeperator); //$NON-NLS-1$ + formatMessages(r, sb); + Object[] params; + if ((params = r.getParameters()) != null) { + for (Object element : params) { + sb.append(indent).append(("<param>")).append(element).append( //$NON-NLS-1$ + ("</param>")).append(lineSeperator); //$NON-NLS-1$ + } + } + formatThrowable(r, sb); + sb.append(("</record>")).append(lineSeperator); //$NON-NLS-1$ + return sb.toString(); + } + + private void formatMessages(LogRecord r, StringBuilder sb) { + //get localized message if has, but don't call Formatter.formatMessage to parse pattern string + ResourceBundle rb = r.getResourceBundle(); + String pattern = r.getMessage(); + if (null != rb && null != pattern) { + String message; + try { + message = rb.getString(pattern); + } catch (Exception e) { + message = null; + } + + if (message == null) { + message = pattern; + sb.append(indent).append(("<message>")).append(message).append( //$NON-NLS-1$ + ("</message>")).append(lineSeperator); //$NON-NLS-1$ + } else { + sb.append(indent).append(("<message>")).append(message).append( //$NON-NLS-1$ + ("</message>")).append(lineSeperator); //$NON-NLS-1$ + sb.append(indent).append(("<key>")).append(pattern).append( //$NON-NLS-1$ + ("</key>")).append(lineSeperator); //$NON-NLS-1$ + sb.append(indent).append(("<catalog>")).append( //$NON-NLS-1$ + r.getResourceBundleName()).append(("</catalog>")) //$NON-NLS-1$ + .append(lineSeperator); + } + } else if(null != pattern){ + sb.append(indent).append(("<message>")).append(pattern).append( //$NON-NLS-1$ + ("</message>")).append(lineSeperator); //$NON-NLS-1$ + } else{ + sb.append(indent).append(("<message/>")); //$NON-NLS-1$ + } + } + + private void formatThrowable(LogRecord r, StringBuilder sb) { + Throwable t; + if ((t = r.getThrown()) != null) { + sb.append(indent).append("<exception>").append(lineSeperator); //$NON-NLS-1$ + sb.append(indent).append(indent).append("<message>").append( //$NON-NLS-1$ + t.toString()).append("</message>").append(lineSeperator); //$NON-NLS-1$ + //format throwable's stack trace + StackTraceElement[] elements = t.getStackTrace(); + for (StackTraceElement e : elements) { + sb.append(indent).append(indent).append("<frame>").append( //$NON-NLS-1$ + lineSeperator); + sb.append(indent).append(indent).append(indent).append( + "<class>").append(e.getClassName()).append("</class>") //$NON-NLS-1$//$NON-NLS-2$ + .append(lineSeperator); + sb.append(indent).append(indent).append(indent).append( + "<method>").append(e.getMethodName()).append( //$NON-NLS-1$ + "</method>").append(lineSeperator); //$NON-NLS-1$ + sb.append(indent).append(indent).append(indent) + .append("<line>").append(e.getLineNumber()).append( //$NON-NLS-1$ + "</line>").append(lineSeperator); //$NON-NLS-1$ + sb.append(indent).append(indent).append("</frame>").append( //$NON-NLS-1$ + lineSeperator); + } + sb.append(indent).append("</exception>").append(lineSeperator); //$NON-NLS-1$ + } + } + + /** + * Returns the header string for a set of log records formatted as XML + * strings, using the output handler's encoding if it is defined, otherwise + * using the default platform encoding. + * + * @param h + * the output handler, may be {@code null}. + * @return the header string for log records formatted as XML strings. + * @since Android 1.0 + */ + @Override + public String getHead(Handler h) { + String encoding = null; + if(null != h) { + encoding = h.getEncoding(); + } + if (null == encoding) { + encoding = getSystemProperty("file.encoding"); //$NON-NLS-1$ + } + StringBuilder sb = new StringBuilder(); + sb.append("<?xml version=\"1.0\" encoding=\"").append(encoding).append( //$NON-NLS-1$ + "\" standalone=\"no\"?>").append(lineSeperator); //$NON-NLS-1$ + sb.append("<!DOCTYPE log SYSTEM \"logger.dtd\">").append(lineSeperator); //$NON-NLS-1$ + sb.append(("<log>")); //$NON-NLS-1$ + return sb.toString(); + } + + /** + * Returns the tail string for a set of log records formatted as XML + * strings. + * + * @param h + * the output handler, may be {@code null}. + * @return the tail string for log records formatted as XML strings. + * @since Android 1.0 + */ + @Override + @SuppressWarnings("unused") + public String getTail(Handler h) { + return "</log>"; //$NON-NLS-1$ + } + + //use privilege code to get system property + private static String getSystemProperty(final String key) { + return AccessController.doPrivileged( + new PrivilegedAction<String>() { + public String run() { + return System.getProperty(key); + } + }); + } + +} + + diff --git a/logging/src/main/java/java/util/logging/logging.properties b/logging/src/main/java/java/util/logging/logging.properties new file mode 100644 index 0000000..f99fe3f --- /dev/null +++ b/logging/src/main/java/java/util/logging/logging.properties @@ -0,0 +1,65 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +#------------------------------------------------------------------------------ +# Default logging property file. +# This file is used by java.util.logging package as default settings, users can +# specify another file instead with java.util.logging.config.file system +# property, this property can be set via the Preference API, or as VM arguments +# passed to "java" command, or as property definition passed to JNI_CreateJavaVM. +# You can refer to JavaDoc of java.util.logging package for more information +# about this file. +#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ +# Global settings +#------------------------------------------------------------------------------ + +# Specify default level for global logger, the event whose level is below won't +# be logged. You can specify level for every logger, otherwise the level of parent +# logger will be used. You can also set the level for every handler, as below for +# java.util.logging.ConsoleHandler. +.level=INFO + +# Specify handler classes list, these classes will be instantiated during the +# logging framework initialization. The list should be white space separated. +# For example, use the line below to add SocketHandler. Note that the handler +# classes must be in the classpath. +# +# handlers=java.util.logging.ConsoleHandler java.util.logging.SocketHandler +# +handlers=java.util.logging.ConsoleHandler + +# Specify a class names list, these classes' default constructor will be executed +# during logging package initialization, which may contain some code to set the +# logging configuration. The list should be white space separated, and the +# classes must be in the classpath. +# +# config= + + +#------------------------------------------------------------------------------ +# Handler settings +#------------------------------------------------------------------------------ + +# The properties below are samples for handler settings. +#java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +#java.util.logging.ConsoleHandler.level=INFO +#java.util.logging.FileHandler.limit=100000 +#java.util.logging.FileHandler.count=1 +#java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter +#java.util.logging.FileHandler.pattern=%h/java%u.log + diff --git a/logging/src/main/java/java/util/logging/package.html b/logging/src/main/java/java/util/logging/package.html new file mode 100644 index 0000000..c523c7a --- /dev/null +++ b/logging/src/main/java/java/util/logging/package.html @@ -0,0 +1,10 @@ +<html> + <body> + <p> + This package allows to add logging to any application. It + supports different levels of importance of a message that needs to be + logged. The output written to the target can be filtered by this level. + </p> + @since Android 1.0 + </body> +</html> diff --git a/logging/src/main/java/org/apache/harmony/logging/internal/nls/Messages.java b/logging/src/main/java/org/apache/harmony/logging/internal/nls/Messages.java new file mode 100644 index 0000000..87535ae --- /dev/null +++ b/logging/src/main/java/org/apache/harmony/logging/internal/nls/Messages.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * THE FILE HAS BEEN AUTOGENERATED BY MSGTOOL TOOL. + * All changes made to this file manually will be overwritten + * if this tool runs again. Better make changes in the template file. + */ + +// BEGIN android-note +// Redundant code has been removed and is now called from MsgHelp. +// END android-note + +package org.apache.harmony.logging.internal.nls; + + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +// BEGIN android-changed +import org.apache.harmony.luni.util.MsgHelp; +// END android-changed + +/** + * This class retrieves strings from a resource bundle and returns them, + * formatting them with MessageFormat when required. + * <p> + * It is used by the system classes to provide national language support, by + * looking up messages in the <code> + * org.apache.harmony.logging.internal.nls.messages + * </code> + * resource bundle. Note that if this file is not available, or an invalid key + * is looked up, or resource bundle support is not available, the key itself + * will be returned as the associated message. This means that the <em>KEY</em> + * should a reasonable human-readable (english) string. + * + */ +public class Messages { + + // BEGIN android-changed + private static final String sResource = + "org.apache.harmony.logging.internal.nls.messages"; //$NON-NLS-1$ + // END android-changed + + /** + * Retrieves a message which has no arguments. + * + * @param msg + * String the key to look up. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg) { + // BEGIN android-changed + return MsgHelp.getString(sResource, msg); + // END android-changed + } + + /** + * Retrieves a message which takes 1 argument. + * + * @param msg + * String the key to look up. + * @param arg + * Object the object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg) { + return getString(msg, new Object[] { arg }); + } + + /** + * Retrieves a message which takes 1 integer argument. + * + * @param msg + * String the key to look up. + * @param arg + * int the integer to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, int arg) { + return getString(msg, new Object[] { Integer.toString(arg) }); + } + + /** + * Retrieves a message which takes 1 character argument. + * + * @param msg + * String the key to look up. + * @param arg + * char the character to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, char arg) { + return getString(msg, new Object[] { String.valueOf(arg) }); + } + + /** + * Retrieves a message which takes 2 arguments. + * + * @param msg + * String the key to look up. + * @param arg1 + * Object an object to insert in the formatted output. + * @param arg2 + * Object another object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg1, Object arg2) { + return getString(msg, new Object[] { arg1, arg2 }); + } + + /** + * Retrieves a message which takes several arguments. + * + * @param msg + * String the key to look up. + * @param args + * Object[] the objects to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object[] args) { + // BEGIN android-changed + return MsgHelp.getString(sResource, msg, args); + // END android-changed + } + + // BEGIN android-note + // Duplicate code was dropped in favor of using MsgHelp. + // END android-note +} diff --git a/logging/src/main/java/org/apache/harmony/logging/internal/nls/messages.properties b/logging/src/main/java/org/apache/harmony/logging/internal/nls/messages.properties new file mode 100644 index 0000000..da3945e --- /dev/null +++ b/logging/src/main/java/org/apache/harmony/logging/internal/nls/messages.properties @@ -0,0 +1,52 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# messages for EN locale +logging.0=This method is not currently implemented. +logging.1=Invalid level name: {0}. +logging.10=Cannot load target handler:{0} +logging.11=Size must be positive. +logging.12=Invalid property value for +logging.13=The encoding "{0}" is not supported. +logging.14=Exception occurred when writing to the output stream. +logging.15=Exception occurred when closing the output stream. +logging.16=Exception occurred while flushing the output stream. +logging.17=Exception occurred while formatting the log record. +logging.18=Exception occurred while logging the record. +logging.19=Pattern cannot be empty +logging.1A=Error happened when open log file. +logging.1B=The limit and count property must be larger than 0 and 1, respectively +logging.1C=The 'name' parameter is null. +logging.1D=Cannot parse this name: {0} +logging.1E=Error message - {0} +logging.1F=Exception - {0} +logging.2=The OutputStream parameter is null +logging.20=Loading class "{0}" failed +logging.21=There Can Be Only One logging MX bean. +logging.22=Exception occurred while getting the logging MX bean. +logging.3=The Formatter parameter is null. +logging.4=The 'level' parameter is null. +logging.5=Different version - {0}.{1} +logging.6=Name must be "control". +logging.7=Actions must be either null or the empty string. +logging.8=Failed to load the specified resource bundle "{0}". +logging.9=The specified resource bundle name "{0}" is inconsistent with the existing one "{1}". +logging.A=The 'handler' parameter is null. +logging.B=The 'parent' parameter is null. +logging.C=Illegal host argument. +logging.D=Illegal port argument. +logging.E=Failed to establish the network connection. +logging.F=Exception occured when closing the socket handler. |