Path: blob/master/src/java.logging/share/classes/java/util/logging/FileHandler.java
41159 views
/*1* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.util.logging;2627import static java.nio.file.StandardOpenOption.APPEND;28import static java.nio.file.StandardOpenOption.CREATE_NEW;29import static java.nio.file.StandardOpenOption.WRITE;3031import java.io.BufferedOutputStream;32import java.io.File;33import java.io.FileOutputStream;34import java.io.IOException;35import java.io.OutputStream;36import java.nio.channels.FileChannel;37import java.nio.channels.OverlappingFileLockException;38import java.nio.file.AccessDeniedException;39import java.nio.file.FileAlreadyExistsException;40import java.nio.file.Files;41import java.nio.file.LinkOption;42import java.nio.file.NoSuchFileException;43import java.nio.file.Path;44import java.nio.file.Paths;45import java.security.AccessController;46import java.security.PrivilegedAction;47import java.util.HashSet;48import java.util.Set;4950/**51* Simple file logging {@code Handler}.52* <p>53* The {@code FileHandler} can either write to a specified file,54* or it can write to a rotating set of files.55* <p>56* For a rotating set of files, as each file reaches a given size57* limit, it is closed, rotated out, and a new file opened.58* Successively older files are named by adding "0", "1", "2",59* etc. into the base filename.60* <p>61* By default buffering is enabled in the IO libraries but each log62* record is flushed out when it is complete.63* <p>64* By default the {@code XMLFormatter} class is used for formatting.65* <p>66* <b>Configuration:</b>67* By default each {@code FileHandler} is initialized using the following68* {@code LogManager} configuration properties where {@code <handler-name>}69* refers to the fully-qualified class name of the handler.70* If properties are not defined71* (or have invalid values) then the specified default values are used.72* <ul>73* <li> <handler-name>.level74* specifies the default level for the {@code Handler}75* (defaults to {@code Level.ALL}). </li>76* <li> <handler-name>.filter77* specifies the name of a {@code Filter} class to use78* (defaults to no {@code Filter}). </li>79* <li> <handler-name>.formatter80* specifies the name of a {@code Formatter} class to use81* (defaults to {@code java.util.logging.XMLFormatter}) </li>82* <li> <handler-name>.encoding83* the name of the character set encoding to use (defaults to84* the default platform encoding). </li>85* <li> <handler-name>.limit86* specifies an approximate maximum amount to write (in bytes)87* to any one file. If this is zero, then there is no limit.88* (Defaults to no limit). </li>89* <li> <handler-name>.count90* specifies how many output files to cycle through (defaults to 1). </li>91* <li> <handler-name>.pattern92* specifies a pattern for generating the output file name. See93* below for details. (Defaults to "%h/java%u.log"). </li>94* <li> <handler-name>.append95* specifies whether the FileHandler should append onto96* any existing files (defaults to false). </li>97* <li> <handler-name>.maxLocks98* specifies the maximum number of concurrent locks held by99* FileHandler (defaults to 100). </li>100* </ul>101* <p>102* For example, the properties for {@code FileHandler} would be:103* <ul>104* <li> java.util.logging.FileHandler.level=INFO </li>105* <li> java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter </li>106* </ul>107* <p>108* For a custom handler, e.g. com.foo.MyHandler, the properties would be:109* <ul>110* <li> com.foo.MyHandler.level=INFO </li>111* <li> com.foo.MyHandler.formatter=java.util.logging.SimpleFormatter </li>112* </ul>113* <p>114* A pattern consists of a string that includes the following special115* components that will be replaced at runtime:116* <ul>117* <li> "/" the local pathname separator </li>118* <li> "%t" the system temporary directory </li>119* <li> "%h" the value of the "user.home" system property </li>120* <li> "%g" the generation number to distinguish rotated logs </li>121* <li> "%u" a unique number to resolve conflicts </li>122* <li> "%%" translates to a single percent sign "%" </li>123* </ul>124* If no "%g" field has been specified and the file count is greater125* than one, then the generation number will be added to the end of126* the generated filename, after a dot.127* <p>128* Thus for example a pattern of "%t/java%g.log" with a count of 2129* would typically cause log files to be written on Solaris to130* /var/tmp/java0.log and /var/tmp/java1.log whereas on Windows 95 they131* would be typically written to C:\TEMP\java0.log and C:\TEMP\java1.log132* <p>133* Generation numbers follow the sequence 0, 1, 2, etc.134* <p>135* Normally the "%u" unique field is set to 0. However, if the {@code FileHandler}136* tries to open the filename and finds the file is currently in use by137* another process it will increment the unique number field and try138* again. This will be repeated until {@code FileHandler} finds a file name that139* is not currently in use. If there is a conflict and no "%u" field has140* been specified, it will be added at the end of the filename after a dot.141* (This will be after any automatically added generation number.)142* <p>143* Thus if three processes were all trying to log to fred%u.%g.txt then144* they might end up using fred0.0.txt, fred1.0.txt, fred2.0.txt as145* the first file in their rotating sequences.146* <p>147* Note that the use of unique ids to avoid conflicts is only guaranteed148* to work reliably when using a local disk file system.149*150* @since 1.4151*/152153public class FileHandler extends StreamHandler {154private MeteredStream meter;155private boolean append;156private long limit; // zero => no limit.157private int count;158private String pattern;159private String lockFileName;160private FileChannel lockFileChannel;161private File files[];162private static final int MAX_LOCKS = 100;163private int maxLocks = MAX_LOCKS;164private static final Set<String> locks = new HashSet<>();165166/**167* A metered stream is a subclass of OutputStream that168* (a) forwards all its output to a target stream169* (b) keeps track of how many bytes have been written170*/171private static final class MeteredStream extends OutputStream {172final OutputStream out;173long written;174175MeteredStream(OutputStream out, long written) {176this.out = out;177this.written = written;178}179180@Override181public void write(int b) throws IOException {182out.write(b);183written++;184}185186@Override187public void write(byte buff[]) throws IOException {188out.write(buff);189written += buff.length;190}191192@Override193public void write(byte buff[], int off, int len) throws IOException {194out.write(buff,off,len);195written += len;196}197198@Override199public void flush() throws IOException {200out.flush();201}202203@Override204public void close() throws IOException {205out.close();206}207}208209private void open(File fname, boolean append) throws IOException {210long len = 0;211if (append) {212len = fname.length();213}214FileOutputStream fout = new FileOutputStream(fname.toString(), append);215BufferedOutputStream bout = new BufferedOutputStream(fout);216meter = new MeteredStream(bout, len);217setOutputStream(meter);218}219220/**221* Configure a FileHandler from LogManager properties and/or default values222* as specified in the class javadoc.223*/224private void configure() {225LogManager manager = LogManager.getLogManager();226227String cname = getClass().getName();228229pattern = manager.getStringProperty(cname + ".pattern", "%h/java%u.log");230limit = manager.getLongProperty(cname + ".limit", 0);231if (limit < 0) {232limit = 0;233}234count = manager.getIntProperty(cname + ".count", 1);235if (count <= 0) {236count = 1;237}238append = manager.getBooleanProperty(cname + ".append", false);239setLevel(manager.getLevelProperty(cname + ".level", Level.ALL));240setFilter(manager.getFilterProperty(cname + ".filter", null));241setFormatter(manager.getFormatterProperty(cname + ".formatter", new XMLFormatter()));242// Initialize maxLocks from the logging.properties file.243// If invalid/no property is provided 100 will be used as a default value.244maxLocks = manager.getIntProperty(cname + ".maxLocks", MAX_LOCKS);245if(maxLocks <= 0) {246maxLocks = MAX_LOCKS;247}248try {249setEncoding(manager.getStringProperty(cname +".encoding", null));250} catch (Exception ex) {251try {252setEncoding(null);253} catch (Exception ex2) {254// doing a setEncoding with null should always work.255// assert false;256}257}258}259260261/**262* Construct a default {@code FileHandler}. This will be configured263* entirely from {@code LogManager} properties (or their default values).264*265* @throws IOException if there are IO problems opening the files.266* @throws SecurityException if a security manager exists and if267* the caller does not have {@code LoggingPermission("control"))}.268* @throws NullPointerException if pattern property is an empty String.269*/270public FileHandler() throws IOException, SecurityException {271checkPermission();272configure();273// pattern will have been set by configure. check that it's not274// empty.275if (pattern.isEmpty()) {276throw new NullPointerException();277}278openFiles();279}280281/**282* Initialize a {@code FileHandler} to write to the given filename.283* <p>284* The {@code FileHandler} is configured based on {@code LogManager}285* properties (or their default values) except that the given pattern286* argument is used as the filename pattern, the file limit is287* set to no limit, and the file count is set to one.288* <p>289* There is no limit on the amount of data that may be written,290* so use this with care.291*292* @param pattern the name of the output file293* @throws IOException if there are IO problems opening the files.294* @throws SecurityException if a security manager exists and if295* the caller does not have {@code LoggingPermission("control")}.296* @throws IllegalArgumentException if pattern is an empty string297*/298public FileHandler(String pattern) throws IOException, SecurityException {299if (pattern.length() < 1 ) {300throw new IllegalArgumentException();301}302checkPermission();303configure();304this.pattern = pattern;305this.limit = 0;306this.count = 1;307openFiles();308}309310/**311* Initialize a {@code FileHandler} to write to the given filename,312* with optional append.313* <p>314* The {@code FileHandler} is configured based on {@code LogManager}315* properties (or their default values) except that the given pattern316* argument is used as the filename pattern, the file limit is317* set to no limit, the file count is set to one, and the append318* mode is set to the given {@code append} argument.319* <p>320* There is no limit on the amount of data that may be written,321* so use this with care.322*323* @param pattern the name of the output file324* @param append specifies append mode325* @throws IOException if there are IO problems opening the files.326* @throws SecurityException if a security manager exists and if327* the caller does not have {@code LoggingPermission("control")}.328* @throws IllegalArgumentException if pattern is an empty string329*/330public FileHandler(String pattern, boolean append) throws IOException,331SecurityException {332if (pattern.length() < 1 ) {333throw new IllegalArgumentException();334}335checkPermission();336configure();337this.pattern = pattern;338this.limit = 0;339this.count = 1;340this.append = append;341openFiles();342}343344/**345* Initialize a {@code FileHandler} to write to a set of files. When346* (approximately) the given limit has been written to one file,347* another file will be opened. The output will cycle through a set348* of count files.349* <p>350* The {@code FileHandler} is configured based on {@code LogManager}351* properties (or their default values) except that the given pattern352* argument is used as the filename pattern, the file limit is353* set to the limit argument, and the file count is set to the354* given count argument.355* <p>356* The count must be at least 1.357*358* @param pattern the pattern for naming the output file359* @param limit the maximum number of bytes to write to any one file360* @param count the number of files to use361* @throws IOException if there are IO problems opening the files.362* @throws SecurityException if a security manager exists and if363* the caller does not have {@code LoggingPermission("control")}.364* @throws IllegalArgumentException if {@code limit < 0}, or {@code count < 1}.365* @throws IllegalArgumentException if pattern is an empty string366*/367public FileHandler(String pattern, int limit, int count)368throws IOException, SecurityException {369if (limit < 0 || count < 1 || pattern.length() < 1) {370throw new IllegalArgumentException();371}372checkPermission();373configure();374this.pattern = pattern;375this.limit = limit;376this.count = count;377openFiles();378}379380/**381* Initialize a {@code FileHandler} to write to a set of files382* with optional append. When (approximately) the given limit has383* been written to one file, another file will be opened. The384* output will cycle through a set of count files.385* <p>386* The {@code FileHandler} is configured based on {@code LogManager}387* properties (or their default values) except that the given pattern388* argument is used as the filename pattern, the file limit is389* set to the limit argument, and the file count is set to the390* given count argument, and the append mode is set to the given391* {@code append} argument.392* <p>393* The count must be at least 1.394*395* @param pattern the pattern for naming the output file396* @param limit the maximum number of bytes to write to any one file397* @param count the number of files to use398* @param append specifies append mode399* @throws IOException if there are IO problems opening the files.400* @throws SecurityException if a security manager exists and if401* the caller does not have {@code LoggingPermission("control")}.402* @throws IllegalArgumentException if {@code limit < 0}, or {@code count < 1}.403* @throws IllegalArgumentException if pattern is an empty string404*405*/406public FileHandler(String pattern, int limit, int count, boolean append)407throws IOException, SecurityException {408this(pattern, (long)limit, count, append);409}410411/**412* Initialize a {@code FileHandler} to write to a set of files413* with optional append. When (approximately) the given limit has414* been written to one file, another file will be opened. The415* output will cycle through a set of count files.416* <p>417* The {@code FileHandler} is configured based on {@code LogManager}418* properties (or their default values) except that the given pattern419* argument is used as the filename pattern, the file limit is420* set to the limit argument, and the file count is set to the421* given count argument, and the append mode is set to the given422* {@code append} argument.423* <p>424* The count must be at least 1.425*426* @param pattern the pattern for naming the output file427* @param limit the maximum number of bytes to write to any one file428* @param count the number of files to use429* @param append specifies append mode430* @throws IOException if there are IO problems opening the files.431* @throws SecurityException if a security manager exists and if432* the caller does not have {@code LoggingPermission("control")}.433* @throws IllegalArgumentException if {@code limit < 0}, or {@code count < 1}.434* @throws IllegalArgumentException if pattern is an empty string435*436* @since 9437*438*/439public FileHandler(String pattern, long limit, int count, boolean append)440throws IOException {441if (limit < 0 || count < 1 || pattern.length() < 1) {442throw new IllegalArgumentException();443}444checkPermission();445configure();446this.pattern = pattern;447this.limit = limit;448this.count = count;449this.append = append;450openFiles();451}452453private boolean isParentWritable(Path path) {454Path parent = path.getParent();455if (parent == null) {456parent = path.toAbsolutePath().getParent();457}458return parent != null && Files.isWritable(parent);459}460461/**462* Open the set of output files, based on the configured463* instance variables.464*/465private void openFiles() throws IOException {466LogManager manager = LogManager.getLogManager();467manager.checkPermission();468if (count < 1) {469throw new IllegalArgumentException("file count = " + count);470}471if (limit < 0) {472limit = 0;473}474475// All constructors check that pattern is neither null nor empty.476assert pattern != null : "pattern should not be null";477assert !pattern.isEmpty() : "pattern should not be empty";478479// We register our own ErrorManager during initialization480// so we can record exceptions.481InitializationErrorManager em = new InitializationErrorManager();482setErrorManager(em);483484// Create a lock file. This grants us exclusive access485// to our set of output files, as long as we are alive.486int unique = -1;487for (;;) {488unique++;489if (unique > maxLocks) {490throw new IOException("Couldn't get lock for " + pattern);491}492// Generate a lock file name from the "unique" int.493lockFileName = generate(pattern, 0, unique).toString() + ".lck";494// Now try to lock that filename.495// Because some systems (e.g., Solaris) can only do file locks496// between processes (and not within a process), we first check497// if we ourself already have the file locked.498synchronized(locks) {499if (locks.contains(lockFileName)) {500// We already own this lock, for a different FileHandler501// object. Try again.502continue;503}504505final Path lockFilePath = Paths.get(lockFileName);506FileChannel channel = null;507int retries = -1;508boolean fileCreated = false;509while (channel == null && retries++ < 1) {510try {511channel = FileChannel.open(lockFilePath,512CREATE_NEW, WRITE);513fileCreated = true;514} catch (AccessDeniedException ade) {515// This can be either a temporary, or a more permanent issue.516// The lock file might be still pending deletion from a previous run517// (temporary), or the parent directory might not be accessible,518// not writable, etc..519// If we can write to the current directory, and this is a regular file,520// let's try again.521if (Files.isRegularFile(lockFilePath, LinkOption.NOFOLLOW_LINKS)522&& isParentWritable(lockFilePath)) {523// Try again. If it doesn't work, then this will524// eventually ensure that we increment "unique" and525// use another file name.526continue;527} else {528throw ade; // no need to retry529}530} catch (FileAlreadyExistsException ix) {531// This may be a zombie file left over by a previous532// execution. Reuse it - but only if we can actually533// write to its directory.534// Note that this is a situation that may happen,535// but not too frequently.536if (Files.isRegularFile(lockFilePath, LinkOption.NOFOLLOW_LINKS)537&& isParentWritable(lockFilePath)) {538try {539channel = FileChannel.open(lockFilePath,540WRITE, APPEND);541} catch (NoSuchFileException x) {542// Race condition - retry once, and if that543// fails again just try the next name in544// the sequence.545continue;546} catch(IOException x) {547// the file may not be writable for us.548// try the next name in the sequence549break;550}551} else {552// at this point channel should still be null.553// break and try the next name in the sequence.554break;555}556}557}558559if (channel == null) continue; // try the next name;560lockFileChannel = channel;561562boolean available;563try {564available = lockFileChannel.tryLock() != null;565// We got the lock OK.566// At this point we could call File.deleteOnExit().567// However, this could have undesirable side effects568// as indicated by JDK-4872014. So we will instead569// rely on the fact that close() will remove the lock570// file and that whoever is creating FileHandlers should571// be responsible for closing them.572} catch (IOException ix) {573// We got an IOException while trying to get the lock.574// This normally indicates that locking is not supported575// on the target directory. We have to proceed without576// getting a lock. Drop through, but only if we did577// create the file...578available = fileCreated;579} catch (OverlappingFileLockException x) {580// someone already locked this file in this VM, through581// some other channel - that is - using something else582// than new FileHandler(...);583// continue searching for an available lock.584available = false;585}586if (available) {587// We got the lock. Remember it.588locks.add(lockFileName);589break;590}591592// We failed to get the lock. Try next file.593lockFileChannel.close();594}595}596597files = new File[count];598for (int i = 0; i < count; i++) {599files[i] = generate(pattern, i, unique);600}601602// Create the initial log file.603if (append) {604open(files[0], true);605} else {606rotate();607}608609// Did we detect any exceptions during initialization?610Exception ex = em.lastException;611if (ex != null) {612if (ex instanceof IOException) {613throw (IOException) ex;614} else if (ex instanceof SecurityException) {615throw (SecurityException) ex;616} else {617throw new IOException("Exception: " + ex);618}619}620621// Install the normal default ErrorManager.622setErrorManager(new ErrorManager());623}624625/**626* Generate a file based on a user-supplied pattern, generation number,627* and an integer uniqueness suffix628* @param pattern the pattern for naming the output file629* @param generation the generation number to distinguish rotated logs630* @param unique a unique number to resolve conflicts631* @return the generated File632* @throws IOException633*/634private File generate(String pattern, int generation, int unique)635throws IOException636{637return generate(pattern, count, generation, unique);638}639640// The static method here is provided for whitebox testing of the algorithm.641static File generate(String pat, int count, int generation, int unique)642throws IOException643{644Path path = Paths.get(pat);645Path result = null;646boolean sawg = false;647boolean sawu = false;648StringBuilder word = new StringBuilder();649Path prev = null;650for (Path elem : path) {651if (prev != null) {652prev = prev.resolveSibling(word.toString());653result = result == null ? prev : result.resolve(prev);654}655String pattern = elem.toString();656int ix = 0;657word.setLength(0);658while (ix < pattern.length()) {659char ch = pattern.charAt(ix);660ix++;661char ch2 = 0;662if (ix < pattern.length()) {663ch2 = Character.toLowerCase(pattern.charAt(ix));664}665if (ch == '%') {666if (ch2 == 't') {667String tmpDir = System.getProperty("java.io.tmpdir");668if (tmpDir == null) {669tmpDir = System.getProperty("user.home");670}671result = Paths.get(tmpDir);672ix++;673word.setLength(0);674continue;675} else if (ch2 == 'h') {676result = Paths.get(System.getProperty("user.home"));677if (jdk.internal.misc.VM.isSetUID()) {678// Ok, we are in a set UID program. For safety's sake679// we disallow attempts to open files relative to %h.680throw new IOException("can't use %h in set UID program");681}682ix++;683word.setLength(0);684continue;685} else if (ch2 == 'g') {686word = word.append(generation);687sawg = true;688ix++;689continue;690} else if (ch2 == 'u') {691word = word.append(unique);692sawu = true;693ix++;694continue;695} else if (ch2 == '%') {696word = word.append('%');697ix++;698continue;699}700}701word = word.append(ch);702}703prev = elem;704}705706if (count > 1 && !sawg) {707word = word.append('.').append(generation);708}709if (unique > 0 && !sawu) {710word = word.append('.').append(unique);711}712if (word.length() > 0) {713String n = word.toString();714Path p = prev == null ? Paths.get(n) : prev.resolveSibling(n);715result = result == null ? p : result.resolve(p);716} else if (result == null) {717result = Paths.get("");718}719720if (path.getRoot() == null) {721return result.toFile();722} else {723return path.getRoot().resolve(result).toFile();724}725}726727/**728* Rotate the set of output files729*/730private synchronized void rotate() {731Level oldLevel = getLevel();732setLevel(Level.OFF);733734super.close();735for (int i = count-2; i >= 0; i--) {736File f1 = files[i];737File f2 = files[i+1];738if (f1.exists()) {739if (f2.exists()) {740f2.delete();741}742f1.renameTo(f2);743}744}745try {746open(files[0], false);747} catch (IOException ix) {748// We don't want to throw an exception here, but we749// report the exception to any registered ErrorManager.750reportError(null, ix, ErrorManager.OPEN_FAILURE);751752}753setLevel(oldLevel);754}755756/**757* Format and publish a {@code LogRecord}.758*759* @param record description of the log event. A null record is760* silently ignored and is not published761*/762@SuppressWarnings("removal")763@Override764public synchronized void publish(LogRecord record) {765if (!isLoggable(record)) {766return;767}768super.publish(record);769flush();770if (limit > 0 && (meter.written >= limit || meter.written < 0)) {771// We performed access checks in the "init" method to make sure772// we are only initialized from trusted code. So we assume773// it is OK to write the target files, even if we are774// currently being called from untrusted code.775// So it is safe to raise privilege here.776AccessController.doPrivileged(new PrivilegedAction<Object>() {777@Override778public Object run() {779rotate();780return null;781}782});783}784}785786/**787* Close all the files.788*789* @throws SecurityException if a security manager exists and if790* the caller does not have {@code LoggingPermission("control")}.791*/792@Override793public synchronized void close() throws SecurityException {794super.close();795// Unlock any lock file.796if (lockFileName == null) {797return;798}799try {800// Close the lock file channel (which also will free any locks)801lockFileChannel.close();802} catch (Exception ex) {803// Problems closing the stream. Punt.804}805synchronized(locks) {806locks.remove(lockFileName);807}808new File(lockFileName).delete();809lockFileName = null;810lockFileChannel = null;811}812813private static class InitializationErrorManager extends ErrorManager {814Exception lastException;815@Override816public void error(String msg, Exception ex, int code) {817lastException = ex;818}819}820}821822823